# HG changeset patch # User Chris Cannam # Date 1371198510 -3600 # Node ID 4f746d8966dd0c081168e6ec53051ac88e0d3c79 # Parent 0a574315af3eab702b0074a5813cbf4f8ba280a4# Parent 622f24f53b4282a3921576d5a3e29df487b73f4a Merge from redmine-2.3 branch to create new branch redmine-2.3-integration diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/00/0009a621965fc195e93ba67a7d3500cbcd38b084.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/00/0009a621965fc195e93ba67a7d3500cbcd38b084.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,94 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class DocumentsController < ApplicationController + default_search_scope :documents + model_object Document + before_filter :find_project_by_project_id, :only => [:index, :new, :create] + before_filter :find_model_object, :except => [:index, :new, :create] + before_filter :find_project_from_association, :except => [:index, :new, :create] + before_filter :authorize + + helper :attachments + + def index + @sort_by = %w(category date title author).include?(params[:sort_by]) ? params[:sort_by] : 'category' + documents = @project.documents.includes(:attachments, :category).all + case @sort_by + when 'date' + @grouped = documents.group_by {|d| d.updated_on.to_date } + when 'title' + @grouped = documents.group_by {|d| d.title.first.upcase} + when 'author' + @grouped = documents.select{|d| d.attachments.any?}.group_by {|d| d.attachments.last.author} + else + @grouped = documents.group_by(&:category) + end + @document = @project.documents.build + render :layout => false if request.xhr? + end + + def show + @attachments = @document.attachments.all + end + + def new + @document = @project.documents.build + @document.safe_attributes = params[:document] + end + + def create + @document = @project.documents.build + @document.safe_attributes = params[:document] + @document.save_attachments(params[:attachments]) + if @document.save + render_attachment_warning_if_needed(@document) + flash[:notice] = l(:notice_successful_create) + redirect_to project_documents_path(@project) + else + render :action => 'new' + end + end + + def edit + end + + def update + @document.safe_attributes = params[:document] + if request.put? and @document.save + flash[:notice] = l(:notice_successful_update) + redirect_to document_path(@document) + else + render :action => 'edit' + end + end + + def destroy + @document.destroy if request.delete? + redirect_to project_documents_path(@project) + end + + def add_attachment + attachments = Attachment.attach_files(@document, params[:attachments]) + render_attachment_warning_if_needed(@document) + + if attachments.present? && attachments[:files].present? && Setting.notified_events.include?('document_added') + Mailer.attachments_added(attachments[:files]).deliver + end + redirect_to document_path(@document) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/00/002d478f30849d26745d0ec7b1c75e9c7796a254.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/00/002d478f30849d26745d0ec7b1c75e9c7796a254.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,614 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class MailerTest < ActiveSupport::TestCase + include Redmine::I18n + include ActionDispatch::Assertions::SelectorAssertions + fixtures :projects, :enabled_modules, :issues, :users, :members, + :member_roles, :roles, :documents, :attachments, :news, + :tokens, :journals, :journal_details, :changesets, + :trackers, :projects_trackers, + :issue_statuses, :enumerations, :messages, :boards, :repositories, + :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions, + :versions, + :comments + + def setup + ActionMailer::Base.deliveries.clear + Setting.host_name = 'mydomain.foo' + Setting.protocol = 'http' + Setting.plain_text_mail = '0' + end + + def test_generated_links_in_emails + Setting.default_language = 'en' + Setting.host_name = 'mydomain.foo' + Setting.protocol = 'https' + + journal = Journal.find(3) + assert Mailer.issue_edit(journal).deliver + + mail = last_email + assert_not_nil mail + + assert_select_email do + # link to the main ticket + assert_select 'a[href=?]', + 'https://mydomain.foo/issues/2#change-3', + :text => 'Feature request #2: Add ingredients categories' + # link to a referenced ticket + assert_select 'a[href=?][title=?]', + 'https://mydomain.foo/issues/1', + 'Can't print recipes (New)', + :text => '#1' + # link to a changeset + assert_select 'a[href=?][title=?]', + 'https://mydomain.foo/projects/ecookbook/repository/revisions/2', + 'This commit fixes #1, #2 and references #1 & #3', + :text => 'r2' + # link to a description diff + assert_select 'a[href=?][title=?]', + 'https://mydomain.foo/journals/diff/3?detail_id=4', + 'View differences', + :text => 'diff' + # link to an attachment + assert_select 'a[href=?]', + 'https://mydomain.foo/attachments/download/4/source.rb', + :text => 'source.rb' + end + end + + def test_generated_links_with_prefix + Setting.default_language = 'en' + relative_url_root = Redmine::Utils.relative_url_root + Setting.host_name = 'mydomain.foo/rdm' + Setting.protocol = 'http' + + journal = Journal.find(3) + assert Mailer.issue_edit(journal).deliver + + mail = last_email + assert_not_nil mail + + assert_select_email do + # link to the main ticket + assert_select 'a[href=?]', + 'http://mydomain.foo/rdm/issues/2#change-3', + :text => 'Feature request #2: Add ingredients categories' + # link to a referenced ticket + assert_select 'a[href=?][title=?]', + 'http://mydomain.foo/rdm/issues/1', + 'Can't print recipes (New)', + :text => '#1' + # link to a changeset + assert_select 'a[href=?][title=?]', + 'http://mydomain.foo/rdm/projects/ecookbook/repository/revisions/2', + 'This commit fixes #1, #2 and references #1 & #3', + :text => 'r2' + # link to a description diff + assert_select 'a[href=?][title=?]', + 'http://mydomain.foo/rdm/journals/diff/3?detail_id=4', + 'View differences', + :text => 'diff' + # link to an attachment + assert_select 'a[href=?]', + 'http://mydomain.foo/rdm/attachments/download/4/source.rb', + :text => 'source.rb' + end + end + + def test_generated_links_with_prefix_and_no_relative_url_root + Setting.default_language = 'en' + relative_url_root = Redmine::Utils.relative_url_root + Setting.host_name = 'mydomain.foo/rdm' + Setting.protocol = 'http' + Redmine::Utils.relative_url_root = nil + + journal = Journal.find(3) + assert Mailer.issue_edit(journal).deliver + + mail = last_email + assert_not_nil mail + + assert_select_email do + # link to the main ticket + assert_select 'a[href=?]', + 'http://mydomain.foo/rdm/issues/2#change-3', + :text => 'Feature request #2: Add ingredients categories' + # link to a referenced ticket + assert_select 'a[href=?][title=?]', + 'http://mydomain.foo/rdm/issues/1', + 'Can't print recipes (New)', + :text => '#1' + # link to a changeset + assert_select 'a[href=?][title=?]', + 'http://mydomain.foo/rdm/projects/ecookbook/repository/revisions/2', + 'This commit fixes #1, #2 and references #1 & #3', + :text => 'r2' + # link to a description diff + assert_select 'a[href=?][title=?]', + 'http://mydomain.foo/rdm/journals/diff/3?detail_id=4', + 'View differences', + :text => 'diff' + # link to an attachment + assert_select 'a[href=?]', + 'http://mydomain.foo/rdm/attachments/download/4/source.rb', + :text => 'source.rb' + end + ensure + # restore it + Redmine::Utils.relative_url_root = relative_url_root + end + + def test_email_headers + issue = Issue.find(1) + Mailer.issue_add(issue).deliver + mail = last_email + assert_not_nil mail + assert_equal 'OOF', mail.header['X-Auto-Response-Suppress'].to_s + assert_equal 'auto-generated', mail.header['Auto-Submitted'].to_s + assert_equal '', mail.header['List-Id'].to_s + end + + def test_email_headers_should_include_sender + issue = Issue.find(1) + Mailer.issue_add(issue).deliver + mail = last_email + assert_equal issue.author.login, mail.header['X-Redmine-Sender'].to_s + end + + def test_plain_text_mail + Setting.plain_text_mail = 1 + journal = Journal.find(2) + Mailer.issue_edit(journal).deliver + mail = last_email + assert_equal "text/plain; charset=UTF-8", mail.content_type + assert_equal 0, mail.parts.size + assert !mail.encoded.include?('href') + end + + def test_html_mail + Setting.plain_text_mail = 0 + journal = Journal.find(2) + Mailer.issue_edit(journal).deliver + mail = last_email + assert_equal 2, mail.parts.size + assert mail.encoded.include?('href') + end + + def test_from_header + with_settings :mail_from => 'redmine@example.net' do + Mailer.test_email(User.find(1)).deliver + end + mail = last_email + assert_equal 'redmine@example.net', mail.from_addrs.first + end + + def test_from_header_with_phrase + with_settings :mail_from => 'Redmine app ' do + Mailer.test_email(User.find(1)).deliver + end + mail = last_email + assert_equal 'redmine@example.net', mail.from_addrs.first + assert_equal 'Redmine app ', mail.header['From'].to_s + end + + def test_should_not_send_email_without_recipient + news = News.first + user = news.author + # Remove members except news author + news.project.memberships.each {|m| m.destroy unless m.user == user} + + user.pref[:no_self_notified] = false + user.pref.save + User.current = user + Mailer.news_added(news.reload).deliver + assert_equal 1, last_email.bcc.size + + # nobody to notify + user.pref[:no_self_notified] = true + user.pref.save + User.current = user + ActionMailer::Base.deliveries.clear + Mailer.news_added(news.reload).deliver + assert ActionMailer::Base.deliveries.empty? + end + + def test_issue_add_message_id + issue = Issue.find(1) + Mailer.issue_add(issue).deliver + mail = last_email + assert_equal Mailer.message_id_for(issue), mail.message_id + assert_nil mail.references + end + + def test_issue_edit_message_id + journal = Journal.find(1) + Mailer.issue_edit(journal).deliver + mail = last_email + assert_equal Mailer.message_id_for(journal), mail.message_id + assert_include Mailer.message_id_for(journal.issue), mail.references + assert_select_email do + # link to the update + assert_select "a[href=?]", + "http://mydomain.foo/issues/#{journal.journalized_id}#change-#{journal.id}" + end + end + + def test_message_posted_message_id + message = Message.find(1) + Mailer.message_posted(message).deliver + mail = last_email + assert_equal Mailer.message_id_for(message), mail.message_id + assert_nil mail.references + assert_select_email do + # link to the message + assert_select "a[href=?]", + "http://mydomain.foo/boards/#{message.board.id}/topics/#{message.id}", + :text => message.subject + end + end + + def test_reply_posted_message_id + message = Message.find(3) + Mailer.message_posted(message).deliver + mail = last_email + assert_equal Mailer.message_id_for(message), mail.message_id + assert_include Mailer.message_id_for(message.parent), mail.references + assert_select_email do + # link to the reply + assert_select "a[href=?]", + "http://mydomain.foo/boards/#{message.board.id}/topics/#{message.root.id}?r=#{message.id}#message-#{message.id}", + :text => message.subject + end + end + + test "#issue_add should notify project members" do + issue = Issue.find(1) + assert Mailer.issue_add(issue).deliver + assert last_email.bcc.include?('dlopper@somenet.foo') + end + + test "#issue_add should not notify project members that are not allow to view the issue" do + issue = Issue.find(1) + Role.find(2).remove_permission!(:view_issues) + assert Mailer.issue_add(issue).deliver + assert !last_email.bcc.include?('dlopper@somenet.foo') + end + + test "#issue_add should notify issue watchers" do + issue = Issue.find(1) + user = User.find(9) + # minimal email notification options + user.pref[:no_self_notified] = '1' + user.pref.save + user.mail_notification = false + user.save + + Watcher.create!(:watchable => issue, :user => user) + assert Mailer.issue_add(issue).deliver + assert last_email.bcc.include?(user.mail) + end + + test "#issue_add should not notify watchers not allowed to view the issue" do + issue = Issue.find(1) + user = User.find(9) + Watcher.create!(:watchable => issue, :user => user) + Role.non_member.remove_permission!(:view_issues) + assert Mailer.issue_add(issue).deliver + assert !last_email.bcc.include?(user.mail) + end + + # test mailer methods for each language + def test_issue_add + issue = Issue.find(1) + valid_languages.each do |lang| + Setting.default_language = lang.to_s + assert Mailer.issue_add(issue).deliver + end + end + + def test_issue_edit + journal = Journal.find(1) + valid_languages.each do |lang| + Setting.default_language = lang.to_s + assert Mailer.issue_edit(journal).deliver + end + end + + def test_issue_edit_should_send_private_notes_to_users_with_permission_only + journal = Journal.find(1) + journal.private_notes = true + journal.save! + + Role.find(2).add_permission! :view_private_notes + Mailer.issue_edit(journal).deliver + assert_equal %w(dlopper@somenet.foo jsmith@somenet.foo), ActionMailer::Base.deliveries.last.bcc.sort + + Role.find(2).remove_permission! :view_private_notes + Mailer.issue_edit(journal).deliver + assert_equal %w(jsmith@somenet.foo), ActionMailer::Base.deliveries.last.bcc.sort + end + + def test_issue_edit_should_send_private_notes_to_watchers_with_permission_only + Issue.find(1).set_watcher(User.find_by_login('someone')) + journal = Journal.find(1) + journal.private_notes = true + journal.save! + + Role.non_member.add_permission! :view_private_notes + Mailer.issue_edit(journal).deliver + assert_include 'someone@foo.bar', ActionMailer::Base.deliveries.last.bcc.sort + + Role.non_member.remove_permission! :view_private_notes + Mailer.issue_edit(journal).deliver + assert_not_include 'someone@foo.bar', ActionMailer::Base.deliveries.last.bcc.sort + end + + def test_document_added + document = Document.find(1) + valid_languages.each do |lang| + Setting.default_language = lang.to_s + assert Mailer.document_added(document).deliver + end + end + + def test_attachments_added + attachements = [ Attachment.find_by_container_type('Document') ] + valid_languages.each do |lang| + Setting.default_language = lang.to_s + assert Mailer.attachments_added(attachements).deliver + end + end + + def test_version_file_added + attachements = [ Attachment.find_by_container_type('Version') ] + assert Mailer.attachments_added(attachements).deliver + assert_not_nil last_email.bcc + assert last_email.bcc.any? + assert_select_email do + assert_select "a[href=?]", "http://mydomain.foo/projects/ecookbook/files" + end + end + + def test_project_file_added + attachements = [ Attachment.find_by_container_type('Project') ] + assert Mailer.attachments_added(attachements).deliver + assert_not_nil last_email.bcc + assert last_email.bcc.any? + assert_select_email do + assert_select "a[href=?]", "http://mydomain.foo/projects/ecookbook/files" + end + end + + def test_news_added + news = News.first + valid_languages.each do |lang| + Setting.default_language = lang.to_s + assert Mailer.news_added(news).deliver + end + end + + def test_news_comment_added + comment = Comment.find(2) + valid_languages.each do |lang| + Setting.default_language = lang.to_s + assert Mailer.news_comment_added(comment).deliver + end + end + + def test_message_posted + message = Message.first + recipients = ([message.root] + message.root.children).collect {|m| m.author.mail if m.author} + recipients = recipients.compact.uniq + valid_languages.each do |lang| + Setting.default_language = lang.to_s + assert Mailer.message_posted(message).deliver + end + end + + def test_wiki_content_added + content = WikiContent.find(1) + valid_languages.each do |lang| + Setting.default_language = lang.to_s + assert_difference 'ActionMailer::Base.deliveries.size' do + assert Mailer.wiki_content_added(content).deliver + assert_select_email do + assert_select 'a[href=?]', + 'http://mydomain.foo/projects/ecookbook/wiki/CookBook_documentation', + :text => 'CookBook documentation' + end + end + end + end + + def test_wiki_content_updated + content = WikiContent.find(1) + valid_languages.each do |lang| + Setting.default_language = lang.to_s + assert_difference 'ActionMailer::Base.deliveries.size' do + assert Mailer.wiki_content_updated(content).deliver + assert_select_email do + assert_select 'a[href=?]', + 'http://mydomain.foo/projects/ecookbook/wiki/CookBook_documentation', + :text => 'CookBook documentation' + end + end + end + end + + def test_account_information + user = User.find(2) + valid_languages.each do |lang| + user.update_attribute :language, lang.to_s + user.reload + assert Mailer.account_information(user, 'pAsswORd').deliver + end + end + + def test_lost_password + token = Token.find(2) + valid_languages.each do |lang| + token.user.update_attribute :language, lang.to_s + token.reload + assert Mailer.lost_password(token).deliver + end + end + + def test_register + token = Token.find(1) + Setting.host_name = 'redmine.foo' + Setting.protocol = 'https' + + valid_languages.each do |lang| + token.user.update_attribute :language, lang.to_s + token.reload + ActionMailer::Base.deliveries.clear + assert Mailer.register(token).deliver + mail = last_email + assert_select_email do + assert_select "a[href=?]", + "https://redmine.foo/account/activate?token=#{token.value}", + :text => "https://redmine.foo/account/activate?token=#{token.value}" + end + end + end + + def test_test + user = User.find(1) + valid_languages.each do |lang| + user.update_attribute :language, lang.to_s + assert Mailer.test_email(user).deliver + end + end + + def test_reminders + Mailer.reminders(:days => 42) + assert_equal 1, ActionMailer::Base.deliveries.size + mail = last_email + assert mail.bcc.include?('dlopper@somenet.foo') + assert_mail_body_match 'Bug #3: Error 281 when updating a recipe', mail + assert_equal '1 issue(s) due in the next 42 days', mail.subject + end + + def test_reminders_should_not_include_closed_issues + with_settings :default_language => 'en' do + Issue.create!(:project_id => 1, :tracker_id => 1, :status_id => 5, + :subject => 'Closed issue', :assigned_to_id => 3, + :due_date => 5.days.from_now, + :author_id => 2) + ActionMailer::Base.deliveries.clear + + Mailer.reminders(:days => 42) + assert_equal 1, ActionMailer::Base.deliveries.size + mail = last_email + assert mail.bcc.include?('dlopper@somenet.foo') + assert_mail_body_no_match 'Closed issue', mail + end + end + + def test_reminders_for_users + Mailer.reminders(:days => 42, :users => ['5']) + assert_equal 0, ActionMailer::Base.deliveries.size # No mail for dlopper + Mailer.reminders(:days => 42, :users => ['3']) + assert_equal 1, ActionMailer::Base.deliveries.size # No mail for dlopper + mail = last_email + assert mail.bcc.include?('dlopper@somenet.foo') + assert_mail_body_match 'Bug #3: Error 281 when updating a recipe', mail + end + + def test_reminder_should_include_issues_assigned_to_groups + with_settings :default_language => 'en' do + group = Group.generate! + group.users << User.find(2) + group.users << User.find(3) + + Issue.create!(:project_id => 1, :tracker_id => 1, :status_id => 1, + :subject => 'Assigned to group', :assigned_to => group, + :due_date => 5.days.from_now, + :author_id => 2) + ActionMailer::Base.deliveries.clear + + Mailer.reminders(:days => 7) + assert_equal 2, ActionMailer::Base.deliveries.size + assert_equal %w(dlopper@somenet.foo jsmith@somenet.foo), ActionMailer::Base.deliveries.map(&:bcc).flatten.sort + ActionMailer::Base.deliveries.each do |mail| + assert_mail_body_match 'Assigned to group', mail + end + end + end + + def test_mailer_should_not_change_locale + Setting.default_language = 'en' + # Set current language to italian + set_language_if_valid 'it' + # Send an email to a french user + user = User.find(1) + user.language = 'fr' + Mailer.account_activated(user).deliver + mail = last_email + assert_mail_body_match 'Votre compte', mail + + assert_equal :it, current_language + end + + def test_with_deliveries_off + Mailer.with_deliveries false do + Mailer.test_email(User.find(1)).deliver + end + assert ActionMailer::Base.deliveries.empty? + # should restore perform_deliveries + assert ActionMailer::Base.perform_deliveries + end + + def test_layout_should_include_the_emails_header + with_settings :emails_header => "*Header content*" do + assert Mailer.test_email(User.find(1)).deliver + assert_select_email do + assert_select ".header" do + assert_select "strong", :text => "Header content" + end + end + end + end + + def test_should_escape_html_templates_only + Issue.generate!(:project_id => 1, :tracker_id => 1, :subject => 'Subject with a ') + mail = last_email + assert_equal 2, mail.parts.size + assert_include '', text_part.body.encoded + assert_include '<tag>', html_part.body.encoded + end + + private + + def last_email + mail = ActionMailer::Base.deliveries.last + assert_not_nil mail + mail + end + + def text_part + last_email.parts.detect {|part| part.content_type.include?('text/plain')} + end + + def html_part + last_email.parts.detect {|part| part.content_type.include?('text/html')} + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/00/007e22f777adc578342260747f72f3fcfceb129d.svn-base --- a/.svn/pristine/00/007e22f777adc578342260747f72f3fcfceb129d.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class ProjectCustomField < CustomField - def type_name - :label_project_plural - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/00/0081b5051789911922bdd0b18a28f0ad87c64e8d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/00/0081b5051789911922bdd0b18a28f0ad87c64e8d.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,39 @@ +
+<%= link_to l(:label_tracker_new), new_tracker_path, :class => 'icon icon-add' %> +<%= link_to l(:field_summary), fields_trackers_path, :class => 'icon icon-summary' %> +
+ +

<%=l(:label_tracker_plural)%>

+ + + + + + + + + +<% for tracker in @trackers %> + "> + + + + + +<% end %> + +
<%=l(:label_tracker)%><%=l(:button_sort)%>
<%= link_to h(tracker.name), edit_tracker_path(tracker) %> + <% unless tracker.workflow_rules.count > 0 %> + + <%= l(:text_tracker_no_workflow) %> (<%= link_to l(:button_edit), workflows_edit_path(:tracker_id => tracker) %>) + + <% end %> + + <%= reorder_links('tracker', {:action => 'update', :id => tracker}, :put) %> + + <%= delete_link tracker_path(tracker) %> +
+ +

<%= pagination_links_full @tracker_pages %>

+ +<% html_title(l(:label_tracker_plural)) -%> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/00/00bd76ee7194e922fe9340f2d39d910c9f79b20e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/00/00bd76ee7194e922fe9340f2d39d910c9f79b20e.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,162 @@ +module ObjectHelpers + def User.generate!(attributes={}) + @generated_user_login ||= 'user0' + @generated_user_login.succ! + user = User.new(attributes) + user.login = @generated_user_login.dup if user.login.blank? + user.mail = "#{@generated_user_login}@example.com" if user.mail.blank? + user.firstname = "Bob" if user.firstname.blank? + user.lastname = "Doe" if user.lastname.blank? + yield user if block_given? + user.save! + user + end + + def User.add_to_project(user, project, roles=nil) + roles = Role.find(1) if roles.nil? + roles = [roles] unless roles.is_a?(Array) + Member.create!(:principal => user, :project => project, :roles => roles) + end + + def Group.generate!(attributes={}) + @generated_group_name ||= 'Group 0' + @generated_group_name.succ! + group = Group.new(attributes) + group.name = @generated_group_name.dup if group.name.blank? + yield group if block_given? + group.save! + group + end + + def Project.generate!(attributes={}) + @generated_project_identifier ||= 'project-0000' + @generated_project_identifier.succ! + project = Project.new(attributes) + project.name = @generated_project_identifier.dup if project.name.blank? + project.identifier = @generated_project_identifier.dup if project.identifier.blank? + yield project if block_given? + project.save! + project + end + + def Project.generate_with_parent!(parent, attributes={}) + project = Project.generate!(attributes) + project.set_parent!(parent) + project + end + + def Tracker.generate!(attributes={}) + @generated_tracker_name ||= 'Tracker 0' + @generated_tracker_name.succ! + tracker = Tracker.new(attributes) + tracker.name = @generated_tracker_name.dup if tracker.name.blank? + yield tracker if block_given? + tracker.save! + tracker + end + + def Role.generate!(attributes={}) + @generated_role_name ||= 'Role 0' + @generated_role_name.succ! + role = Role.new(attributes) + role.name = @generated_role_name.dup if role.name.blank? + yield role if block_given? + role.save! + role + end + + def Issue.generate!(attributes={}) + issue = Issue.new(attributes) + issue.project ||= Project.find(1) + issue.tracker ||= issue.project.trackers.first + issue.subject = 'Generated' if issue.subject.blank? + issue.author ||= User.find(2) + yield issue if block_given? + issue.save! + issue + end + + # Generates an issue with 2 children and a grandchild + def Issue.generate_with_descendants!(attributes={}) + issue = Issue.generate!(attributes) + child = Issue.generate!(:project => issue.project, :subject => 'Child1', :parent_issue_id => issue.id) + Issue.generate!(:project => issue.project, :subject => 'Child2', :parent_issue_id => issue.id) + Issue.generate!(:project => issue.project, :subject => 'Child11', :parent_issue_id => child.id) + issue.reload + end + + def Journal.generate!(attributes={}) + journal = Journal.new(attributes) + journal.user ||= User.first + journal.journalized ||= Issue.first + yield journal if block_given? + journal.save! + journal + end + + def Version.generate!(attributes={}) + @generated_version_name ||= 'Version 0' + @generated_version_name.succ! + version = Version.new(attributes) + version.name = @generated_version_name.dup if version.name.blank? + yield version if block_given? + version.save! + version + end + + def TimeEntry.generate!(attributes={}) + entry = TimeEntry.new(attributes) + entry.user ||= User.find(2) + entry.issue ||= Issue.find(1) unless entry.project + entry.project ||= entry.issue.project + entry.activity ||= TimeEntryActivity.first + entry.spent_on ||= Date.today + entry.hours ||= 1.0 + entry.save! + entry + end + + def AuthSource.generate!(attributes={}) + @generated_auth_source_name ||= 'Auth 0' + @generated_auth_source_name.succ! + source = AuthSource.new(attributes) + source.name = @generated_auth_source_name.dup if source.name.blank? + yield source if block_given? + source.save! + source + end + + def Board.generate!(attributes={}) + @generated_board_name ||= 'Forum 0' + @generated_board_name.succ! + board = Board.new(attributes) + board.name = @generated_board_name.dup if board.name.blank? + board.description = @generated_board_name.dup if board.description.blank? + yield board if block_given? + board.save! + board + end + + def Attachment.generate!(attributes={}) + @generated_filename ||= 'testfile0' + @generated_filename.succ! + attributes = attributes.dup + attachment = Attachment.new(attributes) + attachment.container ||= Issue.find(1) + attachment.author ||= User.find(2) + attachment.filename = @generated_filename.dup if attachment.filename.blank? + attachment.save! + attachment + end + + def CustomField.generate!(attributes={}) + @generated_custom_field_name ||= 'Custom field 0' + @generated_custom_field_name.succ! + field = new(attributes) + field.name = @generated_custom_field_name.dup if field.name.blank? + field.field_format = 'string' if field.field_format.blank? + yield field if block_given? + field.save! + field + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/00/00c1a9b51dc321ecb980f25b62c13dfa7fd628ff.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/00/00c1a9b51dc321ecb980f25b62c13dfa7fd628ff.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,24 @@ +<%= error_messages_for 'auth_source' %> + +
+

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

+

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

+

<%= f.text_field :port, :required => true, :size => 6 %> <%= f.check_box :tls, :no_label => true %> LDAPS

+

<%= f.text_field :account %>

+

<%= f.password_field :account_password, :label => :field_password, + :name => 'dummy_password', + :value => ((@auth_source.new_record? || @auth_source.account_password.blank?) ? '' : ('x'*15)), + :onfocus => "this.value=''; this.name='auth_source[account_password]';", + :onchange => "this.name='auth_source[account_password]';" %>

+

<%= f.text_field :base_dn, :required => true, :size => 60 %>

+

<%= f.text_field :filter, :size => 60, :label => :field_auth_source_ldap_filter %>

+

<%= f.text_field :timeout, :size => 4 %>

+

<%= f.check_box :onthefly_register, :label => :field_onthefly %>

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

<%= f.text_field :attr_login, :required => true, :size => 20 %>

+

<%= f.text_field :attr_firstname, :size => 20 %>

+

<%= f.text_field :attr_lastname, :size => 20 %>

+

<%= f.text_field :attr_mail, :size => 20 %>

+
diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/00/00c4bd6691f32db8720a826db98aecc930f926d9.svn-base --- a/.svn/pristine/00/00c4bd6691f32db8720a826db98aecc930f926d9.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,165 +0,0 @@ -# $Id: entry.rb 123 2006-05-18 03:52:38Z blackhedd $ -# -# LDAP Entry (search-result) support classes -# -# -#---------------------------------------------------------------------------- -# -# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved. -# -# Gmail: garbagecat10 -# -# 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 St, Fifth Floor, Boston, MA 02110-1301 USA -# -#--------------------------------------------------------------------------- -# - - - - -module Net -class LDAP - - - # Objects of this class represent individual entries in an LDAP - # directory. User code generally does not instantiate this class. - # Net::LDAP#search provides objects of this class to user code, - # either as block parameters or as return values. - # - # In LDAP-land, an "entry" is a collection of attributes that are - # uniquely and globally identified by a DN ("Distinguished Name"). - # Attributes are identified by short, descriptive words or phrases. - # Although a directory is - # free to implement any attribute name, most of them follow rigorous - # standards so that the range of commonly-encountered attribute - # names is not large. - # - # An attribute name is case-insensitive. Most directories also - # restrict the range of characters allowed in attribute names. - # To simplify handling attribute names, Net::LDAP::Entry - # internally converts them to a standard format. Therefore, the - # methods which take attribute names can take Strings or Symbols, - # and work correctly regardless of case or capitalization. - # - # An attribute consists of zero or more data items called - # values. An entry is the combination of a unique DN, a set of attribute - # names, and a (possibly-empty) array of values for each attribute. - # - # Class Net::LDAP::Entry provides convenience methods for dealing - # with LDAP entries. - # In addition to the methods documented below, you may access individual - # attributes of an entry simply by giving the attribute name as - # the name of a method call. For example: - # ldap.search( ... ) do |entry| - # puts "Common name: #{entry.cn}" - # puts "Email addresses:" - # entry.mail.each {|ma| puts ma} - # end - # If you use this technique to access an attribute that is not present - # in a particular Entry object, a NoMethodError exception will be raised. - # - #-- - # Ugly problem to fix someday: We key off the internal hash with - # a canonical form of the attribute name: convert to a string, - # downcase, then take the symbol. Unfortunately we do this in - # at least three places. Should do it in ONE place. - class Entry - - # This constructor is not generally called by user code. - def initialize dn = nil # :nodoc: - @myhash = Hash.new {|k,v| k[v] = [] } - @myhash[:dn] = [dn] - end - - - def []= name, value # :nodoc: - sym = name.to_s.downcase.intern - @myhash[sym] = value - end - - - #-- - # We have to deal with this one as we do with []= - # because this one and not the other one gets called - # in formulations like entry["CN"] << cn. - # - def [] name # :nodoc: - name = name.to_s.downcase.intern unless name.is_a?(Symbol) - @myhash[name] - end - - # Returns the dn of the Entry as a String. - def dn - self[:dn][0] - end - - # Returns an array of the attribute names present in the Entry. - def attribute_names - @myhash.keys - end - - # Accesses each of the attributes present in the Entry. - # Calls a user-supplied block with each attribute in turn, - # passing two arguments to the block: a Symbol giving - # the name of the attribute, and a (possibly empty) - # Array of data values. - # - def each - if block_given? - attribute_names.each {|a| - attr_name,values = a,self[a] - yield attr_name, values - } - end - end - - alias_method :each_attribute, :each - - - #-- - # Convenience method to convert unknown method names - # to attribute references. Of course the method name - # comes to us as a symbol, so let's save a little time - # and not bother with the to_s.downcase two-step. - # Of course that means that a method name like mAIL - # won't work, but we shouldn't be encouraging that - # kind of bad behavior in the first place. - # Maybe we should thow something if the caller sends - # arguments or a block... - # - def method_missing *args, &block # :nodoc: - s = args[0].to_s.downcase.intern - if attribute_names.include?(s) - self[s] - elsif s.to_s[-1] == 61 and s.to_s.length > 1 - value = args[1] or raise RuntimeError.new( "unable to set value" ) - value = [value] unless value.is_a?(Array) - name = s.to_s[0..-2].intern - self[name] = value - else - raise NoMethodError.new( "undefined method '#{s}'" ) - end - end - - def write - end - - end # class Entry - - -end # class LDAP -end # module Net - - diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/01/011d952bc7773843d5a8219df22dce0959479d6d.svn-base --- a/.svn/pristine/01/011d952bc7773843d5a8219df22dce0959479d6d.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) - -class DocumentCategoryTest < ActiveSupport::TestCase - fixtures :enumerations, :documents, :issues - - def test_should_be_an_enumeration - assert DocumentCategory.ancestors.include?(Enumeration) - end - - def test_objects_count - assert_equal 2, DocumentCategory.find_by_name("Uncategorized").objects_count - assert_equal 0, DocumentCategory.find_by_name("User documentation").objects_count - end - - def test_option_name - assert_equal :enumeration_doc_categories, DocumentCategory.new.option_name - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/01/01272907f6842a5e924d61c923ee0994110ab686.svn-base --- a/.svn/pristine/01/01272907f6842a5e924d61c923ee0994110ab686.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,260 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../../../../test_helper', __FILE__) - -class Redmine::MenuManager::MenuHelperTest < HelperTestCase - include Redmine::MenuManager::MenuHelper - include ActionController::Assertions::SelectorAssertions - fixtures :users, :members, :projects, :enabled_modules - - # Used by assert_select - def html_document - HTML::Document.new(@response.body) - end - - def setup - super - @response = ActionController::TestResponse.new - # Stub the current menu item in the controller - def current_menu_item - :index - end - end - - - context "MenuManager#current_menu_item" do - should "be tested" - end - - context "MenuManager#render_main_menu" do - should "be tested" - end - - context "MenuManager#render_menu" do - should "be tested" - end - - context "MenuManager#menu_item_and_children" do - should "be tested" - end - - context "MenuManager#extract_node_details" do - should "be tested" - end - - def test_render_single_menu_node - node = Redmine::MenuManager::MenuItem.new(:testing, '/test', { }) - @response.body = render_single_menu_node(node, 'This is a test', node.url, false) - - assert_select("a.testing", "This is a test") - end - - def test_render_menu_node - single_node = Redmine::MenuManager::MenuItem.new(:single_node, '/test', { }) - @response.body = render_menu_node(single_node, nil) - - assert_select("li") do - assert_select("a.single-node", "Single node") - end - end - - def test_render_menu_node_with_nested_items - parent_node = Redmine::MenuManager::MenuItem.new(:parent_node, '/test', { }) - parent_node << Redmine::MenuManager::MenuItem.new(:child_one_node, '/test', { }) - parent_node << Redmine::MenuManager::MenuItem.new(:child_two_node, '/test', { }) - parent_node << - Redmine::MenuManager::MenuItem.new(:child_three_node, '/test', { }) << - Redmine::MenuManager::MenuItem.new(:child_three_inner_node, '/test', { }) - - @response.body = render_menu_node(parent_node, nil) - - assert_select("li") do - assert_select("a.parent-node", "Parent node") - assert_select("ul") do - assert_select("li a.child-one-node", "Child one node") - assert_select("li a.child-two-node", "Child two node") - assert_select("li") do - assert_select("a.child-three-node", "Child three node") - assert_select("ul") do - assert_select("li a.child-three-inner-node", "Child three inner node") - end - end - end - end - - end - - def test_render_menu_node_with_children - User.current = User.find(2) - - parent_node = Redmine::MenuManager::MenuItem.new(:parent_node, - '/test', - { - :children => Proc.new {|p| - children = [] - 3.times do |time| - children << Redmine::MenuManager::MenuItem.new("test_child_#{time}", - {:controller => 'issues', :action => 'index'}, - {}) - end - children - } - }) - @response.body = render_menu_node(parent_node, Project.find(1)) - - assert_select("li") do - assert_select("a.parent-node", "Parent node") - assert_select("ul") do - assert_select("li a.test-child-0", "Test child 0") - assert_select("li a.test-child-1", "Test child 1") - assert_select("li a.test-child-2", "Test child 2") - end - end - end - - def test_render_menu_node_with_nested_items_and_children - User.current = User.find(2) - - parent_node = Redmine::MenuManager::MenuItem.new(:parent_node, - '/test', - { - :children => Proc.new {|p| - children = [] - 3.times do |time| - children << Redmine::MenuManager::MenuItem.new("test_child_#{time}", {:controller => 'issues', :action => 'index'}, {}) - end - children - } - }) - - parent_node << Redmine::MenuManager::MenuItem.new(:child_node, - '/test', - { - :children => Proc.new {|p| - children = [] - 6.times do |time| - children << Redmine::MenuManager::MenuItem.new("test_dynamic_child_#{time}", {:controller => 'issues', :action => 'index'}, {}) - end - children - } - }) - - @response.body = render_menu_node(parent_node, Project.find(1)) - - assert_select("li") do - assert_select("a.parent-node", "Parent node") - assert_select("ul") do - assert_select("li a.child-node", "Child node") - assert_select("ul") do - assert_select("li a.test-dynamic-child-0", "Test dynamic child 0") - assert_select("li a.test-dynamic-child-1", "Test dynamic child 1") - assert_select("li a.test-dynamic-child-2", "Test dynamic child 2") - assert_select("li a.test-dynamic-child-3", "Test dynamic child 3") - assert_select("li a.test-dynamic-child-4", "Test dynamic child 4") - assert_select("li a.test-dynamic-child-5", "Test dynamic child 5") - end - assert_select("li a.test-child-0", "Test child 0") - assert_select("li a.test-child-1", "Test child 1") - assert_select("li a.test-child-2", "Test child 2") - end - end - end - - def test_render_menu_node_with_children_without_an_array - parent_node = Redmine::MenuManager::MenuItem.new(:parent_node, - '/test', - { - :children => Proc.new {|p| Redmine::MenuManager::MenuItem.new("test_child", "/testing", {})} - }) - - assert_raises Redmine::MenuManager::MenuError, ":children must be an array of MenuItems" do - @response.body = render_menu_node(parent_node, Project.find(1)) - end - end - - def test_render_menu_node_with_incorrect_children - parent_node = Redmine::MenuManager::MenuItem.new(:parent_node, - '/test', - { - :children => Proc.new {|p| ["a string"] } - }) - - assert_raises Redmine::MenuManager::MenuError, ":children must be an array of MenuItems" do - @response.body = render_menu_node(parent_node, Project.find(1)) - end - - end - - def test_menu_items_for_should_yield_all_items_if_passed_a_block - menu_name = :test_menu_items_for_should_yield_all_items_if_passed_a_block - Redmine::MenuManager.map menu_name do |menu| - menu.push(:a_menu, '/', { }) - menu.push(:a_menu_2, '/', { }) - menu.push(:a_menu_3, '/', { }) - end - - items_yielded = [] - menu_items_for(menu_name) do |item| - items_yielded << item - end - - assert_equal 3, items_yielded.size - end - - def test_menu_items_for_should_return_all_items - menu_name = :test_menu_items_for_should_return_all_items - Redmine::MenuManager.map menu_name do |menu| - menu.push(:a_menu, '/', { }) - menu.push(:a_menu_2, '/', { }) - menu.push(:a_menu_3, '/', { }) - end - - items = menu_items_for(menu_name) - assert_equal 3, items.size - end - - def test_menu_items_for_should_skip_unallowed_items_on_a_project - menu_name = :test_menu_items_for_should_skip_unallowed_items_on_a_project - Redmine::MenuManager.map menu_name do |menu| - menu.push(:a_menu, {:controller => 'issues', :action => 'index' }, { }) - menu.push(:a_menu_2, {:controller => 'issues', :action => 'index' }, { }) - menu.push(:unallowed, {:controller => 'issues', :action => 'unallowed' }, { }) - end - - User.current = User.find(2) - - items = menu_items_for(menu_name, Project.find(1)) - assert_equal 2, items.size - end - - def test_menu_items_for_should_skip_items_that_fail_the_conditions - menu_name = :test_menu_items_for_should_skip_items_that_fail_the_conditions - Redmine::MenuManager.map menu_name do |menu| - menu.push(:a_menu, {:controller => 'issues', :action => 'index' }, { }) - menu.push(:unallowed, - {:controller => 'issues', :action => 'index' }, - { :if => Proc.new { false }}) - end - - User.current = User.find(2) - - items = menu_items_for(menu_name, Project.find(1)) - assert_equal 1, items.size - end - -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/01/0154cdd03545376764b58ded20a14138238e65d0.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/01/0154cdd03545376764b58ded20a14138238e65d0.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,178 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../../test_helper', __FILE__) + +class Redmine::Hook::ManagerTest < ActionView::TestCase + fixtures :projects, :users, :members, :member_roles, :roles, + :groups_users, + :trackers, :projects_trackers, + :enabled_modules, + :versions, + :issue_statuses, :issue_categories, :issue_relations, + :enumerations, + :issues + + # Some hooks that are manually registered in these tests + class TestHook < Redmine::Hook::ViewListener; end + + class TestHook1 < TestHook + def view_layouts_base_html_head(context) + 'Test hook 1 listener.' + end + end + + class TestHook2 < TestHook + def view_layouts_base_html_head(context) + 'Test hook 2 listener.' + end + end + + class TestHook3 < TestHook + def view_layouts_base_html_head(context) + "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 + + class TestHookHelperController < ActionController::Base + include Redmine::Hook::Helper + end + + class TestHookHelperView < ActionView::Base + include Redmine::Hook::Helper + end + + Redmine::Hook.clear_listeners + + def setup + @hook_module = Redmine::Hook + end + + def teardown + @hook_module.clear_listeners + end + + def test_clear_listeners + assert_equal 0, @hook_module.hook_listeners(:view_layouts_base_html_head).size + @hook_module.add_listener(TestHook1) + @hook_module.add_listener(TestHook2) + assert_equal 2, @hook_module.hook_listeners(:view_layouts_base_html_head).size + + @hook_module.clear_listeners + assert_equal 0, @hook_module.hook_listeners(:view_layouts_base_html_head).size + end + + def test_add_listener + assert_equal 0, @hook_module.hook_listeners(:view_layouts_base_html_head).size + @hook_module.add_listener(TestHook1) + assert_equal 1, @hook_module.hook_listeners(:view_layouts_base_html_head).size + end + + def test_call_hook + @hook_module.add_listener(TestHook1) + assert_equal ['Test hook 1 listener.'], hook_helper.call_hook(:view_layouts_base_html_head) + end + + def test_call_hook_with_context + @hook_module.add_listener(TestHook3) + assert_equal ['Context keys: bar, controller, foo, hook_caller, project, request.'], + hook_helper.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_helper.call_hook(:view_layouts_base_html_head) + end + + # Context: Redmine::Hook::Helper.call_hook default_url + def test_call_hook_default_url_options + @hook_module.add_listener(TestLinkToHook) + + assert_equal ['Issues'], hook_helper.call_hook(:view_layouts_base_html_head) + end + + # Context: Redmine::Hook::Helper.call_hook + def test_call_hook_with_project_added_to_context + @hook_module.add_listener(TestHook3) + assert_match /project/i, hook_helper.call_hook(:view_layouts_base_html_head)[0] + end + + def test_call_hook_from_controller_with_controller_added_to_context + @hook_module.add_listener(TestHook3) + assert_match /controller/i, hook_helper.call_hook(:view_layouts_base_html_head)[0] + end + + def test_call_hook_from_controller_with_request_added_to_context + @hook_module.add_listener(TestHook3) + assert_match /request/i, hook_helper.call_hook(:view_layouts_base_html_head)[0] + end + + def test_call_hook_from_view_with_project_added_to_context + @hook_module.add_listener(TestHook3) + assert_match /project/i, view_hook_helper.call_hook(:view_layouts_base_html_head) + end + + def test_call_hook_from_view_with_controller_added_to_context + @hook_module.add_listener(TestHook3) + assert_match /controller/i, view_hook_helper.call_hook(:view_layouts_base_html_head) + end + + def test_call_hook_from_view_with_request_added_to_context + @hook_module.add_listener(TestHook3) + assert_match /request/i, view_hook_helper.call_hook(:view_layouts_base_html_head) + end + + def test_call_hook_from_view_should_join_responses_with_a_space + @hook_module.add_listener(TestHook1) + @hook_module.add_listener(TestHook2) + assert_equal 'Test hook 1 listener. Test hook 2 listener.', + view_hook_helper.call_hook(:view_layouts_base_html_head) + end + + def test_call_hook_should_not_change_the_default_url_for_email_notifications + issue = Issue.find(1) + + ActionMailer::Base.deliveries.clear + Mailer.issue_add(issue).deliver + mail = ActionMailer::Base.deliveries.last + + @hook_module.add_listener(TestLinkToHook) + hook_helper.call_hook(:view_layouts_base_html_head) + + ActionMailer::Base.deliveries.clear + Mailer.issue_add(issue).deliver + mail2 = ActionMailer::Base.deliveries.last + + assert_equal mail_body(mail), mail_body(mail2) + end + + def hook_helper + @hook_helper ||= TestHookHelperController.new + end + + def view_hook_helper + @view_hook_helper ||= TestHookHelperView.new(Rails.root.to_s + '/app/views') + end +end + diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/01/01a6ee1f20ebd9960efb3d88db810fb607deb030.svn-base Binary file .svn/pristine/01/01a6ee1f20ebd9960efb3d88db810fb607deb030.svn-base has changed diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/01/01b21d04c9e912327dc0059ef282ceb1a603f54f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/01/01b21d04c9e912327dc0059ef282ceb1a603f54f.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,90 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class AutoCompletesControllerTest < ActionController::TestCase + fixtures :projects, :issues, :issue_statuses, + :enumerations, :users, :issue_categories, + :trackers, + :projects_trackers, + :roles, + :member_roles, + :members, + :enabled_modules, + :journals, :journal_details + + def test_issues_should_not_be_case_sensitive + get :issues, :project_id => 'ecookbook', :q => 'ReCiPe' + assert_response :success + assert_not_nil assigns(:issues) + assert assigns(:issues).detect {|issue| issue.subject.match /recipe/} + end + + def test_issues_should_accept_term_param + get :issues, :project_id => 'ecookbook', :term => 'ReCiPe' + assert_response :success + assert_not_nil assigns(:issues) + assert assigns(:issues).detect {|issue| issue.subject.match /recipe/} + end + + def test_issues_should_return_issue_with_given_id + get :issues, :project_id => 'subproject1', :q => '13' + assert_response :success + assert_not_nil assigns(:issues) + assert assigns(:issues).include?(Issue.find(13)) + end + + def test_issues_should_return_issue_with_given_id_preceded_with_hash + get :issues, :project_id => 'subproject1', :q => '#13' + assert_response :success + assert_not_nil assigns(:issues) + assert assigns(:issues).include?(Issue.find(13)) + end + + def test_auto_complete_with_scope_all_should_search_other_projects + get :issues, :project_id => 'ecookbook', :q => '13', :scope => 'all' + assert_response :success + assert_not_nil assigns(:issues) + assert assigns(:issues).include?(Issue.find(13)) + end + + def test_auto_complete_without_project_should_search_all_projects + get :issues, :q => '13' + assert_response :success + assert_not_nil assigns(:issues) + assert assigns(:issues).include?(Issue.find(13)) + end + + def test_auto_complete_without_scope_all_should_not_search_other_projects + get :issues, :project_id => 'ecookbook', :q => '13' + assert_response :success + assert_equal [], assigns(:issues) + end + + def test_issues_should_return_json + get :issues, :project_id => 'subproject1', :q => '13' + assert_response :success + json = ActiveSupport::JSON.decode(response.body) + assert_kind_of Array, json + issue = json.first + assert_kind_of Hash, issue + assert_equal 13, issue['id'] + assert_equal 13, issue['value'] + assert_equal 'Bug #13: Subproject issue two', issue['label'] + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/01/01fd40d061a2be270b23a0152d0699de1966efc7.svn-base --- a/.svn/pristine/01/01fd40d061a2be270b23a0152d0699de1966efc7.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,194 +0,0 @@ ---- -roles_001: - name: Manager - id: 1 - builtin: 0 - issues_visibility: all - permissions: | - --- - - :add_project - - :edit_project - - :select_project_modules - - :manage_members - - :manage_versions - - :manage_categories - - :view_issues - - :add_issues - - :edit_issues - - :manage_issue_relations - - :manage_subtasks - - :add_issue_notes - - :move_issues - - :delete_issues - - :view_issue_watchers - - :add_issue_watchers - - :set_issues_private - - :delete_issue_watchers - - :manage_public_queries - - :save_queries - - :view_gantt - - :view_calendar - - :log_time - - :view_time_entries - - :edit_time_entries - - :delete_time_entries - - :manage_news - - :comment_news - - :view_documents - - :manage_documents - - :view_wiki_pages - - :export_wiki_pages - - :view_wiki_edits - - :edit_wiki_pages - - :delete_wiki_pages_attachments - - :protect_wiki_pages - - :delete_wiki_pages - - :rename_wiki_pages - - :add_messages - - :edit_messages - - :delete_messages - - :manage_boards - - :view_files - - :manage_files - - :browse_repository - - :manage_repository - - :view_changesets - - :manage_project_activities - - position: 1 -roles_002: - name: Developer - id: 2 - builtin: 0 - issues_visibility: default - permissions: | - --- - - :edit_project - - :manage_members - - :manage_versions - - :manage_categories - - :view_issues - - :add_issues - - :edit_issues - - :manage_issue_relations - - :manage_subtasks - - :add_issue_notes - - :move_issues - - :delete_issues - - :view_issue_watchers - - :save_queries - - :view_gantt - - :view_calendar - - :log_time - - :view_time_entries - - :edit_own_time_entries - - :manage_news - - :comment_news - - :view_documents - - :manage_documents - - :view_wiki_pages - - :view_wiki_edits - - :edit_wiki_pages - - :protect_wiki_pages - - :delete_wiki_pages - - :add_messages - - :edit_own_messages - - :delete_own_messages - - :manage_boards - - :view_files - - :manage_files - - :browse_repository - - :view_changesets - - position: 2 -roles_003: - name: Reporter - id: 3 - builtin: 0 - issues_visibility: default - permissions: | - --- - - :edit_project - - :manage_members - - :manage_versions - - :manage_categories - - :view_issues - - :add_issues - - :edit_issues - - :manage_issue_relations - - :add_issue_notes - - :move_issues - - :view_issue_watchers - - :save_queries - - :view_gantt - - :view_calendar - - :log_time - - :view_time_entries - - :manage_news - - :comment_news - - :view_documents - - :manage_documents - - :view_wiki_pages - - :view_wiki_edits - - :edit_wiki_pages - - :delete_wiki_pages - - :add_messages - - :manage_boards - - :view_files - - :manage_files - - :browse_repository - - :view_changesets - - position: 3 -roles_004: - name: Non member - id: 4 - builtin: 1 - issues_visibility: default - permissions: | - --- - - :view_issues - - :add_issues - - :edit_issues - - :manage_issue_relations - - :add_issue_notes - - :move_issues - - :save_queries - - :view_gantt - - :view_calendar - - :log_time - - :view_time_entries - - :comment_news - - :view_documents - - :manage_documents - - :view_wiki_pages - - :view_wiki_edits - - :edit_wiki_pages - - :add_messages - - :view_files - - :manage_files - - :browse_repository - - :view_changesets - - position: 4 -roles_005: - name: Anonymous - id: 5 - builtin: 2 - issues_visibility: default - permissions: | - --- - - :view_issues - - :add_issue_notes - - :view_gantt - - :view_calendar - - :view_time_entries - - :view_documents - - :view_wiki_pages - - :view_wiki_edits - - :view_files - - :browse_repository - - :view_changesets - - position: 5 - diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/02/025612e4a47b7cb1c36f52b9661b0cc1f94b2a45.svn-base --- a/.svn/pristine/02/025612e4a47b7cb1c36f52b9661b0cc1f94b2a45.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ -# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html -one: - id: 1 -<% for attribute in attributes -%> - <%= attribute.name %>: <%= attribute.default %> -<% end -%> -two: - id: 2 -<% for attribute in attributes -%> - <%= attribute.name %>: <%= attribute.default %> -<% end -%> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/02/026bb2d46eaa121e79862de3515bb99ff0827ca3.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/02/026bb2d46eaa121e79862de3515bb99ff0827ca3.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,943 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 Helpers + # Simple class to handle gantt chart data + class Gantt + include ERB::Util + include Redmine::I18n + include Redmine::Utils::DateCalculation + + # Relation types that are rendered + DRAW_TYPES = { + IssueRelation::TYPE_BLOCKS => { :landscape_margin => 16, :color => '#F34F4F' }, + IssueRelation::TYPE_PRECEDES => { :landscape_margin => 20, :color => '#628FEA' } + }.freeze + + # :nodoc: + # Some utility methods for the PDF export + class PDF + MaxCharactorsForSubject = 45 + TotalWidth = 280 + LeftPaneWidth = 100 + + def self.right_pane_width + TotalWidth - LeftPaneWidth + end + end + + attr_reader :year_from, :month_from, :date_from, :date_to, :zoom, :months, :truncated, :max_rows + attr_accessor :query + attr_accessor :project + attr_accessor :view + + def initialize(options={}) + options = options.dup + if options[:year] && options[:year].to_i >0 + @year_from = options[:year].to_i + if options[:month] && options[:month].to_i >=1 && options[:month].to_i <= 12 + @month_from = options[:month].to_i + else + @month_from = 1 + end + else + @month_from ||= Date.today.month + @year_from ||= Date.today.year + end + zoom = (options[:zoom] || User.current.pref[:gantt_zoom]).to_i + @zoom = (zoom > 0 && zoom < 5) ? zoom : 2 + months = (options[:months] || User.current.pref[:gantt_months]).to_i + @months = (months > 0 && months < 25) ? months : 6 + # Save gantt parameters as user preference (zoom and months count) + if (User.current.logged? && (@zoom != User.current.pref[:gantt_zoom] || + @months != User.current.pref[:gantt_months])) + User.current.pref[:gantt_zoom], User.current.pref[:gantt_months] = @zoom, @months + User.current.preference.save + end + @date_from = Date.civil(@year_from, @month_from, 1) + @date_to = (@date_from >> @months) - 1 + @subjects = '' + @lines = '' + @number_of_rows = nil + @issue_ancestors = [] + @truncated = false + if options.has_key?(:max_rows) + @max_rows = options[:max_rows] + else + @max_rows = Setting.gantt_items_limit.blank? ? nil : Setting.gantt_items_limit.to_i + end + end + + def common_params + { :controller => 'gantts', :action => 'show', :project_id => @project } + end + + def params + common_params.merge({:zoom => zoom, :year => year_from, + :month => month_from, :months => months}) + end + + def params_previous + common_params.merge({:year => (date_from << months).year, + :month => (date_from << months).month, + :zoom => zoom, :months => months}) + end + + def params_next + common_params.merge({:year => (date_from >> months).year, + :month => (date_from >> months).month, + :zoom => zoom, :months => months}) + end + + # Returns the number of rows that will be rendered on the Gantt chart + def number_of_rows + return @number_of_rows if @number_of_rows + rows = projects.inject(0) {|total, p| total += number_of_rows_on_project(p)} + rows > @max_rows ? @max_rows : rows + end + + # Returns the number of rows that will be used to list a project on + # the Gantt chart. This will recurse for each subproject. + def number_of_rows_on_project(project) + return 0 unless projects.include?(project) + count = 1 + count += project_issues(project).size + count += project_versions(project).size + count + end + + # Renders the subjects of the Gantt chart, the left side. + def subjects(options={}) + render(options.merge(:only => :subjects)) unless @subjects_rendered + @subjects + end + + # Renders the lines of the Gantt chart, the right side + def lines(options={}) + render(options.merge(:only => :lines)) unless @lines_rendered + @lines + end + + # Returns issues that will be rendered + def issues + @issues ||= @query.issues( + :include => [:assigned_to, :tracker, :priority, :category, :fixed_version], + :order => "#{Project.table_name}.lft ASC, #{Issue.table_name}.id ASC", + :limit => @max_rows + ) + end + + # Returns a hash of the relations between the issues that are present on the gantt + # and that should be displayed, grouped by issue ids. + def relations + return @relations if @relations + if issues.any? + issue_ids = issues.map(&:id) + @relations = IssueRelation. + where(:issue_from_id => issue_ids, :issue_to_id => issue_ids, :relation_type => DRAW_TYPES.keys). + group_by(&:issue_from_id) + else + @relations = {} + end + end + + # Return all the project nodes that will be displayed + def projects + return @projects if @projects + ids = issues.collect(&:project).uniq.collect(&:id) + if ids.any? + # All issues projects and their visible ancestors + @projects = Project.visible.all( + :joins => "LEFT JOIN #{Project.table_name} child ON #{Project.table_name}.lft <= child.lft AND #{Project.table_name}.rgt >= child.rgt", + :conditions => ["child.id IN (?)", ids], + :order => "#{Project.table_name}.lft ASC" + ).uniq + else + @projects = [] + end + end + + # Returns the issues that belong to +project+ + def project_issues(project) + @issues_by_project ||= issues.group_by(&:project) + @issues_by_project[project] || [] + end + + # Returns the distinct versions of the issues that belong to +project+ + def project_versions(project) + project_issues(project).collect(&:fixed_version).compact.uniq + end + + # Returns the issues that belong to +project+ and are assigned to +version+ + def version_issues(project, version) + project_issues(project).select {|issue| issue.fixed_version == version} + end + + def render(options={}) + options = {:top => 0, :top_increment => 20, + :indent_increment => 20, :render => :subject, + :format => :html}.merge(options) + indent = options[:indent] || 4 + @subjects = '' unless options[:only] == :lines + @lines = '' unless options[:only] == :subjects + @number_of_rows = 0 + Project.project_tree(projects) do |project, level| + options[:indent] = indent + level * options[:indent_increment] + render_project(project, options) + break if abort? + end + @subjects_rendered = true unless options[:only] == :lines + @lines_rendered = true unless options[:only] == :subjects + render_end(options) + end + + def render_project(project, options={}) + subject_for_project(project, options) unless options[:only] == :lines + line_for_project(project, options) unless options[:only] == :subjects + options[:top] += options[:top_increment] + options[:indent] += options[:indent_increment] + @number_of_rows += 1 + return if abort? + issues = project_issues(project).select {|i| i.fixed_version.nil?} + sort_issues!(issues) + if issues + render_issues(issues, options) + return if abort? + end + versions = project_versions(project) + versions.each do |version| + render_version(project, version, options) + end + # Remove indent to hit the next sibling + options[:indent] -= options[:indent_increment] + end + + def render_issues(issues, options={}) + @issue_ancestors = [] + issues.each do |i| + subject_for_issue(i, options) unless options[:only] == :lines + line_for_issue(i, options) unless options[:only] == :subjects + options[:top] += options[:top_increment] + @number_of_rows += 1 + break if abort? + end + options[:indent] -= (options[:indent_increment] * @issue_ancestors.size) + end + + def render_version(project, version, options={}) + # Version header + subject_for_version(version, options) unless options[:only] == :lines + line_for_version(version, options) unless options[:only] == :subjects + options[:top] += options[:top_increment] + @number_of_rows += 1 + return if abort? + issues = version_issues(project, version) + if issues + sort_issues!(issues) + # Indent issues + options[:indent] += options[:indent_increment] + render_issues(issues, options) + options[:indent] -= options[:indent_increment] + end + end + + def render_end(options={}) + case options[:format] + when :pdf + options[:pdf].Line(15, options[:top], PDF::TotalWidth, options[:top]) + end + end + + def subject_for_project(project, options) + case options[:format] + when :html + html_class = "" + html_class << 'icon icon-projects ' + html_class << (project.overdue? ? 'project-overdue' : '') + s = view.link_to_project(project).html_safe + subject = view.content_tag(:span, s, + :class => html_class).html_safe + html_subject(options, subject, :css => "project-name") + when :image + image_subject(options, project.name) + when :pdf + pdf_new_page?(options) + pdf_subject(options, project.name) + end + end + + def line_for_project(project, options) + # Skip versions that don't have a start_date or due date + if project.is_a?(Project) && project.start_date && project.due_date + options[:zoom] ||= 1 + options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom] + coords = coordinates(project.start_date, project.due_date, nil, options[:zoom]) + label = h(project) + case options[:format] + when :html + html_task(options, coords, :css => "project task", :label => label, :markers => true) + when :image + image_task(options, coords, :label => label, :markers => true, :height => 3) + when :pdf + pdf_task(options, coords, :label => label, :markers => true, :height => 0.8) + end + else + '' + end + end + + def subject_for_version(version, options) + case options[:format] + when :html + html_class = "" + html_class << 'icon icon-package ' + html_class << (version.behind_schedule? ? 'version-behind-schedule' : '') << " " + html_class << (version.overdue? ? 'version-overdue' : '') + html_class << ' version-closed' unless version.open? + if version.start_date && version.due_date && version.completed_pourcent + progress_date = calc_progress_date(version.start_date, + version.due_date, version.completed_pourcent) + html_class << ' behind-start-date' if progress_date < self.date_from + html_class << ' over-end-date' if progress_date > self.date_to + end + s = view.link_to_version(version).html_safe + subject = view.content_tag(:span, s, + :class => html_class).html_safe + html_subject(options, subject, :css => "version-name", + :id => "version-#{version.id}") + when :image + image_subject(options, version.to_s_with_project) + when :pdf + pdf_new_page?(options) + pdf_subject(options, version.to_s_with_project) + end + end + + def line_for_version(version, options) + # Skip versions that don't have a start_date + if version.is_a?(Version) && version.due_date && version.start_date + options[:zoom] ||= 1 + options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom] + coords = coordinates(version.start_date, + version.due_date, version.completed_percent, + options[:zoom]) + label = "#{h version} #{h version.completed_percent.to_i.to_s}%" + label = h("#{version.project} -") + label unless @project && @project == version.project + case options[:format] + when :html + html_task(options, coords, :css => "version task", + :label => label, :markers => true, :version => version) + when :image + image_task(options, coords, :label => label, :markers => true, :height => 3) + when :pdf + pdf_task(options, coords, :label => label, :markers => true, :height => 0.8) + end + else + '' + end + end + + def subject_for_issue(issue, options) + while @issue_ancestors.any? && !issue.is_descendant_of?(@issue_ancestors.last) + @issue_ancestors.pop + options[:indent] -= options[:indent_increment] + end + output = case options[:format] + when :html + css_classes = '' + css_classes << ' issue-overdue' if issue.overdue? + css_classes << ' issue-behind-schedule' if issue.behind_schedule? + css_classes << ' icon icon-issue' unless Setting.gravatar_enabled? && issue.assigned_to + css_classes << ' issue-closed' if issue.closed? + if issue.start_date && issue.due_before && issue.done_ratio + progress_date = calc_progress_date(issue.start_date, + issue.due_before, issue.done_ratio) + css_classes << ' behind-start-date' if progress_date < self.date_from + css_classes << ' over-end-date' if progress_date > self.date_to + end + s = "".html_safe + if issue.assigned_to.present? + assigned_string = l(:field_assigned_to) + ": " + issue.assigned_to.name + s << view.avatar(issue.assigned_to, + :class => 'gravatar icon-gravatar', + :size => 10, + :title => assigned_string).to_s.html_safe + end + s << view.link_to_issue(issue).html_safe + subject = view.content_tag(:span, s, :class => css_classes).html_safe + html_subject(options, subject, :css => "issue-subject", + :title => issue.subject, :id => "issue-#{issue.id}") + "\n" + when :image + image_subject(options, issue.subject) + when :pdf + pdf_new_page?(options) + pdf_subject(options, issue.subject) + end + unless issue.leaf? + @issue_ancestors << issue + options[:indent] += options[:indent_increment] + end + output + end + + def line_for_issue(issue, options) + # Skip issues that don't have a due_before (due_date or version's due_date) + if issue.is_a?(Issue) && issue.due_before + coords = coordinates(issue.start_date, issue.due_before, issue.done_ratio, options[:zoom]) + label = "#{issue.status.name} #{issue.done_ratio}%" + case options[:format] + when :html + html_task(options, coords, + :css => "task " + (issue.leaf? ? 'leaf' : 'parent'), + :label => label, :issue => issue, + :markers => !issue.leaf?) + when :image + image_task(options, coords, :label => label) + when :pdf + pdf_task(options, coords, :label => label) + end + else + '' + end + end + + # Generates a gantt image + # Only defined if RMagick is avalaible + def to_image(format='PNG') + date_to = (@date_from >> @months) - 1 + show_weeks = @zoom > 1 + show_days = @zoom > 2 + subject_width = 400 + header_height = 18 + # width of one day in pixels + zoom = @zoom * 2 + g_width = (@date_to - @date_from + 1) * zoom + g_height = 20 * number_of_rows + 30 + headers_height = (show_weeks ? 2 * header_height : header_height) + height = g_height + headers_height + imgl = Magick::ImageList.new + imgl.new_image(subject_width + g_width + 1, height) + gc = Magick::Draw.new + gc.font = Redmine::Configuration['rmagick_font_path'] || "" + # Subjects + gc.stroke('transparent') + subjects(:image => gc, :top => (headers_height + 20), :indent => 4, :format => :image) + # Months headers + month_f = @date_from + left = subject_width + @months.times do + width = ((month_f >> 1) - month_f) * zoom + gc.fill('white') + gc.stroke('grey') + gc.stroke_width(1) + gc.rectangle(left, 0, left + width, height) + gc.fill('black') + gc.stroke('transparent') + gc.stroke_width(1) + gc.text(left.round + 8, 14, "#{month_f.year}-#{month_f.month}") + left = left + width + month_f = month_f >> 1 + end + # Weeks headers + if show_weeks + left = subject_width + height = header_height + if @date_from.cwday == 1 + # date_from is monday + week_f = date_from + else + # find next monday after date_from + week_f = @date_from + (7 - @date_from.cwday + 1) + width = (7 - @date_from.cwday + 1) * zoom + gc.fill('white') + gc.stroke('grey') + gc.stroke_width(1) + gc.rectangle(left, header_height, left + width, 2 * header_height + g_height - 1) + left = left + width + end + while week_f <= date_to + width = (week_f + 6 <= date_to) ? 7 * zoom : (date_to - week_f + 1) * zoom + gc.fill('white') + gc.stroke('grey') + gc.stroke_width(1) + gc.rectangle(left.round, header_height, left.round + width, 2 * header_height + g_height - 1) + gc.fill('black') + gc.stroke('transparent') + gc.stroke_width(1) + gc.text(left.round + 2, header_height + 14, week_f.cweek.to_s) + left = left + width + week_f = week_f + 7 + end + end + # Days details (week-end in grey) + if show_days + left = subject_width + height = g_height + header_height - 1 + wday = @date_from.cwday + (date_to - @date_from + 1).to_i.times do + width = zoom + gc.fill(non_working_week_days.include?(wday) ? '#eee' : 'white') + gc.stroke('#ddd') + gc.stroke_width(1) + gc.rectangle(left, 2 * header_height, left + width, 2 * header_height + g_height - 1) + left = left + width + wday = wday + 1 + wday = 1 if wday > 7 + end + end + # border + gc.fill('transparent') + gc.stroke('grey') + gc.stroke_width(1) + gc.rectangle(0, 0, subject_width + g_width, headers_height) + gc.stroke('black') + gc.rectangle(0, 0, subject_width + g_width, g_height + headers_height - 1) + # content + top = headers_height + 20 + gc.stroke('transparent') + lines(:image => gc, :top => top, :zoom => zoom, + :subject_width => subject_width, :format => :image) + # today red line + if Date.today >= @date_from and Date.today <= date_to + gc.stroke('red') + x = (Date.today - @date_from + 1) * zoom + subject_width + gc.line(x, headers_height, x, headers_height + g_height - 1) + end + gc.draw(imgl) + imgl.format = format + imgl.to_blob + end if Object.const_defined?(:Magick) + + def to_pdf + pdf = ::Redmine::Export::PDF::ITCPDF.new(current_language) + pdf.SetTitle("#{l(:label_gantt)} #{project}") + pdf.alias_nb_pages + pdf.footer_date = format_date(Date.today) + pdf.AddPage("L") + pdf.SetFontStyle('B', 12) + pdf.SetX(15) + pdf.RDMCell(PDF::LeftPaneWidth, 20, project.to_s) + pdf.Ln + pdf.SetFontStyle('B', 9) + subject_width = PDF::LeftPaneWidth + header_height = 5 + headers_height = header_height + show_weeks = false + show_days = false + if self.months < 7 + show_weeks = true + headers_height = 2 * header_height + if self.months < 3 + show_days = true + headers_height = 3 * header_height + end + end + g_width = PDF.right_pane_width + zoom = (g_width) / (self.date_to - self.date_from + 1) + g_height = 120 + t_height = g_height + headers_height + y_start = pdf.GetY + # Months headers + month_f = self.date_from + left = subject_width + height = header_height + self.months.times do + width = ((month_f >> 1) - month_f) * zoom + pdf.SetY(y_start) + pdf.SetX(left) + pdf.RDMCell(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_height + if self.date_from.cwday == 1 + # self.date_from is monday + week_f = self.date_from + else + # find next monday after self.date_from + week_f = self.date_from + (7 - self.date_from.cwday + 1) + width = (7 - self.date_from.cwday + 1) * zoom-1 + pdf.SetY(y_start + header_height) + pdf.SetX(left) + pdf.RDMCell(width + 1, height, "", "LTR") + left = left + width + 1 + end + while week_f <= self.date_to + width = (week_f + 6 <= self.date_to) ? 7 * zoom : (self.date_to - week_f + 1) * zoom + pdf.SetY(y_start + header_height) + pdf.SetX(left) + pdf.RDMCell(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_height + wday = self.date_from.cwday + pdf.SetFontStyle('B', 7) + (self.date_to - self.date_from + 1).to_i.times do + width = zoom + pdf.SetY(y_start + 2 * header_height) + pdf.SetX(left) + pdf.RDMCell(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.RDMCell(subject_width + g_width - 15, headers_height, "", 1) + # Tasks + top = headers_height + y_start + options = { + :top => top, + :zoom => zoom, + :subject_width => subject_width, + :g_width => g_width, + :indent => 0, + :indent_increment => 5, + :top_increment => 5, + :format => :pdf, + :pdf => pdf + } + render(options) + pdf.Output + end + + private + + def coordinates(start_date, end_date, progress, zoom=nil) + zoom ||= @zoom + coords = {} + if start_date && end_date && start_date < self.date_to && end_date > self.date_from + if start_date > self.date_from + coords[:start] = start_date - self.date_from + coords[:bar_start] = start_date - self.date_from + else + coords[:bar_start] = 0 + end + if end_date < self.date_to + coords[:end] = end_date - self.date_from + coords[:bar_end] = end_date - self.date_from + 1 + else + coords[:bar_end] = self.date_to - self.date_from + 1 + end + if progress + progress_date = calc_progress_date(start_date, end_date, progress) + if progress_date > self.date_from && progress_date > start_date + if progress_date < self.date_to + coords[:bar_progress_end] = progress_date - self.date_from + else + coords[:bar_progress_end] = self.date_to - self.date_from + 1 + end + end + if progress_date < Date.today + late_date = [Date.today, end_date].min + if late_date > self.date_from && late_date > start_date + if late_date < self.date_to + coords[:bar_late_end] = late_date - self.date_from + 1 + else + coords[:bar_late_end] = self.date_to - self.date_from + 1 + end + end + end + end + end + # Transforms dates into pixels witdh + coords.keys.each do |key| + coords[key] = (coords[key] * zoom).floor + end + coords + end + + def calc_progress_date(start_date, end_date, progress) + start_date + (end_date - start_date + 1) * (progress / 100.0) + end + + # TODO: Sorts a collection of issues by start_date, due_date, id for gantt rendering + def sort_issues!(issues) + issues.sort! { |a, b| gantt_issue_compare(a, b) } + end + + # TODO: top level issues should be sorted by start date + def gantt_issue_compare(x, y) + if x.root_id == y.root_id + x.lft <=> y.lft + else + x.root_id <=> y.root_id + end + end + + def current_limit + if @max_rows + @max_rows - @number_of_rows + else + nil + end + end + + def abort? + if @max_rows && @number_of_rows >= @max_rows + @truncated = true + end + end + + def pdf_new_page?(options) + if options[:top] > 180 + options[:pdf].Line(15, options[:top], PDF::TotalWidth, options[:top]) + options[:pdf].AddPage("L") + options[:top] = 15 + options[:pdf].Line(15, options[:top] - 0.1, PDF::TotalWidth, options[:top] - 0.1) + end + end + + def html_subject(params, subject, options={}) + style = "position: absolute;top:#{params[:top]}px;left:#{params[:indent]}px;" + style << "width:#{params[:subject_width] - params[:indent]}px;" if params[:subject_width] + output = view.content_tag(:div, subject, + :class => options[:css], :style => style, + :title => options[:title], + :id => options[:id]) + @subjects << output + output + end + + def pdf_subject(params, subject, options={}) + params[:pdf].SetY(params[:top]) + params[:pdf].SetX(15) + char_limit = PDF::MaxCharactorsForSubject - params[:indent] + params[:pdf].RDMCell(params[:subject_width] - 15, 5, + (" " * params[:indent]) + + subject.to_s.sub(/^(.{#{char_limit}}[^\s]*\s).*$/, '\1 (...)'), + "LR") + params[:pdf].SetY(params[:top]) + params[:pdf].SetX(params[:subject_width]) + params[:pdf].RDMCell(params[:g_width], 5, "", "LR") + end + + def image_subject(params, subject, options={}) + params[:image].fill('black') + params[:image].stroke('transparent') + params[:image].stroke_width(1) + params[:image].text(params[:indent], params[:top] + 2, subject) + end + + def issue_relations(issue) + rels = {} + if relations[issue.id] + relations[issue.id].each do |relation| + (rels[relation.relation_type] ||= []) << relation.issue_to_id + end + end + rels + end + + def html_task(params, coords, options={}) + output = '' + # Renders the task bar, with progress and late + if coords[:bar_start] && coords[:bar_end] + width = coords[:bar_end] - coords[:bar_start] - 2 + style = "" + style << "top:#{params[:top]}px;" + style << "left:#{coords[:bar_start]}px;" + style << "width:#{width}px;" + html_id = "task-todo-issue-#{options[:issue].id}" if options[:issue] + html_id = "task-todo-version-#{options[:version].id}" if options[:version] + content_opt = {:style => style, + :class => "#{options[:css]} task_todo", + :id => html_id} + if options[:issue] + rels = issue_relations(options[:issue]) + if rels.present? + content_opt[:data] = {"rels" => rels.to_json} + end + end + output << view.content_tag(:div, ' '.html_safe, content_opt) + if coords[:bar_late_end] + width = coords[:bar_late_end] - coords[:bar_start] - 2 + style = "" + style << "top:#{params[:top]}px;" + style << "left:#{coords[:bar_start]}px;" + style << "width:#{width}px;" + output << view.content_tag(:div, ' '.html_safe, + :style => style, + :class => "#{options[:css]} task_late") + end + if coords[:bar_progress_end] + width = coords[:bar_progress_end] - coords[:bar_start] - 2 + style = "" + style << "top:#{params[:top]}px;" + style << "left:#{coords[:bar_start]}px;" + style << "width:#{width}px;" + html_id = "task-done-issue-#{options[:issue].id}" if options[:issue] + html_id = "task-done-version-#{options[:version].id}" if options[:version] + output << view.content_tag(:div, ' '.html_safe, + :style => style, + :class => "#{options[:css]} task_done", + :id => html_id) + end + end + # Renders the markers + if options[:markers] + if coords[:start] + style = "" + style << "top:#{params[:top]}px;" + style << "left:#{coords[:start]}px;" + style << "width:15px;" + output << view.content_tag(:div, ' '.html_safe, + :style => style, + :class => "#{options[:css]} marker starting") + end + if coords[:end] + style = "" + style << "top:#{params[:top]}px;" + style << "left:#{coords[:end] + params[:zoom]}px;" + style << "width:15px;" + output << view.content_tag(:div, ' '.html_safe, + :style => style, + :class => "#{options[:css]} marker ending") + end + end + # Renders the label on the right + if options[:label] + style = "" + style << "top:#{params[:top]}px;" + style << "left:#{(coords[:bar_end] || 0) + 8}px;" + style << "width:15px;" + output << view.content_tag(:div, options[:label], + :style => style, + :class => "#{options[:css]} label") + end + # Renders the tooltip + if options[:issue] && coords[:bar_start] && coords[:bar_end] + s = view.content_tag(:span, + view.render_issue_tooltip(options[:issue]).html_safe, + :class => "tip") + style = "" + style << "position: absolute;" + style << "top:#{params[:top]}px;" + style << "left:#{coords[:bar_start]}px;" + style << "width:#{coords[:bar_end] - coords[:bar_start]}px;" + style << "height:12px;" + output << view.content_tag(:div, s.html_safe, + :style => style, + :class => "tooltip") + end + @lines << output + output + end + + def pdf_task(params, coords, options={}) + height = options[:height] || 2 + # Renders the task bar, with progress and late + if coords[:bar_start] && coords[:bar_end] + params[:pdf].SetY(params[:top] + 1.5) + params[:pdf].SetX(params[:subject_width] + coords[:bar_start]) + params[:pdf].SetFillColor(200, 200, 200) + params[:pdf].RDMCell(coords[:bar_end] - coords[:bar_start], height, "", 0, 0, "", 1) + if coords[:bar_late_end] + params[:pdf].SetY(params[:top] + 1.5) + params[:pdf].SetX(params[:subject_width] + coords[:bar_start]) + params[:pdf].SetFillColor(255, 100, 100) + params[:pdf].RDMCell(coords[:bar_late_end] - coords[:bar_start], height, "", 0, 0, "", 1) + end + if coords[:bar_progress_end] + params[:pdf].SetY(params[:top] + 1.5) + params[:pdf].SetX(params[:subject_width] + coords[:bar_start]) + params[:pdf].SetFillColor(90, 200, 90) + params[:pdf].RDMCell(coords[:bar_progress_end] - coords[:bar_start], height, "", 0, 0, "", 1) + end + end + # Renders the markers + if options[:markers] + if coords[:start] + params[:pdf].SetY(params[:top] + 1) + params[:pdf].SetX(params[:subject_width] + coords[:start] - 1) + params[:pdf].SetFillColor(50, 50, 200) + params[:pdf].RDMCell(2, 2, "", 0, 0, "", 1) + end + if coords[:end] + params[:pdf].SetY(params[:top] + 1) + params[:pdf].SetX(params[:subject_width] + coords[:end] - 1) + params[:pdf].SetFillColor(50, 50, 200) + params[:pdf].RDMCell(2, 2, "", 0, 0, "", 1) + end + end + # Renders the label on the right + if options[:label] + params[:pdf].SetX(params[:subject_width] + (coords[:bar_end] || 0) + 5) + params[:pdf].RDMCell(30, 2, options[:label]) + end + end + + def image_task(params, coords, options={}) + height = options[:height] || 6 + # Renders the task bar, with progress and late + if coords[:bar_start] && coords[:bar_end] + params[:image].fill('#aaa') + params[:image].rectangle(params[:subject_width] + coords[:bar_start], + params[:top], + params[:subject_width] + coords[:bar_end], + params[:top] - height) + if coords[:bar_late_end] + params[:image].fill('#f66') + params[:image].rectangle(params[:subject_width] + coords[:bar_start], + params[:top], + params[:subject_width] + coords[:bar_late_end], + params[:top] - height) + end + if coords[:bar_progress_end] + params[:image].fill('#00c600') + params[:image].rectangle(params[:subject_width] + coords[:bar_start], + params[:top], + params[:subject_width] + coords[:bar_progress_end], + params[:top] - height) + end + end + # Renders the markers + if options[:markers] + if coords[:start] + x = params[:subject_width] + coords[:start] + y = params[:top] - height / 2 + params[:image].fill('blue') + params[:image].polygon(x - 4, y, x, y - 4, x + 4, y, x, y + 4) + end + if coords[:end] + x = params[:subject_width] + coords[:end] + params[:zoom] + y = params[:top] - height / 2 + params[:image].fill('blue') + params[:image].polygon(x - 4, y, x, y - 4, x + 4, y, x, y + 4) + end + end + # Renders the label on the right + if options[:label] + params[:image].fill('black') + params[:image].text(params[:subject_width] + (coords[:bar_end] || 0) + 5, + params[:top] + 1, + options[:label]) + end + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/02/02c68441083bdea630158440f0e7d9d62ff8f790.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/02/02c68441083bdea630158440f0e7d9d62ff8f790.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,822 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class MailHandlerTest < ActiveSupport::TestCase + fixtures :users, :projects, :enabled_modules, :roles, + :members, :member_roles, :users, + :issues, :issue_statuses, + :workflows, :trackers, :projects_trackers, + :versions, :enumerations, :issue_categories, + :custom_fields, :custom_fields_trackers, :custom_fields_projects, + :boards, :messages + + FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler' + + def setup + ActionMailer::Base.deliveries.clear + Setting.notified_events = Redmine::Notifiable.all.collect(&:name) + end + + def teardown + Setting.clear_cache + end + + def test_add_issue + ActionMailer::Base.deliveries.clear + # This email contains: 'Project: onlinestore' + issue = submit_email('ticket_on_given_project.eml') + assert issue.is_a?(Issue) + assert !issue.new_record? + issue.reload + assert_equal Project.find(2), issue.project + assert_equal issue.project.trackers.first, issue.tracker + assert_equal 'New ticket on a given project', issue.subject + assert_equal User.find_by_login('jsmith'), issue.author + assert_equal IssueStatus.find_by_name('Resolved'), issue.status + assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.') + assert_equal '2010-01-01', issue.start_date.to_s + assert_equal '2010-12-31', issue.due_date.to_s + assert_equal User.find_by_login('jsmith'), issue.assigned_to + assert_equal Version.find_by_name('Alpha'), issue.fixed_version + assert_equal 2.5, issue.estimated_hours + assert_equal 30, issue.done_ratio + assert_equal [issue.id, 1, 2], [issue.root_id, issue.lft, issue.rgt] + # keywords should be removed from the email body + assert !issue.description.match(/^Project:/i) + assert !issue.description.match(/^Status:/i) + assert !issue.description.match(/^Start Date:/i) + # Email notification should be sent + mail = ActionMailer::Base.deliveries.last + assert_not_nil mail + assert mail.subject.include?('New ticket on a given project') + end + + def test_add_issue_with_default_tracker + # This email contains: 'Project: onlinestore' + issue = submit_email( + 'ticket_on_given_project.eml', + :issue => {:tracker => 'Support request'} + ) + assert issue.is_a?(Issue) + assert !issue.new_record? + issue.reload + assert_equal 'Support request', issue.tracker.name + end + + def test_add_issue_with_status + # This email contains: 'Project: onlinestore' and 'Status: Resolved' + issue = submit_email('ticket_on_given_project.eml') + assert issue.is_a?(Issue) + assert !issue.new_record? + issue.reload + assert_equal Project.find(2), issue.project + assert_equal IssueStatus.find_by_name("Resolved"), issue.status + end + + def test_add_issue_with_attributes_override + issue = submit_email( + 'ticket_with_attributes.eml', + :allow_override => 'tracker,category,priority' + ) + assert issue.is_a?(Issue) + assert !issue.new_record? + issue.reload + 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 'Feature request', issue.tracker.to_s + assert_equal 'Stock management', issue.category.to_s + assert_equal 'Urgent', issue.priority.to_s + assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.') + end + + def test_add_issue_with_group_assignment + with_settings :issue_group_assignment => '1' do + issue = submit_email('ticket_on_given_project.eml') do |email| + email.gsub!('Assigned to: John Smith', 'Assigned to: B Team') + end + assert issue.is_a?(Issue) + assert !issue.new_record? + issue.reload + assert_equal Group.find(11), issue.assigned_to + end + end + + def test_add_issue_with_partial_attributes_override + issue = submit_email( + 'ticket_with_attributes.eml', + :issue => {:priority => 'High'}, + :allow_override => ['tracker'] + ) + assert issue.is_a?(Issue) + assert !issue.new_record? + issue.reload + 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 'Feature request', issue.tracker.to_s + assert_nil issue.category + assert_equal 'High', issue.priority.to_s + assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.') + end + + def test_add_issue_with_spaces_between_attribute_and_separator + issue = submit_email( + 'ticket_with_spaces_between_attribute_and_separator.eml', + :allow_override => 'tracker,category,priority' + ) + assert issue.is_a?(Issue) + assert !issue.new_record? + issue.reload + 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 'Feature request', issue.tracker.to_s + assert_equal 'Stock management', issue.category.to_s + assert_equal 'Urgent', issue.priority.to_s + assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.') + end + + def test_add_issue_with_attachment_to_specific_project + issue = submit_email('ticket_with_attachment.eml', :issue => {:project => 'onlinestore'}) + assert issue.is_a?(Issue) + assert !issue.new_record? + issue.reload + assert_equal 'Ticket created by email with attachment', issue.subject + assert_equal User.find_by_login('jsmith'), issue.author + assert_equal Project.find(2), issue.project + assert_equal 'This is a new ticket with attachments', issue.description + # Attachment properties + assert_equal 1, issue.attachments.size + assert_equal 'Paella.jpg', issue.attachments.first.filename + assert_equal 'image/jpeg', issue.attachments.first.content_type + 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 'PostgreSQL', issue.custom_field_value(1) + assert_equal 'Value for a custom field', issue.custom_field_value(2) + assert !issue.description.match(/^searchable field:/i) + end + + def test_add_issue_with_version_custom_fields + field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true, :tracker_ids => [1,2,3]) + + issue = submit_email('ticket_with_custom_fields.eml', :issue => {:project => 'ecookbook'}) do |email| + email << "Affected version: 1.0\n" + end + assert issue.is_a?(Issue) + assert !issue.new_record? + issue.reload + assert_equal '2', issue.custom_field_value(field) + end + + def test_add_issue_should_match_assignee_on_display_name + user = User.generate!(:firstname => 'Foo Bar', :lastname => 'Foo Baz') + User.add_to_project(user, Project.find(2)) + issue = submit_email('ticket_on_given_project.eml') do |email| + email.sub!(/^Assigned to.*$/, 'Assigned to: Foo Bar Foo baz') + end + assert issue.is_a?(Issue) + assert_equal user, issue.assigned_to + 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.watcher_user_ids.size + end + + def test_add_issue_by_unknown_user + assert_no_difference 'User.count' do + assert_equal false, + submit_email( + 'ticket_by_unknown_user.eml', + :issue => {:project => 'ecookbook'} + ) + end + end + + def test_add_issue_by_anonymous_user + Role.anonymous.add_permission!(:add_issues) + assert_no_difference 'User.count' do + issue = submit_email( + 'ticket_by_unknown_user.eml', + :issue => {:project => 'ecookbook'}, + :unknown_user => 'accept' + ) + assert issue.is_a?(Issue) + assert issue.author.anonymous? + end + end + + def test_add_issue_by_anonymous_user_with_no_from_address + Role.anonymous.add_permission!(:add_issues) + assert_no_difference 'User.count' do + issue = submit_email( + 'ticket_by_empty_user.eml', + :issue => {:project => 'ecookbook'}, + :unknown_user => 'accept' + ) + assert issue.is_a?(Issue) + assert issue.author.anonymous? + end + end + + def test_add_issue_by_anonymous_user_on_private_project + Role.anonymous.add_permission!(:add_issues) + assert_no_difference 'User.count' do + assert_no_difference 'Issue.count' do + assert_equal false, + submit_email( + 'ticket_by_unknown_user.eml', + :issue => {:project => 'onlinestore'}, + :unknown_user => 'accept' + ) + end + end + end + + def test_add_issue_by_anonymous_user_on_private_project_without_permission_check + assert_no_difference 'User.count' do + assert_difference 'Issue.count' do + issue = submit_email( + 'ticket_by_unknown_user.eml', + :issue => {:project => 'onlinestore'}, + :no_permission_check => '1', + :unknown_user => 'accept' + ) + assert issue.is_a?(Issue) + assert issue.author.anonymous? + assert !issue.project.is_public? + assert_equal [issue.id, 1, 2], [issue.root_id, issue.lft, issue.rgt] + end + end + end + + def test_add_issue_by_created_user + Setting.default_language = 'en' + assert_difference 'User.count' do + issue = submit_email( + 'ticket_by_unknown_user.eml', + :issue => {:project => 'ecookbook'}, + :unknown_user => 'create' + ) + assert issue.is_a?(Issue) + assert issue.author.active? + assert_equal 'john.doe@somenet.foo', issue.author.mail + assert_equal 'John', issue.author.firstname + assert_equal 'Doe', issue.author.lastname + + # account information + email = ActionMailer::Base.deliveries.first + assert_not_nil email + assert email.subject.include?('account activation') + login = mail_body(email).match(/\* Login: (.*)$/)[1].strip + password = mail_body(email).match(/\* Password: (.*)$/)[1].strip + assert_equal issue.author, User.try_to_login(login, password) + end + end + + def test_created_user_should_be_added_to_groups + group1 = Group.generate! + group2 = Group.generate! + + assert_difference 'User.count' do + submit_email( + 'ticket_by_unknown_user.eml', + :issue => {:project => 'ecookbook'}, + :unknown_user => 'create', + :default_group => "#{group1.name},#{group2.name}" + ) + end + user = User.order('id DESC').first + assert_same_elements [group1, group2], user.groups + end + + def test_created_user_should_not_receive_account_information_with_no_account_info_option + assert_difference 'User.count' do + submit_email( + 'ticket_by_unknown_user.eml', + :issue => {:project => 'ecookbook'}, + :unknown_user => 'create', + :no_account_notice => '1' + ) + end + + # only 1 email for the new issue notification + assert_equal 1, ActionMailer::Base.deliveries.size + email = ActionMailer::Base.deliveries.first + assert_include 'Ticket by unknown user', email.subject + end + + def test_created_user_should_have_mail_notification_to_none_with_no_notification_option + assert_difference 'User.count' do + submit_email( + 'ticket_by_unknown_user.eml', + :issue => {:project => 'ecookbook'}, + :unknown_user => 'create', + :no_notification => '1' + ) + end + user = User.order('id DESC').first + assert_equal 'none', user.mail_notification + end + + def test_add_issue_without_from_header + Role.anonymous.add_permission!(:add_issues) + assert_equal false, submit_email('ticket_without_from_header.eml') + end + + def test_add_issue_with_invalid_attributes + issue = submit_email( + 'ticket_with_invalid_attributes.eml', + :allow_override => 'tracker,category,priority' + ) + assert issue.is_a?(Issue) + assert !issue.new_record? + issue.reload + assert_nil issue.assigned_to + assert_nil issue.start_date + assert_nil issue.due_date + assert_equal 0, issue.done_ratio + assert_equal 'Normal', issue.priority.to_s + assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.') + end + + def test_add_issue_with_localized_attributes + User.find_by_mail('jsmith@somenet.foo').update_attribute 'language', 'fr' + issue = submit_email( + 'ticket_with_localized_attributes.eml', + :allow_override => 'tracker,category,priority' + ) + assert issue.is_a?(Issue) + assert !issue.new_record? + issue.reload + 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 'Feature request', issue.tracker.to_s + assert_equal 'Stock management', issue.category.to_s + assert_equal 'Urgent', issue.priority.to_s + assert issue.description.include?('Lorem ipsum dolor sit amet, consectetuer adipiscing elit.') + end + + def test_add_issue_with_japanese_keywords + ja_dev = "\xe9\x96\x8b\xe7\x99\xba" + ja_dev.force_encoding('UTF-8') if ja_dev.respond_to?(:force_encoding) + tracker = Tracker.create!(:name => ja_dev) + Project.find(1).trackers << tracker + issue = submit_email( + 'japanese_keywords_iso_2022_jp.eml', + :issue => {:project => 'ecookbook'}, + :allow_override => 'tracker' + ) + assert_kind_of Issue, issue + assert_equal tracker, issue.tracker + end + + def test_add_issue_from_apple_mail + issue = submit_email( + 'apple_mail_with_attachment.eml', + :issue => {:project => 'ecookbook'} + ) + assert_kind_of Issue, issue + assert_equal 1, issue.attachments.size + + attachment = issue.attachments.first + assert_equal 'paella.jpg', attachment.filename + assert_equal 10790, attachment.filesize + assert File.exist?(attachment.diskfile) + assert_equal 10790, File.size(attachment.diskfile) + assert_equal 'caaf384198bcbc9563ab5c058acd73cd', attachment.digest + end + + def test_thunderbird_with_attachment_ja + issue = submit_email( + 'thunderbird_with_attachment_ja.eml', + :issue => {:project => 'ecookbook'} + ) + assert_kind_of Issue, issue + assert_equal 1, issue.attachments.size + ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88.txt" + ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding) + attachment = issue.attachments.first + assert_equal ja, attachment.filename + assert_equal 5, attachment.filesize + assert File.exist?(attachment.diskfile) + assert_equal 5, File.size(attachment.diskfile) + assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest + end + + def test_gmail_with_attachment_ja + issue = submit_email( + 'gmail_with_attachment_ja.eml', + :issue => {:project => 'ecookbook'} + ) + assert_kind_of Issue, issue + assert_equal 1, issue.attachments.size + ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88.txt" + ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding) + attachment = issue.attachments.first + assert_equal ja, attachment.filename + assert_equal 5, attachment.filesize + assert File.exist?(attachment.diskfile) + assert_equal 5, File.size(attachment.diskfile) + assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest + end + + def test_thunderbird_with_attachment_latin1 + issue = submit_email( + 'thunderbird_with_attachment_iso-8859-1.eml', + :issue => {:project => 'ecookbook'} + ) + assert_kind_of Issue, issue + assert_equal 1, issue.attachments.size + u = "" + u.force_encoding('UTF-8') if u.respond_to?(:force_encoding) + u1 = "\xc3\x84\xc3\xa4\xc3\x96\xc3\xb6\xc3\x9c\xc3\xbc" + u1.force_encoding('UTF-8') if u1.respond_to?(:force_encoding) + 11.times { u << u1 } + attachment = issue.attachments.first + assert_equal "#{u}.png", attachment.filename + assert_equal 130, attachment.filesize + assert File.exist?(attachment.diskfile) + assert_equal 130, File.size(attachment.diskfile) + assert_equal '4d80e667ac37dddfe05502530f152abb', attachment.digest + end + + def test_gmail_with_attachment_latin1 + issue = submit_email( + 'gmail_with_attachment_iso-8859-1.eml', + :issue => {:project => 'ecookbook'} + ) + assert_kind_of Issue, issue + assert_equal 1, issue.attachments.size + u = "" + u.force_encoding('UTF-8') if u.respond_to?(:force_encoding) + u1 = "\xc3\x84\xc3\xa4\xc3\x96\xc3\xb6\xc3\x9c\xc3\xbc" + u1.force_encoding('UTF-8') if u1.respond_to?(:force_encoding) + 11.times { u << u1 } + attachment = issue.attachments.first + assert_equal "#{u}.txt", attachment.filename + assert_equal 5, attachment.filesize + assert File.exist?(attachment.diskfile) + assert_equal 5, File.size(attachment.diskfile) + assert_equal 'd8e8fca2dc0f896fd7cb4cb0031ba249', attachment.digest + end + + def test_add_issue_with_iso_8859_1_subject + issue = submit_email( + 'subject_as_iso-8859-1.eml', + :issue => {:project => 'ecookbook'} + ) + str = "Testmail from Webmail: \xc3\xa4 \xc3\xb6 \xc3\xbc..." + str.force_encoding('UTF-8') if str.respond_to?(:force_encoding) + assert_kind_of Issue, issue + assert_equal str, issue.subject + end + + def test_add_issue_with_japanese_subject + issue = submit_email( + 'subject_japanese_1.eml', + :issue => {:project => 'ecookbook'} + ) + assert_kind_of Issue, issue + ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88" + ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding) + assert_equal ja, issue.subject + end + + def test_add_issue_with_no_subject_header + issue = submit_email( + 'no_subject_header.eml', + :issue => {:project => 'ecookbook'} + ) + assert_kind_of Issue, issue + assert_equal '(no subject)', issue.subject + end + + def test_add_issue_with_mixed_japanese_subject + issue = submit_email( + 'subject_japanese_2.eml', + :issue => {:project => 'ecookbook'} + ) + assert_kind_of Issue, issue + ja = "Re: \xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88" + ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding) + assert_equal ja, issue.subject + end + + def test_should_ignore_emails_from_locked_users + User.find(2).lock! + + MailHandler.any_instance.expects(:dispatch).never + assert_no_difference 'Issue.count' do + assert_equal false, submit_email('ticket_on_given_project.eml') + end + end + + def test_should_ignore_emails_from_emission_address + Role.anonymous.add_permission!(:add_issues) + assert_no_difference 'User.count' do + assert_equal false, + submit_email( + 'ticket_from_emission_address.eml', + :issue => {:project => 'ecookbook'}, + :unknown_user => 'create' + ) + end + end + + def test_should_ignore_auto_replied_emails + MailHandler.any_instance.expects(:dispatch).never + [ + "X-Auto-Response-Suppress: OOF", + "Auto-Submitted: auto-replied", + "Auto-Submitted: Auto-Replied", + "Auto-Submitted: auto-generated" + ].each do |header| + raw = IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml')) + raw = header + "\n" + raw + + assert_no_difference 'Issue.count' do + assert_equal false, MailHandler.receive(raw), "email with #{header} header was not ignored" + end + end + end + + def test_add_issue_should_send_email_notification + Setting.notified_events = ['issue_added'] + ActionMailer::Base.deliveries.clear + # This email contains: 'Project: onlinestore' + issue = submit_email('ticket_on_given_project.eml') + assert issue.is_a?(Issue) + assert_equal 1, ActionMailer::Base.deliveries.size + end + + def test_update_issue + journal = submit_email('ticket_reply.eml') + assert journal.is_a?(Journal) + assert_equal User.find_by_login('jsmith'), journal.user + assert_equal Issue.find(2), journal.journalized + assert_match /This is reply/, journal.notes + assert_equal false, journal.private_notes + assert_equal 'Feature request', journal.issue.tracker.name + end + + def test_update_issue_with_attribute_changes + # This email contains: 'Status: Resolved' + journal = submit_email('ticket_reply_with_status.eml') + assert journal.is_a?(Journal) + issue = Issue.find(journal.issue.id) + assert_equal User.find_by_login('jsmith'), journal.user + assert_equal Issue.find(2), journal.journalized + assert_match /This is reply/, journal.notes + assert_equal 'Feature request', journal.issue.tracker.name + assert_equal IssueStatus.find_by_name("Resolved"), issue.status + assert_equal '2010-01-01', issue.start_date.to_s + assert_equal '2010-12-31', issue.due_date.to_s + assert_equal User.find_by_login('jsmith'), issue.assigned_to + assert_equal "52.6", issue.custom_value_for(CustomField.find_by_name('Float field')).value + # keywords should be removed from the email body + assert !journal.notes.match(/^Status:/i) + assert !journal.notes.match(/^Start Date:/i) + end + + def test_update_issue_with_attachment + assert_difference 'Journal.count' do + assert_difference 'JournalDetail.count' do + assert_difference 'Attachment.count' do + assert_no_difference 'Issue.count' do + journal = submit_email('ticket_with_attachment.eml') do |raw| + raw.gsub! /^Subject: .*$/, 'Subject: Re: [Cookbook - Feature #2] (New) Add ingredients categories' + end + end + end + end + end + journal = Journal.first(:order => 'id DESC') + assert_equal Issue.find(2), journal.journalized + assert_equal 1, journal.details.size + + detail = journal.details.first + assert_equal 'attachment', detail.property + assert_equal 'Paella.jpg', detail.value + end + + def test_update_issue_should_send_email_notification + ActionMailer::Base.deliveries.clear + journal = submit_email('ticket_reply.eml') + assert journal.is_a?(Journal) + assert_equal 1, ActionMailer::Base.deliveries.size + end + + def test_update_issue_should_not_set_defaults + journal = submit_email( + 'ticket_reply.eml', + :issue => {:tracker => 'Support request', :priority => 'High'} + ) + assert journal.is_a?(Journal) + assert_match /This is reply/, journal.notes + assert_equal 'Feature request', journal.issue.tracker.name + assert_equal 'Normal', journal.issue.priority.name + end + + def test_replying_to_a_private_note_should_add_reply_as_private + private_journal = Journal.create!(:notes => 'Private notes', :journalized => Issue.find(1), :private_notes => true, :user_id => 2) + + assert_difference 'Journal.count' do + journal = submit_email('ticket_reply.eml') do |email| + email.sub! %r{^In-Reply-To:.*$}, "In-Reply-To: " + end + + assert_kind_of Journal, journal + assert_match /This is reply/, journal.notes + assert_equal true, journal.private_notes + end + 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 + + test "truncate emails with no setting should add the entire email into the issue" do + with_settings :mail_handler_body_delimiters => '' do + issue = submit_email('ticket_on_given_project.eml') + assert_issue_created(issue) + assert issue.description.include?('---') + assert issue.description.include?('This paragraph is after the delimiter') + end + end + + test "truncate emails with a single string should truncate the email at the delimiter for the issue" do + with_settings :mail_handler_body_delimiters => '---' do + issue = submit_email('ticket_on_given_project.eml') + assert_issue_created(issue) + assert issue.description.include?('This paragraph is before delimiters') + assert issue.description.include?('--- This line starts with a delimiter') + assert !issue.description.match(/^---$/) + assert !issue.description.include?('This paragraph is after the delimiter') + end + end + + test "truncate emails with a single quoted reply should truncate the email at the delimiter with the quoted reply symbols (>)" do + with_settings :mail_handler_body_delimiters => '--- Reply above. Do not remove this line. ---' do + journal = submit_email('issue_update_with_quoted_reply_above.eml') + assert journal.is_a?(Journal) + assert journal.notes.include?('An update to the issue by the sender.') + assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---")) + assert !journal.notes.include?('Looks like the JSON api for projects was missed.') + end + end + + test "truncate emails with multiple quoted replies should truncate the email at the delimiter with the quoted reply symbols (>)" do + with_settings :mail_handler_body_delimiters => '--- Reply above. Do not remove this line. ---' do + journal = submit_email('issue_update_with_multiple_quoted_reply_above.eml') + assert journal.is_a?(Journal) + assert journal.notes.include?('An update to the issue by the sender.') + assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---")) + assert !journal.notes.include?('Looks like the JSON api for projects was missed.') + end + end + + test "truncate emails with multiple strings should truncate the email at the first delimiter found (BREAK)" do + with_settings :mail_handler_body_delimiters => "---\nBREAK" do + issue = submit_email('ticket_on_given_project.eml') + assert_issue_created(issue) + assert issue.description.include?('This paragraph is before delimiters') + assert !issue.description.include?('BREAK') + assert !issue.description.include?('This paragraph is between delimiters') + assert !issue.description.match(/^---$/) + assert !issue.description.include?('This paragraph is after the delimiter') + end + end + + def test_email_with_long_subject_line + issue = submit_email('ticket_with_long_subject.eml') + assert issue.is_a?(Issue) + assert_equal issue.subject, 'New ticket on a given project with a very long subject line which exceeds 255 chars and should not be ignored but chopped off. And if the subject line is still not long enough, we just add more text. And more text. Wow, this is really annoying. Especially, if you have nothing to say...'[0,255] + end + + def test_new_user_from_attributes_should_return_valid_user + to_test = { + # [address, name] => [login, firstname, lastname] + ['jsmith@example.net', nil] => ['jsmith@example.net', 'jsmith', '-'], + ['jsmith@example.net', 'John'] => ['jsmith@example.net', 'John', '-'], + ['jsmith@example.net', 'John Smith'] => ['jsmith@example.net', 'John', 'Smith'], + ['jsmith@example.net', 'John Paul Smith'] => ['jsmith@example.net', 'John', 'Paul Smith'], + ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsTheMaximumLength Smith'] => ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsT', 'Smith'], + ['jsmith@example.net', 'John AVeryLongLastnameThatExceedsTheMaximumLength'] => ['jsmith@example.net', 'John', 'AVeryLongLastnameThatExceedsTh'] + } + + to_test.each do |attrs, expected| + user = MailHandler.new_user_from_attributes(attrs.first, attrs.last) + + assert user.valid?, user.errors.full_messages.to_s + assert_equal attrs.first, user.mail + assert_equal expected[0], user.login + assert_equal expected[1], user.firstname + assert_equal expected[2], user.lastname + assert_equal 'only_my_events', user.mail_notification + end + end + + def test_new_user_from_attributes_should_respect_minimum_password_length + with_settings :password_min_length => 15 do + user = MailHandler.new_user_from_attributes('jsmith@example.net') + assert user.valid? + assert user.password.length >= 15 + end + end + + def test_new_user_from_attributes_should_use_default_login_if_invalid + user = MailHandler.new_user_from_attributes('foo+bar@example.net') + assert user.valid? + assert user.login =~ /^user[a-f0-9]+$/ + assert_equal 'foo+bar@example.net', user.mail + end + + def test_new_user_with_utf8_encoded_fullname_should_be_decoded + assert_difference 'User.count' do + issue = submit_email( + 'fullname_of_sender_as_utf8_encoded.eml', + :issue => {:project => 'ecookbook'}, + :unknown_user => 'create' + ) + end + + user = User.first(:order => 'id DESC') + assert_equal "foo@example.org", user.mail + str1 = "\xc3\x84\xc3\xa4" + str2 = "\xc3\x96\xc3\xb6" + str1.force_encoding('UTF-8') if str1.respond_to?(:force_encoding) + str2.force_encoding('UTF-8') if str2.respond_to?(:force_encoding) + assert_equal str1, user.firstname + assert_equal str2, user.lastname + end + + private + + def submit_email(filename, options={}) + raw = IO.read(File.join(FIXTURES_PATH, filename)) + yield raw if block_given? + MailHandler.receive(raw, options) + end + + def assert_issue_created(issue) + assert issue.is_a?(Issue) + assert !issue.new_record? + issue.reload + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/02/02c8aed5827fd37b4953522a150e77160bca0811.svn-base --- a/.svn/pristine/02/02c8aed5827fd37b4953522a150e77160bca0811.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ ---- -issue_categories_001: - name: Printing - project_id: 1 - assigned_to_id: 2 - id: 1 -issue_categories_002: - name: Recipes - project_id: 1 - assigned_to_id: - id: 2 -issue_categories_003: - name: Stock management - project_id: 2 - assigned_to_id: - id: 3 -issue_categories_004: - name: Printing - project_id: 2 - assigned_to_id: - id: 4 - \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/02/02dadac204e591b101d6e3bacd9c59cdeb84b1b6.svn-base --- a/.svn/pristine/02/02dadac204e591b101d6e3bacd9c59cdeb84b1b6.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +0,0 @@ -
    - <% if !@time_entry.nil? -%> -
  • <%= context_menu_link l(:button_edit), {:controller => 'timelog', :action => 'edit', :id => @time_entry}, - :class => 'icon-edit', :disabled => !@can[:edit] %>
  • - <% else %> -
  • <%= context_menu_link l(:button_edit), {:controller => 'timelog', :action => 'bulk_edit', :ids => @time_entries.collect(&:id)}, - :class => 'icon-edit', :disabled => !@can[:edit] %>
  • - <% end %> - - <%= call_hook(:view_time_entries_context_menu_start, {:time_entries => @time_entries, :can => @can, :back => @back }) %> - - <% if @activities.present? -%> -
  • - <%= l(:field_activity) %> -
      - <% @activities.each do |u| -%> -
    • <%= context_menu_link h(u.name), {:controller => 'timelog', :action => 'bulk_edit', :ids => @time_entries.collect(&:id), :time_entry => {'activity_id' => u}, :back_url => @back}, :method => :post, - :selected => (@time_entry && u == @time_entry.activity), :disabled => !@can[:edit] %>
    • - <% end -%> -
    • <%= context_menu_link l(:label_none), {:controller => 'timelog', :action => 'bulk_edit', :ids => @time_entries.collect(&:id), :time_entry => {'activity_id' => 'none'}, :back_url => @back}, :method => :post, - :selected => (@time_entry && @time_entry.activity.nil?), :disabled => !@can[:edit] %>
    • -
    -
  • - <% end %> - - <%= call_hook(:view_time_entries_context_menu_end, {:time_entries => @time_entries, :can => @can, :back => @back }) %> - -
  • - <%= context_menu_link l(:button_delete), - {:controller => 'timelog', :action => 'destroy', :ids => @time_entries.collect(&:id), :back_url => @back}, - :method => :delete, :confirm => l(:text_time_entries_destroy_confirmation), :class => 'icon-del', :disabled => !@can[:delete] %> -
  • -
diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/02/02fe391dd14e50d29ad45f8bf8dea30be02556c5.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/02/02fe391dd14e50d29ad45f8bf8dea30be02556c5.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,127 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 Themes + + # Return an array of installed themes + def self.themes + @@installed_themes ||= scan_themes + end + + # Rescan themes directory + def self.rescan + @@installed_themes = scan_themes + end + + # Return theme for given id, or nil if it's not found + def self.theme(id, options={}) + return nil if id.blank? + + found = themes.find {|t| t.id == id} + if found.nil? && options[:rescan] != false + rescan + found = theme(id, :rescan => false) + end + found + end + + # Class used to represent a theme + class Theme + attr_reader :path, :name, :dir + + def initialize(path) + @path = path + @dir = File.basename(path) + @name = @dir.humanize + @stylesheets = nil + @javascripts = nil + end + + # Directory name used as the theme id + def id; dir end + + def ==(theme) + theme.is_a?(Theme) && theme.dir == dir + end + + def <=>(theme) + name <=> theme.name + end + + def stylesheets + @stylesheets ||= assets("stylesheets", "css") + end + + def images + @images ||= assets("images") + end + + def javascripts + @javascripts ||= assets("javascripts", "js") + end + + def stylesheet_path(source) + "/themes/#{dir}/stylesheets/#{source}" + end + + def image_path(source) + "/themes/#{dir}/images/#{source}" + end + + def javascript_path(source) + "/themes/#{dir}/javascripts/#{source}" + end + + private + + def assets(dir, ext=nil) + if ext + Dir.glob("#{path}/#{dir}/*.#{ext}").collect {|f| File.basename(f).gsub(/\.#{ext}$/, '')} + else + Dir.glob("#{path}/#{dir}/*").collect {|f| File.basename(f)} + end + end + end + + private + + def self.scan_themes + dirs = Dir.glob("#{Rails.public_path}/themes/*").select do |f| + # A theme should at least override application.css + File.directory?(f) && File.exist?("#{f}/stylesheets/application.css") + end + dirs.collect {|dir| Theme.new(dir)}.sort + end + end +end + +module ApplicationHelper + def current_theme + unless instance_variable_defined?(:@current_theme) + @current_theme = Redmine::Themes.theme(Setting.ui_theme) + end + @current_theme + end + + # Returns the header tags for the current theme + def heads_for_theme + if current_theme && current_theme.javascripts.include?('theme') + javascript_include_tag current_theme.javascript_path('theme') + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/03/032e9aa73399010aaf3eb408ecd8fb8f296696b1.svn-base --- a/.svn/pristine/03/032e9aa73399010aaf3eb408ecd8fb8f296696b1.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,80 +0,0 @@ -
- <% if User.current.allowed_to?(:add_subprojects, @project) %> - <%= link_to l(:label_subproject_new), {:controller => 'projects', :action => 'new', :parent_id => @project}, :class => 'icon icon-add' %> - <% end %> -
- -

<%=l(:label_overview)%>

- -
-
- <%= textilizable @project.description %> -
-
    - <% unless @project.homepage.blank? %>
  • <%=l(:field_homepage)%>: <%= auto_link(h(@project.homepage)) %>
  • <% end %> - <% if @subprojects.any? %> -
  • <%=l(:label_subproject_plural)%>: - <%= @subprojects.collect{|p| link_to(h(p), :action => 'show', :id => p)}.join(", ").html_safe %>
  • - <% end %> - <% @project.visible_custom_field_values.each do |custom_value| %> - <% if !custom_value.value.blank? %> -
  • <%=h custom_value.custom_field.name %>: <%=h show_value(custom_value) %>
  • - <% end %> - <% end %> -
- - <% if User.current.allowed_to?(:view_issues, @project) %> -
-

<%=l(:label_issue_tracking)%>

-
    - <% for tracker in @trackers %> -
  • <%= link_to h(tracker.name), :controller => 'issues', :action => 'index', :project_id => @project, - :set_filter => 1, - "tracker_id" => tracker.id %>: - <%= l(:label_x_open_issues_abbr_on_total, :count => @open_issues_by_tracker[tracker].to_i, - :total => @total_issues_by_tracker[tracker].to_i) %> -
  • - <% end %> -
-

- <%= link_to l(:label_issue_view_all), :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1 %> - <% if User.current.allowed_to?(:view_calendar, @project, :global => true) %> - | <%= link_to(l(:label_calendar), :controller => 'calendars', :action => 'show', :project_id => @project) %> - <% end %> - <% if User.current.allowed_to?(:view_gantt, @project, :global => true) %> - | <%= link_to(l(:label_gantt), :controller => 'gantts', :action => 'show', :project_id => @project) %> - <% end %> -

-
- <% end %> - <%= call_hook(:view_projects_show_left, :project => @project) %> -
- -
- <%= render :partial => 'members_box' %> - - <% if @news.any? && authorize_for('news', 'index') %> -
-

<%=l(:label_news_latest)%>

- <%= render :partial => 'news/news', :collection => @news %> -

<%= link_to l(:label_news_view_all), :controller => 'news', :action => 'index', :project_id => @project %>

-
- <% end %> - <%= call_hook(:view_projects_show_right, :project => @project) %> -
- -<% content_for :sidebar do %> - <% if @total_hours.present? %> -

<%= l(:label_spent_time) %>

-

<%= l_hours(@total_hours) %>

-

<%= link_to(l(:label_details), {:controller => 'timelog', :action => 'index', :project_id => @project}) %> | - <%= link_to(l(:label_report), {:controller => 'time_entry_reports', :action => 'report', :project_id => @project}) %>

- <% end %> - <%= call_hook(:view_projects_show_sidebar_bottom, :project => @project) %> -<% end %> - -<% content_for :header_tags do %> -<%= auto_discovery_link_tag(:atom, {:controller => 'activities', :action => 'index', :id => @project, :format => 'atom', :key => User.current.rss_key}) %> -<% end %> - -<% html_title(l(:label_overview)) -%> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/03/03389ce7c25a023f73b895dc03af8c3f1ee755e9.svn-base --- a/.svn/pristine/03/03389ce7c25a023f73b895dc03af8c3f1ee755e9.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,8 +0,0 @@ -

<%=l(:label_document)%>

- -<% form_tag({:action => 'edit', :id => @document}, :class => "tabular") do %> - <%= render :partial => 'form' %> - <%= submit_tag l(:button_save) %> -<% end %> - - diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/03/033d1080f94d03c5b655f6847c2f7606bd5d5f60.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/03/033d1080f94d03c5b655f6847c2f7606bd5d5f60.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,54 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +module UsersHelper + def users_status_options_for_select(selected) + user_count_by_status = User.count(:group => 'status').to_hash + options_for_select([[l(:label_all), ''], + ["#{l(:status_active)} (#{user_count_by_status[1].to_i})", '1'], + ["#{l(:status_registered)} (#{user_count_by_status[2].to_i})", '2'], + ["#{l(:status_locked)} (#{user_count_by_status[3].to_i})", '3']], selected.to_s) + end + + def user_mail_notification_options(user) + user.valid_notification_options.collect {|o| [l(o.last), o.first]} + end + + def change_status_link(user) + url = {:controller => 'users', :action => 'update', :id => user, :page => params[:page], :status => params[:status], :tab => nil} + + if user.locked? + link_to l(:button_unlock), url.merge(:user => {:status => User::STATUS_ACTIVE}), :method => :put, :class => 'icon icon-unlock' + elsif user.registered? + link_to l(:button_activate), url.merge(:user => {:status => User::STATUS_ACTIVE}), :method => :put, :class => 'icon icon-unlock' + elsif user != User.current + link_to l(:button_lock), url.merge(:user => {:status => User::STATUS_LOCKED}), :method => :put, :class => 'icon icon-lock' + end + end + + def user_settings_tabs + tabs = [{:name => 'general', :partial => 'users/general', :label => :label_general}, + {:name => 'memberships', :partial => 'users/memberships', :label => :label_project_plural} + ] + if Group.all.any? + tabs.insert 1, {:name => 'groups', :partial => 'users/groups', :label => :label_group_plural} + end + tabs + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/03/033e041df5add14807dd8e40c1a8ef8894cdf897.svn-base --- a/.svn/pristine/03/033e041df5add14807dd8e40c1a8ef8894cdf897.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,18 +0,0 @@ -Return-Path: -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 Doe" -To: -Subject: Ticket by unknown user -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 - -This is a ticket submitted by an unknown user. - diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/03/0367c769a1dec5c6ffea095893b82d493a02e68a.svn-base --- a/.svn/pristine/03/0367c769a1dec5c6ffea095893b82d493a02e68a.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,93 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -require 'redmine/scm/adapters/subversion_adapter' - -class Repository::Subversion < Repository - attr_protected :root_url - validates_presence_of :url - validates_format_of :url, :with => /^(http|https|svn(\+[^\s:\/\\]+)?|file):\/\/.+/i - - def self.scm_adapter_class - Redmine::Scm::Adapters::SubversionAdapter - end - - def self.scm_name - 'Subversion' - end - - def supports_directory_revisions? - true - end - - def repo_log_encoding - 'UTF-8' - end - - def latest_changesets(path, rev, limit=10) - revisions = scm.revisions(path, rev, nil, :limit => 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 - def relative_path(path) - path.gsub(Regexp.new("^\/?#{Regexp.escape(relative_url)}"), '') - end - - def fetch_changesets - scm_info = scm.info - if scm_info - # latest revision found in database - db_revision = latest_changeset ? latest_changeset.revision.to_i : 0 - # latest revision in the repository - scm_revision = scm_info.lastrev.identifier.to_i - if db_revision < scm_revision - logger.debug "Fetching changesets for repository #{url}" if logger && logger.debug? - identifier_from = db_revision + 1 - while (identifier_from <= scm_revision) - # loads changesets by batches of 200 - identifier_to = [identifier_from + 199, scm_revision].min - revisions = scm.revisions('', identifier_to, identifier_from, :with_paths => true) - revisions.reverse_each do |revision| - transaction do - changeset = Changeset.create(:repository => self, - :revision => revision.identifier, - :committer => revision.author, - :committed_on => revision.time, - :comments => revision.message) - - revision.paths.each do |change| - changeset.create_change(change) - end unless changeset.new_record? - end - end unless revisions.nil? - identifier_from = identifier_to + 1 - end - end - end - end - - private - - # Returns the relative url of the repository - # Eg: root_url = file:///var/svn/foo - # url = file:///var/svn/foo/bar - # => returns /bar - def relative_url - @relative_url ||= url.gsub(Regexp.new("^#{Regexp.escape(root_url || scm.root_url)}", Regexp::IGNORECASE), '') - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/03/03b7cab2c78e7db2be70be489d305cf0f31baf79.svn-base --- a/.svn/pristine/03/03b7cab2c78e7db2be70be489d305cf0f31baf79.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,68 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class Tracker < ActiveRecord::Base - before_destroy :check_integrity - has_many :issues - has_many :workflows, :dependent => :delete_all do - def copy(source_tracker) - Workflow.copy(source_tracker, nil, proxy_owner, nil) - end - end - - has_and_belongs_to_many :projects - has_and_belongs_to_many :custom_fields, :class_name => 'IssueCustomField', :join_table => "#{table_name_prefix}custom_fields_trackers#{table_name_suffix}", :association_foreign_key => 'custom_field_id' - acts_as_list - - validates_presence_of :name - validates_uniqueness_of :name - validates_length_of :name, :maximum => 30 - - named_scope :named, lambda {|arg| { :conditions => ["LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip]}} - - def to_s; name end - - def <=>(tracker) - name <=> tracker.name - end - - def self.all - find(:all, :order => 'position') - end - - # Returns an array of IssueStatus that are used - # in the tracker's workflows - def issue_statuses - if @issue_statuses - return @issue_statuses - elsif new_record? - return [] - end - - ids = Workflow. - connection.select_rows("SELECT DISTINCT old_status_id, new_status_id FROM #{Workflow.table_name} WHERE tracker_id = #{id}"). - flatten. - uniq - - @issue_statuses = IssueStatus.find_all_by_id(ids).sort - end - -private - def check_integrity - raise "Can't delete tracker" if Issue.find(:first, :conditions => ["tracker_id=?", self.id]) - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/03/03b977fa002b4a1e6202e05fed1b5c5f4bee2ed3.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/03/03b977fa002b4a1e6202e05fed1b5c5f4bee2ed3.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,1029 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class Project < ActiveRecord::Base + include Redmine::SafeAttributes + + # Project statuses + STATUS_ACTIVE = 1 + STATUS_CLOSED = 5 + STATUS_ARCHIVED = 9 + + # Maximum length for project identifiers + IDENTIFIER_MAX_LENGTH = 100 + + # Specific overidden Activities + has_many :time_entry_activities + has_many :members, :include => [:principal, :roles], :conditions => "#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{Principal::STATUS_ACTIVE}" + has_many :memberships, :class_name => 'Member' + has_many :member_principals, :class_name => 'Member', + :include => :principal, + :conditions => "#{Principal.table_name}.type='Group' OR (#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{Principal::STATUS_ACTIVE})" + has_many :users, :through => :members + has_many :principals, :through => :member_principals, :source => :principal + + has_many :enabled_modules, :dependent => :delete_all + has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position" + has_many :issues, :dependent => :destroy, :include => [:status, :tracker] + has_many :issue_changes, :through => :issues, :source => :journals + has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC" + has_many :time_entries, :dependent => :delete_all + has_many :queries, :class_name => 'IssueQuery', :dependent => :delete_all + has_many :documents, :dependent => :destroy + has_many :news, :dependent => :destroy, :include => :author + has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name" + has_many :boards, :dependent => :destroy, :order => "position ASC" + has_one :repository, :conditions => ["is_default = ?", true] + has_many :repositories, :dependent => :destroy + has_many :changesets, :through => :repository + has_one :wiki, :dependent => :destroy + # Custom field for the project issues + has_and_belongs_to_many :issue_custom_fields, + :class_name => 'IssueCustomField', + :order => "#{CustomField.table_name}.position", + :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}", + :association_foreign_key => 'custom_field_id' + + 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', 'identifier', 'description'], :project_key => 'id', :permission => nil + acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"}, + :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o}}, + :author => nil + + attr_protected :status + + validates_presence_of :name, :identifier + validates_uniqueness_of :identifier + validates_associated :repository, :wiki + validates_length_of :name, :maximum => 255 + validates_length_of :homepage, :maximum => 255 + validates_length_of :identifier, :in => 1..IDENTIFIER_MAX_LENGTH + # donwcase letters, digits, dashes but not digits only + validates_format_of :identifier, :with => /\A(?!\d+$)[a-z0-9\-_]*\z/, :if => Proc.new { |p| p.identifier_changed? } + # reserved words + validates_exclusion_of :identifier, :in => %w( new ) + + after_save :update_position_under_parent, :if => Proc.new {|project| project.name_changed?} + after_save :update_inherited_members, :if => Proc.new {|project| project.inherit_members_changed?} + before_destroy :delete_all_members + + scope :has_module, lambda {|mod| + where("#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s) + } + scope :active, lambda { where(:status => STATUS_ACTIVE) } + scope :status, lambda {|arg| where(arg.blank? ? nil : {:status => arg.to_i}) } + scope :all_public, lambda { where(:is_public => true) } + scope :visible, lambda {|*args| where(Project.visible_condition(args.shift || User.current, *args)) } + scope :allowed_to, lambda {|*args| + user = User.current + permission = nil + if args.first.is_a?(Symbol) + permission = args.shift + else + user = args.shift + permission = args.shift + end + where(Project.allowed_to_condition(user, permission, *args)) + } + scope :like, lambda {|arg| + if arg.blank? + where(nil) + else + pattern = "%#{arg.to_s.strip.downcase}%" + where("LOWER(identifier) LIKE :p OR LOWER(name) LIKE :p", :p => pattern) + end + } + + def initialize(attributes=nil, *args) + super + + initialized = (attributes || {}).stringify_keys + if !initialized.key?('identifier') && Setting.sequential_project_identifiers? + self.identifier = Project.next_identifier + end + if !initialized.key?('is_public') + self.is_public = Setting.default_projects_public? + end + if !initialized.key?('enabled_module_names') + self.enabled_module_names = Setting.default_projects_modules + end + if !initialized.key?('trackers') && !initialized.key?('tracker_ids') + default = Setting.default_projects_tracker_ids + if default.is_a?(Array) + self.trackers = Tracker.where(:id => default.map(&:to_i)).sorted.all + else + self.trackers = Tracker.sorted.all + end + end + end + + def identifier=(identifier) + super unless identifier_frozen? + end + + def identifier_frozen? + errors[:identifier].blank? && !(new_record? || identifier.blank?) + end + + # returns latest created projects + # non public projects will be returned only if user is a member of those + def self.latest(user=nil, count=5) + visible(user).limit(count).order("created_on DESC").all + end + + # Returns true if the project is visible to +user+ or to the current user. + def visible?(user=User.current) + user.allowed_to?(:view_project, self) + end + + # Returns a SQL conditions string used to find all projects visible by the specified user. + # + # Examples: + # Project.visible_condition(admin) => "projects.status = 1" + # Project.visible_condition(normal_user) => "((projects.status = 1) AND (projects.is_public = 1 OR projects.id IN (1,3,4)))" + # Project.visible_condition(anonymous) => "((projects.status = 1) AND (projects.is_public = 1))" + def self.visible_condition(user, options={}) + allowed_to_condition(user, :view_project, options) + end + + # Returns a SQL conditions string used to find all projects for which +user+ has the given +permission+ + # + # Valid options: + # * :project => limit the condition to project + # * :with_subprojects => limit the condition to project and its subprojects + # * :member => limit the condition to the user projects + def self.allowed_to_condition(user, permission, options={}) + perm = Redmine::AccessControl.permission(permission) + base_statement = (perm && perm.read? ? "#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED}" : "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}") + if perm && perm.project_module + # If the permission belongs to a project module, make sure the module is enabled + base_statement << " AND #{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}')" + end + if options[:project] + project_statement = "#{Project.table_name}.id = #{options[:project].id}" + 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? + base_statement + else + statement_by_role = {} + unless options[:member] + role = user.logged? ? Role.non_member : Role.anonymous + if role.allowed_to?(permission) + statement_by_role[role] = "#{Project.table_name}.is_public = #{connection.quoted_true}" + end + end + if user.logged? + user.projects_by_role.each do |role, projects| + if role.allowed_to?(permission) && projects.any? + statement_by_role[role] = "#{Project.table_name}.id IN (#{projects.collect(&:id).join(',')})" + end + end + end + if statement_by_role.empty? + "1=0" + else + if block_given? + statement_by_role.each do |role, statement| + if s = yield(role, user) + statement_by_role[role] = "(#{statement} AND (#{s}))" + end + end + end + "((#{base_statement}) AND (#{statement_by_role.values.join(' OR ')}))" + end + end + end + + # Returns the Systemwide and project specific activities + def activities(include_inactive=false) + if include_inactive + return all_activities + else + return active_activities + end + end + + # Will create a new Project specific Activity or update an existing one + # + # This will raise a ActiveRecord::Rollback if the TimeEntryActivity + # does not successfully save. + def update_or_create_time_entry_activity(id, activity_hash) + if activity_hash.respond_to?(:has_key?) && activity_hash.has_key?('parent_id') + self.create_time_entry_activity_if_needed(activity_hash) + else + activity = project.time_entry_activities.find_by_id(id.to_i) + activity.update_attributes(activity_hash) if activity + end + end + + # Create a new TimeEntryActivity if it overrides a system TimeEntryActivity + # + # This will raise a ActiveRecord::Rollback if the TimeEntryActivity + # does not successfully save. + def create_time_entry_activity_if_needed(activity) + if activity['parent_id'] + + parent_activity = TimeEntryActivity.find(activity['parent_id']) + activity['name'] = parent_activity.name + activity['position'] = parent_activity.position + + if Enumeration.overridding_change?(activity, parent_activity) + project_activity = self.time_entry_activities.create(activity) + + if project_activity.new_record? + raise ActiveRecord::Rollback, "Overridding TimeEntryActivity was not successfully saved" + else + self.time_entries.update_all("activity_id = #{project_activity.id}", ["activity_id = ?", parent_activity.id]) + end + end + end + end + + # Returns a :conditions SQL string that can be used to find the issues associated with this project. + # + # Examples: + # project.project_condition(true) => "(projects.id = 1 OR (projects.lft > 1 AND projects.rgt < 10))" + # project.project_condition(false) => "projects.id = 1" + def project_condition(with_subprojects) + cond = "#{Project.table_name}.id = #{id}" + cond = "(#{cond} OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt}))" if with_subprojects + cond + end + + def self.find(*args) + if args.first && args.first.is_a?(String) && !args.first.match(/^\d*$/) + project = find_by_identifier(*args) + raise ActiveRecord::RecordNotFound, "Couldn't find Project with identifier=#{args.first}" if project.nil? + project + else + super + end + end + + def self.find_by_param(*args) + self.find(*args) + end + + alias :base_reload :reload + def reload(*args) + @shared_versions = nil + @rolled_up_versions = nil + @rolled_up_trackers = nil + @all_issue_custom_fields = nil + @all_time_entry_custom_fields = nil + @to_param = nil + @allowed_parents = nil + @allowed_permissions = nil + @actions_allowed = nil + @start_date = nil + @due_date = nil + base_reload(*args) + end + + def to_param + # id is used for projects with a numeric identifier (compatibility) + @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id.to_s : identifier) + end + + def active? + self.status == STATUS_ACTIVE + end + + def archived? + self.status == STATUS_ARCHIVED + end + + # Archives the project and its descendants + def archive + # Check that there is no issue of a non descendant project that is assigned + # to one of the project or descendant versions + v_ids = self_and_descendants.collect {|p| p.version_ids}.flatten + if v_ids.any? && + Issue. + includes(:project). + where("#{Project.table_name}.lft < ? OR #{Project.table_name}.rgt > ?", lft, rgt). + where("#{Issue.table_name}.fixed_version_id IN (?)", v_ids). + exists? + return false + end + Project.transaction do + archive! + end + true + end + + # Unarchives the project + # All its ancestors must be active + def unarchive + return false if ancestors.detect {|a| !a.active?} + update_attribute :status, STATUS_ACTIVE + end + + def close + self_and_descendants.status(STATUS_ACTIVE).update_all :status => STATUS_CLOSED + end + + def reopen + self_and_descendants.status(STATUS_CLOSED).update_all :status => STATUS_ACTIVE + end + + # Returns an array of projects the project can be moved to + # by the current user + def allowed_parents + return @allowed_parents if @allowed_parents + @allowed_parents = Project.where(Project.allowed_to_condition(User.current, :add_subprojects)).all + @allowed_parents = @allowed_parents - self_and_descendants + if User.current.allowed_to?(:add_project, nil, :global => true) || (!new_record? && parent.nil?) + @allowed_parents << nil + end + unless parent.nil? || @allowed_parents.empty? || @allowed_parents.include?(parent) + @allowed_parents << parent + end + @allowed_parents + end + + # Sets the parent of the project with authorization check + def set_allowed_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.nil? + if !new_record? && allowed_parents.empty? + return false + end + elsif !allowed_parents.include?(p) + return false + end + set_parent!(p) + 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)) + set_or_update_position_under(p) + Issue.update_versions_from_hierarchy_change(self) + true + else + # Can not move to the given target + false + end + end + + # Recalculates all lft and rgt values based on project names + # Unlike Project.rebuild!, these values are recalculated even if the tree "looks" valid + # Used in BuildProjectsTree migration + def self.rebuild_tree! + transaction do + update_all "lft = NULL, rgt = NULL" + rebuild!(false) + end + end + + # Returns an array of the trackers used by the project and its active sub projects + def rolled_up_trackers + @rolled_up_trackers ||= + Tracker. + joins(:projects). + joins("JOIN #{EnabledModule.table_name} ON #{EnabledModule.table_name}.project_id = #{Project.table_name}.id AND #{EnabledModule.table_name}.name = 'issue_tracking'"). + select("DISTINCT #{Tracker.table_name}.*"). + where("#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status <> #{STATUS_ARCHIVED}", lft, rgt). + sorted. + all + end + + # Closes open and locked project versions that are completed + def close_completed_versions + Version.transaction do + versions.where(:status => %w(open locked)).all.each do |version| + if version.completed? + version.update_attribute(:status, 'closed') + end + end + end + end + + # Returns a scope of the Versions on subprojects + def rolled_up_versions + @rolled_up_versions ||= + Version.scoped(:include => :project, + :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status <> #{STATUS_ARCHIVED}", lft, rgt]) + end + + # Returns a scope of the Versions used by the project + def shared_versions + if new_record? + Version.scoped(:include => :project, + :conditions => "#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED} AND #{Version.table_name}.sharing = 'system'") + else + @shared_versions ||= begin + r = root? ? self : root + Version.scoped(:include => :project, + :conditions => "#{Project.table_name}.id = #{id}" + + " OR (#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED} AND (" + + " #{Version.table_name}.sharing = 'system'" + + " OR (#{Project.table_name}.lft >= #{r.lft} AND #{Project.table_name}.rgt <= #{r.rgt} AND #{Version.table_name}.sharing = 'tree')" + + " OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{Version.table_name}.sharing IN ('hierarchy', 'descendants'))" + + " OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt} AND #{Version.table_name}.sharing = 'hierarchy')" + + "))") + end + end + end + + # Returns a hash of project users grouped by role + def users_by_role + members.includes(:user, :roles).all.inject({}) do |h, m| + m.roles.each do |r| + h[r] ||= [] + h[r] << m.user + end + h + end + end + + # Deletes all project's members + def delete_all_members + me, mr = Member.table_name, MemberRole.table_name + connection.delete("DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} WHERE #{me}.project_id = #{id})") + Member.delete_all(['project_id = ?', id]) + end + + # Users/groups issues can be assigned to + def assignable_users + assignable = Setting.issue_group_assignment? ? member_principals : members + assignable.select {|m| m.roles.detect {|role| role.assignable?}}.collect {|m| m.principal}.sort + end + + # Returns the mail adresses of users that should be always notified on project events + def recipients + notified_users.collect {|user| user.mail} + end + + # Returns the users that should be notified on project events + def notified_users + # TODO: User part should be extracted to User#notify_about? + members.select {|m| m.principal.present? && (m.mail_notification? || m.principal.mail_notification == 'all')}.collect {|m| m.principal} + end + + # Returns an array of all custom fields enabled for project issues + # (explictly associated custom fields and custom fields enabled for all projects) + def all_issue_custom_fields + @all_issue_custom_fields ||= (IssueCustomField.for_all + issue_custom_fields).uniq.sort + end + + # Returns an array of all custom fields enabled for project time entries + # (explictly associated custom fields and custom fields enabled for all projects) + def all_time_entry_custom_fields + @all_time_entry_custom_fields ||= (TimeEntryCustomField.for_all + time_entry_custom_fields).uniq.sort + end + + def project + self + end + + def <=>(project) + name.downcase <=> project.name.downcase + end + + def to_s + name + end + + # Returns a short description of the projects (first lines) + def short_description(length = 255) + description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description + end + + def css_classes + s = 'project' + s << ' root' if root? + s << ' child' if child? + s << (leaf? ? ' leaf' : ' parent') + unless active? + if archived? + s << ' archived' + else + s << ' closed' + end + end + s + end + + # The earliest start date of a project, based on it's issues and versions + def start_date + @start_date ||= [ + issues.minimum('start_date'), + shared_versions.minimum('effective_date'), + Issue.fixed_version(shared_versions).minimum('start_date') + ].compact.min + end + + # The latest due date of an issue or version + def due_date + @due_date ||= [ + issues.maximum('due_date'), + shared_versions.maximum('effective_date'), + Issue.fixed_version(shared_versions).maximum('due_date') + ].compact.max + end + + def overdue? + active? && !due_date.nil? && (due_date < Date.today) + end + + # Returns the percent completed for this project, based on the + # progress on it's versions. + def completed_percent(options={:include_subprojects => false}) + if options.delete(:include_subprojects) + total = self_and_descendants.collect(&:completed_percent).sum + + total / self_and_descendants.count + else + if versions.count > 0 + total = versions.collect(&:completed_percent).sum + + total / versions.count + else + 100 + end + end + end + + # Return true if this project allows to do the specified action. + # action can be: + # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit') + # * a permission Symbol (eg. :edit_project) + def allows_to?(action) + if archived? + # No action allowed on archived projects + return false + end + unless active? || Redmine::AccessControl.read_action?(action) + # No write action allowed on closed projects + return false + end + # No action allowed on disabled modules + if action.is_a? Hash + allowed_actions.include? "#{action[:controller]}/#{action[:action]}" + else + allowed_permissions.include? action + end + end + + def module_enabled?(module_name) + module_name = module_name.to_s + enabled_modules.detect {|m| m.name == module_name} + end + + def enabled_module_names=(module_names) + if module_names && module_names.is_a?(Array) + module_names = module_names.collect(&:to_s).reject(&:blank?) + self.enabled_modules = module_names.collect {|name| enabled_modules.detect {|mod| mod.name == name} || EnabledModule.new(:name => name)} + else + enabled_modules.clear + end + end + + # Returns an array of the enabled modules names + def enabled_module_names + enabled_modules.collect(&:name) + end + + # Enable a specific module + # + # Examples: + # project.enable_module!(:issue_tracking) + # project.enable_module!("issue_tracking") + def enable_module!(name) + enabled_modules << EnabledModule.new(:name => name.to_s) unless module_enabled?(name) + end + + # Disable a module if it exists + # + # Examples: + # project.disable_module!(:issue_tracking) + # project.disable_module!("issue_tracking") + # project.disable_module!(project.enabled_modules.first) + def disable_module!(target) + target = enabled_modules.detect{|mod| target.to_s == mod.name} unless enabled_modules.include?(target) + target.destroy unless target.blank? + end + + safe_attributes 'name', + 'description', + 'homepage', + 'is_public', + 'identifier', + 'custom_field_values', + 'custom_fields', + 'tracker_ids', + 'issue_custom_field_ids' + + safe_attributes 'enabled_module_names', + :if => lambda {|project, user| project.new_record? || user.allowed_to?(:select_project_modules, project) } + + safe_attributes 'inherit_members', + :if => lambda {|project, user| project.parent.nil? || project.parent.visible?(user)} + + # Returns an array of projects that are in this project's hierarchy + # + # Example: parents, children, siblings + def hierarchy + parents = project.self_and_ancestors || [] + descendants = project.descendants || [] + project_hierarchy = parents | descendants # Set union + end + + # Returns an auto-generated project identifier based on the last identifier used + def self.next_identifier + p = Project.order('id DESC').first + p.nil? ? nil : p.identifier.to_s.succ + end + + # Copies and saves the Project instance based on the +project+. + # Duplicates the source project's: + # * Wiki + # * Versions + # * Categories + # * Issues + # * Members + # * Queries + # + # Accepts an +options+ argument to specify what to copy + # + # Examples: + # project.copy(1) # => copies everything + # project.copy(1, :only => 'members') # => copies members only + # project.copy(1, :only => ['members', 'versions']) # => copies members and versions + def copy(project, options={}) + project = project.is_a?(Project) ? project : Project.find(project) + + to_be_copied = %w(wiki versions issue_categories issues members queries boards) + to_be_copied = to_be_copied & options[:only].to_a unless options[:only].nil? + + Project.transaction do + if save + reload + to_be_copied.each do |name| + send "copy_#{name}", project + end + Redmine::Hook.call_hook(:model_project_copy_before_save, :source_project => project, :destination_project => self) + save + end + end + end + + # Returns a new unsaved Project instance with attributes copied from +project+ + def self.copy_from(project) + project = project.is_a?(Project) ? project : Project.find(project) + # clear unique attributes + attributes = project.attributes.dup.except('id', 'name', 'identifier', 'status', 'parent_id', 'lft', 'rgt') + copy = Project.new(attributes) + copy.enabled_modules = project.enabled_modules + copy.trackers = project.trackers + copy.custom_values = project.custom_values.collect {|v| v.clone} + copy.issue_custom_fields = project.issue_custom_fields + copy + end + + # Yields the given block for each project with its level in the tree + def self.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 + + private + + def after_parent_changed(parent_was) + remove_inherited_member_roles + add_inherited_member_roles + end + + def update_inherited_members + if parent + if inherit_members? && !inherit_members_was + remove_inherited_member_roles + add_inherited_member_roles + elsif !inherit_members? && inherit_members_was + remove_inherited_member_roles + end + end + end + + def remove_inherited_member_roles + member_roles = memberships.map(&:member_roles).flatten + member_role_ids = member_roles.map(&:id) + member_roles.each do |member_role| + if member_role.inherited_from && !member_role_ids.include?(member_role.inherited_from) + member_role.destroy + end + end + end + + def add_inherited_member_roles + if inherit_members? && parent + parent.memberships.each do |parent_member| + member = Member.find_or_new(self.id, parent_member.user_id) + parent_member.member_roles.each do |parent_member_role| + member.member_roles << MemberRole.new(:role => parent_member_role.role, :inherited_from => parent_member_role.id) + end + member.save! + end + end + end + + # Copies wiki from +project+ + def copy_wiki(project) + # Check that the source project has a wiki first + unless project.wiki.nil? + wiki = self.wiki || Wiki.new + wiki.attributes = project.wiki.attributes.dup.except("id", "project_id") + wiki_pages_map = {} + project.wiki.pages.each do |page| + # Skip pages without content + next if page.content.nil? + new_wiki_content = WikiContent.new(page.content.attributes.dup.except("id", "page_id", "updated_on")) + new_wiki_page = WikiPage.new(page.attributes.dup.except("id", "wiki_id", "created_on", "parent_id")) + new_wiki_page.content = new_wiki_content + wiki.pages << new_wiki_page + wiki_pages_map[page.id] = new_wiki_page + end + + self.wiki = wiki + wiki.save + # Reproduce page hierarchy + project.wiki.pages.each do |page| + if page.parent_id && wiki_pages_map[page.id] + wiki_pages_map[page.id].parent = wiki_pages_map[page.parent_id] + wiki_pages_map[page.id].save + end + end + end + end + + # Copies versions from +project+ + def copy_versions(project) + project.versions.each do |version| + new_version = Version.new + new_version.attributes = version.attributes.dup.except("id", "project_id", "created_on", "updated_on") + self.versions << new_version + end + end + + # Copies issue categories from +project+ + def copy_issue_categories(project) + project.issue_categories.each do |issue_category| + new_issue_category = IssueCategory.new + new_issue_category.attributes = issue_category.attributes.dup.except("id", "project_id") + self.issue_categories << new_issue_category + end + end + + # Copies issues from +project+ + def copy_issues(project) + # Stores the source issue id as a key and the copied issues as the + # value. Used to map the two togeather for issue relations. + issues_map = {} + + # Store status and reopen locked/closed versions + version_statuses = versions.reject(&:open?).map {|version| [version, version.status]} + version_statuses.each do |version, status| + version.update_attribute :status, 'open' + end + + # Get issues sorted by root_id, lft so that parent issues + # get copied before their children + project.issues.reorder('root_id, lft').all.each do |issue| + new_issue = Issue.new + new_issue.copy_from(issue, :subtasks => false, :link => false) + new_issue.project = self + # Changing project resets the custom field values + # TODO: handle this in Issue#project= + new_issue.custom_field_values = issue.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h} + # Reassign fixed_versions by name, since names are unique per project + if issue.fixed_version && issue.fixed_version.project == project + new_issue.fixed_version = self.versions.detect {|v| v.name == issue.fixed_version.name} + end + # Reassign the category by name, since names are unique per project + if issue.category + new_issue.category = self.issue_categories.detect {|c| c.name == issue.category.name} + end + # Parent issue + if issue.parent_id + if copied_parent = issues_map[issue.parent_id] + new_issue.parent_issue_id = copied_parent.id + end + end + + self.issues << new_issue + if new_issue.new_record? + logger.info "Project#copy_issues: issue ##{issue.id} could not be copied: #{new_issue.errors.full_messages}" if logger && logger.info + else + issues_map[issue.id] = new_issue unless new_issue.new_record? + end + end + + # Restore locked/closed version statuses + version_statuses.each do |version, status| + version.update_attribute :status, status + end + + # Relations after in case issues related each other + project.issues.each do |issue| + new_issue = issues_map[issue.id] + unless new_issue + # Issue was not copied + next + end + + # Relations + issue.relations_from.each do |source_relation| + new_issue_relation = IssueRelation.new + new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id") + new_issue_relation.issue_to = issues_map[source_relation.issue_to_id] + if new_issue_relation.issue_to.nil? && Setting.cross_project_issue_relations? + new_issue_relation.issue_to = source_relation.issue_to + end + new_issue.relations_from << new_issue_relation + end + + issue.relations_to.each do |source_relation| + new_issue_relation = IssueRelation.new + new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id") + new_issue_relation.issue_from = issues_map[source_relation.issue_from_id] + if new_issue_relation.issue_from.nil? && Setting.cross_project_issue_relations? + new_issue_relation.issue_from = source_relation.issue_from + end + new_issue.relations_to << new_issue_relation + end + end + end + + # Copies members from +project+ + def copy_members(project) + # Copy users first, then groups to handle members with inherited and given roles + members_to_copy = [] + members_to_copy += project.memberships.select {|m| m.principal.is_a?(User)} + members_to_copy += project.memberships.select {|m| !m.principal.is_a?(User)} + + members_to_copy.each do |member| + new_member = Member.new + new_member.attributes = member.attributes.dup.except("id", "project_id", "created_on") + # only copy non inherited roles + # inherited roles will be added when copying the group membership + role_ids = member.member_roles.reject(&:inherited?).collect(&:role_id) + next if role_ids.empty? + new_member.role_ids = role_ids + new_member.project = self + self.members << new_member + end + end + + # Copies queries from +project+ + def copy_queries(project) + project.queries.each do |query| + new_query = IssueQuery.new + new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria") + new_query.sort_criteria = query.sort_criteria if query.sort_criteria + new_query.project = self + new_query.user_id = query.user_id + self.queries << new_query + end + end + + # Copies boards from +project+ + def copy_boards(project) + project.boards.each do |board| + new_board = Board.new + new_board.attributes = board.attributes.dup.except("id", "project_id", "topics_count", "messages_count", "last_message_id") + new_board.project = self + self.boards << new_board + end + end + + def allowed_permissions + @allowed_permissions ||= begin + module_names = enabled_modules.all(:select => :name).collect {|m| m.name} + Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name} + end + end + + def allowed_actions + @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten + end + + # Returns all the active Systemwide and project specific activities + def active_activities + overridden_activity_ids = self.time_entry_activities.collect(&:parent_id) + + if overridden_activity_ids.empty? + return TimeEntryActivity.shared.active + else + return system_activities_and_project_overrides + end + end + + # Returns all the Systemwide and project specific activities + # (inactive and active) + def all_activities + overridden_activity_ids = self.time_entry_activities.collect(&:parent_id) + + if overridden_activity_ids.empty? + return TimeEntryActivity.shared + else + return system_activities_and_project_overrides(true) + end + end + + # Returns the systemwide active activities merged with the project specific overrides + def system_activities_and_project_overrides(include_inactive=false) + if include_inactive + return TimeEntryActivity.shared. + where("id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)).all + + self.time_entry_activities + else + return TimeEntryActivity.shared.active. + where("id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)).all + + self.time_entry_activities.active + end + end + + # Archives subprojects recursively + def archive! + children.each do |subproject| + subproject.send :archive! + end + update_attribute :status, STATUS_ARCHIVED + end + + def update_position_under_parent + set_or_update_position_under(parent) + end + + # Inserts/moves the project so that target's children or root projects stay alphabetically sorted + def set_or_update_position_under(target_parent) + parent_was = parent + sibs = (target_parent.nil? ? self.class.roots : target_parent.children) + to_be_inserted_before = sibs.sort_by {|c| c.name.to_s.downcase}.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 target_parent.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(target_parent) + end + if parent_was != target_parent + after_parent_changed(parent_was) + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/04/045b82572747e8f57f122a0ac4102b9d20ea2c04.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/04/045b82572747e8f57f122a0ac4102b9d20ea2c04.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,592 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class ProjectsControllerTest < ActionController::TestCase + fixtures :projects, :versions, :users, :roles, :members, :member_roles, :issues, :journals, :journal_details, + :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages, + :attachments, :custom_fields, :custom_values, :time_entries + + def setup + @request.session[:user_id] = nil + Setting.default_language = 'en' + end + + def test_index_by_anonymous_should_not_show_private_projects + get :index + assert_response :success + assert_template 'index' + projects = assigns(:projects) + assert_not_nil projects + assert projects.all?(&:is_public?) + + assert_select 'ul' do + assert_select 'li' do + assert_select 'a', :text => 'eCookbook' + assert_select 'ul' do + assert_select 'a', :text => 'Child of private child' + end + end + end + assert_select 'a', :text => /Private child of eCookbook/, :count => 0 + end + + def test_index_atom + get :index, :format => 'atom' + assert_response :success + assert_template 'common/feed' + assert_select 'feed>title', :text => 'Redmine: Latest projects' + assert_select 'feed>entry', :count => Project.count(:conditions => Project.visible_condition(User.current)) + end + + test "#index by non-admin user with view_time_entries permission should show overall spent time link" do + @request.session[:user_id] = 3 + get :index + assert_template 'index' + assert_select 'a[href=?]', '/time_entries' + end + + test "#index by non-admin user without view_time_entries permission should not show overall spent time link" do + Role.find(2).remove_permission! :view_time_entries + Role.non_member.remove_permission! :view_time_entries + Role.anonymous.remove_permission! :view_time_entries + @request.session[:user_id] = 3 + + get :index + assert_template 'index' + assert_select 'a[href=?]', '/time_entries', 0 + end + + test "#new by admin user should accept get" do + @request.session[:user_id] = 1 + + get :new + assert_response :success + assert_template 'new' + end + + test "#new by non-admin user with add_project permission should accept get" do + Role.non_member.add_permission! :add_project + @request.session[:user_id] = 9 + + get :new + assert_response :success + assert_template 'new' + assert_select 'select[name=?]', 'project[parent_id]', 0 + end + + test "#new by non-admin user with add_subprojects permission should accept get" do + Role.find(1).remove_permission! :add_project + Role.find(1).add_permission! :add_subprojects + @request.session[:user_id] = 2 + + get :new, :parent_id => 'ecookbook' + assert_response :success + assert_template 'new' + + assert_select 'select[name=?]', 'project[parent_id]' do + # parent project selected + assert_select 'option[value=1][selected=selected]' + # no empty value + assert_select 'option[value=]', 0 + end + end + + test "#create by admin user should create a new project" do + @request.session[:user_id] = 1 + + post :create, + :project => { + :name => "blog", + :description => "weblog", + :homepage => 'http://weblog', + :identifier => "blog", + :is_public => 1, + :custom_field_values => { '3' => 'Beta' }, + :tracker_ids => ['1', '3'], + # an issue custom field that is not for all project + :issue_custom_field_ids => ['9'], + :enabled_module_names => ['issue_tracking', 'news', 'repository'] + } + assert_redirected_to '/projects/blog/settings' + + project = Project.find_by_name('blog') + assert_kind_of Project, project + assert project.active? + assert_equal 'weblog', project.description + assert_equal 'http://weblog', project.homepage + assert_equal true, project.is_public? + assert_nil project.parent + assert_equal 'Beta', project.custom_value_for(3).value + assert_equal [1, 3], project.trackers.map(&:id).sort + assert_equal ['issue_tracking', 'news', 'repository'], project.enabled_module_names.sort + assert project.issue_custom_fields.include?(IssueCustomField.find(9)) + end + + test "#create by admin user should create a new subproject" do + @request.session[:user_id] = 1 + + assert_difference 'Project.count' do + post :create, :project => { :name => "blog", + :description => "weblog", + :identifier => "blog", + :is_public => 1, + :custom_field_values => { '3' => 'Beta' }, + :parent_id => 1 + } + assert_redirected_to '/projects/blog/settings' + end + + project = Project.find_by_name('blog') + assert_kind_of Project, project + assert_equal Project.find(1), project.parent + end + + test "#create by admin user should continue" do + @request.session[:user_id] = 1 + + assert_difference 'Project.count' do + post :create, :project => {:name => "blog", :identifier => "blog"}, :continue => 'Create and continue' + end + assert_redirected_to '/projects/new' + end + + test "#create by non-admin user with add_project permission should create a new project" do + Role.non_member.add_permission! :add_project + @request.session[:user_id] = 9 + + post :create, :project => { :name => "blog", + :description => "weblog", + :identifier => "blog", + :is_public => 1, + :custom_field_values => { '3' => 'Beta' }, + :tracker_ids => ['1', '3'], + :enabled_module_names => ['issue_tracking', 'news', 'repository'] + } + + assert_redirected_to '/projects/blog/settings' + + project = Project.find_by_name('blog') + assert_kind_of Project, project + assert_equal 'weblog', project.description + assert_equal true, project.is_public? + assert_equal [1, 3], project.trackers.map(&:id).sort + assert_equal ['issue_tracking', 'news', 'repository'], project.enabled_module_names.sort + + # User should be added as a project member + assert User.find(9).member_of?(project) + assert_equal 1, project.members.size + end + + test "#create by non-admin user with add_project permission should fail with parent_id" do + Role.non_member.add_permission! :add_project + @request.session[:user_id] = 9 + + assert_no_difference 'Project.count' do + post :create, :project => { :name => "blog", + :description => "weblog", + :identifier => "blog", + :is_public => 1, + :custom_field_values => { '3' => 'Beta' }, + :parent_id => 1 + } + end + assert_response :success + project = assigns(:project) + assert_kind_of Project, project + assert_not_nil project.errors[:parent_id] + end + + test "#create by non-admin user with add_subprojects permission should create a project with a parent_id" do + Role.find(1).remove_permission! :add_project + Role.find(1).add_permission! :add_subprojects + @request.session[:user_id] = 2 + + post :create, :project => { :name => "blog", + :description => "weblog", + :identifier => "blog", + :is_public => 1, + :custom_field_values => { '3' => 'Beta' }, + :parent_id => 1 + } + assert_redirected_to '/projects/blog/settings' + project = Project.find_by_name('blog') + end + + test "#create by non-admin user with add_subprojects permission should fail without parent_id" do + Role.find(1).remove_permission! :add_project + Role.find(1).add_permission! :add_subprojects + @request.session[:user_id] = 2 + + assert_no_difference 'Project.count' do + post :create, :project => { :name => "blog", + :description => "weblog", + :identifier => "blog", + :is_public => 1, + :custom_field_values => { '3' => 'Beta' } + } + end + assert_response :success + project = assigns(:project) + assert_kind_of Project, project + assert_not_nil project.errors[:parent_id] + end + + test "#create by non-admin user with add_subprojects permission should fail with unauthorized parent_id" do + Role.find(1).remove_permission! :add_project + Role.find(1).add_permission! :add_subprojects + @request.session[:user_id] = 2 + + assert !User.find(2).member_of?(Project.find(6)) + assert_no_difference 'Project.count' do + post :create, :project => { :name => "blog", + :description => "weblog", + :identifier => "blog", + :is_public => 1, + :custom_field_values => { '3' => 'Beta' }, + :parent_id => 6 + } + end + assert_response :success + project = assigns(:project) + assert_kind_of Project, project + assert_not_nil project.errors[:parent_id] + end + + def test_create_subproject_with_inherit_members_should_inherit_members + Role.find_by_name('Manager').add_permission! :add_subprojects + parent = Project.find(1) + @request.session[:user_id] = 2 + + assert_difference 'Project.count' do + post :create, :project => { + :name => 'inherited', :identifier => 'inherited', :parent_id => parent.id, :inherit_members => '1' + } + assert_response 302 + end + + project = Project.order('id desc').first + assert_equal 'inherited', project.name + assert_equal parent, project.parent + assert project.memberships.count > 0 + assert_equal parent.memberships.count, project.memberships.count + end + + def test_create_should_preserve_modules_on_validation_failure + with_settings :default_projects_modules => ['issue_tracking', 'repository'] do + @request.session[:user_id] = 1 + assert_no_difference 'Project.count' do + post :create, :project => { + :name => "blog", + :identifier => "", + :enabled_module_names => %w(issue_tracking news) + } + end + assert_response :success + project = assigns(:project) + assert_equal %w(issue_tracking news), project.enabled_module_names.sort + end + end + + def test_show_by_id + get :show, :id => 1 + assert_response :success + assert_template 'show' + assert_not_nil assigns(:project) + end + + def test_show_by_identifier + get :show, :id => 'ecookbook' + assert_response :success + assert_template 'show' + assert_not_nil assigns(:project) + assert_equal Project.find_by_identifier('ecookbook'), assigns(:project) + + assert_select 'li', :text => /Development status/ + end + + def test_show_should_not_display_hidden_custom_fields + ProjectCustomField.find_by_name('Development status').update_attribute :visible, false + get :show, :id => 'ecookbook' + assert_response :success + assert_template 'show' + assert_not_nil assigns(:project) + + assert_select 'li', :text => /Development status/, :count => 0 + end + + def test_show_should_not_fail_when_custom_values_are_nil + project = Project.find_by_identifier('ecookbook') + project.custom_values.first.update_attribute(:value, nil) + get :show, :id => 'ecookbook' + assert_response :success + assert_template 'show' + assert_not_nil assigns(:project) + assert_equal Project.find_by_identifier('ecookbook'), assigns(:project) + end + + def show_archived_project_should_be_denied + project = Project.find_by_identifier('ecookbook') + project.archive! + + get :show, :id => 'ecookbook' + assert_response 403 + assert_nil assigns(:project) + assert_select 'p', :text => /archived/ + end + + def test_show_should_not_show_private_subprojects_that_are_not_visible + get :show, :id => 'ecookbook' + assert_response :success + assert_template 'show' + assert_select 'a', :text => /Private child/, :count => 0 + end + + def test_show_should_show_private_subprojects_that_are_visible + @request.session[:user_id] = 2 # manager who is a member of the private subproject + get :show, :id => 'ecookbook' + assert_response :success + assert_template 'show' + assert_select 'a', :text => /Private child/ + end + + def test_settings + @request.session[:user_id] = 2 # manager + get :settings, :id => 1 + assert_response :success + assert_template 'settings' + end + + def test_settings_of_subproject + @request.session[:user_id] = 2 + get :settings, :id => 'private-child' + assert_response :success + assert_template 'settings' + + assert_select 'input[type=checkbox][name=?]', 'project[inherit_members]' + end + + def test_settings_should_be_denied_for_member_on_closed_project + Project.find(1).close + @request.session[:user_id] = 2 # manager + + get :settings, :id => 1 + assert_response 403 + end + + def test_settings_should_be_denied_for_anonymous_on_closed_project + Project.find(1).close + + get :settings, :id => 1 + assert_response 302 + end + + def test_update + @request.session[:user_id] = 2 # manager + post :update, :id => 1, :project => {:name => 'Test changed name', + :issue_custom_field_ids => ['']} + assert_redirected_to '/projects/ecookbook/settings' + project = Project.find(1) + assert_equal 'Test changed name', project.name + end + + def test_update_with_failure + @request.session[:user_id] = 2 # manager + post :update, :id => 1, :project => {:name => ''} + assert_response :success + assert_template 'settings' + assert_error_tag :content => /name can't be blank/i + end + + def test_update_should_be_denied_for_member_on_closed_project + Project.find(1).close + @request.session[:user_id] = 2 # manager + + post :update, :id => 1, :project => {:name => 'Closed'} + assert_response 403 + assert_equal 'eCookbook', Project.find(1).name + end + + def test_update_should_be_denied_for_anonymous_on_closed_project + Project.find(1).close + + post :update, :id => 1, :project => {:name => 'Closed'} + assert_response 302 + assert_equal 'eCookbook', Project.find(1).name + end + + def test_modules + @request.session[:user_id] = 2 + Project.find(1).enabled_module_names = ['issue_tracking', 'news'] + + post :modules, :id => 1, :enabled_module_names => ['issue_tracking', 'repository', 'documents'] + assert_redirected_to '/projects/ecookbook/settings/modules' + assert_equal ['documents', 'issue_tracking', 'repository'], Project.find(1).enabled_module_names.sort + end + + def test_destroy_leaf_project_without_confirmation_should_show_confirmation + @request.session[:user_id] = 1 # admin + + assert_no_difference 'Project.count' do + delete :destroy, :id => 2 + assert_response :success + assert_template 'destroy' + end + end + + def test_destroy_without_confirmation_should_show_confirmation_with_subprojects + @request.session[:user_id] = 1 # admin + + assert_no_difference 'Project.count' do + delete :destroy, :id => 1 + assert_response :success + assert_template 'destroy' + end + assert_select 'strong', + :text => ['Private child of eCookbook', + 'Child of private child, eCookbook Subproject 1', + 'eCookbook Subproject 2'].join(', ') + end + + def test_destroy_with_confirmation_should_destroy_the_project_and_subprojects + @request.session[:user_id] = 1 # admin + + assert_difference 'Project.count', -5 do + delete :destroy, :id => 1, :confirm => 1 + assert_redirected_to '/admin/projects' + end + assert_nil Project.find_by_id(1) + 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_archive_with_failure + @request.session[:user_id] = 1 + Project.any_instance.stubs(:archive).returns(false) + post :archive, :id => 1 + assert_redirected_to '/admin/projects' + assert_match /project cannot be archived/i, flash[:error] + end + + def test_unarchive + @request.session[:user_id] = 1 # admin + Project.find(1).archive + post :unarchive, :id => 1 + assert_redirected_to '/admin/projects' + assert Project.find(1).active? + end + + def test_close + @request.session[:user_id] = 2 + post :close, :id => 1 + assert_redirected_to '/projects/ecookbook' + assert_equal Project::STATUS_CLOSED, Project.find(1).status + end + + def test_reopen + Project.find(1).close + @request.session[:user_id] = 2 + post :reopen, :id => 1 + assert_redirected_to '/projects/ecookbook' + assert Project.find(1).active? + end + + def test_project_breadcrumbs_should_be_limited_to_3_ancestors + CustomField.delete_all + parent = nil + 6.times do |i| + p = Project.generate_with_parent!(parent) + get :show, :id => p + assert_select '#header h1' do + assert_select 'a', :count => [i, 3].min + end + + parent = p + end + end + + def test_get_copy + @request.session[:user_id] = 1 # admin + get :copy, :id => 1 + assert_response :success + assert_template 'copy' + assert assigns(:project) + assert_equal Project.find(1).description, assigns(:project).description + assert_nil assigns(:project).id + + assert_select 'input[name=?][value=?]', 'project[enabled_module_names][]', 'issue_tracking', 1 + end + + def test_get_copy_with_invalid_source_should_respond_with_404 + @request.session[:user_id] = 1 + get :copy, :id => 99 + assert_response 404 + end + + def test_post_copy_should_copy_requested_items + @request.session[:user_id] = 1 # admin + CustomField.delete_all + + assert_difference 'Project.count' do + post :copy, :id => 1, + :project => { + :name => 'Copy', + :identifier => 'unique-copy', + :tracker_ids => ['1', '2', '3', ''], + :enabled_module_names => %w(issue_tracking time_tracking) + }, + :only => %w(issues versions) + end + project = Project.find('unique-copy') + source = Project.find(1) + assert_equal %w(issue_tracking time_tracking), project.enabled_module_names.sort + + assert_equal source.versions.count, project.versions.count, "All versions were not copied" + assert_equal source.issues.count, project.issues.count, "All issues were not copied" + assert_equal 0, project.members.count + end + + def test_post_copy_should_redirect_to_settings_when_successful + @request.session[:user_id] = 1 # admin + post :copy, :id => 1, :project => {:name => 'Copy', :identifier => 'unique-copy'} + assert_response :redirect + assert_redirected_to :controller => 'projects', :action => 'settings', :id => 'unique-copy' + 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 +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/04/0490719dc3185896fb07e210967a75c0f5e40a0a.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/04/0490719dc3185896fb07e210967a75c0f5e40a0a.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,54 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class IssueTransactionTest < ActiveSupport::TestCase + fixtures :projects, :users, :members, :member_roles, :roles, + :trackers, :projects_trackers, + :versions, + :issue_statuses, :issue_categories, :issue_relations, :workflows, + :enumerations, + :issues, + :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values, + :time_entries + + self.use_transactional_fixtures = false + + def test_invalid_move_to_another_project + parent1 = Issue.generate! + child = Issue.generate!(:parent_issue_id => parent1.id) + grandchild = Issue.generate!(:parent_issue_id => child.id, :tracker_id => 2) + Project.find(2).tracker_ids = [1] + + parent1.reload + assert_equal [1, parent1.id, 1, 6], [parent1.project_id, parent1.root_id, parent1.lft, parent1.rgt] + + # child can not be moved to Project 2 because its child is on a disabled tracker + child = Issue.find(child.id) + child.project = Project.find(2) + assert !child.save + child.reload + grandchild.reload + parent1.reload + + # no change + assert_equal [1, parent1.id, 1, 6], [parent1.project_id, parent1.root_id, parent1.lft, parent1.rgt] + assert_equal [1, parent1.id, 2, 5], [child.project_id, child.root_id, child.lft, child.rgt] + assert_equal [1, parent1.id, 3, 4], [grandchild.project_id, grandchild.root_id, grandchild.lft, grandchild.rgt] + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/04/04bafe6e6b402a0f59316ed78a7582dc8951a46f.svn-base --- a/.svn/pristine/04/04bafe6e6b402a0f59316ed78a7582dc8951a46f.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,26 +0,0 @@ -<% content_for :header_tags do %> - <%= auto_discovery_link_tag(:atom, {:action => 'index', :format => 'atom', :key => User.current.rss_key}) %> -<% end %> - -
- <%= link_to(l(:label_project_new), {:controller => 'projects', :action => 'new'}, :class => 'icon icon-add') + ' |' if User.current.allowed_to?(:add_project, nil, :global => true) %> - <%= link_to(l(:label_issue_view_all), { :controller => 'issues' }) + ' |' if User.current.allowed_to?(:view_issues, nil, :global => true) %> - <%= link_to(l(:label_overall_spent_time), { :controller => 'time_entries' }) + ' |' if User.current.allowed_to?(:view_time_entries, nil, :global => true) %> - <%= link_to l(:label_overall_activity), { :controller => 'activities', :action => 'index' }%> -
- -

<%=l(:label_project_plural)%>

- -<%= render_project_hierarchy(@projects)%> - -<% if User.current.logged? %> -

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

-<% end %> - -<% other_formats_links do |f| %> - <%= f.link_to 'Atom', :url => {:key => User.current.rss_key} %> -<% end %> - -<% html_title(l(:label_project_plural)) -%> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/04/04bd6b422ff086dcb557f90d7ba79b9553b3ae87.svn-base --- a/.svn/pristine/04/04bd6b422ff086dcb557f90d7ba79b9553b3ae87.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,71 +0,0 @@ -# encoding: utf-8 -module CodeRay -module Scanners - - class Ruby - - class StringState < Struct.new :type, :interpreted, :delim, :heredoc, - :opening_paren, :paren_depth, :pattern, :next_state # :nodoc: all - - CLOSING_PAREN = Hash[ *%w[ - ( ) - [ ] - < > - { } - ] ].each { |k,v| k.freeze; v.freeze } # debug, if I try to change it with << - - STRING_PATTERN = Hash.new do |h, k| - delim, interpreted = *k - # delim = delim.dup # workaround for old Ruby - delim_pattern = Regexp.escape(delim) - if closing_paren = CLOSING_PAREN[delim] - delim_pattern << Regexp.escape(closing_paren) - end - delim_pattern << '\\\\' unless delim == '\\' - - # special_escapes = - # case interpreted - # when :regexp_symbols - # '| [|?*+(){}\[\].^$]' - # end - - h[k] = - if interpreted && delim != '#' - / (?= [#{delim_pattern}] | \# [{$@] ) /mx - else - / (?= [#{delim_pattern}] ) /mx - end - end - - def initialize kind, interpreted, delim, heredoc = false - if heredoc - pattern = heredoc_pattern delim, interpreted, heredoc == :indented - delim = nil - else - pattern = STRING_PATTERN[ [delim, interpreted] ] - if closing_paren = CLOSING_PAREN[delim] - opening_paren = delim - delim = closing_paren - paren_depth = 1 - end - end - super kind, interpreted, delim, heredoc, opening_paren, paren_depth, pattern, :initial - end - - def heredoc_pattern delim, interpreted, indented - # delim = delim.dup # workaround for old Ruby - delim_pattern = Regexp.escape(delim) - delim_pattern = / (?:\A|\n) #{ '(?>[ \t]*)' if indented } #{ Regexp.new delim_pattern } $ /x - if interpreted - / (?= #{delim_pattern}() | \\ | \# [{$@] ) /mx # $1 set == end of heredoc - else - / (?= #{delim_pattern}() | \\ ) /mx - end - end - - end - - end - -end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/04/04dcbe1c1303811a184d8f2171e27c9cdde0c767.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/04/04dcbe1c1303811a184d8f2171e27c9cdde0c767.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,1091 @@ +fa: + # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl) + direction: rtl + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%Y/%m/%d" + short: "%b %d" + long: "%B %d, %Y" + + day_names: [یک‌شنبه, دوشنبه, سه‌شنبه, چهارشنبه, پنج‌شنبه, آدینه, شنبه] + abbr_day_names: [یک, دو, سه, چهار, پنج, آدینه, شنبه] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, ژانویه, Ùوریه, مارس, آوریل, مه, ژوئن, ژوئیه, اوت, سپتامبر, اکتبر, نوامبر, دسامبر] + abbr_month_names: [~, ژان, Ùور, مار, آور, مه, ژوئن, ژوئیه, اوت, سپت, اکت, نوا, دسا] + # Used in date_select and datime_select. + order: + - :year + - :month + - :day + + time: + formats: + default: "%Y/%m/%d - %H:%M" + time: "%H:%M" + short: "%d %b %H:%M" + long: "%d %B %Y ساعت %H:%M" + am: "صبح" + pm: "عصر" + + datetime: + distance_in_words: + half_a_minute: "نیم دقیقه" + less_than_x_seconds: + one: "کمتر از 1 ثانیه" + other: "کمتر از %{count} ثانیه" + x_seconds: + one: "1 ثانیه" + other: "%{count} ثانیه" + less_than_x_minutes: + one: "کمتر از 1 دقیقه" + other: "کمتر از %{count} دقیقه" + x_minutes: + one: "1 دقیقه" + other: "%{count} دقیقه" + about_x_hours: + one: "نزدیک 1 ساعت" + other: "نزدیک %{count} ساعت" + x_hours: + one: "1 ساعت" + other: "%{count} ساعت" + x_days: + one: "1 روز" + other: "%{count} روز" + about_x_months: + one: "نزدیک 1 ماه" + other: "نزدیک %{count} ماه" + x_months: + one: "1 ماه" + other: "%{count} ماه" + about_x_years: + one: "نزدیک 1 سال" + other: "نزدیک %{count} سال" + over_x_years: + one: "بیش از 1 سال" + other: "بیش از %{count} سال" + almost_x_years: + one: "نزدیک 1 سال" + other: "نزدیک %{count} سال" + + number: + # Default format for numbers + format: + separator: "Ù«" + delimiter: "" + precision: 3 + human: + format: + delimiter: "" + precision: 3 + storage_units: + format: "%n %u" + units: + byte: + one: "بایت" + other: "بایت" + kb: "کیلوبایت" + mb: "مگابایت" + gb: "گیگابایت" + tb: "ترابایت" + +# Used in array.to_sentence. + support: + array: + sentence_connector: "Ùˆ" + skip_last_comma: false + + activerecord: + errors: + template: + header: + one: "1 ایراد از ذخیره سازی این %{model} جلوگیری کرد" + other: "%{count} ایراد از ذخیره سازی این %{model} جلوگیری کرد" + messages: + inclusion: "در Ùهرست نیامده است" + exclusion: "رزرو شده است" + invalid: "نادرست است" + confirmation: "با بررسی سازگاری ندارد" + accepted: "باید Ù¾Ø°ÛŒØ±ÙØªÙ‡ شود" + empty: "نمی‌تواند تهی باشد" + blank: "نمی‌تواند تهی باشد" + too_long: "خیلی بلند است (بیشترین اندازه %{count} نویسه است)" + too_short: "خیلی کوتاه است (کمترین اندازه %{count} نویسه است)" + wrong_length: "اندازه نادرست است (باید %{count} نویسه باشد)" + taken: "پیش از این Ú¯Ø±ÙØªÙ‡ شده است" + not_a_number: "شماره درستی نیست" + not_a_date: "تاریخ درستی نیست" + greater_than: "باید بزرگتر از %{count} باشد" + greater_than_or_equal_to: "باید بزرگتر از یا برابر با %{count} باشد" + equal_to: "باید برابر با %{count} باشد" + less_than: "باید کمتر از %{count} باشد" + less_than_or_equal_to: "باید کمتر از یا برابر با %{count} باشد" + odd: "باید ÙØ±Ø¯ باشد" + even: "باید زوج باشد" + greater_than_start_date: "باید از تاریخ آغاز بزرگتر باشد" + not_same_project: "به همان پروژه وابسته نیست" + circular_dependency: "این وابستگی یک وابستگی دایره وار خواهد ساخت" + cant_link_an_issue_with_a_descendant: "یک پیامد نمی‌تواند به یکی از زیر کارهایش پیوند بخورد" + + actionview_instancetag_blank_option: گزینش کنید + + general_text_No: 'خیر' + general_text_Yes: 'آری' + general_text_no: 'خیر' + general_text_yes: 'آری' + general_lang_name: 'Persian (پارسی)' + general_csv_separator: ',' + general_csv_decimal_separator: '.' + general_csv_encoding: UTF-8 + general_pdf_encoding: UTF-8 + general_first_day_of_week: '6' + + notice_account_updated: حساب شما بروز شد. + notice_account_invalid_creditentials: نام کاربری یا گذرواژه نادرست است + notice_account_password_updated: گذرواژه بروز شد + notice_account_wrong_password: گذرواژه نادرست است + notice_account_register_done: حساب ساخته شد. برای ÙØ¹Ø§Ù„ نمودن آن، روی پیوندی Ú©Ù‡ به شما ایمیل شده کلیک کنید. + notice_account_unknown_email: کاربر شناخته نشد. + notice_can_t_change_password: این حساب یک روش شناسایی بیرونی را به کار Ú¯Ø±ÙØªÙ‡ است. گذرواژه را نمی‌توان جایگزین کرد. + notice_account_lost_email_sent: یک ایمیل با راهنمایی درباره گزینش گذرواژه تازه برای شما ÙØ±Ø³ØªØ§Ø¯Ù‡ شد. + notice_account_activated: حساب شما ÙØ¹Ø§Ù„ شده است. اکنون می‌توانید وارد شوید. + notice_successful_create: با موÙقیت ساخته شد. + notice_successful_update: با موÙقیت بروز شد. + notice_successful_delete: با موÙقیت برداشته شد. + notice_successful_connection: با موÙقیت متصل شد. + notice_file_not_found: برگه درخواستی شما در دسترس نیست یا پاک شده است. + notice_locking_conflict: داده‌ها را کاربر دیگری بروز کرده است. + notice_not_authorized: شما به این برگه دسترسی ندارید. + notice_not_authorized_archived_project: پروژه درخواستی شما بایگانی شده است. + notice_email_sent: "یک ایمیل به %{value} ÙØ±Ø³ØªØ§Ø¯Ù‡ شد." + notice_email_error: "یک ایراد در ÙØ±Ø³ØªØ§Ø¯Ù† ایمیل پیش آمد (%{value})." + notice_feeds_access_key_reseted: کلید دسترسی RSS شما بازنشانی شد. + notice_api_access_key_reseted: کلید دسترسی API شما بازنشانی شد. + notice_failed_to_save_issues: "ذخیره سازی %{count} پیامد از %{total} پیامد گزینش شده شکست خورد: %{ids}." + notice_failed_to_save_members: "ذخیره سازی اعضا شکست خورد: %{errors}." + notice_no_issue_selected: "هیچ پیامدی برگزیده نشده است! پیامدهایی Ú©Ù‡ می‌خواهید ویرایش کنید را برگزینید." + notice_account_pending: "حساب شما ساخته شد Ùˆ اکنون چشم به راه روادید سرپرست است." + notice_default_data_loaded: پیکربندی پیش‌گزیده با موÙقیت بار شد. + notice_unable_delete_version: نگارش را نمی‌توان پاک کرد. + notice_unable_delete_time_entry: زمان گزارش شده را نمی‌توان پاک کرد. + notice_issue_done_ratios_updated: اندازه انجام شده پیامد بروز شد. + notice_gantt_chart_truncated: "نمودار بریده شد چون از بیشترین شماری Ú©Ù‡ می‌توان نشان داد بزگتر است (%{max})." + + error_can_t_load_default_data: "پیکربندی پیش‌گزیده نمی‌تواند بار شود: %{value}" + error_scm_not_found: "بخش یا نگارش در انباره پیدا نشد." + error_scm_command_failed: "ایرادی در دسترسی به انباره پیش آمد: %{value}" + error_scm_annotate: "بخش پیدا نشد یا نمی‌توان برای آن یادداشت نوشت." + error_issue_not_found_in_project: 'پیامد پیدا نشد یا به این پروژه وابسته نیست.' + error_no_tracker_in_project: 'هیچ پیگردی به این پروژه پیوسته نشده است. پیکربندی پروژه را بررسی کنید.' + error_no_default_issue_status: 'هیچ وضعیت پیامد پیش‌گزیده‌ای مشخص نشده است. پیکربندی را بررسی کنید (به «پیکربندی -> وضعیت‌های پیامد» بروید).' + error_can_not_delete_custom_field: Ùیلد Ø³ÙØ§Ø±Ø´ÛŒ را نمی‌توان پاک کرد. + error_can_not_delete_tracker: "این پیگرد دارای پیامد است Ùˆ نمی‌توان آن را پاک کرد." + error_can_not_remove_role: "این نقش به کار Ú¯Ø±ÙØªÙ‡ شده است Ùˆ نمی‌توان آن را پاک کرد." + error_can_not_reopen_issue_on_closed_version: 'یک پیامد Ú©Ù‡ به یک نگارش بسته شده وابسته است را نمی‌توان باز کرد.' + error_can_not_archive_project: این پروژه را نمی‌توان بایگانی کرد. + error_issue_done_ratios_not_updated: "اندازه انجام شده پیامد بروز نشد." + error_workflow_copy_source: 'یک پیگرد یا نقش منبع را برگزینید.' + error_workflow_copy_target: 'پیگردها یا نقش‌های مقصد را برگزینید.' + error_unable_delete_issue_status: 'وضعیت پیامد را نمی‌توان پاک کرد.' + error_unable_to_connect: "نمی‌توان متصل شد (%{value})" + warning_attachments_not_saved: "%{count} پرونده ذخیره نشد." + + mail_subject_lost_password: "گذرواژه حساب %{value} شما" + mail_body_lost_password: 'برای جایگزینی گذرواژه خود، بر روی پیوند زیر کلیک کنید:' + mail_subject_register: "ÙØ¹Ø§Ù„سازی حساب %{value} شما" + mail_body_register: 'برای ÙØ¹Ø§Ù„سازی حساب خود، بر روی پیوند زیر کلیک کنید:' + mail_body_account_information_external: "شما می‌توانید حساب %{value} خود را برای ورود به کار برید." + mail_body_account_information: داده‌های حساب شما + mail_subject_account_activation_request: "درخواست ÙØ¹Ø§Ù„سازی حساب %{value}" + mail_body_account_activation_request: "یک کاربر تازه (%{value}) نامنویسی کرده است. این حساب چشم به راه روادید شماست:" + mail_subject_reminder: "زمان رسیدگی به %{count} پیامد در %{days} روز آینده سر می‌رسد" + mail_body_reminder: "زمان رسیدگی به %{count} پیامد Ú©Ù‡ به شما واگذار شده است، در %{days} روز آینده سر می‌رسد:" + mail_subject_wiki_content_added: "برگه ویکی «%{id}» Ø§ÙØ²ÙˆØ¯Ù‡ شد" + mail_body_wiki_content_added: "برگه ویکی «%{id}» به دست %{author} Ø§ÙØ²ÙˆØ¯Ù‡ شد." + mail_subject_wiki_content_updated: "برگه ویکی «%{id}» بروز شد" + mail_body_wiki_content_updated: "برگه ویکی «%{id}» به دست %{author} بروز شد." + + + 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: عبارت منظم + field_min_length: کمترین اندازه + field_max_length: بیشترین اندازه + field_value: مقدار + field_category: دسته + field_title: عنوان + field_project: پروژه + field_issue: پیامد + field_status: وضعیت + field_notes: یادداشت + field_is_closed: پیامد بسته شده + field_is_default: مقدار پیش‌گزیده + field_tracker: پیگرد + field_subject: موضوع + field_due_date: زمان سررسید + field_assigned_to: واگذار شده به + field_priority: برتری + field_fixed_version: نگارش هد٠+ field_user: کاربر + field_principal: دستور دهنده + field_role: نقش + field_homepage: برگه خانه + field_is_public: همگانی + field_parent: پروژه پدر + field_is_in_roadmap: این پیامدها در چشم‌انداز نشان داده شوند + field_login: ورود + field_mail_notification: آگاه سازی‌های ایمیلی + field_admin: سرپرست + field_last_login_on: آخرین ورود + field_language: زبان + field_effective_date: تاریخ + field_password: گذرواژه + field_new_password: گذرواژه تازه + field_password_confirmation: بررسی گذرواژه + field_version: نگارش + field_type: گونه + field_host: میزبان + field_port: درگاه + field_account: حساب + field_base_dn: DN پایه + field_attr_login: نشانه ورود + field_attr_firstname: نشانه نام Ú©ÙˆÚ†Ú© + field_attr_lastname: نشانه نام خانوادگی + field_attr_mail: نشانه ایمیل + field_onthefly: ساخت کاربر بیدرنگ + field_start_date: تاریخ آغاز + field_done_ratio: Ùª انجام شده + field_auth_source: روش شناسایی + field_hide_mail: ایمیل من پنهان شود + field_comments: دیدگاه + field_url: نشانی + field_start_page: برگه آغاز + field_subproject: زیر پروژه + field_hours: ساعت‌ + field_activity: گزارش + field_spent_on: در تاریخ + field_identifier: شناسه + field_is_filter: پالایش پذیر + field_issue_to: پیامد وابسته + field_delay: دیرکرد + field_assignable: پیامدها می‌توانند به این نقش واگذار شوند + field_redirect_existing_links: پیوندهای پیشین به پیوند تازه راهنمایی شوند + field_estimated_hours: زمان برآورد شده + field_column_names: ستون‌ها + field_time_entries: زمان نوشتن + field_time_zone: پهنه زمانی + field_searchable: جستجو پذیر + field_default_value: مقدار پیش‌گزیده + field_comments_sorting: نمایش دیدگاه‌ها + field_parent_title: برگه پدر + field_editable: ویرایش پذیر + field_watcher: دیده‌بان + field_identity_url: نشانی OpenID + field_content: محتوا + field_group_by: دسته بندی با + field_sharing: اشتراک گذاری + field_parent_issue: کار پدر + field_member_of_group: "دسته واگذار شونده" + field_assigned_to_role: "نقش واگذار شونده" + field_text: Ùیلد متنی + field_visible: آشکار + + setting_app_title: نام برنامه + setting_app_subtitle: زیرنام برنامه + setting_welcome_text: نوشتار خوش‌آمد گویی + setting_default_language: زبان پیش‌گزیده + setting_login_required: الزامی بودن ورود + setting_self_registration: خود نام نویسی + setting_attachment_max_size: بیشترین اندازه پیوست + setting_issues_export_limit: کرانه صدور پییامدها + setting_mail_from: نشانی ÙØ±Ø³ØªÙ†Ø¯Ù‡ ایمیل + setting_bcc_recipients: گیرندگان ایمیل دیده نشوند (bcc) + setting_plain_text_mail: ایمیل نوشته ساده (بدون HTML) + setting_host_name: نام میزبان Ùˆ نشانی + setting_text_formatting: قالب بندی نوشته + setting_wiki_compression: ÙØ´Ø±Ø¯Ù‡â€ŒØ³Ø§Ø²ÛŒ پیشینه ویکی + setting_feeds_limit: کرانه محتوای خوراک + setting_default_projects_public: حالت پیش‌گزیده پروژه‌های تازه، همگانی است + setting_autofetch_changesets: Ø¯Ø±ÛŒØ§ÙØª خودکار تغییرات + setting_sys_api_enabled: ÙØ¹Ø§Ù„ سازی وب سرویس برای سرپرستی انباره + setting_commit_ref_keywords: کلیدواژه‌های نشانه + setting_commit_fix_keywords: کلیدواژه‌های انجام + setting_autologin: ورود خودکار + setting_date_format: قالب تاریخ + setting_time_format: قالب زمان + setting_cross_project_issue_relations: توانایی وابستگی میان پروژه‌ای پیامدها + setting_issue_list_default_columns: ستون‌های پیش‌گزیده نمایش داده شده در Ùهرست پیامدها + setting_emails_header: سرنویس ایمیل‌ها + setting_emails_footer: پانویس ایمیل‌ها + setting_protocol: پیوندنامه + setting_per_page_options: گزینه‌های اندازه داده‌های هر برگ + setting_user_format: قالب نمایشی کاربران + setting_activity_days_default: روزهای نمایش داده شده در گزارش پروژه + setting_display_subprojects_issues: پیش‌گزیده نمایش پیامدهای زیرپروژه در پروژه پدر + setting_enabled_scm: ÙØ¹Ø§Ù„سازی SCM + setting_mail_handler_body_delimiters: "بریدن ایمیل‌ها پس از یکی از این ردیÙ‌ها" + setting_mail_handler_api_enabled: ÙØ¹Ø§Ù„سازی وب سرویس برای ایمیل‌های آمده + setting_mail_handler_api_key: کلید API + setting_sequential_project_identifiers: ساخت پشت سر هم شناسه پروژه + setting_gravatar_enabled: کاربرد Gravatar برای عکس کاربر + setting_gravatar_default: عکس Gravatar پیش‌گزیده + setting_diff_max_lines_displayed: بیشترین اندازه ردیÙ‌های ØªÙØ§ÙˆØª نشان داده شده + setting_file_max_size_displayed: بیشترین اندازه پرونده‌های نمایش داده شده درون خطی + setting_repository_log_display_limit: بیشترین شمار نگارش‌های نمایش داده شده در گزارش پرونده + setting_openid: پذیرش ورود Ùˆ نام نویسی با OpenID + setting_password_min_length: کمترین اندازه گذرواژه + setting_new_project_user_role_id: نقش داده شده به کاربری Ú©Ù‡ سرپرست نیست Ùˆ پروژه می‌سازد + setting_default_projects_modules: پیمانه‌های پیش‌گزیده ÙØ¹Ø§Ù„ برای پروژه‌های تازه + setting_issue_done_ratio: برآورد اندازه انجام شده پیامد با + setting_issue_done_ratio_issue_field: کاربرد Ùیلد پیامد + setting_issue_done_ratio_issue_status: کاربرد وضعیت پیامد + setting_start_of_week: آغاز گاهشمار از + setting_rest_api_enabled: ÙØ¹Ø§Ù„سازی وب سرویس‌های REST + setting_cache_formatted_text: نهان سازی نوشته‌های قالب بندی شده + setting_default_notification_option: آگاه سازی پیش‌گزیده + setting_commit_logtime_enabled: ÙØ¹Ø§Ù„سازی زمان گذاشته شده + setting_commit_logtime_activity_id: کار زمان گذاشته شده + setting_gantt_items_limit: بیشترین شمار بخش‌های نمایش داده شده در نمودار گانت + + permission_add_project: ساخت پروژه + permission_add_subprojects: ساخت زیرپروژه + permission_edit_project: ویرایش پروژه + permission_select_project_modules: گزینش پیمانه‌های پروژه + permission_manage_members: سرپرستی اعضا + permission_manage_project_activities: سرپرستی کارهای پروژه + permission_manage_versions: سرپرستی نگارش‌ها + permission_manage_categories: سرپرستی دسته‌های پیامد + permission_view_issues: دیدن پیامدها + 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_delete_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_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: نگه‌داری برگه‌های ویکی + 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: پاک کردن پیام خود + permission_export_wiki_pages: صدور برگه‌های ویکی + permission_manage_subtasks: سرپرستی زیرکارها + + project_module_issue_tracking: پیگیری پیامدها + project_module_time_tracking: پیگیری زمان + project_module_news: رویدادها + project_module_documents: نوشتارها + project_module_files: پرونده‌ها + project_module_wiki: ویکی + project_module_repository: انباره + project_module_boards: انجمن‌ها + project_module_calendar: گاهشمار + project_module_gantt: گانت + + label_user: کاربر + label_user_plural: کاربر + label_user_new: کاربر تازه + label_user_anonymous: ناشناس + label_project: پروژه + label_project_new: پروژه تازه + label_project_plural: پروژه + label_x_projects: + zero: بدون پروژه + one: "1 پروژه" + other: "%{count} پروژه" + label_project_all: همه پروژه‌ها + label_project_latest: آخرین پروژه‌ها + label_issue: پیامد + label_issue_new: پیامد تازه + label_issue_plural: پیامد + label_issue_view_all: دیدن همه پیامدها + label_issues_by: "دسته‌بندی پیامدها با %{value}" + label_issue_added: پیامد Ø§ÙØ²ÙˆØ¯Ù‡ شد + label_issue_updated: پیامد بروز شد + label_document: نوشتار + label_document_new: نوشتار تازه + label_document_plural: نوشتار + label_document_added: نوشتار Ø§ÙØ²ÙˆØ¯Ù‡ شد + label_role: نقش + label_role_plural: نقش + label_role_new: نقش تازه + label_role_and_permissions: نقش‌ها Ùˆ پروانه‌ها + label_member: عضو + label_member_new: عضو تازه + label_member_plural: عضو + label_tracker: پیگرد + label_tracker_plural: پیگرد + label_tracker_new: پیگرد تازه + label_workflow: گردش کار + label_issue_status: وضعیت پیامد + label_issue_status_plural: وضعیت پیامد + label_issue_status_new: وضعیت تازه + label_issue_category: دسته پیامد + label_issue_category_plural: دسته پیامد + label_issue_category_new: دسته تازه + label_custom_field: Ùیلد Ø³ÙØ§Ø±Ø´ÛŒ + label_custom_field_plural: Ùیلد Ø³ÙØ§Ø±Ø´ÛŒ + label_custom_field_new: Ùیلد Ø³ÙØ§Ø±Ø´ÛŒ تازه + label_enumerations: برشمردنی‌ها + label_enumeration_new: مقدار تازه + label_information: داده + label_information_plural: داده + label_please_login: وارد شوید + label_register: نام نویسی کنید + label_login_with_open_id_option: یا با OpenID وارد شوید + label_password_lost: Ø¨Ø§Ø²ÛŒØ§ÙØª گذرواژه + label_home: سرآغاز + label_my_page: برگه من + label_my_account: حساب من + label_my_projects: پروژه‌های من + label_my_page_block: بخش برگه من + label_administration: سرپرستی + label_login: ورود + label_logout: خروج + label_help: راهنما + label_reported_issues: پیامدهای گزارش شده + label_assigned_to_me_issues: پیامدهای واگذار شده به من + label_last_login: آخرین ورود + label_registered_on: نام نویسی شده در + label_activity: گزارش + label_overall_activity: گزارش روی هم Ø±ÙØªÙ‡ + label_user_activity: "گزارش %{value}" + label_new: تازه + label_logged_as: "نام کاربری:" + label_environment: محیط + label_authentication: شناسایی + label_auth_source: روش شناسایی + label_auth_source_new: روش شناسایی تازه + label_auth_source_plural: روش شناسایی + label_subproject_plural: زیرپروژه + label_subproject_new: زیرپروژه تازه + label_and_its_subprojects: "%{value} Ùˆ زیرپروژه‌هایش" + label_min_max_length: کمترین Ùˆ بیشترین اندازه + label_list: Ùهرست + label_date: تاریخ + label_integer: شماره درست + label_float: شماره شناور + label_boolean: درست/نادرست + label_string: نوشته + label_text: نوشته بلند + label_attribute: نشانه + label_attribute_plural: نشانه + 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_settings: پیکربندی + label_overview: در یک نگاه + label_version: نگارش + label_version_new: نگارش تازه + label_version_plural: نگارش + label_close_versions: بستن نگارش‌های انجام شده + 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_x_open_issues_abbr_on_total: + zero: 0 باز از %{total} + one: 1 باز از %{total} + other: "%{count} باز از %{total}" + label_x_open_issues_abbr: + zero: 0 باز + one: 1 باز + other: "%{count} باز" + label_x_closed_issues_abbr: + zero: 0 بسته + one: 1 بسته + other: "%{count} بسته" + 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: گانت + label_internal: درونی + label_last_changes: "%{count} تغییر آخر" + label_change_view_all: دیدن همه تغییرات + label_personalize_page: Ø³ÙØ§Ø±Ø´ÛŒ نمودن این برگه + label_comment: دیدگاه + label_comment_plural: دیدگاه + label_x_comments: + zero: بدون دیدگاه + one: 1 دیدگاه + other: "%{count} دیدگاه" + label_comment_add: Ø§ÙØ²ÙˆØ¯Ù† دیدگاه + label_comment_added: دیدگاه Ø§ÙØ²ÙˆØ¯Ù‡ شد + label_comment_delete: پاک کردن دیدگاه‌ها + label_query: پرس‌وجوی Ø³ÙØ§Ø±Ø´ÛŒ + label_query_plural: پرس‌وجوی Ø³ÙØ§Ø±Ø´ÛŒ + label_query_new: پرس‌وجوی تازه + label_filter_add: Ø§ÙØ²ÙˆØ¯Ù† پالایه + label_filter_plural: پالایه + label_equals: برابر است با + label_not_equals: برابر نیست با + label_in_less_than: کمتر است از + label_in_more_than: بیشتر است از + label_greater_or_equal: بیشتر یا برابر است با + label_less_or_equal: کمتر یا برابر است با + label_in: در + label_today: امروز + label_all_time: همیشه + label_yesterday: دیروز + label_this_week: این Ù‡ÙØªÙ‡ + label_last_week: Ù‡ÙØªÙ‡ پیشین + label_last_n_days: "%{count} روز گذشته" + label_this_month: این ماه + label_last_month: ماه پیشین + label_this_year: امسال + label_date_range: بازه تاریخ + label_less_than_ago: کمتر از چند روز پیشین + label_more_than_ago: بیشتر از چند روز پیشین + label_ago: روز پیشین + label_contains: دارد + label_not_contains: ندارد + label_day_plural: روز + label_repository: انباره + label_repository_plural: انباره + label_browse: چریدن + label_branch: شاخه + label_tag: برچسب + label_revision: بازبینی + label_revision_plural: بازبینی + label_revision_id: "بازبینی %{value}" + label_associated_revisions: بازبینی‌های وابسته + label_added: Ø§ÙØ²ÙˆØ¯Ù‡ شده + label_modified: پیراسته شده + label_copied: رونویسی شده + label_renamed: نامگذاری شده + label_deleted: پاکسازی شده + label_latest_revision: آخرین بازبینی + label_latest_revision_plural: آخرین بازبینی + label_view_revisions: دیدن بازبینی‌ها + label_view_all_revisions: دیدن همه بازبینی‌ها + label_max_size: بیشترین اندازه + label_sort_highest: بردن به آغاز + label_sort_higher: بردن به بالا + label_sort_lower: بردن به پایین + label_sort_lowest: بردن به پایان + label_roadmap: چشم‌انداز + label_roadmap_due_in: "سررسید در %{value}" + label_roadmap_overdue: "%{value} دیرکرد" + 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: شاخص بر اساس نام + label_index_by_date: شاخص بر اساس تاریخ + label_current_version: نگارش کنونی + label_preview: پیش‌نمایش + label_feed_plural: خوراک + label_changes_details: ریز همه جایگذاری‌ها + label_issue_tracking: پیگیری پیامد + label_spent_time: زمان گذاشته شده + label_overall_spent_time: زمان گذاشته شده روی هم + label_f_hour: "%{value} ساعت" + label_f_hour_plural: "%{value} ساعت" + label_time_tracking: پیگیری زمان + label_change_plural: جایگذاری + label_statistics: سرشماری + label_commits_per_month: تغییر در هر ماه + label_commits_per_author: تغییر هر نویسنده + label_view_diff: دیدن ØªÙØ§ÙˆØªâ€ŒÙ‡Ø§ + label_diff_inline: همراستا + label_diff_side_by_side: کنار به کنار + label_options: گزینه‌ها + label_copy_workflow_from: رونویسی گردش کار از روی + label_permissions_report: گزارش پروانه‌ها + label_watched_issues: پیامدهای دیده‌بانی شده + label_related_issues: پیامدهای وابسته + label_applied_status: وضعیت به کار Ø±ÙØªÙ‡ + label_loading: بار گذاری... + label_relation_new: وابستگی تازه + label_relation_delete: پاک کردن وابستگی + label_relates_to: وابسته به + label_duplicates: نگارش دیگری از + label_duplicated_by: نگارشی دیگر در + label_blocks: بازداشت‌ها + label_blocked_by: بازداشت به دست + label_precedes: جلوتر است از + label_follows: پستر است از + label_end_to_start: پایان به آغاز + label_end_to_end: پایان به پایان + label_start_to_start: آغاز به آغاز + label_start_to_end: آغاز به پایان + label_stay_logged_in: وارد شده بمانید + label_disabled: ØºÛŒØ±ÙØ¹Ø§Ù„ + label_show_completed_versions: نمایش نگارش‌های انجام شده + label_me: من + label_board: انجمن + label_board_new: انجمن تازه + label_board_plural: انجمن + label_board_locked: Ù‚ÙÙ„ شده + label_board_sticky: چسبناک + label_topic_plural: Ø³Ø±ÙØµÙ„ + label_message_plural: پیام + label_message_last: آخرین پیام + label_message_new: پیام تازه + label_message_posted: پیام Ø§ÙØ²ÙˆØ¯Ù‡ شد + label_reply_plural: پاسخ + label_send_information: ÙØ±Ø³ØªØ§Ø¯Ù† داده‌های حساب به کاربر + label_year: سال + label_month: ماه + label_week: Ù‡ÙØªÙ‡ + label_date_from: از + label_date_to: تا + label_language_based: بر اساس زبان کاربر + label_sort_by: "جور کرد با %{value}" + label_send_test_email: ÙØ±Ø³ØªØ§Ø¯Ù† ایمیل آزمایشی + label_feeds_access_key: کلید دسترسی RSS + label_missing_feeds_access_key: کلید دسترسی RSS در دسترس نیست + label_feeds_access_key_created_on: "کلید دسترسی RSS %{value} پیش ساخته شده است" + label_module_plural: پیمانه + label_added_time_by: "Ø§ÙØ²ÙˆØ¯Ù‡ شده به دست %{author} در %{age} پیش" + label_updated_time_by: "بروز شده به دست %{author} در %{age} پیش" + label_updated_time: "بروز شده در %{value} پیش" + label_jump_to_a_project: پرش به یک پروژه... + label_file_plural: پرونده + label_changeset_plural: تغییر + label_default_columns: ستون‌های پیش‌گزیده + label_no_change_option: (بدون تغییر) + label_bulk_edit_selected_issues: ویرایش دسته‌ای پیامدهای گزینش شده + label_theme: پوسته + label_default: پیش‌گزیده + label_search_titles_only: تنها نام‌ها جستجو شود + label_user_mail_option_all: "برای هر رویداد در همه پروژه‌ها" + label_user_mail_option_selected: "برای هر رویداد تنها در پروژه‌های گزینش شده..." + label_user_mail_option_none: "هیچ رویدادی" + label_user_mail_option_only_my_events: "تنها برای چیزهایی Ú©Ù‡ دیده‌بان هستم یا در آن‌ها درگیر هستم" + label_user_mail_option_only_assigned: "تنها برای چیزهایی Ú©Ù‡ به من واگذار شده" + label_user_mail_option_only_owner: "تنها برای چیزهایی Ú©Ù‡ من دارنده آن‌ها هستم" + label_user_mail_no_self_notified: "نمی‌خواهم از تغییراتی Ú©Ù‡ خودم می‌دهم آگاه شوم" + label_registration_activation_by_email: ÙØ¹Ø§Ù„سازی حساب با ایمیل + label_registration_manual_activation: ÙØ¹Ø§Ù„سازی حساب دستی + label_registration_automatic_activation: ÙØ¹Ø§Ù„سازی حساب خودکار + label_display_per_page: "ردیÙ‌ها در هر برگه: %{value}" + label_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: به ترتیب تاریخ + label_reverse_chronological_order: برعکس ترتیب تاریخ + label_planning: برنامه ریزی + label_incoming_emails: ایمیل‌های آمده + label_generate_key: ساخت کلید + label_issue_watchers: دیده‌بان‌ها + label_example: نمونه + label_display: نمایش + label_sort: جور کرد + label_ascending: Ø§ÙØ²Ø§ÛŒØ´ÛŒ + label_descending: کاهشی + label_date_from_to: از %{start} تا %{end} + label_wiki_content_added: برگه ویکی Ø§ÙØ²ÙˆØ¯Ù‡ شد + label_wiki_content_updated: برگه ویکی بروز شد + label_group: دسته + label_group_plural: دسته + label_group_new: دسته تازه + label_time_entry_plural: زمان گذاشته شده + label_version_sharing_none: بدون اشتراک + label_version_sharing_descendants: با زیر پروژه‌ها + label_version_sharing_hierarchy: با رشته پروژه‌ها + label_version_sharing_tree: با درخت پروژه + label_version_sharing_system: با همه پروژه‌ها + label_update_issue_done_ratios: بروز رسانی اندازه انجام شده پیامد + label_copy_source: منبع + label_copy_target: مقصد + label_copy_same_as_target: مانند مقصد + label_display_used_statuses_only: تنها وضعیت‌هایی نشان داده شوند Ú©Ù‡ در این پیگرد به کار Ø±ÙØªÙ‡â€ŒØ§Ù†Ø¯ + label_api_access_key: کلید دسترسی API + label_missing_api_access_key: کلید دسترسی API در دسترس نیست + label_api_access_key_created_on: "کلید دسترسی API %{value} پیش ساخته شده است" + label_profile: نمایه + label_subtask_plural: زیرکار + label_project_copy_notifications: در هنگام رونویسی پروژه ایمیل‌های آگاه‌سازی را Ø¨ÙØ±Ø³Øª + label_principal_search: "جستجو برای کاربر یا دسته:" + label_user_search: "جستجو برای کاربر:" + + button_login: ورود + button_submit: واگذاری + button_save: نگهداری + button_check_all: گزینش همه + button_uncheck_all: گزینش هیچ + button_delete: پاک + button_create: ساخت + button_create_and_continue: ساخت Ùˆ ادامه + button_test: آزمایش + button_edit: ویرایش + button_edit_associated_wikipage: "ویرایش برگه ویکی وابسته: %{page_title}" + button_add: Ø§ÙØ²ÙˆØ¯Ù† + button_change: ویرایش + button_apply: انجام + button_clear: پاک + button_lock: گذاشتن Ù‚ÙÙ„ + button_unlock: برداشتن Ù‚ÙÙ„ + button_download: Ø¯Ø±ÛŒØ§ÙØª + button_list: Ùهرست + button_view: دیدن + button_move: جابجایی + button_move_and_follow: جابجایی Ùˆ ادامه + button_back: برگشت + button_cancel: بازگشت + button_activate: ÙØ¹Ø§Ù„سازی + button_sort: جور کرد + button_log_time: زمان‌نویسی + button_rollback: برگرد به این نگارش + button_watch: دیده‌بانی + button_unwatch: نا‌دیده‌بانی + button_reply: پاسخ + button_archive: بایگانی + button_unarchive: برگشت از بایگانی + button_reset: بازنشانی + button_rename: نامگذاری + button_change_password: جایگزینی گذرواژه + button_copy: رونوشت + button_copy_and_follow: رونوشت Ùˆ ادامه + button_annotate: یادداشت + button_update: بروز رسانی + button_configure: پیکربندی + button_quote: نقل قول + button_duplicate: نگارش دیگر + button_show: نمایش + + status_active: ÙØ¹Ø§Ù„ + status_registered: نام‌نویسی شده + status_locked: Ù‚ÙÙ„ + + version_status_open: باز + version_status_locked: Ù‚ÙÙ„ + version_status_closed: بسته + + field_active: ÙØ¹Ø§Ù„ + + text_select_mail_notifications: ÙØ±Ù…ان‌هایی Ú©Ù‡ برای آن‌ها باید ایمیل ÙØ±Ø³ØªØ§Ø¯Ù‡ شود را برگزینید. + text_regexp_info: برای نمونه ^[A-Z0-9]+$ + text_min_max_length_info: 0 یعنی بدون کران + text_project_destroy_confirmation: آیا براستی می‌خواهید این پروژه Ùˆ همه داده‌های آن را پاک کنید؟ + text_subprojects_destroy_warning: "زیرپروژه‌های آن: %{value} هم پاک خواهند شد." + text_workflow_edit: یک نقش Ùˆ یک پیگرد را برای ویرایش گردش کار برگزینید + text_are_you_sure: آیا این کار انجام شود؟ + text_journal_changed: "«%{label}» از «%{old}» به «%{new}» جایگزین شد" + text_journal_set_to: "«%{label}» به «%{value}» نشانده شد" + text_journal_deleted: "«%{label}» پاک شد (%{old})" + text_journal_added: "«%{label}»، «%{value}» را Ø§ÙØ²ÙˆØ¯" + text_tip_task_begin_day: روز آغاز پیامد + text_tip_task_end_day: روز پایان پیامد + text_tip_task_begin_end_day: روز آغاز Ùˆ پایان پیامد + text_caracters_maximum: "بیشترین اندازه %{count} است." + text_caracters_minimum: "کمترین اندازه %{count} است." + text_length_between: "باید میان %{min} Ùˆ %{max} نویسه باشد." + text_tracker_no_workflow: هیچ گردش کاری برای این پیگرد مشخص نشده است + text_unallowed_characters: نویسه‌های ناپسند + text_comma_separated: چند مقدار Ù¾Ø°ÛŒØ±ÙØªÙ†ÛŒ است (با «,» از هم جدا شوند). + text_line_separated: چند مقدار Ù¾Ø°ÛŒØ±ÙØªÙ†ÛŒ است (هر مقدار در یک خط). + text_issues_ref_in_commit_messages: نشانه روی Ùˆ بستن پیامدها در پیام‌های انباره + text_issue_added: "پیامد %{id} به دست %{author} گزارش شد." + text_issue_updated: "پیامد %{id} به دست %{author} بروز شد." + text_wiki_destroy_confirmation: آیا براستی می‌خواهید این ویکی Ùˆ همه محتوای آن را پاک کنید؟ + text_issue_category_destroy_question: "برخی پیامدها (%{count}) به این دسته واگذار شده‌اند. می‌خواهید Ú†Ù‡ کنید؟" + text_issue_category_destroy_assignments: پاک کردن واگذاری به دسته + text_issue_category_reassign_to: واگذاری دوباره پیامدها به این دسته + text_user_mail_option: "برای پروژه‌های گزینش نشده، تنها ایمیل‌هایی درباره چیزهایی Ú©Ù‡ دیده‌بان یا درگیر آن‌ها هستید Ø¯Ø±ÛŒØ§ÙØª خواهید کرد (مانند پیامدهایی Ú©Ù‡ نویسنده آن‌ها هستید یا به شما واگذار شده‌اند)." + text_no_configuration_data: "نقش‌ها، پیگردها، وضعیت‌های پیامد Ùˆ گردش کار هنوز پیکربندی نشده‌اند. \nبه سختی پیشنهاد می‌شود Ú©Ù‡ پیکربندی پیش‌گزیده را بار کنید. سپس می‌توانید آن را ویرایش کنید." + text_load_default_configuration: بارگذاری پیکربندی پیش‌گزیده + text_status_changed_by_changeset: "در تغییر %{value} بروز شده است." + text_time_logged_by_changeset: "در تغییر %{value} نوشته شده است." + text_issues_destroy_confirmation: 'آیا براستی می‌خواهید پیامدهای گزینش شده را پاک کنید؟' + text_select_project_modules: 'پیمانه‌هایی Ú©Ù‡ باید برای این پروژه ÙØ¹Ø§Ù„ شوند را برگزینید:' + text_default_administrator_account_changed: حساب سرپرستی پیش‌گزیده جایگزین شد + text_file_repository_writable: پوشه پیوست‌ها نوشتنی است + text_plugin_assets_writable: پوشه دارایی‌های Ø§ÙØ²ÙˆÙ†Ù‡â€ŒÙ‡Ø§ نوشتنی است + text_rmagick_available: RMagick در دسترس است (اختیاری) + text_destroy_time_entries_question: "%{hours} ساعت روی پیامدهایی Ú©Ù‡ می‌خواهید پاک کنید کار گزارش شده است. می‌خواهید Ú†Ù‡ کنید؟" + text_destroy_time_entries: ساعت‌های گزارش شده پاک شوند + text_assign_time_entries_to_project: ساعت‌های گزارش شده به پروژه واگذار شوند + text_reassign_time_entries: 'ساعت‌های گزارش شده به این پیامد واگذار شوند:' + text_user_wrote: "%{value} نوشت:" + text_enumeration_destroy_question: "%{count} داده به این برشمردنی وابسته شده‌اند." + text_enumeration_category_reassign_to: 'به این برشمردنی وابسته شوند:' + text_email_delivery_not_configured: "Ø¯Ø±ÛŒØ§ÙØª ایمیل پیکربندی نشده است Ùˆ آگاه‌سازی‌ها غیر ÙØ¹Ø§Ù„ هستند.\nکارگزار SMTP خود را در config/configuration.yml پیکربندی کنید Ùˆ برنامه را بازنشانی کنید تا ÙØ¹Ø§Ù„ شوند." + text_repository_usernames_mapping: "کاربر Redmine Ú©Ù‡ به هر نام کاربری پیام‌های انباره نگاشت می‌شود را برگزینید.\nکاربرانی Ú©Ù‡ نام کاربری یا ایمیل همسان دارند، خود به خود نگاشت می‌شوند." + text_diff_truncated: '... این ØªÙØ§ÙˆØª بریده شده چون بیشتر از بیشترین اندازه نمایش دادنی است.' + text_custom_field_possible_values_info: 'یک خط برای هر مقدار' + text_wiki_page_destroy_question: "این برگه %{descendants} زیربرگه دارد.می‌خواهید Ú†Ù‡ کنید؟" + text_wiki_page_nullify_children: "زیربرگه‌ها برگه ریشه شوند" + text_wiki_page_destroy_children: "زیربرگه‌ها Ùˆ زیربرگه‌های آن‌ها پاک شوند" + text_wiki_page_reassign_children: "زیربرگه‌ها به زیر این برگه پدر بروند" + text_own_membership_delete_confirmation: "شما دارید برخی یا همه پروانه‌های خود را برمی‌دارید Ùˆ شاید پس از این دیگر نتوانید این پروژه را ویرایش کنید.\nآیا می‌خواهید این کار را بکنید؟" + text_zoom_in: درشتنمایی + text_zoom_out: ریزنمایی + + default_role_manager: سرپرست + default_role_developer: برنامه‌نویس + default_role_reporter: گزارش‌دهنده + default_tracker_bug: ایراد + default_tracker_feature: ویژگی + default_tracker_support: پشتیبانی + default_issue_status_new: تازه + default_issue_status_in_progress: در گردش + default_issue_status_resolved: درست شده + default_issue_status_feedback: بازخورد + default_issue_status_closed: بسته + default_issue_status_rejected: برگشت خورده + default_doc_category_user: نوشتار کاربر + default_doc_category_tech: نوشتار ÙÙ†ÛŒ + default_priority_low: پایین + default_priority_normal: میانه + default_priority_high: بالا + default_priority_urgent: زود + default_priority_immediate: بیدرنگ + default_activity_design: طراحی + default_activity_development: ساخت + + enumeration_issue_priorities: برتری‌های پیامد + enumeration_doc_categories: دسته‌های نوشتار + enumeration_activities: کارها (پیگیری زمان) + enumeration_system_activity: کار سامانه + + text_tip_issue_begin_day: پیامد در این روز آغاز می‌شود + field_warn_on_leaving_unsaved: هنگام ترک برگه‌ای Ú©Ù‡ نوشته‌های آن نگهداری نشده، به من هشدار بده + text_tip_issue_begin_end_day: پیامد در این روز آغاز می‌شود Ùˆ پایان می‌پذیرد + text_tip_issue_end_day: پیامد در این روز پایان می‌پذیرد + text_warn_on_leaving_unsaved: این برگه دارای نوشته‌های نگهداری نشده است Ú©Ù‡ اگر آن را ترک کنید، از میان می‌روند. + label_my_queries: جستارهای Ø³ÙØ§Ø±Ø´ÛŒ من + text_journal_changed_no_detail: "%{label} بروز شد" + label_news_comment_added: دیدگاه به یک رویداد Ø§ÙØ²ÙˆØ¯Ù‡ شد + button_expand_all: باز کردن همه + button_collapse_all: بستن همه + label_additional_workflow_transitions_for_assignee: زمانی Ú©Ù‡ به کاربر واگذار شده، گذارهای بیشتر Ù¾Ø°ÛŒØ±ÙØªÙ‡ می‌شود + label_additional_workflow_transitions_for_author: زمانی Ú©Ù‡ کاربر نویسنده است، گذارهای بیشتر Ù¾Ø°ÛŒØ±ÙØªÙ‡ می‌شود + label_bulk_edit_selected_time_entries: ویرایش دسته‌ای زمان‌های گزارش شده گزینش شده + text_time_entries_destroy_confirmation: آیا می‌خواهید زمان‌های گزارش شده گزینش شده پاک شوند؟ + label_role_anonymous: ناشناس + label_role_non_member: غیر عضو + label_issue_note_added: یادداشت Ø§ÙØ²ÙˆØ¯Ù‡ شد + label_issue_status_updated: وضعیت بروز شد + label_issue_priority_updated: برتری بروز شد + label_issues_visibility_own: Issues created by or assigned to the user + field_issues_visibility: Issues visibility + label_issues_visibility_all: All issues + permission_set_own_issues_private: Set own issues public or private + field_is_private: Private + permission_set_issues_private: Set issues public or private + label_issues_visibility_public: All non private issues + text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). + field_commit_logs_encoding: کدگذاری پیام‌های انباره + field_scm_path_encoding: Path encoding + text_scm_path_encoding_note: "Default: UTF-8" + field_path_to_repository: Path to repository + field_root_directory: Root directory + field_cvs_module: Module + field_cvsroot: CVSROOT + text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) + text_scm_command: Command + text_scm_command_version: Version + label_git_report_last_commit: Report last commit for files and directories + notice_issue_successful_create: Issue %{id} created. + label_between: between + setting_issue_group_assignment: Allow issue assignment to groups + label_diff: diff + text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) + description_query_sort_criteria_direction: Sort direction + description_project_scope: Search scope + description_filter: Filter + description_user_mail_notification: Mail notification settings + description_date_from: Enter start date + description_message_content: Message content + description_available_columns: Available Columns + description_date_range_interval: Choose range by selecting start and end date + description_issue_category_reassign: Choose issue category + description_search: Searchfield + description_notes: Notes + description_date_range_list: Choose range from list + description_choose_project: Projects + description_date_to: Enter end date + description_query_sort_criteria_attribute: Sort attribute + description_wiki_subpages_reassign: Choose new parent page + description_selected_columns: Selected Columns + label_parent_revision: Parent + label_child_revision: Child + error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. + setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues + button_edit_section: Edit this section + setting_repositories_encodings: Attachments and repositories encodings + description_all_columns: All Columns + button_export: Export + label_export_options: "%{export_format} export options" + error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) + notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." + label_x_issues: + zero: 0 پیامد + one: 1 پیامد + other: "%{count} پیامد" + label_repository_new: New repository + field_repository_is_default: Main repository + label_copy_attachments: Copy attachments + label_item_position: "%{position}/%{count}" + label_completed_versions: Completed versions + text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
Once saved, the identifier cannot be changed. + field_multiple: Multiple values + setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed + text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes + text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) + notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. + text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} + permission_manage_related_issues: Manage related issues + field_auth_source_ldap_filter: LDAP filter + label_search_for_watchers: Search for watchers to add + notice_account_deleted: Your account has been permanently deleted. + setting_unsubscribe: Allow users to delete their own account + button_delete_my_account: Delete my account + text_account_destroy_confirmation: |- + Are you sure you want to proceed? + Your account will be permanently deleted, with no way to reactivate it. + error_session_expired: Your session has expired. Please login again. + text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." + setting_session_lifetime: Session maximum lifetime + setting_session_timeout: Session inactivity timeout + label_session_expiration: Session expiration + permission_close_project: Close / reopen the project + label_show_closed_projects: View closed projects + button_close: Close + button_reopen: Reopen + project_status_active: active + project_status_closed: closed + project_status_archived: archived + text_project_closed: This project is closed and read-only. + notice_user_successful_create: User %{id} created. + field_core_fields: Standard fields + field_timeout: Timeout (in seconds) + setting_thumbnails_enabled: Display attachment thumbnails + setting_thumbnails_size: Thumbnails size (in pixels) + label_status_transitions: Status transitions + label_fields_permissions: Fields permissions + label_readonly: Read-only + label_required: Required + text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
Once saved, the identifier cannot be changed. + field_board_parent: Parent forum + label_attribute_of_project: Project's %{name} + label_attribute_of_author: Author's %{name} + label_attribute_of_assigned_to: Assignee's %{name} + label_attribute_of_fixed_version: Target version's %{name} + label_copy_subtasks: Copy subtasks + label_copied_to: copied to + label_copied_from: copied from + label_any_issues_in_project: any issues in project + label_any_issues_not_in_project: any issues not in project + field_private_notes: Private notes + permission_view_private_notes: View private notes + permission_set_notes_private: Set notes as private + label_no_issues_in_project: no issues in project + label_any: همه + label_last_n_weeks: last %{count} weeks + setting_cross_project_subtasks: Allow cross-project subtasks + label_cross_project_descendants: با زیر پروژه‌ها + label_cross_project_tree: با درخت پروژه + label_cross_project_hierarchy: با رشته پروژه‌ها + label_cross_project_system: با همه پروژه‌ها + button_hide: Hide + setting_non_working_week_days: Non-working days + label_in_the_next_days: in the next + label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: جمله + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/05/05366a1767d0cebdb112d7b0a839d8704040e98a.svn-base --- a/.svn/pristine/05/05366a1767d0cebdb112d7b0a839d8704040e98a.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,24 +0,0 @@ -#!/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) -# and the number of requests to process before running garbage collection. -# -# By default, the FastCGI crash log is RAILS_ROOT/log/fastcgi.crash.log -# and the GC period is nil (turned off). A reasonable number of requests -# could range from 10-100 depending on the memory footprint of your app. -# -# Example: -# # Default log path, normal GC behavior. -# RailsFCGIHandler.process! -# -# # Default log path, 50 requests between GC. -# RailsFCGIHandler.process! nil, 50 -# -# # Custom log path, normal GC behavior. -# RailsFCGIHandler.process! '/var/log/myapp_fcgi_crash.log' -# -require File.dirname(__FILE__) + "/../config/environment" -require 'fcgi_handler' - -RailsFCGIHandler.process! diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/05/05764e8ea551fb41de1ac455c66cb6f2907259b8.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/05/05764e8ea551fb41de1ac455c66cb6f2907259b8.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,22 @@ +/* Portuguese initialisation for the jQuery UI date picker plugin. */ +jQuery(function($){ + $.datepicker.regional['pt'] = { + closeText: 'Fechar', + prevText: '<Anterior', + nextText: 'Seguinte', + currentText: 'Hoje', + monthNames: ['Janeiro','Fevereiro','Março','Abril','Maio','Junho', + 'Julho','Agosto','Setembro','Outubro','Novembro','Dezembro'], + monthNamesShort: ['Jan','Fev','Mar','Abr','Mai','Jun', + 'Jul','Ago','Set','Out','Nov','Dez'], + dayNames: ['Domingo','Segunda-feira','Terça-feira','Quarta-feira','Quinta-feira','Sexta-feira','Sábado'], + dayNamesShort: ['Dom','Seg','Ter','Qua','Qui','Sex','Sáb'], + dayNamesMin: ['Dom','Seg','Ter','Qua','Qui','Sex','Sáb'], + weekHeader: 'Sem', + dateFormat: 'dd/mm/yy', + firstDay: 0, + isRTL: false, + showMonthAfterYear: false, + yearSuffix: ''}; + $.datepicker.setDefaults($.datepicker.regional['pt']); +}); diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/05/05d53773172688f95b7f90e6ffa69f1cbdf7504b.svn-base --- a/.svn/pristine/05/05d53773172688f95b7f90e6ffa69f1cbdf7504b.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,97 +0,0 @@ -
- « - <% unless @changeset.previous.nil? -%> - <%= link_to_revision(@changeset.previous, @project, :text => l(:label_previous)) %> - <% else -%> - <%= l(:label_previous) %> - <% end -%> -| - <% unless @changeset.next.nil? -%> - <%= link_to_revision(@changeset.next, @project, :text => l(:label_next)) %> - <% else -%> - <%= l(:label_next) %> - <% end -%> - »  - - <% form_tag({:controller => 'repositories', - :action => 'revision', - :id => @project, - :rev => nil}, - :method => :get) do %> - <%= text_field_tag 'rev', @rev, :size => 8 %> - <%= submit_tag 'OK', :name => nil %> - <% end %> -
- -

<%= l(:label_revision) %> <%= format_revision(@changeset) %>

- - - <% if @changeset.scmid %> - - - - <% end %> - <% unless @changeset.parents.blank? %> - - - - - <% end %> - <% unless @changeset.children.blank? %> - - - - - <% end %> -
ID<%= h(@changeset.scmid) %>
<%= l(:label_parent_revision) %> - <%= @changeset.parents.collect{ - |p| link_to_revision(p, @project, :text => format_revision(p)) - }.join(", ") %> -
<%= l(:label_child_revision) %> - <%= @changeset.children.collect{ - |p| link_to_revision(p, @project, :text => format_revision(p)) - }.join(", ") %> -
-

- -<%= authoring(@changeset.committed_on, @changeset.author) %> - -

- -<%= textilizable @changeset.comments %> - -<% if @changeset.issues.visible.any? %> -

<%= l(:label_related_issues) %>

-
    -<% @changeset.issues.visible.each do |issue| %> -
  • <%= link_to_issue issue %>
  • -<% end %> -
-<% end %> - -<% if User.current.allowed_to?(:browse_repository, @project) %> -

<%= l(:label_attachment_plural) %>

-
    -
  • <%= l(:label_added) %>
  • -
  • <%= l(:label_modified) %>
  • -
  • <%= l(:label_copied) %>
  • -
  • <%= l(:label_renamed) %>
  • -
  • <%= l(:label_deleted) %>
  • -
- -

<%= link_to(l(:label_view_diff), - :action => 'diff', - :id => @project, - :path => "", - :rev => @changeset.identifier) if @changeset.changes.any? %>

- -
-<%= render_changeset_changes %> -
-<% end %> - -<% content_for :header_tags do %> -<%= stylesheet_link_tag "scm" %> -<% end %> - -<% html_title("#{l(:label_revision)} #{format_revision(@changeset)}") -%> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/05/05d8cde4a4e9772f7dcbafb481eff20153ca4409.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/05/05d8cde4a4e9772f7dcbafb481eff20153ca4409.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,98 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 MimeType + + MIME_TYPES = { + 'text/plain' => 'txt,tpl,properties,patch,diff,ini,readme,install,upgrade', + 'text/css' => 'css', + 'text/html' => 'html,htm,xhtml', + 'text/jsp' => 'jsp', + 'text/x-c' => 'c,cpp,cc,h,hh', + 'text/x-csharp' => 'cs', + 'text/x-java' => 'java', + 'text/x-html-template' => 'rhtml', + 'text/x-perl' => 'pl,pm', + 'text/x-php' => 'php,php3,php4,php5', + 'text/x-python' => 'py', + 'text/x-ruby' => 'rb,rbw,ruby,rake,erb', + 'text/x-csh' => 'csh', + 'text/x-sh' => 'sh', + 'text/xml' => 'xml,xsd,mxml', + 'text/yaml' => 'yml,yaml', + 'text/csv' => 'csv', + 'text/x-po' => 'po', + 'image/gif' => 'gif', + 'image/jpeg' => 'jpg,jpeg,jpe', + 'image/png' => 'png', + 'image/tiff' => 'tiff,tif', + 'image/x-ms-bmp' => 'bmp', + 'image/x-xpixmap' => 'xpm', + 'image/svg+xml'=> 'svg', + 'application/javascript' => 'js', + 'application/pdf' => 'pdf', + 'application/rtf' => 'rtf', + 'application/msword' => 'doc', + 'application/vnd.ms-excel' => 'xls', + 'application/vnd.ms-powerpoint' => 'ppt,pps', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx', + 'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => 'ppsx', + 'application/vnd.oasis.opendocument.spreadsheet' => 'ods', + 'application/vnd.oasis.opendocument.text' => 'odt', + 'application/vnd.oasis.opendocument.presentation' => 'odp', + 'application/x-7z-compressed' => '7z', + 'application/x-rar-compressed' => 'rar', + 'application/x-tar' => 'tar', + 'application/zip' => 'zip', + 'application/x-gzip' => 'gz', + }.freeze + + EXTENSIONS = MIME_TYPES.inject({}) do |map, (type, exts)| + exts.split(',').each {|ext| map[ext.strip] = type} + map + end + + # returns mime type for name or nil if unknown + def self.of(name) + return nil unless name + m = name.to_s.match(/(^|\.)([^\.]+)$/) + EXTENSIONS[m[2].downcase] if m + end + + # Returns the css class associated to + # the mime type of name + def self.css_class_of(name) + mime = of(name) + mime && mime.gsub('/', '-') + end + + def self.main_mimetype_of(name) + mimetype = of(name) + mimetype.split('/').first if mimetype + end + + # return true if mime-type for name is type/* + # otherwise false + def self.is_type?(type, name) + main_mimetype = main_mimetype_of(name) + type.to_s == main_mimetype + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/05/05fb4e3ebed828cf639f80e5d1d6da6095d43000.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/05/05fb4e3ebed828cf639f80e5d1d6da6095d43000.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,42 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 ProjectEnumerationsController < ApplicationController + before_filter :find_project_by_project_id + before_filter :authorize + + def update + if request.put? && params[:enumerations] + Project.transaction do + params[:enumerations].each do |id, activity| + @project.update_or_create_time_entry_activity(id, activity) + end + end + flash[:notice] = l(:notice_successful_update) + end + + redirect_to settings_project_path(@project, :tab => 'activities') + end + + def destroy + @project.time_entry_activities.each do |time_entry_activity| + time_entry_activity.destroy(time_entry_activity.parent) + end + flash[:notice] = l(:notice_successful_update) + redirect_to settings_project_path(@project, :tab => 'activities') + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/06/061fdfd5800e4aa71d26012579cbbe6954d78738.svn-base --- a/.svn/pristine/06/061fdfd5800e4aa71d26012579cbbe6954d78738.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ -# $Id: testem.rb 121 2006-05-15 18:36:24Z blackhedd $ -# -# - -require 'test/unit' -require 'tests/testber' -require 'tests/testldif' -require 'tests/testldap' -require 'tests/testpsw' -require 'tests/testfilter' - - diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/06/0661103167f6b9ecf574c505f5bd6a17eadf893e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/06/0661103167f6b9ecf574c505f5bd6a17eadf893e.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,703 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require "digest/sha1" + +class User < Principal + include Redmine::SafeAttributes + + # Different ways of displaying/sorting users + USER_FORMATS = { + :firstname_lastname => { + :string => '#{firstname} #{lastname}', + :order => %w(firstname lastname id), + :setting_order => 1 + }, + :firstname_lastinitial => { + :string => '#{firstname} #{lastname.to_s.chars.first}.', + :order => %w(firstname lastname id), + :setting_order => 2 + }, + :firstname => { + :string => '#{firstname}', + :order => %w(firstname id), + :setting_order => 3 + }, + :lastname_firstname => { + :string => '#{lastname} #{firstname}', + :order => %w(lastname firstname id), + :setting_order => 4 + }, + :lastname_coma_firstname => { + :string => '#{lastname}, #{firstname}', + :order => %w(lastname firstname id), + :setting_order => 5 + }, + :lastname => { + :string => '#{lastname}', + :order => %w(lastname id), + :setting_order => 6 + }, + :username => { + :string => '#{login}', + :order => %w(login id), + :setting_order => 7 + }, + } + + MAIL_NOTIFICATION_OPTIONS = [ + ['all', :label_user_mail_option_all], + ['selected', :label_user_mail_option_selected], + ['only_my_events', :label_user_mail_option_only_my_events], + ['only_assigned', :label_user_mail_option_only_assigned], + ['only_owner', :label_user_mail_option_only_owner], + ['none', :label_user_mail_option_none] + ] + + has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)}, + :after_remove => Proc.new {|user, group| group.user_removed(user)} + has_many :changesets, :dependent => :nullify + has_one :preference, :dependent => :destroy, :class_name => 'UserPreference' + has_one :rss_token, :class_name => 'Token', :conditions => "action='feeds'" + has_one :api_token, :class_name => 'Token', :conditions => "action='api'" + belongs_to :auth_source + + scope :logged, lambda { where("#{User.table_name}.status <> #{STATUS_ANONYMOUS}") } + scope :status, lambda {|arg| where(arg.blank? ? nil : {:status => arg.to_i}) } + + acts_as_customizable + + attr_accessor :password, :password_confirmation + attr_accessor :last_before_login_on + # Prevents unauthorized assignments + attr_protected :login, :admin, :password, :password_confirmation, :hashed_password + + LOGIN_LENGTH_LIMIT = 60 + MAIL_LENGTH_LIMIT = 60 + + 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_changed? && user.login.present? }, :case_sensitive => false + validates_uniqueness_of :mail, :if => Proc.new { |user| user.mail_changed? && user.mail.present? }, :case_sensitive => false + # Login must contain letters, numbers, underscores only + validates_format_of :login, :with => /\A[a-z0-9_\-@\.]*\z/i + validates_length_of :login, :maximum => LOGIN_LENGTH_LIMIT + validates_length_of :firstname, :lastname, :maximum => 30 + validates_format_of :mail, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, :allow_blank => true + validates_length_of :mail, :maximum => MAIL_LENGTH_LIMIT, :allow_nil => true + validates_confirmation_of :password, :allow_nil => true + validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true + validate :validate_password_length + + before_create :set_mail_notification + before_save :update_hashed_password + before_destroy :remove_references_before_destroy + + scope :in_group, lambda {|group| + group_id = group.is_a?(Group) ? group.id : group.to_i + where("#{User.table_name}.id IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id) + } + scope :not_in_group, lambda {|group| + group_id = group.is_a?(Group) ? group.id : group.to_i + where("#{User.table_name}.id NOT IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id) + } + scope :sorted, lambda { order(*User.fields_for_order_statement)} + + def set_mail_notification + self.mail_notification = Setting.default_notification_option if self.mail_notification.blank? + true + end + + def update_hashed_password + # update hashed_password if password was set + if self.password && self.auth_source_id.blank? + salt_password(password) + end + end + + alias :base_reload :reload + def reload(*args) + @name = nil + @projects_by_role = nil + @membership_by_project_id = nil + base_reload(*args) + end + + def mail=(arg) + write_attribute(:mail, arg.to_s.strip) + end + + def identity_url=(url) + if url.blank? + write_attribute(:identity_url, '') + else + begin + write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url)) + rescue OpenIdAuthentication::InvalidOpenId + # Invalid url, don't save + end + end + self.read_attribute(:identity_url) + end + + # Returns the user that matches provided login and password, or nil + def self.try_to_login(login, password) + login = login.to_s + password = password.to_s + + # Make sure no one can sign in with an empty login or password + return nil if login.empty? || password.empty? + user = find_by_login(login) + if user + # user is already in local database + return nil unless user.active? + return nil unless user.check_password?(password) + else + # user is not yet registered, try to authenticate with available sources + attrs = AuthSource.authenticate(login, password) + if attrs + user = new(attrs) + user.login = login + user.language = Setting.default_language + if user.save + user.reload + logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source + end + end + end + user.update_column(:last_login_on, Time.now) if user && !user.new_record? + user + rescue => text + raise text + end + + # Returns the user who matches the given autologin +key+ or nil + def self.try_to_autologin(key) + user = Token.find_active_user('autologin', key, Setting.autologin.to_i) + if user + user.update_column(:last_login_on, Time.now) + user + end + end + + def self.name_formatter(formatter = nil) + USER_FORMATS[formatter || Setting.user_format] || USER_FORMATS[:firstname_lastname] + end + + # Returns an array of fields names than can be used to make an order statement for users + # according to how user names are displayed + # Examples: + # + # User.fields_for_order_statement => ['users.login', 'users.id'] + # User.fields_for_order_statement('authors') => ['authors.login', 'authors.id'] + def self.fields_for_order_statement(table=nil) + table ||= table_name + name_formatter[:order].map {|field| "#{table}.#{field}"} + end + + # Return user's full name for display + def name(formatter = nil) + f = self.class.name_formatter(formatter) + if formatter + eval('"' + f[:string] + '"') + else + @name ||= eval('"' + f[:string] + '"') + end + end + + def active? + self.status == STATUS_ACTIVE + end + + def registered? + self.status == STATUS_REGISTERED + end + + def locked? + self.status == STATUS_LOCKED + end + + def activate + self.status = STATUS_ACTIVE + end + + def register + self.status = STATUS_REGISTERED + end + + def lock + self.status = STATUS_LOCKED + end + + def activate! + update_attribute(:status, STATUS_ACTIVE) + end + + def register! + update_attribute(:status, STATUS_REGISTERED) + end + + def lock! + update_attribute(:status, STATUS_LOCKED) + end + + # Returns true if +clear_password+ is the correct user's password, otherwise false + def check_password?(clear_password) + if auth_source_id.present? + auth_source.authenticate(self.login, clear_password) + else + User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password + end + end + + # Generates a random salt and computes hashed_password for +clear_password+ + # The hashed password is stored in the following form: SHA1(salt + SHA1(password)) + def salt_password(clear_password) + self.salt = User.generate_salt + self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}") + end + + # Does the backend storage allow this user to change their password? + def change_password_allowed? + return true if auth_source.nil? + return auth_source.allow_password_changes? + 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 : ActiveSupport::TimeZone[self.pref.time_zone]) + end + + def wants_comments_in_reverse_order? + self.pref[:comments_sorting] == 'desc' + end + + # Return user's RSS key (a 40 chars long string), used to access feeds + def rss_key + if rss_token.nil? + create_rss_token(:action => 'feeds') + end + rss_token.value + end + + # Return user's API key (a 40 chars long string), used to access the API + def api_key + if api_token.nil? + create_api_token(:action => 'api') + end + api_token.value + end + + # Return an array of project ids for which the user has explicitly turned mail notifications on + def notified_projects_ids + @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id) + end + + def notified_project_ids=(ids) + Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id]) + Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty? + @notified_projects_ids = nil + notified_projects_ids + end + + def valid_notification_options + self.class.valid_notification_options(self) + end + + # Only users that belong to more than 1 project can select projects for which they are notified + def self.valid_notification_options(user=nil) + # Note that @user.membership.size would fail since AR ignores + # :include association option when doing a count + if user.nil? || user.memberships.length < 1 + MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'} + else + MAIL_NOTIFICATION_OPTIONS + end + end + + # Find a user account by matching the exact login and then a case-insensitive + # version. Exact matches will be given priority. + def self.find_by_login(login) + if login.present? + login = login.to_s + # First look for an exact match + user = where(:login => login).all.detect {|u| u.login == login} + unless user + # Fail over to case-insensitive if none was found + user = where("LOWER(login) = ?", login.downcase).first + end + user + end + end + + def self.find_by_rss_key(key) + Token.find_active_user('feeds', key) + end + + def self.find_by_api_key(key) + Token.find_active_user('api', key) + end + + # Makes find_by_mail case-insensitive + def self.find_by_mail(mail) + where("LOWER(mail) = ?", mail.to_s.downcase).first + end + + # Returns true if the default admin account can no longer be used + def self.default_admin_account_changed? + !User.active.find_by_login("admin").try(:check_password?, "admin") + end + + def to_s + name + end + + CSS_CLASS_BY_STATUS = { + STATUS_ANONYMOUS => 'anon', + STATUS_ACTIVE => 'active', + STATUS_REGISTERED => 'registered', + STATUS_LOCKED => 'locked' + } + + def css_classes + "user #{CSS_CLASS_BY_STATUS[status]}" + end + + # Returns the current day according to user's time zone + def today + if time_zone.nil? + Date.today + else + Time.now.in_time_zone(time_zone).to_date + end + end + + # Returns the day of +time+ according to user's time zone + def time_to_date(time) + if time_zone.nil? + time.to_date + else + time.in_time_zone(time_zone).to_date + end + end + + def logged? + true + end + + def anonymous? + !logged? + end + + # Returns user's membership for the given project + # or nil if the user is not a member of project + def membership(project) + project_id = project.is_a?(Project) ? project.id : project + + @membership_by_project_id ||= Hash.new {|h, project_id| + h[project_id] = memberships.where(:project_id => project_id).first + } + @membership_by_project_id[project_id] + end + + # Return user's roles for project + def roles_for_project(project) + roles = [] + # No role on archived projects + return roles if project.nil? || project.archived? + if logged? + # Find project membership + membership = membership(project) + if membership + roles = membership.roles + else + @role_non_member ||= Role.non_member + roles << @role_non_member + end + else + @role_anonymous ||= Role.anonymous + roles << @role_anonymous + end + roles + end + + # Return true if the user is a member of project + def member_of?(project) + projects.to_a.include?(project) + end + + # Returns a hash of user's projects grouped by roles + def projects_by_role + return @projects_by_role if @projects_by_role + + @projects_by_role = Hash.new([]) + memberships.each do |membership| + if membership.project + membership.roles.each do |role| + @projects_by_role[role] = [] unless @projects_by_role.key?(role) + @projects_by_role[role] << membership.project + end + end + end + @projects_by_role.each do |role, projects| + projects.uniq! + end + + @projects_by_role + end + + # Returns true if user is arg or belongs to arg + def is_or_belongs_to?(arg) + if arg.is_a?(User) + self == arg + elsif arg.is_a?(Group) + arg.users.include?(self) + else + false + end + end + + # Return true if the user is allowed to do the specified action on a specific context + # Action can be: + # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit') + # * a permission Symbol (eg. :edit_project) + # Context can be: + # * a project : returns true if user is allowed to do the specified action on this project + # * an array of projects : returns true if user is allowed on every project + # * nil with options[:global] set : check if user has at least one role allowed for this action, + # or falls back to Non Member / Anonymous permissions depending if the user is logged + def allowed_to?(action, context, options={}, &block) + if context && context.is_a?(Project) + return false unless context.allows_to?(action) + # Admin users are authorized for anything else + return true if admin? + + roles = roles_for_project(context) + return false unless roles + roles.any? {|role| + (context.is_public? || role.member?) && + role.allowed_to?(action) && + (block_given? ? yield(role, self) : true) + } + elsif context && context.is_a?(Array) + if context.empty? + false + else + # Authorize if user is authorized on every element of the array + context.map {|project| allowed_to?(action, project, options, &block)}.reduce(:&) + end + elsif options[:global] + # Admin users are always authorized + return true if admin? + + # authorize if user has at least one role that has this permission + roles = memberships.collect {|m| m.roles}.flatten.uniq + roles << (self.logged? ? Role.non_member : Role.anonymous) + roles.any? {|role| + role.allowed_to?(action) && + (block_given? ? yield(role, self) : true) + } + else + false + end + end + + # Is the user allowed to do the specified action on any project? + # See allowed_to? for the actions and valid options. + def allowed_to_globally?(action, options, &block) + allowed_to?(action, nil, options.reverse_merge(:global => true), &block) + end + + # Returns true if the user is allowed to delete his own account + def own_account_deletable? + Setting.unsubscribe? && + (!admin? || User.active.where("admin = ? AND id <> ?", true, id).exists?) + end + + safe_attributes 'login', + 'firstname', + 'lastname', + 'mail', + 'mail_notification', + 'language', + 'custom_field_values', + 'custom_fields', + 'identity_url' + + safe_attributes 'status', + 'auth_source_id', + :if => lambda {|user, current_user| current_user.admin?} + + safe_attributes 'group_ids', + :if => lambda {|user, current_user| current_user.admin? && !user.new_record?} + + # Utility method to help check if a user should be notified about an + # event. + # + # TODO: only supports Issue events currently + def notify_about?(object) + if mail_notification == 'all' + true + elsif mail_notification.blank? || mail_notification == 'none' + false + else + case object + when Issue + case mail_notification + when 'selected', 'only_my_events' + # user receives notifications for created/assigned issues on unselected projects + object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was) + when 'only_assigned' + is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was) + when 'only_owner' + object.author == self + end + when News + # always send to project members except when mail_notification is set to 'none' + true + end + end + end + + def self.current=(user) + Thread.current[:current_user] = user + end + + def self.current + Thread.current[:current_user] ||= User.anonymous + end + + # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only + # one anonymous user per database. + def self.anonymous + anonymous_user = AnonymousUser.first + if anonymous_user.nil? + anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0) + raise 'Unable to create the anonymous user.' if anonymous_user.new_record? + end + anonymous_user + end + + # Salts all existing unsalted passwords + # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password)) + # This method is used in the SaltPasswords migration and is to be kept as is + def self.salt_unsalted_passwords! + transaction do + User.where("salt IS NULL OR salt = ''").find_each do |user| + next if user.hashed_password.blank? + salt = User.generate_salt + hashed_password = User.hash_password("#{salt}#{user.hashed_password}") + User.where(:id => user.id).update_all(:salt => salt, :hashed_password => hashed_password) + end + end + end + + protected + + def validate_password_length + # Password length validation based on setting + if !password.nil? && password.size < Setting.password_min_length.to_i + errors.add(:password, :too_short, :count => Setting.password_min_length.to_i) + end + end + + private + + # Removes references that are not handled by associations + # Things that are not deleted are reassociated with the anonymous user + def remove_references_before_destroy + return if self.id.nil? + + substitute = User.anonymous + Attachment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] + Comment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] + Issue.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] + Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id] + Journal.update_all ['user_id = ?', substitute.id], ['user_id = ?', id] + JournalDetail.update_all ['old_value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s] + JournalDetail.update_all ['value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s] + Message.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] + News.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] + # Remove private queries and keep public ones + ::Query.delete_all ['user_id = ? AND is_public = ?', id, false] + ::Query.update_all ['user_id = ?', substitute.id], ['user_id = ?', id] + TimeEntry.update_all ['user_id = ?', substitute.id], ['user_id = ?', id] + Token.delete_all ['user_id = ?', id] + Watcher.delete_all ['user_id = ?', id] + WikiContent.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] + WikiContent::Version.update_all ['author_id = ?', substitute.id], ['author_id = ?', id] + end + + # Return password digest + def self.hash_password(clear_password) + Digest::SHA1.hexdigest(clear_password || "") + end + + # Returns a 128bits random salt as a hex string (32 chars long) + def self.generate_salt + Redmine::Utils.random_hex(16) + end + +end + +class AnonymousUser < User + validate :validate_anonymous_uniqueness, :on => :create + + def validate_anonymous_uniqueness + # There should be only one AnonymousUser in the database + errors.add :base, 'An anonymous user already exists.' if AnonymousUser.exists? + end + + def available_custom_fields + [] + end + + # Overrides a few properties + def logged?; false end + def admin; false end + def name(*args); I18n.t(:label_user_anonymous) end + def mail; nil end + def time_zone; nil end + def rss_key; nil end + + def pref + UserPreference.new(:user => self) + end + + def member_of?(project) + false + end + + # Anonymous user can not be destroyed + def destroy + false + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/06/066f939eced969c1eec65f1fe6ed629130668c67.svn-base --- a/.svn/pristine/06/066f939eced969c1eec65f1fe6ed629130668c67.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,25 +0,0 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -module IssueRelationsHelper - def collection_for_relation_type_select - values = IssueRelation::TYPES - values.keys.sort{|x,y| values[x][:order] <=> values[y][:order]}.collect{|k| [l(values[k][:name]), k]} - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/06/067fc70eea1018fdccc706144d28aa0ea0565e34.svn-base --- a/.svn/pristine/06/067fc70eea1018fdccc706144d28aa0ea0565e34.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ ---- -watchers_001: - watchable_type: Issue - watchable_id: 2 - user_id: 3 -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 -r 0a574315af3e -r 4f746d8966dd .svn/pristine/06/068885f0c95ca2ed440e38b13dd95f7bb13f11dc.svn-base --- a/.svn/pristine/06/068885f0c95ca2ed440e38b13dd95f7bb13f11dc.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,59 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) - -class CustomFieldTest < ActiveSupport::TestCase - fixtures :custom_fields - - def test_create - field = UserCustomField.new(:name => 'Money money money', :field_format => 'float') - assert field.save - end - - def test_regexp_validation - field = IssueCustomField.new(:name => 'regexp', :field_format => 'text', :regexp => '[a-z0-9') - assert !field.save - assert_equal I18n.t('activerecord.errors.messages.invalid'), field.errors.on(:regexp) - - field.regexp = '[a-z0-9]' - 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 - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/06/06aa9d1d0636535d93ef529aee72095eced64467.svn-base --- a/.svn/pristine/06/06aa9d1d0636535d93ef529aee72095eced64467.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,18 +0,0 @@ -<%= call_hook(:view_repositories_show_contextual, { :repository => @repository, :project => @project }) %> - -
- <%= render :partial => 'navigation' %> -
- -

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

- -

<%= render :partial => 'link_to_functions' %>

- -<%= render_properties(@properties) %> - -<%= render(:partial => 'revisions', - :locals => {:project => @project, :path => @path, :revisions => @changesets, :entry => @entry }) unless @changesets.empty? %> - -<% html_title(l(:label_change_plural)) -%> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/06/06b89fac956f5eaa8ae34a2daf2088c102b5cfb1.svn-base --- a/.svn/pristine/06/06b89fac956f5eaa8ae34a2daf2088c102b5cfb1.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,136 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class Enumeration < ActiveRecord::Base - default_scope :order => "#{Enumeration.table_name}.position ASC" - - belongs_to :project - - acts_as_list :scope => 'type = \'#{type}\'' - acts_as_customizable - acts_as_tree :order => 'position ASC' - - before_destroy :check_integrity - before_save :check_default - - validates_presence_of :name - validates_uniqueness_of :name, :scope => [:type, :project_id] - validates_length_of :name, :maximum => 30 - - named_scope :shared, :conditions => { :project_id => nil } - named_scope :active, :conditions => { :active => true } - named_scope :named, lambda {|arg| { :conditions => ["LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip]}} - - def self.default - # Creates a fake default scope so Enumeration.default will check - # it's type. STI subclasses will automatically add their own - # types to the finder. - if self.descends_from_active_record? - find(:first, :conditions => { :is_default => true, :type => 'Enumeration' }) - else - # STI classes are - find(:first, :conditions => { :is_default => true }) - end - end - - # Overloaded on concrete classes - def option_name - nil - end - - def check_default - if is_default? && is_default_changed? - Enumeration.update_all("is_default = #{connection.quoted_false}", {:type => type}) - end - end - - # Overloaded on concrete classes - def objects_count - 0 - end - - def in_use? - self.objects_count != 0 - end - - # Is this enumeration overiding a system level enumeration? - def is_override? - !self.parent.nil? - end - - alias :destroy_without_reassign :destroy - - # Destroy the enumeration - # If a enumeration is specified, objects are reassigned - def destroy(reassign_to = nil) - if reassign_to && reassign_to.is_a?(Enumeration) - self.transfer_relations(reassign_to) - end - destroy_without_reassign - end - - def <=>(enumeration) - position <=> enumeration.position - end - - def to_s; name end - - # Returns the Subclasses of Enumeration. Each Subclass needs to be - # required in development mode. - # - # Note: subclasses is protected in ActiveRecord - def self.get_subclasses - @@subclasses[Enumeration] - end - - # Does the +new+ Hash override the previous Enumeration? - def self.overridding_change?(new, previous) - if (same_active_state?(new['active'], previous.active)) && same_custom_values?(new,previous) - return false - else - return true - end - end - - # Does the +new+ Hash have the same custom values as the previous Enumeration? - def self.same_custom_values?(new, previous) - previous.custom_field_values.each do |custom_value| - if custom_value.value != new["custom_field_values"][custom_value.custom_field_id.to_s] - return false - end - end - - return true - end - - # Are the new and previous fields equal? - def self.same_active_state?(new, previous) - new = (new == "1" ? true : false) - return new == previous - end - -private - def check_integrity - raise "Can't delete enumeration" if self.in_use? - end - -end - -# Force load the subclasses in development mode -require_dependency 'time_entry_activity' -require_dependency 'document_category' -require_dependency 'issue_priority' diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/06/06d13f36c723de5d4d11734c74881a345af8508f.svn-base --- a/.svn/pristine/06/06d13f36c723de5d4d11734c74881a345af8508f.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -<%= TestHelper.view_path_for __FILE__ %> (from app) \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/06/06efa9c81bf4ffa47e3211023e46fcb09d4eac0f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/06/06efa9c81bf4ffa47e3211023e46fcb09d4eac0f.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,216 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class RolesControllerTest < ActionController::TestCase + fixtures :roles, :users, :members, :member_roles, :workflows, :trackers + + def setup + User.current = nil + @request.session[:user_id] = 1 # admin + end + + def test_index + get :index + assert_response :success + assert_template 'index' + + assert_not_nil assigns(:roles) + assert_equal Role.order('builtin, position').all, assigns(:roles) + + assert_tag :tag => 'a', :attributes => { :href => '/roles/1/edit' }, + :content => 'Manager' + end + + def test_new + get :new + assert_response :success + assert_template 'new' + end + + def test_new_with_copy + copy_from = Role.find(2) + + get :new, :copy => copy_from.id.to_s + assert_response :success + assert_template 'new' + + role = assigns(:role) + assert_equal copy_from.permissions, role.permissions + + assert_select 'form' do + # blank name + assert_select 'input[name=?][value=]', 'role[name]' + # edit_project permission checked + assert_select 'input[type=checkbox][name=?][value=edit_project][checked=checked]', 'role[permissions][]' + # add_project permission not checked + assert_select 'input[type=checkbox][name=?][value=add_project]', 'role[permissions][]' + assert_select 'input[type=checkbox][name=?][value=add_project][checked=checked]', 'role[permissions][]', 0 + # workflow copy selected + assert_select 'select[name=?]', 'copy_workflow_from' do + assert_select 'option[value=2][selected=selected]' + end + end + end + + def test_create_with_validaton_failure + post :create, :role => {:name => '', + :permissions => ['add_issues', 'edit_issues', 'log_time', ''], + :assignable => '0'} + + assert_response :success + assert_template 'new' + assert_tag :tag => 'div', :attributes => { :id => 'errorExplanation' } + end + + def test_create_without_workflow_copy + post :create, :role => {:name => 'RoleWithoutWorkflowCopy', + :permissions => ['add_issues', 'edit_issues', 'log_time', ''], + :assignable => '0'} + + assert_redirected_to '/roles' + role = Role.find_by_name('RoleWithoutWorkflowCopy') + assert_not_nil role + assert_equal [:add_issues, :edit_issues, :log_time], role.permissions + assert !role.assignable? + end + + def test_create_with_workflow_copy + post :create, :role => {:name => 'RoleWithWorkflowCopy', + :permissions => ['add_issues', 'edit_issues', 'log_time', ''], + :assignable => '0'}, + :copy_workflow_from => '1' + + assert_redirected_to '/roles' + role = Role.find_by_name('RoleWithWorkflowCopy') + assert_not_nil role + assert_equal Role.find(1).workflow_rules.size, role.workflow_rules.size + end + + def test_edit + get :edit, :id => 1 + assert_response :success + assert_template 'edit' + assert_equal Role.find(1), assigns(:role) + assert_select 'select[name=?]', 'role[issues_visibility]' + end + + def test_edit_anonymous + get :edit, :id => Role.anonymous.id + assert_response :success + assert_template 'edit' + assert_select 'select[name=?]', 'role[issues_visibility]', 0 + end + + def test_edit_invalid_should_respond_with_404 + get :edit, :id => 999 + assert_response 404 + end + + def test_update + put :update, :id => 1, + :role => {:name => 'Manager', + :permissions => ['edit_project', ''], + :assignable => '0'} + + assert_redirected_to '/roles' + role = Role.find(1) + assert_equal [:edit_project], role.permissions + end + + def test_update_with_failure + put :update, :id => 1, :role => {:name => ''} + assert_response :success + assert_template 'edit' + end + + def test_destroy + r = Role.create!(:name => 'ToBeDestroyed', :permissions => [:view_wiki_pages]) + + delete :destroy, :id => r + assert_redirected_to '/roles' + assert_nil Role.find_by_id(r.id) + end + + def test_destroy_role_in_use + delete :destroy, :id => 1 + assert_redirected_to '/roles' + assert_equal 'This role is in use and cannot be deleted.', flash[:error] + assert_not_nil Role.find_by_id(1) + end + + def test_get_permissions + get :permissions + assert_response :success + assert_template 'permissions' + + assert_not_nil assigns(:roles) + assert_equal Role.order('builtin, position').all, assigns(:roles) + + assert_tag :tag => 'input', :attributes => { :type => 'checkbox', + :name => 'permissions[3][]', + :value => 'add_issues', + :checked => 'checked' } + + assert_tag :tag => 'input', :attributes => { :type => 'checkbox', + :name => 'permissions[3][]', + :value => 'delete_issues', + :checked => nil } + end + + def test_post_permissions + post :permissions, :permissions => { '0' => '', '1' => ['edit_issues'], '3' => ['add_issues', 'delete_issues']} + assert_redirected_to '/roles' + + assert_equal [:edit_issues], Role.find(1).permissions + assert_equal [:add_issues, :delete_issues], Role.find(3).permissions + assert Role.find(2).permissions.empty? + end + + def test_clear_all_permissions + post :permissions, :permissions => { '0' => '' } + assert_redirected_to '/roles' + assert Role.find(1).permissions.empty? + end + + def test_move_highest + put :update, :id => 3, :role => {:move_to => 'highest'} + assert_redirected_to '/roles' + assert_equal 1, Role.find(3).position + end + + def test_move_higher + position = Role.find(3).position + put :update, :id => 3, :role => {:move_to => 'higher'} + assert_redirected_to '/roles' + assert_equal position - 1, Role.find(3).position + end + + def test_move_lower + position = Role.find(2).position + put :update, :id => 2, :role => {:move_to => 'lower'} + assert_redirected_to '/roles' + assert_equal position + 1, Role.find(2).position + end + + def test_move_lowest + put :update, :id => 2, :role => {:move_to => 'lowest'} + assert_redirected_to '/roles' + assert_equal Role.count, Role.find(2).position + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/07/071c1af1d5f365dd709aaf606fafd0332e258d64.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/07/071c1af1d5f365dd709aaf606fafd0332e258d64.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,476 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 #:nodoc: + + class PluginNotFound < StandardError; end + class PluginRequirementError < StandardError; end + + # Base class for Redmine plugins. + # Plugins are registered using the register class method that acts as the public constructor. + # + # Redmine::Plugin.register :example do + # name 'Example plugin' + # author 'John Smith' + # description 'This is an example plugin for Redmine' + # version '0.0.1' + # settings :default => {'foo'=>'bar'}, :partial => 'settings/settings' + # end + # + # === Plugin attributes + # + # +settings+ is an optional attribute that let the plugin be configurable. + # It must be a hash with the following keys: + # * :default: default value for the plugin settings + # * :partial: path of the configuration partial view, relative to the plugin app/views directory + # Example: + # settings :default => {'foo'=>'bar'}, :partial => 'settings/settings' + # In this example, the settings partial will be found here in the plugin directory: app/views/settings/_settings.rhtml. + # + # When rendered, the plugin settings value is available as the local variable +settings+ + class Plugin + cattr_accessor :directory + self.directory = File.join(Rails.root, 'plugins') + + cattr_accessor :public_directory + self.public_directory = File.join(Rails.root, 'public', 'plugin_assets') + + @registered_plugins = {} + class << self + attr_reader :registered_plugins + private :new + + def def_field(*names) + class_eval do + names.each do |name| + define_method(name) do |*args| + args.empty? ? instance_variable_get("@#{name}") : instance_variable_set("@#{name}", *args) + end + end + end + end + end + def_field :name, :description, :url, :author, :author_url, :version, :settings + attr_reader :id + + # Plugin constructor + def self.register(id, &block) + p = new(id) + p.instance_eval(&block) + # Set a default name if it was not provided during registration + p.name(id.to_s.humanize) if p.name.nil? + + # Adds plugin locales if any + # YAML translation files should be found under /config/locales/ + ::I18n.load_path += Dir.glob(File.join(p.directory, 'config', 'locales', '*.yml')) + + # Prepends the app/views directory of the plugin to the view path + view_path = File.join(p.directory, 'app', 'views') + if File.directory?(view_path) + ActionController::Base.prepend_view_path(view_path) + ActionMailer::Base.prepend_view_path(view_path) + end + + # Adds the app/{controllers,helpers,models} directories of the plugin to the autoload path + Dir.glob File.expand_path(File.join(p.directory, 'app', '{controllers,helpers,models}')) do |dir| + ActiveSupport::Dependencies.autoload_paths += [dir] + end + + registered_plugins[id] = p + end + + # Returns an array of 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 + + # Checks if a plugin is installed + # + # @param [String] id name of the plugin + def self.installed?(id) + registered_plugins[id.to_sym].present? + end + + def self.load + Dir.glob(File.join(self.directory, '*')).sort.each do |directory| + if File.directory?(directory) + lib = File.join(directory, "lib") + if File.directory?(lib) + $:.unshift lib + ActiveSupport::Dependencies.autoload_paths += [lib] + end + initializer = File.join(directory, "init.rb") + if File.file?(initializer) + require initializer + end + end + end + end + + def initialize(id) + @id = id.to_sym + end + + def directory + File.join(self.class.directory, id.to_s) + end + + def public_directory + File.join(self.class.public_directory, id.to_s) + end + + def to_param + id + end + + def assets_directory + File.join(directory, 'assets') + 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 Redmine 0.7.x or higher + # requires_redmine '0.7' + # + # # Requires a specific Redmine version + # requires_redmine :version => '0.7.3' # 0.7.3 only + # requires_redmine :version => '0.7' # 0.7.x + # requires_redmine :version => ['0.7.3', '0.8.0'] # 0.7.3 or 0.8.0 + # + # # Requires a Redmine version within a range + # requires_redmine :version => '0.7.3'..'0.9.1' # >= 0.7.3 and <= 0.9.1 + # requires_redmine :version => '0.7'..'0.9' # >= 0.7.x and <= 0.9.x + 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, req| + case k + when :version_or_higher + raise ArgumentError.new(":version_or_higher accepts a version string only") unless req.is_a?(String) + unless compare_versions(req, current) <= 0 + raise PluginRequirementError.new("#{id} plugin requires Redmine #{req} or higher but current is #{current.join('.')}") + end + when :version + req = [req] if req.is_a?(String) + if req.is_a?(Array) + unless req.detect {|ver| compare_versions(ver, current) == 0} + raise PluginRequirementError.new("#{id} plugin requires one the following Redmine versions: #{req.join(', ')} but current is #{current.join('.')}") + end + elsif req.is_a?(Range) + unless compare_versions(req.first, current) <= 0 && compare_versions(req.last, current) >= 0 + raise PluginRequirementError.new("#{id} plugin requires a Redmine version between #{req.first} and #{req.last} but current is #{current.join('.')}") + end + else + raise ArgumentError.new(":version option accepts a version string, an array or a range of versions") + end + end + end + true + end + + def compare_versions(requirement, current) + requirement = requirement.split('.').collect(&:to_i) + requirement <=> current.slice(0, requirement.size) + end + private :compare_versions + + # Sets a requirement on a Redmine plugin version + # Raises a PluginRequirementError exception if the requirement is not met + # + # Examples + # # Requires a plugin named :foo version 0.7.3 or higher + # requires_redmine_plugin :foo, :version_or_higher => '0.7.3' + # requires_redmine_plugin :foo, '0.7.3' + # + # # Requires a specific version of a Redmine plugin + # requires_redmine_plugin :foo, :version => '0.7.3' # 0.7.3 only + # requires_redmine_plugin :foo, :version => ['0.7.3', '0.8.0'] # 0.7.3 or 0.8.0 + def requires_redmine_plugin(plugin_name, arg) + arg = { :version_or_higher => arg } unless arg.is_a?(Hash) + arg.assert_valid_keys(:version, :version_or_higher) + + plugin = Plugin.find(plugin_name) + current = plugin.version.split('.').collect(&:to_i) + + 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 the #{plugin_name} plugin #{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 versions of #{plugin_name}: #{v.join(', ')} but current is #{current.join('.')}") + end + end + end + true + end + + # Adds an item to the given +menu+. + # The +id+ parameter (equals to the project id) is automatically added to the url. + # menu :project_menu, :plugin_example, { :controller => 'example', :action => 'say_hello' }, :caption => 'Sample' + # + # +name+ parameter can be: :top_menu, :account_menu, :application_menu or :project_menu + # + 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+. + # + # The +actions+ argument is a hash with controllers as keys and actions as values (a single value or an array): + # permission :destroy_contacts, { :contacts => :destroy } + # permission :view_contacts, { :contacts => [:index, :show] } + # + # The +options+ argument is a hash that accept the following keys: + # * :public => the permission is public if set to true (implicitly given to any user) + # * :require => can be set to one of the following values to restrict users the permission can be given to: :loggedin, :member + # * :read => set it to true so that the permission is still granted on closed projects + # + # Examples + # # A permission that is implicitly given to any user + # # This permission won't appear on the Roles & Permissions setup screen + # permission :say_hello, { :example => :say_hello }, :public => true, :read => true + # + # # A permission that can be given to any user + # permission :say_hello, { :example => :say_hello } + # + # # A permission that can be given to registered users only + # permission :say_hello, { :example => :say_hello }, :require => :loggedin + # + # # A permission that can be given to project members only + # permission :say_hello, { :example => :say_hello }, :require => :member + def permission(name, actions, options = {}) + if @project_module + Redmine::AccessControl.map {|map| map.project_module(@project_module) {|map|map.permission(name, actions, options)}} + else + Redmine::AccessControl.map {|map| map.permission(name, actions, options)} + end + end + + # Defines a project module, that can be enabled/disabled for each project. + # Permissions defined inside +block+ will be bind to the module. + # + # project_module :things do + # permission :view_contacts, { :contacts => [:list, :show] }, :public => true + # permission :destroy_contacts, { :contacts => :destroy } + # end + def project_module(name, &block) + @project_module = name + self.instance_eval(&block) + @project_module = nil + end + + # Registers an activity provider. + # + # Options: + # * :class_name - one or more model(s) that provide these events (inferred from event_type by default) + # * :default - setting this option to false will make the events not displayed by default + # + # A model can provide several activity event types. + # + # Examples: + # register :news + # register :scrums, :class_name => 'Meeting' + # register :issues, :class_name => ['Issue', 'Journal'] + # + # Retrieving events: + # Associated model(s) must implement the find_events class method. + # ActiveRecord models can use acts_as_activity_provider as a way to implement this class method. + # + # The following call should return all the scrum events visible by current user that occured in the 5 last days: + # Meeting.find_events('scrums', User.current, 5.days.ago, Date.today) + # Meeting.find_events('scrums', User.current, 5.days.ago, Date.today, :project => foo) # events for project foo only + # + # Note that :view_scrums permission is required to view these events in the activity view. + def activity_provider(*args) + Redmine::Activity.register(*args) + end + + # 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? + settings && settings.is_a?(Hash) && !settings[:partial].blank? + end + + def mirror_assets + source = assets_directory + destination = public_directory + return unless File.directory?(source) + + source_files = Dir[source + "/**/*"] + source_dirs = source_files.select { |d| File.directory?(d) } + source_files -= source_dirs + + unless source_files.empty? + base_target_dir = File.join(destination, File.dirname(source_files.first).gsub(source, '')) + begin + FileUtils.mkdir_p(base_target_dir) + rescue Exception => e + raise "Could not create directory #{base_target_dir}: " + e.message + end + end + + source_dirs.each do |dir| + # strip down these paths so we have simple, relative paths we can + # add to the destination + target_dir = File.join(destination, dir.gsub(source, '')) + begin + FileUtils.mkdir_p(target_dir) + rescue Exception => e + raise "Could not create directory #{target_dir}: " + e.message + end + end + + source_files.each do |file| + begin + target = File.join(destination, file.gsub(source, '')) + unless File.exist?(target) && FileUtils.identical?(file, target) + FileUtils.cp(file, target) + end + rescue Exception => e + raise "Could not copy #{file} to #{target}: " + e.message + end + end + end + + # Mirrors assets from one or all plugins to public/plugin_assets + def self.mirror_assets(name=nil) + if name.present? + find(name).mirror_assets + else + all.each do |plugin| + plugin.mirror_assets + end + end + end + + # The directory containing this plugin's migrations (plugin/db/migrate) + def migration_directory + File.join(Rails.root, 'plugins', id.to_s, 'db', 'migrate') + end + + # Returns the version number of the latest migration for this plugin. Returns + # nil if this plugin has no migrations. + def latest_migration + migrations.last + end + + # Returns the version numbers of all migrations for this plugin. + def migrations + migrations = Dir[migration_directory+"/*.rb"] + migrations.map { |p| File.basename(p).match(/0*(\d+)\_/)[1].to_i }.sort + end + + # Migrate this plugin to the given version + def migrate(version = nil) + puts "Migrating #{id} (#{name})..." + Redmine::Plugin::Migrator.migrate_plugin(self, version) + end + + # Migrates all plugins or a single plugin to a given version + # Exemples: + # Plugin.migrate + # Plugin.migrate('sample_plugin') + # Plugin.migrate('sample_plugin', 1) + # + def self.migrate(name=nil, version=nil) + if name.present? + find(name).migrate(version) + else + all.each do |plugin| + plugin.migrate + end + end + end + + class Migrator < ActiveRecord::Migrator + # We need to be able to set the 'current' plugin being migrated. + cattr_accessor :current_plugin + + class << self + # Runs the migrations from a plugin, up (or down) to the version given + def migrate_plugin(plugin, version) + self.current_plugin = plugin + return if current_version(plugin) == version + migrate(plugin.migration_directory, version) + end + + def current_version(plugin=current_plugin) + # Delete migrations that don't match .. to_i will work because the number comes first + ::ActiveRecord::Base.connection.select_values( + "SELECT version FROM #{schema_migrations_table_name}" + ).delete_if{ |v| v.match(/-#{plugin.id}/) == nil }.map(&:to_i).max || 0 + end + end + + def migrated + sm_table = self.class.schema_migrations_table_name + ::ActiveRecord::Base.connection.select_values( + "SELECT version FROM #{sm_table}" + ).delete_if{ |v| v.match(/-#{current_plugin.id}/) == nil }.map(&:to_i).sort + end + + def record_version_state_after_migrating(version) + super(version.to_s + "-" + current_plugin.id.to_s) + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/07/073a34bd97c7ca9066ab7d9fa3f008e0e0da8c60.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/07/073a34bd97c7ca9066ab7d9fa3f008e0e0da8c60.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,172 @@ +var draw_gantt = null; +var draw_top; +var draw_right; +var draw_left; + +var rels_stroke_width = 2; + +function setDrawArea() { + draw_top = $("#gantt_draw_area").position().top; + draw_right = $("#gantt_draw_area").width(); + draw_left = $("#gantt_area").scrollLeft(); +} + +function getRelationsArray() { + var arr = new Array(); + $.each($('div.task_todo[data-rels]'), function(index_div, element) { + var element_id = $(element).attr("id"); + if (element_id != null) { + var issue_id = element_id.replace("task-todo-issue-", ""); + var data_rels = $(element).data("rels"); + for (rel_type_key in data_rels) { + $.each(data_rels[rel_type_key], function(index_issue, element_issue) { + arr.push({issue_from: issue_id, issue_to: element_issue, + rel_type: rel_type_key}); + }); + } + } + }); + return arr; +} + +function drawRelations() { + var arr = getRelationsArray(); + $.each(arr, function(index_issue, element_issue) { + var issue_from = $("#task-todo-issue-" + element_issue["issue_from"]); + var issue_to = $("#task-todo-issue-" + element_issue["issue_to"]); + if (issue_from.size() == 0 || issue_to.size() == 0) { + return; + } + var issue_height = issue_from.height(); + var issue_from_top = issue_from.position().top + (issue_height / 2) - draw_top; + var issue_from_right = issue_from.position().left + issue_from.width(); + var issue_to_top = issue_to.position().top + (issue_height / 2) - draw_top; + var issue_to_left = issue_to.position().left; + var color = issue_relation_type[element_issue["rel_type"]]["color"]; + var landscape_margin = issue_relation_type[element_issue["rel_type"]]["landscape_margin"]; + var issue_from_right_rel = issue_from_right + landscape_margin; + var issue_to_left_rel = issue_to_left - landscape_margin; + draw_gantt.path(["M", issue_from_right + draw_left, issue_from_top, + "L", issue_from_right_rel + draw_left, issue_from_top]) + .attr({stroke: color, + "stroke-width": rels_stroke_width + }); + if (issue_from_right_rel < issue_to_left_rel) { + draw_gantt.path(["M", issue_from_right_rel + draw_left, issue_from_top, + "L", issue_from_right_rel + draw_left, issue_to_top]) + .attr({stroke: color, + "stroke-width": rels_stroke_width + }); + draw_gantt.path(["M", issue_from_right_rel + draw_left, issue_to_top, + "L", issue_to_left + draw_left, issue_to_top]) + .attr({stroke: color, + "stroke-width": rels_stroke_width + }); + } else { + var issue_middle_top = issue_to_top + + (issue_height * + ((issue_from_top > issue_to_top) ? 1 : -1)); + draw_gantt.path(["M", issue_from_right_rel + draw_left, issue_from_top, + "L", issue_from_right_rel + draw_left, issue_middle_top]) + .attr({stroke: color, + "stroke-width": rels_stroke_width + }); + draw_gantt.path(["M", issue_from_right_rel + draw_left, issue_middle_top, + "L", issue_to_left_rel + draw_left, issue_middle_top]) + .attr({stroke: color, + "stroke-width": rels_stroke_width + }); + draw_gantt.path(["M", issue_to_left_rel + draw_left, issue_middle_top, + "L", issue_to_left_rel + draw_left, issue_to_top]) + .attr({stroke: color, + "stroke-width": rels_stroke_width + }); + draw_gantt.path(["M", issue_to_left_rel + draw_left, issue_to_top, + "L", issue_to_left + draw_left, issue_to_top]) + .attr({stroke: color, + "stroke-width": rels_stroke_width + }); + } + draw_gantt.path(["M", issue_to_left + draw_left, issue_to_top, + "l", -4 * rels_stroke_width, -2 * rels_stroke_width, + "l", 0, 4 * rels_stroke_width, "z"]) + .attr({stroke: "none", + fill: color, + "stroke-linecap": "butt", + "stroke-linejoin": "miter" + }); + }); +} + +function getProgressLinesArray() { + var arr = new Array(); + var today_left = $('#today_line').position().left; + arr.push({left: today_left, top: 0}); + $.each($('div.issue-subject, div.version-name'), function(index, element) { + var t = $(element).position().top - draw_top ; + var h = ($(element).height() / 9); + var element_top_upper = t - h; + var element_top_center = t + (h * 3); + var element_top_lower = t + (h * 8); + var issue_closed = $(element).children('span').hasClass('issue-closed'); + var version_closed = $(element).children('span').hasClass('version-closed'); + if (issue_closed || version_closed) { + arr.push({left: today_left, top: element_top_center}); + } else { + var issue_done = $("#task-done-" + $(element).attr("id")); + var is_behind_start = $(element).children('span').hasClass('behind-start-date'); + var is_over_end = $(element).children('span').hasClass('over-end-date'); + if (is_over_end) { + arr.push({left: draw_right, top: element_top_upper, is_right_edge: true}); + arr.push({left: draw_right, top: element_top_lower, is_right_edge: true, none_stroke: true}); + } else if (issue_done.size() > 0) { + var done_left = issue_done.first().position().left + + issue_done.first().width(); + arr.push({left: done_left, top: element_top_center}); + } else if (is_behind_start) { + arr.push({left: 0 , top: element_top_upper, is_left_edge: true}); + arr.push({left: 0 , top: element_top_lower, is_left_edge: true, none_stroke: true}); + } else { + var todo_left = today_left; + var issue_todo = $("#task-todo-" + $(element).attr("id")); + if (issue_todo.size() > 0){ + todo_left = issue_todo.first().position().left; + } + arr.push({left: Math.min(today_left, todo_left), top: element_top_center}); + } + } + }); + return arr; +} + +function drawGanttProgressLines() { + var arr = getProgressLinesArray(); + var color = $("#today_line") + .css("border-left-color"); + var i; + for(i = 1 ; i < arr.length ; i++) { + if (!("none_stroke" in arr[i]) && + (!("is_right_edge" in arr[i - 1] && "is_right_edge" in arr[i]) && + !("is_left_edge" in arr[i - 1] && "is_left_edge" in arr[i])) + ) { + var x1 = (arr[i - 1].left == 0) ? 0 : arr[i - 1].left + draw_left; + var x2 = (arr[i].left == 0) ? 0 : arr[i].left + draw_left; + draw_gantt.path(["M", x1, arr[i - 1].top, + "L", x2, arr[i].top]) + .attr({stroke: color, "stroke-width": 2}); + } + } +} + +function drawGanttHandler() { + var folder = document.getElementById('gantt_draw_area'); + if(draw_gantt != null) + draw_gantt.clear(); + else + draw_gantt = Raphael(folder); + setDrawArea(); + if ($("#draw_progress_line").attr('checked')) + drawGanttProgressLines(); + if ($("#draw_rels").attr('checked')) + drawRelations(); +} diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/07/07402976102eaabe37c42cc6aab7aeb00d221891.svn-base --- a/.svn/pristine/07/07402976102eaabe37c42cc6aab7aeb00d221891.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,164 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) -require 'repositories_controller' - -# Re-raise errors caught by the controller. -class RepositoriesController; def rescue_action(e) raise e end; end - -class RepositoriesDarcsControllerTest < ActionController::TestCase - fixtures :projects, :users, :roles, :members, :member_roles, - :repositories, :enabled_modules - - REPOSITORY_PATH = Rails.root.join('tmp/test/darcs_repository').to_s - PRJ_ID = 3 - NUM_REV = 6 - - def setup - @controller = RepositoriesController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - User.current = nil - @project = Project.find(PRJ_ID) - @repository = Repository::Darcs.create( - :project => @project, - :url => REPOSITORY_PATH, - :log_encoding => 'UTF-8' - ) - assert @repository - end - - if File.directory?(REPOSITORY_PATH) - def test_browse_root - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - get :show, :id => PRJ_ID - assert_response :success - assert_template 'show' - assert_not_nil assigns(:entries) - assert_equal 3, assigns(:entries).size - assert assigns(:entries).detect {|e| e.name == 'images' && e.kind == 'dir'} - assert assigns(:entries).detect {|e| e.name == 'sources' && e.kind == 'dir'} - assert assigns(:entries).detect {|e| e.name == 'README' && e.kind == 'file'} - end - - def test_browse_directory - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - get :show, :id => PRJ_ID, :path => ['images'] - assert_response :success - assert_template 'show' - assert_not_nil assigns(:entries) - assert_equal ['delete.png', 'edit.png'], assigns(:entries).collect(&:name) - entry = assigns(:entries).detect {|e| e.name == 'edit.png'} - assert_not_nil entry - assert_equal 'file', entry.kind - assert_equal 'images/edit.png', entry.path - end - - def test_browse_at_given_revision - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - get :show, :id => PRJ_ID, :path => ['images'], :rev => 1 - assert_response :success - assert_template 'show' - assert_not_nil assigns(:entries) - assert_equal ['delete.png'], assigns(:entries).collect(&:name) - end - - def test_changes - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - get :changes, :id => PRJ_ID, :path => ['images', 'edit.png'] - assert_response :success - assert_template 'changes' - assert_tag :tag => 'h2', :content => 'edit.png' - end - - def test_diff - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - # Full diff of changeset 5 - ['inline', 'sbs'].each do |dt| - get :diff, :id => PRJ_ID, :rev => 5, :type => dt - assert_response :success - assert_template 'diff' - # Line 22 removed - assert_tag :tag => 'th', - :content => '22', - :sibling => { :tag => 'td', - :attributes => { :class => /diff_out/ }, - :content => /def remove/ } - end - end - - def test_destroy_valid_repository - @request.session[:user_id] = 1 # admin - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - - get :destroy, :id => PRJ_ID - assert_response 302 - @project.reload - assert_nil @project.repository - end - - def test_destroy_invalid_repository - @request.session[:user_id] = 1 # admin - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - - get :destroy, :id => PRJ_ID - assert_response 302 - @project.reload - assert_nil @project.repository - - @repository = Repository::Darcs.create( - :project => @project, - :url => "/invalid", - :log_encoding => 'UTF-8' - ) - assert @repository - @repository.fetch_changesets - @project.reload - assert_equal 0, @repository.changesets.count - - get :destroy, :id => PRJ_ID - assert_response 302 - @project.reload - assert_nil @project.repository - end - else - puts "Darcs test repository NOT FOUND. Skipping functional tests !!!" - def test_fake; assert true end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/07/074588d42b3ac157ab70417170443c5531685d7e.svn-base --- a/.svn/pristine/07/074588d42b3ac157ab70417170443c5531685d7e.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,270 +0,0 @@ -module ActiveRecord - module Acts #:nodoc: - module List #:nodoc: - def self.included(base) - base.extend(ClassMethods) - end - - # This +acts_as+ extension provides the capabilities for sorting and reordering a number of objects in a list. - # The class that has this specified needs to have a +position+ column defined as an integer on - # the mapped database table. - # - # Todo list example: - # - # class TodoList < ActiveRecord::Base - # has_many :todo_items, :order => "position" - # end - # - # class TodoItem < ActiveRecord::Base - # belongs_to :todo_list - # acts_as_list :scope => :todo_list - # end - # - # todo_list.first.move_to_bottom - # todo_list.last.move_higher - module ClassMethods - # Configuration options are: - # - # * +column+ - specifies the column name to use for keeping the position integer (default: +position+) - # * +scope+ - restricts what is to be considered a list. Given a symbol, it'll attach _id - # (if it hasn't already been added) and use that as the foreign key restriction. It's also possible - # to give it an entire string that is interpolated if you need a tighter scope than just a foreign key. - # Example: acts_as_list :scope => 'todo_list_id = #{todo_list_id} AND completed = 0' - def acts_as_list(options = {}) - configuration = { :column => "position", :scope => "1 = 1" } - configuration.update(options) if options.is_a?(Hash) - - configuration[:scope] = "#{configuration[:scope]}_id".intern if configuration[:scope].is_a?(Symbol) && configuration[:scope].to_s !~ /_id$/ - - if configuration[:scope].is_a?(Symbol) - scope_condition_method = %( - def scope_condition - if #{configuration[:scope].to_s}.nil? - "#{configuration[:scope].to_s} IS NULL" - else - "#{configuration[:scope].to_s} = \#{#{configuration[:scope].to_s}}" - end - end - ) - else - scope_condition_method = "def scope_condition() \"#{configuration[:scope]}\" end" - end - - class_eval <<-EOV - include ActiveRecord::Acts::List::InstanceMethods - - def acts_as_list_class - ::#{self.name} - end - - def position_column - '#{configuration[:column]}' - end - - #{scope_condition_method} - - before_destroy :remove_from_list - before_create :add_to_list_bottom - EOV - end - end - - # All the methods available to a record that has had acts_as_list specified. Each method works - # by assuming the object to be the item in the list, so chapter.move_lower would move that chapter - # lower in the list of all chapters. Likewise, chapter.first? would return +true+ if that chapter is - # the first in the list of all chapters. - module InstanceMethods - # Insert the item at the given position (defaults to the top position of 1). - def insert_at(position = 1) - insert_at_position(position) - end - - # Swap positions with the next lower item, if one exists. - def move_lower - return unless lower_item - - acts_as_list_class.transaction do - lower_item.decrement_position - increment_position - end - end - - # Swap positions with the next higher item, if one exists. - def move_higher - return unless higher_item - - acts_as_list_class.transaction do - higher_item.increment_position - decrement_position - end - end - - # Move to the bottom of the list. If the item is already in the list, the items below it have their - # position adjusted accordingly. - def move_to_bottom - return unless in_list? - acts_as_list_class.transaction do - decrement_positions_on_lower_items - assume_bottom_position - end - end - - # Move to the top of the list. If the item is already in the list, the items above it have their - # position adjusted accordingly. - def move_to_top - return unless in_list? - acts_as_list_class.transaction do - increment_positions_on_higher_items - assume_top_position - end - end - - # Move to the given position - def move_to=(pos) - case pos.to_s - when 'highest' - move_to_top - when 'higher' - move_higher - when 'lower' - move_lower - when 'lowest' - move_to_bottom - end - end - - # Removes the item from the list. - def remove_from_list - if in_list? - decrement_positions_on_lower_items - update_attribute position_column, nil - end - end - - # Increase the position of this item without adjusting the rest of the list. - def increment_position - return unless in_list? - update_attribute position_column, self.send(position_column).to_i + 1 - end - - # Decrease the position of this item without adjusting the rest of the list. - def decrement_position - return unless in_list? - update_attribute position_column, self.send(position_column).to_i - 1 - end - - # Return +true+ if this object is the first in the list. - def first? - return false unless in_list? - self.send(position_column) == 1 - end - - # Return +true+ if this object is the last in the list. - def last? - return false unless in_list? - self.send(position_column) == bottom_position_in_list - end - - # Return the next higher item in the list. - def higher_item - return nil unless in_list? - acts_as_list_class.find(:first, :conditions => - "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i - 1).to_s}" - ) - end - - # Return the next lower item in the list. - def lower_item - return nil unless in_list? - acts_as_list_class.find(:first, :conditions => - "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i + 1).to_s}" - ) - end - - # Test if this record is in a list - def in_list? - !send(position_column).nil? - end - - private - def add_to_list_top - increment_positions_on_all_items - end - - def add_to_list_bottom - self[position_column] = bottom_position_in_list.to_i + 1 - end - - # Overwrite this method to define the scope of the list changes - def scope_condition() "1" end - - # Returns the bottom position number in the list. - # bottom_position_in_list # => 2 - def bottom_position_in_list(except = nil) - item = bottom_item(except) - item ? item.send(position_column) : 0 - end - - # Returns the bottom item - def bottom_item(except = nil) - conditions = scope_condition - conditions = "#{conditions} AND #{self.class.primary_key} != #{except.id}" if except - acts_as_list_class.find(:first, :conditions => conditions, :order => "#{position_column} DESC") - end - - # Forces item to assume the bottom position in the list. - def assume_bottom_position - update_attribute(position_column, bottom_position_in_list(self).to_i + 1) - end - - # Forces item to assume the top position in the list. - def assume_top_position - update_attribute(position_column, 1) - end - - # This has the effect of moving all the higher items up one. - def decrement_positions_on_higher_items(position) - acts_as_list_class.update_all( - "#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} <= #{position}" - ) - end - - # This has the effect of moving all the lower items up one. - def decrement_positions_on_lower_items - return unless in_list? - acts_as_list_class.update_all( - "#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} > #{send(position_column).to_i}" - ) - end - - # This has the effect of moving all the higher items down one. - def increment_positions_on_higher_items - return unless in_list? - acts_as_list_class.update_all( - "#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} < #{send(position_column).to_i}" - ) - end - - # This has the effect of moving all the lower items down one. - def increment_positions_on_lower_items(position) - acts_as_list_class.update_all( - "#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} >= #{position}" - ) - end - - # Increments position (position_column) of all items in the list. - def increment_positions_on_all_items - acts_as_list_class.update_all( - "#{position_column} = (#{position_column} + 1)", "#{scope_condition}" - ) - end - - def insert_at_position(position) - remove_from_list - increment_positions_on_lower_items(position) - self.update_attribute(position_column, position) - end - end - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/07/07ff3aff4af2ca5266644ab7e838820cda8c8752.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/07/07ff3aff4af2ca5266644ab7e838820cda8c8752.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,27 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class RoutingAutoCompletesTest < ActionController::IntegrationTest + def test_auto_completes + assert_routing( + { :method => 'get', :path => "/issues/auto_complete" }, + { :controller => 'auto_completes', :action => 'issues' } + ) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/08/0856b80a2e3d388f4f051bee68228eb434db38ab.svn-base --- a/.svn/pristine/08/0856b80a2e3d388f4f051bee68228eb434db38ab.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../../../test_helper', __FILE__) - -class Redmine::AccessControlTest < ActiveSupport::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 -r 0a574315af3e -r 4f746d8966dd .svn/pristine/08/08671dfda70fdd4f87dd0171bec11e1d6e0c22e1.svn-base --- a/.svn/pristine/08/08671dfda70fdd4f87dd0171bec11e1d6e0c22e1.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,779 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../../../../test_helper', __FILE__) - -class Redmine::Helpers::GanttTest < ActiveSupport::TestCase - # Utility methods and classes so assert_select can be used. - class GanttViewTest < ActionView::Base - include ActionView::Helpers::UrlHelper - include ActionView::Helpers::TextHelper - include ActionController::UrlWriter - include ApplicationHelper - include ProjectsHelper - include IssuesHelper - - def self.default_url_options - {:only_path => true } - end - - end - - include ActionController::Assertions::SelectorAssertions - - def setup - @response = ActionController::TestResponse.new - # Fixtures - ProjectCustomField.delete_all - Project.destroy_all - - User.current = User.find(1) - end - - def build_view - @view = GanttViewTest.new - end - - def html_document - HTML::Document.new(@response.body) - end - - # Creates a Gantt chart for a 4 week span - def create_gantt(project=Project.generate!, options={}) - @project = project - @gantt = Redmine::Helpers::Gantt.new(options) - @gantt.project = @project - @gantt.query = Query.generate_default!(:project => @project) - @gantt.view = build_view - @gantt.instance_variable_set('@date_from', options[:date_from] || 2.weeks.ago.to_date) - @gantt.instance_variable_set('@date_to', options[:date_to] || 2.weeks.from_now.to_date) - end - - context "#number_of_rows" do - - context "with one project" do - should "return the number of rows just for that project" - end - - context "with no project" do - should "return the total number of rows for all the projects, resursively" - end - - should "not exceed max_rows option" do - p = Project.generate! - 5.times do - Issue.generate_for_project!(p) - end - - create_gantt(p) - @gantt.render - assert_equal 6, @gantt.number_of_rows - assert !@gantt.truncated - - create_gantt(p, :max_rows => 3) - @gantt.render - assert_equal 3, @gantt.number_of_rows - assert @gantt.truncated - end - end - - context "#number_of_rows_on_project" do - setup do - create_gantt - end - - should "count 0 for an empty the project" do - assert_equal 0, @gantt.number_of_rows_on_project(@project) - end - - should "count the number of issues without a version" do - @project.issues << Issue.generate_for_project!(@project, :fixed_version => nil) - assert_equal 2, @gantt.number_of_rows_on_project(@project) - end - - should "count the number of issues on versions, including cross-project" do - version = Version.generate! - @project.versions << version - @project.issues << Issue.generate_for_project!(@project, :fixed_version => version) - - assert_equal 3, @gantt.number_of_rows_on_project(@project) - end - end - - # TODO: more of an integration test - context "#subjects" do - setup do - create_gantt - @project.enabled_module_names = [:issue_tracking] - @tracker = Tracker.generate! - @project.trackers << @tracker - @version = Version.generate!(:effective_date => 1.week.from_now.to_date, :sharing => 'none') - @project.versions << @version - - @issue = Issue.generate!(:fixed_version => @version, - :subject => "gantt#line_for_project", - :tracker => @tracker, - :project => @project, - :done_ratio => 30, - :start_date => Date.yesterday, - :due_date => 1.week.from_now.to_date) - @project.issues << @issue - end - - context "project" do - should "be rendered" do - @response.body = @gantt.subjects - assert_select "div.project-name a", /#{@project.name}/ - end - - should "have an indent of 4" do - @response.body = @gantt.subjects - assert_select "div.project-name[style*=left:4px]" - end - end - - context "version" do - should "be rendered" do - @response.body = @gantt.subjects - assert_select "div.version-name a", /#{@version.name}/ - end - - should "be indented 24 (one level)" do - @response.body = @gantt.subjects - assert_select "div.version-name[style*=left:24px]" - end - - context "without assigned issues" do - setup do - @version = Version.generate!(:effective_date => 2.week.from_now.to_date, :sharing => 'none', :name => 'empty_version') - @project.versions << @version - end - - should "not be rendered" do - @response.body = @gantt.subjects - assert_select "div.version-name a", :text => /#{@version.name}/, :count => 0 - end - end - end - - context "issue" do - should "be rendered" do - @response.body = @gantt.subjects - assert_select "div.issue-subject", /#{@issue.subject}/ - end - - should "be indented 44 (two levels)" do - @response.body = @gantt.subjects - assert_select "div.issue-subject[style*=left:44px]" - end - - context "assigned to a shared version of another project" do - setup do - p = Project.generate! - p.trackers << @tracker - p.enabled_module_names = [:issue_tracking] - @shared_version = Version.generate!(:sharing => 'system') - p.versions << @shared_version - # Reassign the issue to a shared version of another project - - @issue = Issue.generate!(:fixed_version => @shared_version, - :subject => "gantt#assigned_to_shared_version", - :tracker => @tracker, - :project => @project, - :done_ratio => 30, - :start_date => Date.yesterday, - :due_date => 1.week.from_now.to_date) - @project.issues << @issue - end - - should "be rendered" do - @response.body = @gantt.subjects - assert_select "div.issue-subject", /#{@issue.subject}/ - end - end - - context "with subtasks" do - setup do - attrs = {:project => @project, :tracker => @tracker, :fixed_version => @version} - @child1 = Issue.generate!(attrs.merge(:subject => 'child1', :parent_issue_id => @issue.id, :start_date => Date.yesterday, :due_date => 2.day.from_now.to_date)) - @child2 = Issue.generate!(attrs.merge(:subject => 'child2', :parent_issue_id => @issue.id, :start_date => Date.today, :due_date => 1.week.from_now.to_date)) - @grandchild = Issue.generate!(attrs.merge(:subject => 'grandchild', :parent_issue_id => @child1.id, :start_date => Date.yesterday, :due_date => 2.day.from_now.to_date)) - end - - should "indent subtasks" do - @response.body = @gantt.subjects - # parent task 44px - assert_select "div.issue-subject[style*=left:44px]", /#{@issue.subject}/ - # children 64px - assert_select "div.issue-subject[style*=left:64px]", /child1/ - assert_select "div.issue-subject[style*=left:64px]", /child2/ - # grandchild 84px - assert_select "div.issue-subject[style*=left:84px]", /grandchild/, @response.body - end - end - end - end - - context "#lines" do - setup do - create_gantt - @project.enabled_module_names = [:issue_tracking] - @tracker = Tracker.generate! - @project.trackers << @tracker - @version = Version.generate!(:effective_date => 1.week.from_now.to_date) - @project.versions << @version - @issue = Issue.generate!(:fixed_version => @version, - :subject => "gantt#line_for_project", - :tracker => @tracker, - :project => @project, - :done_ratio => 30, - :start_date => Date.yesterday, - :due_date => 1.week.from_now.to_date) - @project.issues << @issue - - @response.body = @gantt.lines - end - - context "project" do - should "be rendered" do - assert_select "div.project.task_todo" - assert_select "div.project.starting" - assert_select "div.project.ending" - assert_select "div.label.project", /#{@project.name}/ - end - end - - context "version" do - should "be rendered" do - assert_select "div.version.task_todo" - assert_select "div.version.starting" - assert_select "div.version.ending" - assert_select "div.label.version", /#{@version.name}/ - end - end - - context "issue" do - should "be rendered" do - assert_select "div.task_todo" - assert_select "div.task.label", /#{@issue.done_ratio}/ - assert_select "div.tooltip", /#{@issue.subject}/ - end - end - end - - context "#render_project" do - should "be tested" - end - - context "#render_issues" do - should "be tested" - end - - context "#render_version" do - should "be tested" - end - - context "#subject_for_project" do - setup do - create_gantt - end - - context ":html format" do - should "add an absolute positioned div" do - @response.body = @gantt.subject_for_project(@project, {:format => :html}) - assert_select "div[style*=absolute]" - end - - should "use the indent option to move the div to the right" do - @response.body = @gantt.subject_for_project(@project, {:format => :html, :indent => 40}) - assert_select "div[style*=left:40]" - end - - should "include the project name" do - @response.body = @gantt.subject_for_project(@project, {:format => :html}) - assert_select 'div', :text => /#{@project.name}/ - end - - should "include a link to the project" do - @response.body = @gantt.subject_for_project(@project, {:format => :html}) - assert_select 'a[href=?]', "/projects/#{@project.identifier}", :text => /#{@project.name}/ - end - - should "style overdue projects" do - @project.enabled_module_names = [:issue_tracking] - @project.versions << Version.generate!(:effective_date => Date.yesterday) - - assert @project.overdue?, "Need an overdue project for this test" - @response.body = @gantt.subject_for_project(@project, {:format => :html}) - - assert_select 'div span.project-overdue' - end - - - end - - should "test the PNG format" - should "test the PDF format" - end - - context "#line_for_project" do - setup do - create_gantt - @project.enabled_module_names = [:issue_tracking] - @tracker = Tracker.generate! - @project.trackers << @tracker - @version = Version.generate!(:effective_date => Date.yesterday) - @project.versions << @version - - @project.issues << Issue.generate!(:fixed_version => @version, - :subject => "gantt#line_for_project", - :tracker => @tracker, - :project => @project, - :done_ratio => 30, - :start_date => 1.week.ago.to_date, - :due_date => 1.week.from_now.to_date) - end - - context ":html format" do - context "todo line" do - should "start from the starting point on the left" do - @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.task_todo[style*=left:28px]", true, @response.body - end - - should "be the total width of the project" do - @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.task_todo[style*=width:58px]", true, @response.body - end - - end - - context "late line" do - should_eventually "start from the starting point on the left" do - @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.task_late[style*=left:28px]", true, @response.body - end - - should_eventually "be the total delayed width of the project" do - @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.task_late[style*=width:30px]", true, @response.body - end - end - - context "done line" do - should_eventually "start from the starting point on the left" do - @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.task_done[style*=left:28px]", true, @response.body - end - - should_eventually "Be the total done width of the project" do - @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.task_done[style*=width:18px]", true, @response.body - end - end - - context "starting marker" do - should "not appear if the starting point is off the gantt chart" do - # Shift the date range of the chart - @gantt.instance_variable_set('@date_from', Date.today) - - @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.starting", false, @response.body - end - - should "appear at the starting point" do - @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.starting[style*=left:28px]", true, @response.body - end - end - - context "ending marker" do - should "not appear if the starting point is off the gantt chart" do - # Shift the date range of the chart - @gantt.instance_variable_set('@date_to', 2.weeks.ago.to_date) - - @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.ending", false, @response.body - - end - - should "appear at the end of the date range" do - @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.ending[style*=left:88px]", true, @response.body - end - end - - context "status content" do - should "appear at the far left, even if it's far in the past" do - @gantt.instance_variable_set('@date_to', 2.weeks.ago.to_date) - - @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.label", /#{@project.name}/ - end - - should "show the project name" do - @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.label", /#{@project.name}/ - end - - should_eventually "show the percent complete" do - @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.label", /0%/ - end - end - end - - should "test the PNG format" - should "test the PDF format" - end - - context "#subject_for_version" do - setup do - create_gantt - @project.enabled_module_names = [:issue_tracking] - @tracker = Tracker.generate! - @project.trackers << @tracker - @version = Version.generate!(:effective_date => Date.yesterday) - @project.versions << @version - - @project.issues << Issue.generate!(:fixed_version => @version, - :subject => "gantt#subject_for_version", - :tracker => @tracker, - :project => @project, - :start_date => Date.today) - - end - - context ":html format" do - should "add an absolute positioned div" do - @response.body = @gantt.subject_for_version(@version, {:format => :html}) - assert_select "div[style*=absolute]" - end - - should "use the indent option to move the div to the right" do - @response.body = @gantt.subject_for_version(@version, {:format => :html, :indent => 40}) - assert_select "div[style*=left:40]" - end - - should "include the version name" do - @response.body = @gantt.subject_for_version(@version, {:format => :html}) - assert_select 'div', :text => /#{@version.name}/ - end - - should "include a link to the version" do - @response.body = @gantt.subject_for_version(@version, {:format => :html}) - assert_select 'a[href=?]', Regexp.escape("/versions/#{@version.to_param}"), :text => /#{@version.name}/ - end - - should "style late versions" do - assert @version.overdue?, "Need an overdue version for this test" - @response.body = @gantt.subject_for_version(@version, {:format => :html}) - - assert_select 'div span.version-behind-schedule' - end - - should "style behind schedule versions" do - assert @version.behind_schedule?, "Need a behind schedule version for this test" - @response.body = @gantt.subject_for_version(@version, {:format => :html}) - - assert_select 'div span.version-behind-schedule' - end - end - should "test the PNG format" - should "test the PDF format" - end - - context "#line_for_version" do - setup do - create_gantt - @project.enabled_module_names = [:issue_tracking] - @tracker = Tracker.generate! - @project.trackers << @tracker - @version = Version.generate!(:effective_date => 1.week.from_now.to_date) - @project.versions << @version - - @project.issues << Issue.generate!(:fixed_version => @version, - :subject => "gantt#line_for_project", - :tracker => @tracker, - :project => @project, - :done_ratio => 30, - :start_date => 1.week.ago.to_date, - :due_date => 1.week.from_now.to_date) - end - - context ":html format" do - context "todo line" do - should "start from the starting point on the left" do - @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.task_todo[style*=left:28px]", true, @response.body - end - - should "be the total width of the version" do - @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.task_todo[style*=width:58px]", true, @response.body - end - - end - - context "late line" do - should "start from the starting point on the left" do - @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.task_late[style*=left:28px]", true, @response.body - end - - should "be the total delayed width of the version" do - @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.task_late[style*=width:30px]", true, @response.body - end - end - - context "done line" do - should "start from the starting point on the left" do - @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.task_done[style*=left:28px]", true, @response.body - end - - should "be the total done width of the version" do - @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.task_done[style*=width:16px]", true, @response.body - end - end - - context "starting marker" do - should "not appear if the starting point is off the gantt chart" do - # Shift the date range of the chart - @gantt.instance_variable_set('@date_from', Date.today) - - @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.starting", false - end - - should "appear at the starting point" do - @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.starting[style*=left:28px]", true, @response.body - end - end - - context "ending marker" do - should "not appear if the starting point is off the gantt chart" do - # Shift the date range of the chart - @gantt.instance_variable_set('@date_to', 2.weeks.ago.to_date) - - @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.ending", false - - end - - should "appear at the end of the date range" do - @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.ending[style*=left:88px]", true, @response.body - end - end - - context "status content" do - should "appear at the far left, even if it's far in the past" do - @gantt.instance_variable_set('@date_to', 2.weeks.ago.to_date) - - @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.label", /#{@version.name}/ - end - - should "show the version name" do - @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.label", /#{@version.name}/ - end - - should "show the percent complete" do - @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.label", /30%/ - end - end - end - - should "test the PNG format" - should "test the PDF format" - end - - context "#subject_for_issue" do - setup do - create_gantt - @project.enabled_module_names = [:issue_tracking] - @tracker = Tracker.generate! - @project.trackers << @tracker - - @issue = Issue.generate!(:subject => "gantt#subject_for_issue", - :tracker => @tracker, - :project => @project, - :start_date => 3.days.ago.to_date, - :due_date => Date.yesterday) - @project.issues << @issue - - end - - context ":html format" do - should "add an absolute positioned div" do - @response.body = @gantt.subject_for_issue(@issue, {:format => :html}) - assert_select "div[style*=absolute]" - end - - should "use the indent option to move the div to the right" do - @response.body = @gantt.subject_for_issue(@issue, {:format => :html, :indent => 40}) - assert_select "div[style*=left:40]" - end - - should "include the issue subject" do - @response.body = @gantt.subject_for_issue(@issue, {:format => :html}) - assert_select 'div', :text => /#{@issue.subject}/ - end - - should "include a link to the issue" do - @response.body = @gantt.subject_for_issue(@issue, {:format => :html}) - assert_select 'a[href=?]', Regexp.escape("/issues/#{@issue.to_param}"), :text => /#{@tracker.name} ##{@issue.id}/ - end - - should "style overdue issues" do - assert @issue.overdue?, "Need an overdue issue for this test" - @response.body = @gantt.subject_for_issue(@issue, {:format => :html}) - - assert_select 'div span.issue-overdue' - end - - end - should "test the PNG format" - should "test the PDF format" - end - - context "#line_for_issue" do - setup do - create_gantt - @project.enabled_module_names = [:issue_tracking] - @tracker = Tracker.generate! - @project.trackers << @tracker - @version = Version.generate!(:effective_date => 1.week.from_now.to_date) - @project.versions << @version - @issue = Issue.generate!(:fixed_version => @version, - :subject => "gantt#line_for_project", - :tracker => @tracker, - :project => @project, - :done_ratio => 30, - :start_date => 1.week.ago.to_date, - :due_date => 1.week.from_now.to_date) - @project.issues << @issue - end - - context ":html format" do - context "todo line" do - should "start from the starting point on the left" do - @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) - assert_select "div.task_todo[style*=left:28px]", true, @response.body - end - - should "be the total width of the issue" do - @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) - assert_select "div.task_todo[style*=width:58px]", true, @response.body - end - - end - - context "late line" do - should "start from the starting point on the left" do - @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) - assert_select "div.task_late[style*=left:28px]", true, @response.body - end - - should "be the total delayed width of the issue" do - @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) - assert_select "div.task_late[style*=width:30px]", true, @response.body - end - end - - context "done line" do - should "start from the starting point on the left" do - @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) - assert_select "div.task_done[style*=left:28px]", true, @response.body - end - - should "be the total done width of the issue" do - @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) - # 15 days * 4 px * 30% - 2 px for borders = 16 px - assert_select "div.task_done[style*=width:16px]", true, @response.body - end - - should "not be the total done width if the chart starts after issue start date" do - create_gantt(@project, :date_from => 5.days.ago.to_date) - - @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) - assert_select "div.task_done[style*=left:0px]", true, @response.body - assert_select "div.task_done[style*=width:8px]", true, @response.body - end - - context "for completed issue" do - setup do - @issue.done_ratio = 100 - end - - should "be the total width of the issue" do - @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) - assert_select "div.task_done[style*=width:58px]", true, @response.body - end - - should "be the total width of the issue with due_date=start_date" do - @issue.due_date = @issue.start_date - @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) - assert_select "div.task_done[style*=width:2px]", true, @response.body - end - end - end - - context "status content" do - should "appear at the far left, even if it's far in the past" do - @gantt.instance_variable_set('@date_to', 2.weeks.ago.to_date) - - @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) - assert_select "div.task.label", true, @response.body - end - - should "show the issue status" do - @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) - assert_select "div.task.label", /#{@issue.status.name}/ - end - - should "show the percent complete" do - @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) - assert_select "div.task.label", /30%/ - end - end - end - - should "have an issue tooltip" do - @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) - assert_select "div.tooltip", /#{@issue.subject}/ - end - - should "test the PNG format" - should "test the PDF format" - end - - context "#to_image" do - should "be tested" - end - - context "#to_pdf" do - should "be tested" - end - -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/08/086f4bbd984edf33971e918b1e5330fe445e9dd7.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/08/086f4bbd984edf33971e918b1e5330fe445e9dd7.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,253 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../../../test_helper', __FILE__) + +class Redmine::MenuManager::MenuHelperTest < ActionView::TestCase + + include Redmine::MenuManager::MenuHelper + include ERB::Util + fixtures :users, :members, :projects, :enabled_modules, :roles, :member_roles + + def setup + setup_with_controller + # Stub the current menu item in the controller + def current_menu_item + :index + end + end + + context "MenuManager#current_menu_item" do + should "be tested" + end + + context "MenuManager#render_main_menu" do + should "be tested" + end + + context "MenuManager#render_menu" do + should "be tested" + end + + context "MenuManager#menu_item_and_children" do + should "be tested" + end + + context "MenuManager#extract_node_details" do + should "be tested" + end + + def test_render_single_menu_node + node = Redmine::MenuManager::MenuItem.new(:testing, '/test', { }) + @output_buffer = render_single_menu_node(node, 'This is a test', node.url, false) + + assert_select("a.testing", "This is a test") + end + + def test_render_menu_node + single_node = Redmine::MenuManager::MenuItem.new(:single_node, '/test', { }) + @output_buffer = render_menu_node(single_node, nil) + + assert_select("li") do + assert_select("a.single-node", "Single node") + end + end + + def test_render_menu_node_with_nested_items + parent_node = Redmine::MenuManager::MenuItem.new(:parent_node, '/test', { }) + parent_node << Redmine::MenuManager::MenuItem.new(:child_one_node, '/test', { }) + parent_node << Redmine::MenuManager::MenuItem.new(:child_two_node, '/test', { }) + parent_node << + Redmine::MenuManager::MenuItem.new(:child_three_node, '/test', { }) << + Redmine::MenuManager::MenuItem.new(:child_three_inner_node, '/test', { }) + + @output_buffer = render_menu_node(parent_node, nil) + + assert_select("li") do + assert_select("a.parent-node", "Parent node") + assert_select("ul") do + assert_select("li a.child-one-node", "Child one node") + assert_select("li a.child-two-node", "Child two node") + assert_select("li") do + assert_select("a.child-three-node", "Child three node") + assert_select("ul") do + assert_select("li a.child-three-inner-node", "Child three inner node") + end + end + end + end + + end + + def test_render_menu_node_with_children + User.current = User.find(2) + + parent_node = Redmine::MenuManager::MenuItem.new(:parent_node, + '/test', + { + :children => Proc.new {|p| + children = [] + 3.times do |time| + children << Redmine::MenuManager::MenuItem.new("test_child_#{time}", + {:controller => 'issues', :action => 'index'}, + {}) + end + children + } + }) + @output_buffer = render_menu_node(parent_node, Project.find(1)) + + assert_select("li") do + assert_select("a.parent-node", "Parent node") + assert_select("ul") do + assert_select("li a.test-child-0", "Test child 0") + assert_select("li a.test-child-1", "Test child 1") + assert_select("li a.test-child-2", "Test child 2") + end + end + end + + def test_render_menu_node_with_nested_items_and_children + User.current = User.find(2) + + parent_node = Redmine::MenuManager::MenuItem.new(:parent_node, + '/test', + { + :children => Proc.new {|p| + children = [] + 3.times do |time| + children << Redmine::MenuManager::MenuItem.new("test_child_#{time}", {:controller => 'issues', :action => 'index'}, {}) + end + children + } + }) + + parent_node << Redmine::MenuManager::MenuItem.new(:child_node, + '/test', + { + :children => Proc.new {|p| + children = [] + 6.times do |time| + children << Redmine::MenuManager::MenuItem.new("test_dynamic_child_#{time}", {:controller => 'issues', :action => 'index'}, {}) + end + children + } + }) + + @output_buffer = render_menu_node(parent_node, Project.find(1)) + + assert_select("li") do + assert_select("a.parent-node", "Parent node") + assert_select("ul") do + assert_select("li a.child-node", "Child node") + assert_select("ul") do + assert_select("li a.test-dynamic-child-0", "Test dynamic child 0") + assert_select("li a.test-dynamic-child-1", "Test dynamic child 1") + assert_select("li a.test-dynamic-child-2", "Test dynamic child 2") + assert_select("li a.test-dynamic-child-3", "Test dynamic child 3") + assert_select("li a.test-dynamic-child-4", "Test dynamic child 4") + assert_select("li a.test-dynamic-child-5", "Test dynamic child 5") + end + assert_select("li a.test-child-0", "Test child 0") + assert_select("li a.test-child-1", "Test child 1") + assert_select("li a.test-child-2", "Test child 2") + end + end + end + + def test_render_menu_node_with_children_without_an_array + parent_node = Redmine::MenuManager::MenuItem.new(:parent_node, + '/test', + { + :children => Proc.new {|p| Redmine::MenuManager::MenuItem.new("test_child", "/testing", {})} + }) + + assert_raises Redmine::MenuManager::MenuError, ":children must be an array of MenuItems" do + @output_buffer = render_menu_node(parent_node, Project.find(1)) + end + end + + def test_render_menu_node_with_incorrect_children + parent_node = Redmine::MenuManager::MenuItem.new(:parent_node, + '/test', + { + :children => Proc.new {|p| ["a string"] } + }) + + assert_raises Redmine::MenuManager::MenuError, ":children must be an array of MenuItems" do + @output_buffer = render_menu_node(parent_node, Project.find(1)) + end + + end + + def test_menu_items_for_should_yield_all_items_if_passed_a_block + menu_name = :test_menu_items_for_should_yield_all_items_if_passed_a_block + Redmine::MenuManager.map menu_name do |menu| + menu.push(:a_menu, '/', { }) + menu.push(:a_menu_2, '/', { }) + menu.push(:a_menu_3, '/', { }) + end + + items_yielded = [] + menu_items_for(menu_name) do |item| + items_yielded << item + end + + assert_equal 3, items_yielded.size + end + + def test_menu_items_for_should_return_all_items + menu_name = :test_menu_items_for_should_return_all_items + Redmine::MenuManager.map menu_name do |menu| + menu.push(:a_menu, '/', { }) + menu.push(:a_menu_2, '/', { }) + menu.push(:a_menu_3, '/', { }) + end + + items = menu_items_for(menu_name) + assert_equal 3, items.size + end + + def test_menu_items_for_should_skip_unallowed_items_on_a_project + menu_name = :test_menu_items_for_should_skip_unallowed_items_on_a_project + Redmine::MenuManager.map menu_name do |menu| + menu.push(:a_menu, {:controller => 'issues', :action => 'index' }, { }) + menu.push(:a_menu_2, {:controller => 'issues', :action => 'index' }, { }) + menu.push(:unallowed, {:controller => 'issues', :action => 'unallowed' }, { }) + end + + User.current = User.find(2) + + items = menu_items_for(menu_name, Project.find(1)) + assert_equal 2, items.size + end + + def test_menu_items_for_should_skip_items_that_fail_the_conditions + menu_name = :test_menu_items_for_should_skip_items_that_fail_the_conditions + Redmine::MenuManager.map menu_name do |menu| + menu.push(:a_menu, {:controller => 'issues', :action => 'index' }, { }) + menu.push(:unallowed, + {:controller => 'issues', :action => 'index' }, + { :if => Proc.new { false }}) + end + + User.current = User.find(2) + + items = menu_items_for(menu_name, Project.find(1)) + assert_equal 1, items.size + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/08/087ac3ba0dd90a493f814e49e635e06136a040ab.svn-base --- a/.svn/pristine/08/087ac3ba0dd90a493f814e49e635e06136a040ab.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -template from plugin \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/08/088eae42f08379f3106075b388f036d1754d148c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/08/088eae42f08379f3106075b388f036d1754d148c.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,21 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +module EnumerationsHelper +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/08/08c1f1410c30c4d18ef6cbcbd8954d1721fb044b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/08/08c1f1410c30c4d18ef6cbcbd8954d1721fb044b.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,195 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class WatchersControllerTest < ActionController::TestCase + fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules, + :issues, :trackers, :projects_trackers, :issue_statuses, :enumerations, :watchers + + def setup + User.current = nil + end + + def test_watch_a_single_object + @request.session[:user_id] = 3 + assert_difference('Watcher.count') do + xhr :post, :watch, :object_type => 'issue', :object_id => '1' + assert_response :success + assert_include '$(".issue-1-watcher")', response.body + end + assert Issue.find(1).watched_by?(User.find(3)) + end + + def test_watch_a_collection_with_a_single_object + @request.session[:user_id] = 3 + assert_difference('Watcher.count') do + xhr :post, :watch, :object_type => 'issue', :object_id => ['1'] + assert_response :success + assert_include '$(".issue-1-watcher")', response.body + end + assert Issue.find(1).watched_by?(User.find(3)) + end + + def test_watch_a_collection_with_multiple_objects + @request.session[:user_id] = 3 + assert_difference('Watcher.count', 2) do + xhr :post, :watch, :object_type => 'issue', :object_id => ['1', '3'] + assert_response :success + assert_include '$(".issue-bulk-watcher")', response.body + end + assert Issue.find(1).watched_by?(User.find(3)) + assert Issue.find(3).watched_by?(User.find(3)) + end + + def test_watch_should_be_denied_without_permission + Role.find(2).remove_permission! :view_issues + @request.session[:user_id] = 3 + assert_no_difference('Watcher.count') do + xhr :post, :watch, :object_type => 'issue', :object_id => '1' + assert_response 403 + end + end + + def test_watch_invalid_class_should_respond_with_404 + @request.session[:user_id] = 3 + assert_no_difference('Watcher.count') do + xhr :post, :watch, :object_type => 'foo', :object_id => '1' + assert_response 404 + end + end + + def test_watch_invalid_object_should_respond_with_404 + @request.session[:user_id] = 3 + assert_no_difference('Watcher.count') do + xhr :post, :watch, :object_type => 'issue', :object_id => '999' + assert_response 404 + end + end + + def test_unwatch + @request.session[:user_id] = 3 + assert_difference('Watcher.count', -1) do + xhr :delete, :unwatch, :object_type => 'issue', :object_id => '2' + assert_response :success + assert_include '$(".issue-2-watcher")', response.body + end + assert !Issue.find(1).watched_by?(User.find(3)) + end + + def test_unwatch_a_collection_with_multiple_objects + @request.session[:user_id] = 3 + Watcher.create!(:user_id => 3, :watchable => Issue.find(1)) + Watcher.create!(:user_id => 3, :watchable => Issue.find(3)) + + assert_difference('Watcher.count', -2) do + xhr :delete, :unwatch, :object_type => 'issue', :object_id => ['1', '3'] + assert_response :success + assert_include '$(".issue-bulk-watcher")', response.body + end + assert !Issue.find(1).watched_by?(User.find(3)) + assert !Issue.find(3).watched_by?(User.find(3)) + end + + def test_new + @request.session[:user_id] = 2 + xhr :get, :new, :object_type => 'issue', :object_id => '2' + assert_response :success + assert_match /ajax-modal/, response.body + end + + def test_new_for_new_record_with_project_id + @request.session[:user_id] = 2 + xhr :get, :new, :project_id => 1 + assert_response :success + assert_equal Project.find(1), assigns(:project) + assert_match /ajax-modal/, response.body + end + + def test_new_for_new_record_with_project_identifier + @request.session[:user_id] = 2 + xhr :get, :new, :project_id => 'ecookbook' + assert_response :success + assert_equal Project.find(1), assigns(:project) + assert_match /ajax-modal/, response.body + end + + def test_create + @request.session[:user_id] = 2 + assert_difference('Watcher.count') do + xhr :post, :create, :object_type => 'issue', :object_id => '2', :watcher => {:user_id => '4'} + assert_response :success + assert_match /watchers/, response.body + assert_match /ajax-modal/, response.body + end + assert Issue.find(2).watched_by?(User.find(4)) + end + + def test_create_multiple + @request.session[:user_id] = 2 + assert_difference('Watcher.count', 2) do + xhr :post, :create, :object_type => 'issue', :object_id => '2', :watcher => {:user_ids => ['4', '7']} + assert_response :success + assert_match /watchers/, response.body + assert_match /ajax-modal/, response.body + end + assert Issue.find(2).watched_by?(User.find(4)) + assert Issue.find(2).watched_by?(User.find(7)) + end + + def test_autocomplete_on_watchable_creation + @request.session[:user_id] = 2 + xhr :get, :autocomplete_for_user, :q => 'mi', :project_id => 'ecookbook' + assert_response :success + assert_select 'input', :count => 4 + assert_select 'input[name=?][value=1]', 'watcher[user_ids][]' + assert_select 'input[name=?][value=2]', 'watcher[user_ids][]' + assert_select 'input[name=?][value=8]', 'watcher[user_ids][]' + assert_select 'input[name=?][value=9]', 'watcher[user_ids][]' + end + + def test_autocomplete_on_watchable_update + @request.session[:user_id] = 2 + xhr :get, :autocomplete_for_user, :q => 'mi', :object_id => '2' , :object_type => 'issue', :project_id => 'ecookbook' + assert_response :success + assert_select 'input', :count => 3 + assert_select 'input[name=?][value=2]', 'watcher[user_ids][]' + assert_select 'input[name=?][value=8]', 'watcher[user_ids][]' + assert_select 'input[name=?][value=9]', 'watcher[user_ids][]' + + end + + def test_append + @request.session[:user_id] = 2 + assert_no_difference 'Watcher.count' do + xhr :post, :append, :watcher => {:user_ids => ['4', '7']}, :project_id => 'ecookbook' + assert_response :success + assert_include 'watchers_inputs', response.body + assert_include 'issue[watcher_user_ids][]', response.body + end + end + + def test_remove_watcher + @request.session[:user_id] = 2 + assert_difference('Watcher.count', -1) do + xhr :delete, :destroy, :object_type => 'issue', :object_id => '2', :user_id => '3' + assert_response :success + assert_match /watchers/, response.body + end + assert !Issue.find(2).watched_by?(User.find(3)) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/08/08d8c21c9156481395a983e5e0a4c9f73ad91ca9.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/08/08d8c21c9156481395a983e5e0a4c9f73ad91ca9.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,71 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../../../../test_helper', __FILE__) + +begin + require 'mocha' + + class SubversionAdapterTest < ActiveSupport::TestCase + + if repository_configured?('subversion') + def setup + @adapter = Redmine::Scm::Adapters::SubversionAdapter.new(self.class.subversion_repository_url) + end + + def test_client_version + v = Redmine::Scm::Adapters::SubversionAdapter.client_version + assert v.is_a?(Array) + end + + def test_scm_version + to_test = { "svn, version 1.6.13 (r1002816)\n" => [1,6,13], + "svn, versione 1.6.13 (r1002816)\n" => [1,6,13], + "1.6.1\n1.7\n1.8" => [1,6,1], + "1.6.2\r\n1.8.1\r\n1.9.1" => [1,6,2]} + to_test.each do |s, v| + test_scm_version_for(s, v) + end + end + + def test_info_not_nil + assert_not_nil @adapter.info + end + + def test_info_nil + adpt = Redmine::Scm::Adapters::SubversionAdapter.new( + "file:///invalid/invalid/" + ) + assert_nil adpt.info + end + + private + + def test_scm_version_for(scm_version, version) + @adapter.class.expects(:scm_version_from_command_line).returns(scm_version) + assert_equal version, @adapter.class.svn_binary_version + end + else + puts "Subversion test repository NOT FOUND. Skipping unit tests !!!" + def test_fake; assert true end + end + end +rescue LoadError + class SubversionMochaFake < ActiveSupport::TestCase + def test_fake; assert(false, "Requires mocha to run those tests") end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/09/0950951344735156104a9fd2df5df66fc9bd2194.svn-base --- a/.svn/pristine/09/0950951344735156104a9fd2df5df66fc9bd2194.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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 - module Builders - def self.for(format, &block) - builder = case format - when 'xml', :xml; Builders::Xml.new - when 'json', :json; Builders::Json.new - else; raise "No builder for format #{format}" - end - if block - block.call(builder) - else - builder - end - end - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/09/095b395fcfe029ccbb2acac47d6cc8ef84b80f97.svn-base --- a/.svn/pristine/09/095b395fcfe029ccbb2acac47d6cc8ef84b80f97.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +0,0 @@ -syntax: glob - -.project -.loadpath -config/additional_environment.rb -config/configuration.yml -config/database.yml -config/email.yml -config/initializers/session_store.rb -coverage -db/*.db -db/*.sqlite3 -db/schema.rb -files/* -lib/redmine/scm/adapters/mercurial/redminehelper.pyc -lib/redmine/scm/adapters/mercurial/redminehelper.pyo -log/*.log* -log/mongrel_debug -public/dispatch.* -public/plugin_assets -tmp/* -tmp/cache/* -tmp/sessions/* -tmp/sockets/* -tmp/test/* -vendor/rails -*.rbc - -.svn/ -.git/ - -.bundle -Gemfile.lock -Gemfile.local - diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/09/09843d45a0a3ac966e4fda0bad80ec81990f71bb.svn-base --- a/.svn/pristine/09/09843d45a0a3ac966e4fda0bad80ec81990f71bb.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,55 +0,0 @@ -Net::LDAP is copyrighted free software by Francis Cianfrocca -. You can redistribute it and/or modify it under either -the terms of the GPL (see the file COPYING), or the conditions below: - -1. You may make and give away verbatim copies of the source form of the - software without restriction, provided that you duplicate all of the - original copyright notices and associated disclaimers. - -2. You may modify your copy of the software in any way, provided that you do - at least ONE of the following: - - a) place your modifications in the Public Domain or otherwise make them - Freely Available, such as by posting said modifications to Usenet or - an equivalent medium, or by allowing the author to include your - modifications in the software. - - b) use the modified software only within your corporation or - organization. - - c) rename any non-standard executables so the names do not conflict with - standard executables, which must also be provided. - - d) make other distribution arrangements with the author. - -3. You may distribute the software in object code or executable form, - provided that you do at least ONE of the following: - - a) distribute the executables and library files of the software, together - with instructions (in the manual page or equivalent) on where to get - the original distribution. - - b) accompany the distribution with the machine-readable source of the - software. - - c) give non-standard executables non-standard names, with instructions on - where to get the original software distribution. - - d) make other distribution arrangements with the author. - -4. You may modify and include the part of the software into any other - software (possibly commercial). But some files in the distribution are - not written by the author, so that they are not under this terms. - - They are gc.c(partly), utils.c(partly), regex.[ch], st.[ch] and some - files under the ./missing directory. See each file for the copying - condition. - -5. The scripts and library files supplied as input to or produced as output - from the software do not automatically fall under the copyright of the - software, but belong to whomever generated them, and may be sold - commercially, and may be aggregated with this software. - -6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED - WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/09/09d005add00d07aeb449a6c04ad00db8e1f72fbd.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/09/09d005add00d07aeb449a6c04ad00db8e1f72fbd.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,73 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class RoutingMyTest < ActionController::IntegrationTest + def test_my + ["get", "post"].each do |method| + assert_routing( + { :method => method, :path => "/my/account" }, + { :controller => 'my', :action => 'account' } + ) + end + ["get", "post"].each do |method| + assert_routing( + { :method => method, :path => "/my/account/destroy" }, + { :controller => 'my', :action => 'destroy' } + ) + end + assert_routing( + { :method => 'get', :path => "/my/page" }, + { :controller => 'my', :action => 'page' } + ) + assert_routing( + { :method => 'get', :path => "/my" }, + { :controller => 'my', :action => 'index' } + ) + assert_routing( + { :method => 'post', :path => "/my/reset_rss_key" }, + { :controller => 'my', :action => 'reset_rss_key' } + ) + assert_routing( + { :method => 'post', :path => "/my/reset_api_key" }, + { :controller => 'my', :action => 'reset_api_key' } + ) + ["get", "post"].each do |method| + assert_routing( + { :method => method, :path => "/my/password" }, + { :controller => 'my', :action => 'password' } + ) + end + assert_routing( + { :method => 'get', :path => "/my/page_layout" }, + { :controller => 'my', :action => 'page_layout' } + ) + assert_routing( + { :method => 'post', :path => "/my/add_block" }, + { :controller => 'my', :action => 'add_block' } + ) + assert_routing( + { :method => 'post', :path => "/my/remove_block" }, + { :controller => 'my', :action => 'remove_block' } + ) + assert_routing( + { :method => 'post', :path => "/my/order_blocks" }, + { :controller => 'my', :action => 'order_blocks' } + ) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/09/09e163f7a5ba48f41a7d0433361b8d9b46760493.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/09/09e163f7a5ba48f41a7d0433361b8d9b46760493.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,55 @@ +<%= error_messages_for 'query' %> + +
+
+

+<%= text_field 'query', 'name', :size => 80 %>

+ +<% if User.current.admin? || User.current.allowed_to?(:manage_public_queries, @project) %> +

+<%= check_box 'query', 'is_public', + :onchange => (User.current.admin? ? nil : 'if (this.checked) {$("#query_is_for_all").removeAttr("checked"); $("#query_is_for_all").attr("disabled", true);} else {$("#query_is_for_all").removeAttr("disabled");}') %>

+<% end %> + +

+<%= check_box_tag 'query_is_for_all', 1, @query.project.nil?, + :disabled => (!@query.new_record? && (@query.project.nil? || (@query.is_public? && !User.current.admin?))) %>

+ +

+<%= check_box_tag 'default_columns', 1, @query.has_default_columns?, :id => 'query_default_columns', + :onclick => 'if (this.checked) {$("#columns").hide();} else {$("#columns").show();}' %>

+ +

+<%= select 'query', 'group_by', @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]}, :include_blank => true %>

+ +

+<%= available_block_columns_tags(@query) %>

+
+ +
<%= l(:label_filter_plural) %> +<%= render :partial => 'queries/filters', :locals => {:query => query}%> +
+ +
<%= l(:label_sort) %> +<% 3.times do |i| %> +<%= i+1 %>: +<%= label_tag "query_sort_criteria_attribute_" + i.to_s, + l(:description_query_sort_criteria_attribute), :class => "hidden-for-sighted" %> +<%= select_tag("query[sort_criteria][#{i}][]", + options_for_select([[]] + query.available_columns.select(&:sortable?).collect {|column| [column.caption, column.name.to_s]}, @query.sort_criteria_key(i)), + :id => "query_sort_criteria_attribute_" + i.to_s)%> +<%= label_tag "query_sort_criteria_direction_" + i.to_s, + l(:description_query_sort_criteria_direction), :class => "hidden-for-sighted" %> +<%= select_tag("query[sort_criteria][#{i}][]", + options_for_select([[], [l(:label_ascending), 'asc'], [l(:label_descending), 'desc']], @query.sort_criteria_order(i)), + :id => "query_sort_criteria_direction_" + i.to_s) %> +
+<% end %> +
+ +<%= content_tag 'fieldset', :id => 'columns', :style => (query.has_default_columns? ? 'display:none;' : nil) do %> +<%= l(:field_column_names) %> +<%= render_query_columns_selection(query) %> +<% end %> + +
diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/09/09eb85d785f24ac92c1ca5060b63ab8922db4494.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/09/09eb85d785f24ac92c1ca5060b63ab8922db4494.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,474 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class Mailer < ActionMailer::Base + layout 'mailer' + helper :application + helper :issues + helper :custom_fields + + include Redmine::I18n + + def self.default_url_options + { :host => Setting.host_name, :protocol => Setting.protocol } + end + + # Builds a Mail::Message object used to email recipients of the added issue. + # + # Example: + # issue_add(issue) => Mail::Message object + # Mailer.issue_add(issue).deliver => sends an email to issue recipients + 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 + message_id issue + @author = issue.author + @issue = issue + @issue_url = url_for(:controller => 'issues', :action => 'show', :id => issue) + recipients = issue.recipients + cc = issue.watcher_recipients - recipients + mail :to => recipients, + :cc => cc, + :subject => "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}" + end + + # Builds a Mail::Message object used to email recipients of the edited issue. + # + # Example: + # issue_edit(journal) => Mail::Message object + # Mailer.issue_edit(journal).deliver => sends an email to issue recipients + def issue_edit(journal) + issue = journal.journalized.reload + 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 + message_id journal + references issue + @author = journal.user + recipients = journal.recipients + # Watchers in cc + cc = journal.watcher_recipients - recipients + s = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] " + s << "(#{issue.status.name}) " if journal.new_value_for('status_id') + s << issue.subject + @issue = issue + @journal = journal + @issue_url = url_for(:controller => 'issues', :action => 'show', :id => issue, :anchor => "change-#{journal.id}") + mail :to => recipients, + :cc => cc, + :subject => s + end + + def reminder(user, issues, days) + set_language_if_valid user.language + @issues = issues + @days = days + @issues_url = url_for(:controller => 'issues', :action => 'index', + :set_filter => 1, :assigned_to_id => user.id, + :sort => 'due_date:asc') + mail :to => user.mail, + :subject => l(:mail_subject_reminder, :count => issues.size, :days => days) + end + + # Builds a Mail::Message object used to email users belonging to the added document's project. + # + # Example: + # document_added(document) => Mail::Message object + # Mailer.document_added(document).deliver => sends an email to the document's project recipients + def document_added(document) + redmine_headers 'Project' => document.project.identifier + @author = User.current + @document = document + @document_url = url_for(:controller => 'documents', :action => 'show', :id => document) + mail :to => document.recipients, + :subject => "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}" + end + + # Builds a Mail::Message object used to email recipients of a project when an attachements are added. + # + # Example: + # attachments_added(attachments) => Mail::Message object + # Mailer.attachments_added(attachments).deliver => sends an email to the project's recipients + def attachments_added(attachments) + container = attachments.first.container + added_to = '' + added_to_url = '' + @author = attachments.first.author + case container.class.name + when 'Project' + added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container) + added_to = "#{l(:label_project)}: #{container}" + recipients = container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}.collect {|u| u.mail} + when 'Version' + added_to_url = url_for(:controller => 'files', :action => 'index', :project_id => container.project) + added_to = "#{l(:label_version)}: #{container.name}" + recipients = container.project.notified_users.select {|user| user.allowed_to?(:view_files, container.project)}.collect {|u| u.mail} + when 'Document' + added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id) + added_to = "#{l(:label_document)}: #{container.title}" + recipients = container.recipients + end + redmine_headers 'Project' => container.project.identifier + @attachments = attachments + @added_to = added_to + @added_to_url = added_to_url + mail :to => recipients, + :subject => "[#{container.project.name}] #{l(:label_attachment_new)}" + end + + # Builds a Mail::Message object used to email recipients of a news' project when a news item is added. + # + # Example: + # news_added(news) => Mail::Message object + # Mailer.news_added(news).deliver => sends an email to the news' project recipients + def news_added(news) + redmine_headers 'Project' => news.project.identifier + @author = news.author + message_id news + @news = news + @news_url = url_for(:controller => 'news', :action => 'show', :id => news) + mail :to => news.recipients, + :subject => "[#{news.project.name}] #{l(:label_news)}: #{news.title}" + end + + # Builds a Mail::Message object used to email recipients of a news' project when a news comment is added. + # + # Example: + # news_comment_added(comment) => Mail::Message object + # Mailer.news_comment_added(comment) => sends an email to the news' project recipients + def news_comment_added(comment) + news = comment.commented + redmine_headers 'Project' => news.project.identifier + @author = comment.author + message_id comment + @news = news + @comment = comment + @news_url = url_for(:controller => 'news', :action => 'show', :id => news) + mail :to => news.recipients, + :cc => news.watcher_recipients, + :subject => "Re: [#{news.project.name}] #{l(:label_news)}: #{news.title}" + end + + # Builds a Mail::Message object used to email the recipients of the specified message that was posted. + # + # Example: + # message_posted(message) => Mail::Message object + # Mailer.message_posted(message).deliver => sends an email to the recipients + def message_posted(message) + redmine_headers 'Project' => message.project.identifier, + 'Topic-Id' => (message.parent_id || message.id) + @author = message.author + message_id message + references message.parent unless message.parent.nil? + recipients = message.recipients + cc = ((message.root.watcher_recipients + message.board.watcher_recipients).uniq - recipients) + @message = message + @message_url = url_for(message.event_url) + mail :to => recipients, + :cc => cc, + :subject => "[#{message.board.project.name} - #{message.board.name} - msg#{message.root.id}] #{message.subject}" + end + + # Builds a Mail::Message object used to email the recipients of a project of the specified wiki content was added. + # + # Example: + # wiki_content_added(wiki_content) => Mail::Message object + # Mailer.wiki_content_added(wiki_content).deliver => sends an email to the project's recipients + def wiki_content_added(wiki_content) + redmine_headers 'Project' => wiki_content.project.identifier, + 'Wiki-Page-Id' => wiki_content.page.id + @author = wiki_content.author + message_id wiki_content + recipients = wiki_content.recipients + cc = wiki_content.page.wiki.watcher_recipients - recipients + @wiki_content = wiki_content + @wiki_content_url = url_for(:controller => 'wiki', :action => 'show', + :project_id => wiki_content.project, + :id => wiki_content.page.title) + mail :to => recipients, + :cc => cc, + :subject => "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_added, :id => wiki_content.page.pretty_title)}" + end + + # Builds a Mail::Message object used to email the recipients of a project of the specified wiki content was updated. + # + # Example: + # wiki_content_updated(wiki_content) => Mail::Message object + # Mailer.wiki_content_updated(wiki_content).deliver => sends an email to the project's recipients + def wiki_content_updated(wiki_content) + redmine_headers 'Project' => wiki_content.project.identifier, + 'Wiki-Page-Id' => wiki_content.page.id + @author = wiki_content.author + message_id wiki_content + recipients = wiki_content.recipients + cc = wiki_content.page.wiki.watcher_recipients + wiki_content.page.watcher_recipients - recipients + @wiki_content = wiki_content + @wiki_content_url = url_for(:controller => 'wiki', :action => 'show', + :project_id => wiki_content.project, + :id => wiki_content.page.title) + @wiki_diff_url = url_for(:controller => 'wiki', :action => 'diff', + :project_id => wiki_content.project, :id => wiki_content.page.title, + :version => wiki_content.version) + mail :to => recipients, + :cc => cc, + :subject => "[#{wiki_content.project.name}] #{l(:mail_subject_wiki_content_updated, :id => wiki_content.page.pretty_title)}" + end + + # Builds a Mail::Message object used to email the specified user their account information. + # + # Example: + # account_information(user, password) => Mail::Message object + # Mailer.account_information(user, password).deliver => sends account information to the user + def account_information(user, password) + set_language_if_valid user.language + @user = user + @password = password + @login_url = url_for(:controller => 'account', :action => 'login') + mail :to => user.mail, + :subject => l(:mail_subject_register, Setting.app_title) + end + + # Builds a Mail::Message object used to email all active administrators of an account activation request. + # + # Example: + # account_activation_request(user) => Mail::Message object + # Mailer.account_activation_request(user).deliver => sends an email to all active administrators + def account_activation_request(user) + # Send the email to all active administrators + recipients = User.active.where(:admin => true).all.collect { |u| u.mail }.compact + @user = user + @url = url_for(:controller => 'users', :action => 'index', + :status => User::STATUS_REGISTERED, + :sort_key => 'created_on', :sort_order => 'desc') + mail :to => recipients, + :subject => l(:mail_subject_account_activation_request, Setting.app_title) + end + + # Builds a Mail::Message object used to email the specified user that their account was activated by an administrator. + # + # Example: + # account_activated(user) => Mail::Message object + # Mailer.account_activated(user).deliver => sends an email to the registered user + def account_activated(user) + set_language_if_valid user.language + @user = user + @login_url = url_for(:controller => 'account', :action => 'login') + mail :to => user.mail, + :subject => l(:mail_subject_register, Setting.app_title) + end + + def lost_password(token) + set_language_if_valid(token.user.language) + @token = token + @url = url_for(:controller => 'account', :action => 'lost_password', :token => token.value) + mail :to => token.user.mail, + :subject => l(:mail_subject_lost_password, Setting.app_title) + end + + def register(token) + set_language_if_valid(token.user.language) + @token = token + @url = url_for(:controller => 'account', :action => 'activate', :token => token.value) + mail :to => token.user.mail, + :subject => l(:mail_subject_register, Setting.app_title) + end + + def test_email(user) + set_language_if_valid(user.language) + @url = url_for(:controller => 'welcome') + mail :to => user.mail, + :subject => 'Redmine test' + end + + # Overrides default deliver! method to prevent from sending an email + # with no recipient, cc or bcc + def deliver!(mail = @mail) + set_language_if_valid @initial_language + return false if (recipients.nil? || recipients.empty?) && + (cc.nil? || cc.empty?) && + (bcc.nil? || bcc.empty?) + + + # Log errors when raise_delivery_errors is set to false, Rails does not + raise_errors = self.class.raise_delivery_errors + self.class.raise_delivery_errors = true + begin + return super(mail) + rescue Exception => e + if raise_errors + raise e + elsif mylogger + mylogger.error "The following error occured while sending email notification: \"#{e.message}\". Check your configuration in config/configuration.yml." + end + ensure + self.class.raise_delivery_errors = raise_errors + end + end + + # Sends reminders to issue assignees + # Available options: + # * :days => how many days in the future to remind about (defaults to 7) + # * :tracker => id of tracker for filtering issues (defaults to all trackers) + # * :project => id or identifier of project to process (defaults to all projects) + # * :users => array of user/group ids who should be reminded + def self.reminders(options={}) + days = options[:days] || 7 + project = options[:project] ? Project.find(options[:project]) : nil + tracker = options[:tracker] ? Tracker.find(options[:tracker]) : nil + user_ids = options[:users] + + scope = Issue.open.where("#{Issue.table_name}.assigned_to_id IS NOT NULL" + + " AND #{Project.table_name}.status = #{Project::STATUS_ACTIVE}" + + " AND #{Issue.table_name}.due_date <= ?", days.day.from_now.to_date + ) + scope = scope.where(:assigned_to_id => user_ids) if user_ids.present? + scope = scope.where(:project_id => project.id) if project + scope = scope.where(:tracker_id => tracker.id) if tracker + + issues_by_assignee = scope.includes(:status, :assigned_to, :project, :tracker).all.group_by(&:assigned_to) + issues_by_assignee.keys.each do |assignee| + if assignee.is_a?(Group) + assignee.users.each do |user| + issues_by_assignee[user] ||= [] + issues_by_assignee[user] += issues_by_assignee[assignee] + end + end + end + + issues_by_assignee.each do |assignee, issues| + reminder(assignee, issues, days).deliver if assignee.is_a?(User) && assignee.active? + end + end + + # Activates/desactivates email deliveries during +block+ + def self.with_deliveries(enabled = true, &block) + was_enabled = ActionMailer::Base.perform_deliveries + ActionMailer::Base.perform_deliveries = !!enabled + yield + ensure + ActionMailer::Base.perform_deliveries = was_enabled + end + + # Sends emails synchronously in the given block + def self.with_synched_deliveries(&block) + saved_method = ActionMailer::Base.delivery_method + if m = saved_method.to_s.match(%r{^async_(.+)$}) + synched_method = m[1] + ActionMailer::Base.delivery_method = synched_method.to_sym + ActionMailer::Base.send "#{synched_method}_settings=", ActionMailer::Base.send("async_#{synched_method}_settings") + end + yield + ensure + ActionMailer::Base.delivery_method = saved_method + end + + def mail(headers={}) + headers.merge! 'X-Mailer' => 'Redmine', + 'X-Redmine-Host' => Setting.host_name, + 'X-Redmine-Site' => Setting.app_title, + 'X-Auto-Response-Suppress' => 'OOF', + 'Auto-Submitted' => 'auto-generated', + 'From' => Setting.mail_from, + 'List-Id' => "<#{Setting.mail_from.to_s.gsub('@', '.')}>" + + # Removes the author from the recipients and cc + # if he doesn't want to receive notifications about what he does + if @author && @author.logged? && @author.pref[:no_self_notified] + headers[:to].delete(@author.mail) if headers[:to].is_a?(Array) + headers[:cc].delete(@author.mail) if headers[:cc].is_a?(Array) + end + + if @author && @author.logged? + redmine_headers 'Sender' => @author.login + end + + # Blind carbon copy recipients + if Setting.bcc_recipients? + headers[:bcc] = [headers[:to], headers[:cc]].flatten.uniq.reject(&:blank?) + headers[:to] = nil + headers[:cc] = nil + end + + if @message_id_object + headers[:message_id] = "<#{self.class.message_id_for(@message_id_object)}>" + end + if @references_objects + headers[:references] = @references_objects.collect {|o| "<#{self.class.message_id_for(o)}>"}.join(' ') + end + + super headers do |format| + format.text + format.html unless Setting.plain_text_mail? + end + + set_language_if_valid @initial_language + end + + def initialize(*args) + @initial_language = current_language + set_language_if_valid Setting.default_language + super + end + + def self.deliver_mail(mail) + return false if mail.to.blank? && mail.cc.blank? && mail.bcc.blank? + super + end + + def self.method_missing(method, *args, &block) + if m = method.to_s.match(%r{^deliver_(.+)$}) + ActiveSupport::Deprecation.warn "Mailer.deliver_#{m[1]}(*args) is deprecated. Use Mailer.#{m[1]}(*args).deliver instead." + send(m[1], *args).deliver + else + super + end + end + + private + + # Appends a Redmine header field (name is prepended with 'X-Redmine-') + def redmine_headers(h) + h.each { |k,v| headers["X-Redmine-#{k}"] = v.to_s } + end + + # 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 + timestamp = object.send(object.respond_to?(:created_on) ? :created_on : :updated_on) + hash = "redmine.#{object.class.name.demodulize.underscore}-#{object.id}.#{timestamp.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 + + def message_id(object) + @message_id_object = object + end + + def references(object) + @references_objects ||= [] + @references_objects << object + end + + def mylogger + Rails.logger + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/09/09ee2f48a5aafa2ec6da3a021e076cc86af61f01.svn-base --- a/.svn/pristine/09/09ee2f48a5aafa2ec6da3a021e076cc86af61f01.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,6 +0,0 @@ -

<%= l(:label_board_new) %>

- -<% labelled_tabular_form_for :board, @board, :url => {:action => 'new'} do |f| %> - <%= render :partial => 'form', :locals => {:f => f} %> - <%= submit_tag l(:button_create) %> -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/09/09fd6db0926bf35a4db846796898a3efe70dfa6f.svn-base --- a/.svn/pristine/09/09fd6db0926bf35a4db846796898a3efe70dfa6f.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,40 +0,0 @@ - - - - - - - - - <% for new_status in @statuses %> - - <% end %> - - - - <% for old_status in @statuses %> - "> - - <% for new_status in @statuses -%> - - <% end -%> - - <% end %> - -
- <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('table.transitions-#{name} input')", - :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %> - <%=l(:label_current_status)%> - <%=l(:label_new_statuses_allowed)%>
- <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('table.transitions-#{name} input.new-status-#{new_status.id}')", - :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %> - <%=h new_status.name %> -
- <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('table.transitions-#{name} input.old-status-#{old_status.id}')", - :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %> - - <%=h old_status.name %> - - <%= check_box_tag "issue_status[#{ old_status.id }][#{new_status.id}][]", name, workflows.detect {|w| w.old_status_id == old_status.id && w.new_status_id == new_status.id}, - :class => "old-status-#{old_status.id} new-status-#{new_status.id}" %> -
diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/0a/0a01ef88ba34bfc6a3c1cef8891538f7504e9b0c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0a/0a01ef88ba34bfc6a3c1cef8891538f7504e9b0c.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,184 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 I18n + def self.included(base) + base.extend Redmine::I18n + end + + def l(*args) + case args.size + when 1 + ::I18n.t(*args) + when 2 + if args.last.is_a?(Hash) + ::I18n.t(*args) + elsif args.last.is_a?(String) + ::I18n.t(args.first, :value => args.last) + else + ::I18n.t(args.first, :count => args.last) + end + else + raise "Translation string with multiple values: #{args.first}" + end + end + + def l_or_humanize(s, options={}) + k = "#{options[:prefix]}#{s}".to_sym + ::I18n.t(k, :default => s.to_s.humanize) + end + + def l_hours(hours) + hours = hours.to_f + l((hours < 2.0 ? :label_f_hour : :label_f_hour_plural), :value => ("%.2f" % hours.to_f)) + end + + def ll(lang, str, value=nil) + ::I18n.t(str.to_s, :value => value, :locale => lang.to_s.gsub(%r{(.+)\-(.+)$}) { "#{$1}-#{$2.upcase}" }) + end + + def format_date(date) + return nil unless date + options = {} + options[:format] = Setting.date_format unless Setting.date_format.blank? + options[:locale] = User.current.language unless User.current.language.blank? + ::I18n.l(date.to_date, options) + end + + def format_time(time, include_date = true) + return nil unless time + options = {} + options[:format] = (Setting.time_format.blank? ? :time : Setting.time_format) + options[:locale] = User.current.language unless User.current.language.blank? + time = time.to_time if time.is_a?(String) + zone = User.current.time_zone + local = zone ? time.in_time_zone(zone) : (time.utc? ? time.localtime : time) + (include_date ? "#{format_date(local)} " : "") + ::I18n.l(local, options) + end + + def day_name(day) + ::I18n.t('date.day_names')[day % 7] + end + + def day_letter(day) + ::I18n.t('date.abbr_day_names')[day % 7].first + end + + def month_name(month) + ::I18n.t('date.month_names')[month] + end + + def valid_languages + ::I18n.available_locales + end + + # Returns an array of languages names and code sorted by names, example: + # [["Deutsch", "de"], ["English", "en"] ...] + # + # The result is cached to prevent from loading all translations files. + def languages_options + ActionController::Base.cache_store.fetch "i18n/languages_options" do + valid_languages.map {|lang| [ll(lang.to_s, :general_lang_name), lang.to_s]}.sort {|x,y| x.first <=> y.first } + end + end + + def find_language(lang) + @@languages_lookup = valid_languages.inject({}) {|k, v| k[v.to_s.downcase] = v; k } + @@languages_lookup[lang.to_s.downcase] + end + + def set_language_if_valid(lang) + if l = find_language(lang) + ::I18n.locale = l + end + end + + def current_language + ::I18n.locale + end + + # Custom backend based on I18n::Backend::Simple with the following changes: + # * lazy loading of translation files + # * available_locales are determined by looking at translation file names + class Backend + (class << self; self; end).class_eval { public :include } + + module Implementation + include ::I18n::Backend::Base + + # Stores translations for the given locale in memory. + # This uses a deep merge for the translations hash, so existing + # translations will be overwritten by new ones only at the deepest + # level of the hash. + def store_translations(locale, data, options = {}) + locale = locale.to_sym + translations[locale] ||= {} + data = data.deep_symbolize_keys + translations[locale].deep_merge!(data) + end + + # Get available locales from the translations filenames + def available_locales + @available_locales ||= ::I18n.load_path.map {|path| File.basename(path, '.*')}.uniq.sort.map(&:to_sym) + end + + # Clean up translations + def reload! + @translations = nil + @available_locales = nil + super + end + + protected + + def init_translations(locale) + locale = locale.to_s + paths = ::I18n.load_path.select {|path| File.basename(path, '.*') == locale} + load_translations(paths) + translations[locale] ||= {} + end + + def translations + @translations ||= {} + end + + # Looks up a translation from the translations hash. Returns nil if + # eiher key is nil, or locale, scope or key do not exist as a key in the + # nested translations hash. Splits keys or scopes containing dots + # into multiple keys, i.e. currency.format is regarded the same as + # %w(currency format). + def lookup(locale, key, scope = [], options = {}) + init_translations(locale) unless translations.key?(locale) + keys = ::I18n.normalize_keys(locale, key, scope, options[:separator]) + + keys.inject(translations) do |result, _key| + _key = _key.to_sym + return nil unless result.is_a?(Hash) && result.has_key?(_key) + result = result[_key] + result = resolve(locale, _key, result, options.merge(:scope => nil)) if result.is_a?(Symbol) + result + end + end + end + + include Implementation + # Adds fallback to default locale for untranslated strings + include ::I18n::Backend::Fallbacks + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/0a/0a371b310545389947180083feb53371bb868f67.svn-base --- a/.svn/pristine/0a/0a371b310545389947180083feb53371bb868f67.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,17 +0,0 @@ -module CodeRay -module Encoders - - map \ - :loc => :lines_of_code, - :plain => :text, - :plaintext => :text, - :remove_comments => :comment_filter, - :stats => :statistic, - :term => :terminal, - :tty => :terminal, - :yml => :yaml - - # No default because Tokens#nonsense should raise NoMethodError. - -end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/0a/0a62a3ae28a4f2df93a63c650d603c29ff087559.svn-base --- a/.svn/pristine/0a/0a62a3ae28a4f2df93a63c650d603c29ff087559.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,23 +0,0 @@ -<% form_tag({:action => 'edit', :tab => 'mail_handler'}) do %> - -
-

- <%= setting_text_area :mail_handler_body_delimiters, :rows => 5 %> -
<%= l(:text_line_separated) %> -

-
- -
-

<%= setting_check_box :mail_handler_api_enabled, - :onclick => "if (this.checked) { Form.Element.enable('settings_mail_handler_api_key'); } else { Form.Element.disable('settings_mail_handler_api_key'); }"%>

- -

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

-
- -<%= submit_tag l(:button_save) %> - -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/0a/0a884b128ca03268f26234e1c96b240bce5a6e1b.svn-base --- a/.svn/pristine/0a/0a884b128ca03268f26234e1c96b240bce5a6e1b.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,101 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) - -class EnumerationsControllerTest < ActionController::TestCase - fixtures :enumerations, :issues, :users - - def setup - @request.session[:user_id] = 1 # admin - end - - def test_index - get :index - assert_response :success - assert_template 'index' - end - - def test_new - get :new, :type => 'IssuePriority' - assert_response :success - assert_template 'new' - assert_kind_of IssuePriority, assigns(:enumeration) - end - - def test_create - assert_difference 'IssuePriority.count' do - post :create, :enumeration => {:type => 'IssuePriority', :name => 'Lowest'} - end - assert_redirected_to '/enumerations?type=IssuePriority' - e = IssuePriority.first(:order => 'id DESC') - assert_equal 'Lowest', e.name - end - - def test_create_with_failure - assert_no_difference 'IssuePriority.count' do - post :create, :enumeration => {:type => 'IssuePriority', :name => ''} - end - assert_response :success - assert_template 'new' - end - - def test_edit - get :edit, :id => 6 - assert_response :success - assert_template 'edit' - end - - def test_update - assert_no_difference 'IssuePriority.count' do - post :update, :id => 6, :enumeration => {:type => 'IssuePriority', :name => 'New name'} - end - assert_redirected_to '/enumerations?type=IssuePriority' - e = IssuePriority.find(6) - assert_equal 'New name', e.name - end - - def test_update_with_failure - assert_no_difference 'IssuePriority.count' do - post :update, :id => 6, :enumeration => {:type => 'IssuePriority', :name => ''} - end - assert_response :success - assert_template 'edit' - end - - def test_destroy_enumeration_not_in_use - post :destroy, :id => 7 - assert_redirected_to :controller => 'enumerations', :action => 'index' - assert_nil Enumeration.find_by_id(7) - end - - def test_destroy_enumeration_in_use - post :destroy, :id => 4 - assert_response :success - assert_template 'destroy' - assert_not_nil Enumeration.find_by_id(4) - end - - def test_destroy_enumeration_in_use_with_reassignment - issue = Issue.find(:first, :conditions => {:priority_id => 4}) - post :destroy, :id => 4, :reassign_to_id => 6 - assert_redirected_to :controller => 'enumerations', :action => 'index' - assert_nil Enumeration.find_by_id(4) - # check that the issue was reassign - assert_equal 6, issue.reload.priority_id - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/0a/0ab85a80b71d453bc34359193f9b3a077fde14bb.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0a/0ab85a80b71d453bc34359193f9b3a077fde14bb.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,279 @@ +module ActiveRecord + module Acts #:nodoc: + module List #:nodoc: + def self.included(base) + base.extend(ClassMethods) + end + + # This +acts_as+ extension provides the capabilities for sorting and reordering a number of objects in a list. + # The class that has this specified needs to have a +position+ column defined as an integer on + # the mapped database table. + # + # Todo list example: + # + # class TodoList < ActiveRecord::Base + # has_many :todo_items, :order => "position" + # end + # + # class TodoItem < ActiveRecord::Base + # belongs_to :todo_list + # acts_as_list :scope => :todo_list + # end + # + # todo_list.first.move_to_bottom + # todo_list.last.move_higher + module ClassMethods + # Configuration options are: + # + # * +column+ - specifies the column name to use for keeping the position integer (default: +position+) + # * +scope+ - restricts what is to be considered a list. Given a symbol, it'll attach _id + # (if it hasn't already been added) and use that as the foreign key restriction. It's also possible + # to give it an entire string that is interpolated if you need a tighter scope than just a foreign key. + # Example: acts_as_list :scope => 'todo_list_id = #{todo_list_id} AND completed = 0' + def acts_as_list(options = {}) + configuration = { :column => "position", :scope => "1 = 1" } + configuration.update(options) if options.is_a?(Hash) + + configuration[:scope] = "#{configuration[:scope]}_id".intern if configuration[:scope].is_a?(Symbol) && configuration[:scope].to_s !~ /_id$/ + + if configuration[:scope].is_a?(Symbol) + scope_condition_method = %( + def scope_condition + if #{configuration[:scope].to_s}.nil? + "#{configuration[:scope].to_s} IS NULL" + else + "#{configuration[:scope].to_s} = \#{#{configuration[:scope].to_s}}" + end + end + ) + else + scope_condition_method = "def scope_condition() \"#{configuration[:scope]}\" end" + end + + class_eval <<-EOV + include ActiveRecord::Acts::List::InstanceMethods + + def acts_as_list_class + ::#{self.name} + end + + def position_column + '#{configuration[:column]}' + end + + #{scope_condition_method} + + before_destroy :remove_from_list + before_create :add_to_list_bottom + EOV + end + end + + # All the methods available to a record that has had acts_as_list specified. Each method works + # by assuming the object to be the item in the list, so chapter.move_lower would move that chapter + # lower in the list of all chapters. Likewise, chapter.first? would return +true+ if that chapter is + # the first in the list of all chapters. + module InstanceMethods + # Insert the item at the given position (defaults to the top position of 1). + def insert_at(position = 1) + insert_at_position(position) + end + + # Swap positions with the next lower item, if one exists. + def move_lower + return unless lower_item + + acts_as_list_class.transaction do + lower_item.decrement_position + increment_position + end + end + + # Swap positions with the next higher item, if one exists. + def move_higher + return unless higher_item + + acts_as_list_class.transaction do + higher_item.increment_position + decrement_position + end + end + + # Move to the bottom of the list. If the item is already in the list, the items below it have their + # position adjusted accordingly. + def move_to_bottom + return unless in_list? + acts_as_list_class.transaction do + decrement_positions_on_lower_items + assume_bottom_position + end + end + + # Move to the top of the list. If the item is already in the list, the items above it have their + # position adjusted accordingly. + def move_to_top + return unless in_list? + acts_as_list_class.transaction do + increment_positions_on_higher_items + assume_top_position + end + end + + # Move to the given position + def move_to=(pos) + case pos.to_s + when 'highest' + move_to_top + when 'higher' + move_higher + when 'lower' + move_lower + when 'lowest' + move_to_bottom + end + reset_positions_in_list + end + + def reset_positions_in_list + acts_as_list_class.where(scope_condition).reorder("#{position_column} ASC, id ASC").each_with_index do |item, i| + unless item.send(position_column) == (i + 1) + acts_as_list_class.update_all({position_column => (i + 1)}, {:id => item.id}) + end + end + end + + # Removes the item from the list. + def remove_from_list + if in_list? + decrement_positions_on_lower_items + update_attribute position_column, nil + end + end + + # Increase the position of this item without adjusting the rest of the list. + def increment_position + return unless in_list? + update_attribute position_column, self.send(position_column).to_i + 1 + end + + # Decrease the position of this item without adjusting the rest of the list. + def decrement_position + return unless in_list? + update_attribute position_column, self.send(position_column).to_i - 1 + end + + # Return +true+ if this object is the first in the list. + def first? + return false unless in_list? + self.send(position_column) == 1 + end + + # Return +true+ if this object is the last in the list. + def last? + return false unless in_list? + self.send(position_column) == bottom_position_in_list + end + + # Return the next higher item in the list. + def higher_item + return nil unless in_list? + acts_as_list_class.where( + "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i - 1).to_s}" + ).first + end + + # Return the next lower item in the list. + def lower_item + return nil unless in_list? + acts_as_list_class.where( + "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i + 1).to_s}" + ).first + end + + # Test if this record is in a list + def in_list? + !send(position_column).nil? + end + + private + def add_to_list_top + increment_positions_on_all_items + end + + def add_to_list_bottom + self[position_column] = bottom_position_in_list.to_i + 1 + end + + # Overwrite this method to define the scope of the list changes + def scope_condition() "1" end + + # Returns the bottom position number in the list. + # bottom_position_in_list # => 2 + def bottom_position_in_list(except = nil) + item = bottom_item(except) + item ? item.send(position_column) : 0 + end + + # Returns the bottom item + def bottom_item(except = nil) + conditions = scope_condition + conditions = "#{conditions} AND #{self.class.primary_key} != #{except.id}" if except + acts_as_list_class.where(conditions).reorder("#{position_column} DESC").first + end + + # Forces item to assume the bottom position in the list. + def assume_bottom_position + update_attribute(position_column, bottom_position_in_list(self).to_i + 1) + end + + # Forces item to assume the top position in the list. + def assume_top_position + update_attribute(position_column, 1) + end + + # This has the effect of moving all the higher items up one. + def decrement_positions_on_higher_items(position) + acts_as_list_class.update_all( + "#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} <= #{position}" + ) + end + + # This has the effect of moving all the lower items up one. + def decrement_positions_on_lower_items + return unless in_list? + acts_as_list_class.update_all( + "#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} > #{send(position_column).to_i}" + ) + end + + # This has the effect of moving all the higher items down one. + def increment_positions_on_higher_items + return unless in_list? + acts_as_list_class.update_all( + "#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} < #{send(position_column).to_i}" + ) + end + + # This has the effect of moving all the lower items down one. + def increment_positions_on_lower_items(position) + acts_as_list_class.update_all( + "#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} >= #{position}" + ) + end + + # Increments position (position_column) of all items in the list. + def increment_positions_on_all_items + acts_as_list_class.update_all( + "#{position_column} = (#{position_column} + 1)", "#{scope_condition}" + ) + end + + def insert_at_position(position) + remove_from_list + increment_positions_on_lower_items(position) + self.update_attribute(position_column, position) + end + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/0b/0b7befce1d7322f245834ffc9480adf8d5c60890.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0b/0b7befce1d7322f245834ffc9480adf8d5c60890.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,294 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../test_case', __FILE__) +require 'tmpdir' + +class RedminePmTest::RepositorySubversionTest < RedminePmTest::TestCase + fixtures :projects, :users, :members, :roles, :member_roles, :auth_sources + + SVN_BIN = Redmine::Configuration['scm_subversion_command'] || "svn" + + def test_anonymous_read_on_public_repo_with_permission_should_succeed + assert_success "ls", svn_url + end + + def test_anonymous_read_on_public_repo_without_permission_should_fail + Role.anonymous.remove_permission! :browse_repository + assert_failure "ls", svn_url + end + + def test_anonymous_read_on_private_repo_should_fail + Project.find(1).update_attribute :is_public, false + assert_failure "ls", svn_url + end + + def test_anonymous_commit_on_public_repo_should_fail + Role.anonymous.add_permission! :commit_access + assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename) + end + + def test_anonymous_commit_on_private_repo_should_fail + Role.anonymous.add_permission! :commit_access + Project.find(1).update_attribute :is_public, false + assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename) + end + + def test_non_member_read_on_public_repo_with_permission_should_succeed + Role.anonymous.remove_permission! :browse_repository + with_credentials "miscuser8", "foo" do + assert_success "ls", svn_url + end + end + + def test_non_member_read_on_public_repo_without_permission_should_fail + Role.anonymous.remove_permission! :browse_repository + Role.non_member.remove_permission! :browse_repository + with_credentials "miscuser8", "foo" do + assert_failure "ls", svn_url + end + end + + def test_non_member_read_on_private_repo_should_fail + Project.find(1).update_attribute :is_public, false + with_credentials "miscuser8", "foo" do + assert_failure "ls", svn_url + end + end + + def test_non_member_commit_on_public_repo_should_fail + Role.non_member.add_permission! :commit_access + assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename) + end + + def test_non_member_commit_on_private_repo_should_fail + Role.non_member.add_permission! :commit_access + Project.find(1).update_attribute :is_public, false + assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename) + end + + def test_member_read_on_public_repo_with_permission_should_succeed + Role.anonymous.remove_permission! :browse_repository + Role.non_member.remove_permission! :browse_repository + with_credentials "dlopper", "foo" do + assert_success "ls", svn_url + end + end + + def test_member_read_on_public_repo_without_permission_should_fail + Role.anonymous.remove_permission! :browse_repository + Role.non_member.remove_permission! :browse_repository + Role.find(2).remove_permission! :browse_repository + with_credentials "dlopper", "foo" do + assert_failure "ls", svn_url + end + end + + def test_member_read_on_private_repo_with_permission_should_succeed + Project.find(1).update_attribute :is_public, false + with_credentials "dlopper", "foo" do + assert_success "ls", svn_url + end + end + + def test_member_read_on_private_repo_without_permission_should_fail + Role.find(2).remove_permission! :browse_repository + Project.find(1).update_attribute :is_public, false + with_credentials "dlopper", "foo" do + assert_failure "ls", svn_url + end + end + + def test_member_commit_on_public_repo_with_permission_should_succeed + Role.find(2).add_permission! :commit_access + with_credentials "dlopper", "foo" do + assert_success "mkdir --message Creating_a_directory", svn_url(random_filename) + end + end + + def test_member_commit_on_public_repo_without_permission_should_fail + Role.find(2).remove_permission! :commit_access + with_credentials "dlopper", "foo" do + assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename) + end + end + + def test_member_commit_on_private_repo_with_permission_should_succeed + Role.find(2).add_permission! :commit_access + Project.find(1).update_attribute :is_public, false + with_credentials "dlopper", "foo" do + assert_success "mkdir --message Creating_a_directory", svn_url(random_filename) + end + end + + def test_member_commit_on_private_repo_without_permission_should_fail + Role.find(2).remove_permission! :commit_access + Project.find(1).update_attribute :is_public, false + with_credentials "dlopper", "foo" do + assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename) + end + end + + def test_invalid_credentials_should_fail + Project.find(1).update_attribute :is_public, false + with_credentials "dlopper", "foo" do + assert_success "ls", svn_url + end + with_credentials "dlopper", "wrong" do + assert_failure "ls", svn_url + end + end + + def test_anonymous_read_should_fail_with_login_required + assert_success "ls", svn_url + with_settings :login_required => '1' do + assert_failure "ls", svn_url + end + end + + def test_authenticated_read_should_succeed_with_login_required + with_settings :login_required => '1' do + with_credentials "miscuser8", "foo" do + assert_success "ls", svn_url + end + end + end + + def test_read_on_archived_projects_should_fail + Project.find(1).update_attribute :status, Project::STATUS_ARCHIVED + assert_failure "ls", svn_url + end + + def test_read_on_archived_private_projects_should_fail + Project.find(1).update_attribute :status, Project::STATUS_ARCHIVED + Project.find(1).update_attribute :is_public, false + with_credentials "dlopper", "foo" do + assert_failure "ls", svn_url + end + end + + def test_read_on_closed_projects_should_succeed + Project.find(1).update_attribute :status, Project::STATUS_CLOSED + assert_success "ls", svn_url + end + + def test_read_on_closed_private_projects_should_succeed + Project.find(1).update_attribute :status, Project::STATUS_CLOSED + Project.find(1).update_attribute :is_public, false + with_credentials "dlopper", "foo" do + assert_success "ls", svn_url + end + end + + def test_commit_on_closed_projects_should_fail + Project.find(1).update_attribute :status, Project::STATUS_CLOSED + Role.find(2).add_permission! :commit_access + with_credentials "dlopper", "foo" do + assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename) + end + end + + def test_commit_on_closed_private_projects_should_fail + Project.find(1).update_attribute :status, Project::STATUS_CLOSED + Project.find(1).update_attribute :is_public, false + Role.find(2).add_permission! :commit_access + with_credentials "dlopper", "foo" do + assert_failure "mkdir --message Creating_a_directory", svn_url(random_filename) + end + end + + if ldap_configured? + def test_user_with_ldap_auth_source_should_authenticate_with_ldap_credentials + ldap_user = User.new(:mail => 'example1@redmine.org', :firstname => 'LDAP', :lastname => 'user', :auth_source_id => 1) + ldap_user.login = 'example1' + ldap_user.save! + + with_settings :login_required => '1' do + with_credentials "example1", "123456" do + assert_success "ls", svn_url + end + end + + with_settings :login_required => '1' do + with_credentials "example1", "wrong" do + assert_failure "ls", svn_url + end + end + end + end + + def test_checkout + Dir.mktmpdir do |dir| + assert_success "checkout", svn_url, dir + end + end + + def test_read_commands + assert_success "info", svn_url + assert_success "ls", svn_url + assert_success "log", svn_url + end + + def test_write_commands + Role.find(2).add_permission! :commit_access + filename = random_filename + + Dir.mktmpdir do |dir| + assert_success "checkout", svn_url, dir + Dir.chdir(dir) do + # creates a file in the working copy + f = File.new(File.join(dir, filename), "w") + f.write "test file content" + f.close + + assert_success "add", filename + with_credentials "dlopper", "foo" do + assert_success "commit --message Committing_a_file" + assert_success "copy --message Copying_a_file", svn_url(filename), svn_url("#{filename}_copy") + assert_success "delete --message Deleting_a_file", svn_url(filename) + assert_success "mkdir --message Creating_a_directory", svn_url("#{filename}_dir") + end + assert_success "update" + + # checks that the working copy was updated + assert File.exists?(File.join(dir, "#{filename}_copy")) + assert File.directory?(File.join(dir, "#{filename}_dir")) + end + end + end + + def test_read_invalid_repo_should_fail + assert_failure "ls", svn_url("invalid") + end + + protected + + def execute(*args) + a = [SVN_BIN, "--no-auth-cache --non-interactive"] + a << "--username #{username}" if username + a << "--password #{password}" if password + + super a, *args + end + + def svn_url(path=nil) + host = ENV['REDMINE_TEST_DAV_SERVER'] || '127.0.0.1' + url = "http://#{host}/svn/ecookbook" + url << "/#{path}" if path + url + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/0b/0b8467ea6271bd6bbf31748ec5f34d49b8671c8b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0b/0b8467ea6271bd6bbf31748ec5f34d49b8671c8b.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,23 @@ +/* Azerbaijani (UTF-8) initialisation for the jQuery UI date picker plugin. */ +/* Written by Jamil Najafov (necefov33@gmail.com). */ +jQuery(function($) { + $.datepicker.regional['az'] = { + closeText: 'Bağla', + prevText: '<Geri', + nextText: 'İrəli>', + currentText: 'Bugün', + monthNames: ['Yanvar','Fevral','Mart','Aprel','May','İyun', + 'İyul','Avqust','Sentyabr','Oktyabr','Noyabr','Dekabr'], + monthNamesShort: ['Yan','Fev','Mar','Apr','May','İyun', + 'İyul','Avq','Sen','Okt','Noy','Dek'], + dayNames: ['Bazar','Bazar ertəsi','Çərşənbə axşamı','Çərşənbə','Cümə axşamı','Cümə','Şənbə'], + dayNamesShort: ['B','Be','Ça','Ç','Ca','C','Ş'], + dayNamesMin: ['B','B','Ç','С','Ç','C','Ş'], + weekHeader: 'Hf', + dateFormat: 'dd.mm.yy', + firstDay: 1, + isRTL: false, + showMonthAfterYear: false, + yearSuffix: ''}; + $.datepicker.setDefaults($.datepicker.regional['az']); +}); diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/0b/0b8a142f46ed608799d7faf6016772f4b1f09f42.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0b/0b8a142f46ed608799d7faf6016772f4b1f09f42.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,47 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class RoutingAdminTest < ActionController::IntegrationTest + def test_administration_panel + assert_routing( + { :method => 'get', :path => "/admin" }, + { :controller => 'admin', :action => 'index' } + ) + assert_routing( + { :method => 'get', :path => "/admin/projects" }, + { :controller => 'admin', :action => 'projects' } + ) + assert_routing( + { :method => 'get', :path => "/admin/plugins" }, + { :controller => 'admin', :action => 'plugins' } + ) + assert_routing( + { :method => 'get', :path => "/admin/info" }, + { :controller => 'admin', :action => 'info' } + ) + assert_routing( + { :method => 'get', :path => "/admin/test_email" }, + { :controller => 'admin', :action => 'test_email' } + ) + assert_routing( + { :method => 'post', :path => "/admin/default_configuration" }, + { :controller => 'admin', :action => 'default_configuration' } + ) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/0b/0b97b483eed443a57933ffcafedb4eb971db5cf5.svn-base --- a/.svn/pristine/0b/0b97b483eed443a57933ffcafedb4eb971db5cf5.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,325 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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 ScmFetchError < Exception; end - -class Repository < ActiveRecord::Base - include Redmine::Ciphering - - belongs_to :project - has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC" - has_many :changes, :through => :changesets - - serialize :extra_info - - # Raw SQL to delete changesets and changes in the database - # has_many :changesets, :dependent => :destroy is too slow for big repositories - before_destroy :clear_changesets - - validates_length_of :password, :maximum => 255, :allow_nil => true - # Checks if the SCM is enabled when creating a repository - validate :repo_create_validation, :on => :create - - def repo_create_validation - unless Setting.enabled_scm.include?(self.class.name.demodulize) - errors.add(:type, :invalid) - end - end - - def self.human_attribute_name(attribute_key_name) - attr_name = attribute_key_name - if attr_name == "log_encoding" - attr_name = "commit_logs_encoding" - end - super(attr_name) - end - - # Removes leading and trailing whitespace - def url=(arg) - write_attribute(:url, arg ? arg.to_s.strip : nil) - end - - # Removes leading and trailing whitespace - def root_url=(arg) - write_attribute(:root_url, arg ? arg.to_s.strip : nil) - end - - def password - read_ciphered_attribute(:password) - end - - def password=(arg) - write_ciphered_attribute(:password, arg) - end - - def scm_adapter - self.class.scm_adapter_class - end - - def scm - @scm ||= self.scm_adapter.new(url, root_url, - login, password, path_encoding) - update_attribute(:root_url, @scm.root_url) if root_url.blank? - @scm - end - - def scm_name - self.class.scm_name - end - - def merge_extra_info(arg) - h = extra_info || {} - return h if arg.nil? - h.merge!(arg) - write_attribute(:extra_info, h) - end - - def report_last_commit - true - end - - def supports_cat? - scm.supports_cat? - end - - def supports_annotate? - scm.supports_annotate? - end - - def supports_all_revisions? - true - end - - def supports_directory_revisions? - false - end - - def supports_revision_graph? - false - end - - def entry(path=nil, identifier=nil) - scm.entry(path, identifier) - end - - def entries(path=nil, identifier=nil) - scm.entries(path, identifier) - end - - def branches - scm.branches - end - - def tags - scm.tags - end - - def default_branch - nil - end - - def properties(path, identifier=nil) - scm.properties(path, identifier) - end - - def cat(path, identifier=nil) - scm.cat(path, identifier) - end - - def diff(path, rev, rev_to) - scm.diff(path, rev, rev_to) - end - - def diff_format_revisions(cs, cs_to, sep=':') - text = "" - text << cs_to.format_identifier + sep if cs_to - text << cs.format_identifier if cs - text - end - - # Returns a path relative to the url of the repository - def relative_path(path) - path - end - - # Finds and returns a revision with a number or the beginning of a hash - def find_changeset_by_name(name) - return nil if name.blank? - changesets.find(:first, :conditions => (name.match(/^\d*$/) ? - ["revision = ?", name.to_s] : ["revision LIKE ?", name + '%'])) - end - - def latest_changeset - @latest_changeset ||= changesets.find(:first) - end - - # Returns the latest changesets for +path+ - # Default behaviour is to search in cached changesets - def latest_changesets(path, rev, limit=10) - if path.blank? - changesets.find( - :all, - :include => :user, - :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC", - :limit => limit) - else - changes.find( - :all, - :include => {:changeset => :user}, - :conditions => ["path = ?", path.with_leading_slash], - :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC", - :limit => limit - ).collect(&:changeset) - end - end - - def scan_changesets_for_issue_ids - 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 - @found_committer_users = 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) - unless committer.blank? - @found_committer_users ||= {} - return @found_committer_users[committer] if @found_committer_users.has_key?(committer) - - user = nil - c = changesets.find(:first, :conditions => {:committer => committer}, :include => :user) - if c && c.user - 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? - user = u - end - @found_committer_users[committer] = user - user - end - end - - def repo_log_encoding - encoding = log_encoding.to_s.strip - encoding.blank? ? 'UTF-8' : encoding - end - - # Fetches new changesets for all repositories of active projects - # Can be called periodically by an external script - # eg. ruby script/runner "Repository.fetch_changesets" - def self.fetch_changesets - Project.active.has_module(:repository).find(:all, :include => :repository).each do |project| - if project.repository - begin - project.repository.fetch_changesets - rescue Redmine::Scm::Adapters::CommandFailed => e - logger.error "scm: error during fetching changesets: #{e.message}" - end - end - end - end - - # scan changeset comments to find related and fixed issues for all repositories - def self.scan_changesets_for_issue_ids - find(:all).each(&:scan_changesets_for_issue_ids) - end - - def self.scm_name - 'Abstract' - end - - def self.available_scm - subclasses.collect {|klass| [klass.scm_name, klass.name]} - end - - def self.factory(klass_name, *args) - klass = "Repository::#{klass_name}".constantize - klass.new(*args) - rescue - nil - end - - def self.scm_adapter_class - nil - end - - def self.scm_command - ret = "" - begin - ret = self.scm_adapter_class.client_command if self.scm_adapter_class - rescue Exception => e - logger.error "scm: error during get command: #{e.message}" - end - ret - end - - def self.scm_version_string - ret = "" - begin - ret = self.scm_adapter_class.client_version_string if self.scm_adapter_class - rescue Exception => e - logger.error "scm: error during get version string: #{e.message}" - end - ret - end - - def self.scm_available - ret = false - begin - ret = self.scm_adapter_class.client_available if self.scm_adapter_class - rescue Exception => e - logger.error "scm: error during get scm available: #{e.message}" - end - ret - end - - private - - def clear_changesets - cs, ch, ci = Changeset.table_name, Change.table_name, "#{table_name_prefix}changesets_issues#{table_name_suffix}" - connection.delete("DELETE FROM #{ch} WHERE #{ch}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})") - connection.delete("DELETE FROM #{ci} WHERE #{ci}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})") - connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}") - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/0b/0bc8b19a332c3f8ffc74fbaa182a97b748ff7feb.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0b/0bc8b19a332c3f8ffc74fbaa182a97b748ff7feb.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,165 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class WatcherTest < ActiveSupport::TestCase + fixtures :projects, :users, :members, :member_roles, :roles, :enabled_modules, + :issues, :issue_statuses, :enumerations, :trackers, :projects_trackers, + :boards, :messages, + :wikis, :wiki_pages, + :watchers + + def setup + @user = User.find(1) + @issue = Issue.find(1) + end + + def test_validate + user = User.find(5) + assert !user.active? + watcher = Watcher.new(:user_id => user.id) + assert !watcher.save + end + + def test_watch + assert @issue.add_watcher(@user) + @issue.reload + assert @issue.watchers.detect { |w| w.user == @user } + end + + def test_cant_watch_twice + assert @issue.add_watcher(@user) + assert !@issue.add_watcher(@user) + end + + def test_watched_by + assert @issue.add_watcher(@user) + @issue.reload + assert @issue.watched_by?(@user) + assert Issue.watched_by(@user).include?(@issue) + end + + def test_watcher_users + watcher_users = Issue.find(2).watcher_users + assert_kind_of Array, watcher_users + assert_kind_of User, watcher_users.first + end + + def test_watcher_users_should_not_validate_user + User.update_all("firstname = ''", "id=1") + @user.reload + assert !@user.valid? + + issue = Issue.new(:project => Project.find(1), :tracker_id => 1, :subject => "test", :author => User.find(2)) + issue.watcher_users << @user + issue.save! + assert issue.watched_by?(@user) + end + + def test_watcher_user_ids + assert_equal [1, 3], Issue.find(2).watcher_user_ids.sort + end + + def test_watcher_user_ids= + issue = Issue.new + issue.watcher_user_ids = ['1', '3'] + assert issue.watched_by?(User.find(1)) + end + + def test_watcher_user_ids_should_make_ids_uniq + issue = Issue.new(:project => Project.find(1), :tracker_id => 1, :subject => "test", :author => User.find(2)) + issue.watcher_user_ids = ['1', '3', '1'] + issue.save! + assert_equal 2, issue.watchers.count + end + + def test_addable_watcher_users + addable_watcher_users = @issue.addable_watcher_users + assert_kind_of Array, addable_watcher_users + assert_kind_of User, addable_watcher_users.first + end + + def test_addable_watcher_users_should_not_include_user_that_cannot_view_the_object + issue = Issue.new(:project => Project.find(1), :is_private => true) + assert_nil issue.addable_watcher_users.detect {|user| !issue.visible?(user)} + end + + def test_recipients + @issue.watchers.delete_all + @issue.reload + + assert @issue.watcher_recipients.empty? + assert @issue.add_watcher(@user) + + @user.mail_notification = 'all' + @user.save! + @issue.reload + assert @issue.watcher_recipients.include?(@user.mail) + + @user.mail_notification = 'none' + @user.save! + @issue.reload + assert !@issue.watcher_recipients.include?(@user.mail) + end + + def test_unwatch + assert @issue.add_watcher(@user) + @issue.reload + assert_equal 1, @issue.remove_watcher(@user) + end + + def test_prune + Watcher.delete_all("user_id = 9") + user = User.find(9) + + # public + Watcher.create!(:watchable => Issue.find(1), :user => user) + Watcher.create!(:watchable => Issue.find(2), :user => user) + Watcher.create!(:watchable => Message.find(1), :user => user) + Watcher.create!(:watchable => Wiki.find(1), :user => user) + Watcher.create!(:watchable => WikiPage.find(2), :user => user) + + # private project (id: 2) + Member.create!(:project => Project.find(2), :principal => user, :role_ids => [1]) + Watcher.create!(:watchable => Issue.find(4), :user => user) + Watcher.create!(:watchable => Message.find(7), :user => user) + Watcher.create!(:watchable => Wiki.find(2), :user => user) + Watcher.create!(:watchable => WikiPage.find(3), :user => user) + + assert_no_difference 'Watcher.count' do + Watcher.prune(:user => User.find(9)) + end + + Member.delete_all + + assert_difference 'Watcher.count', -4 do + Watcher.prune(:user => User.find(9)) + end + + assert Issue.find(1).watched_by?(user) + assert !Issue.find(4).watched_by?(user) + end + + def test_prune_all + user = User.find(9) + Watcher.new(:watchable => Issue.find(4), :user => User.find(9)).save(:validate => false) + + assert Watcher.prune > 0 + assert !Issue.find(4).watched_by?(user) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/0b/0beba908a316302bd0ca691f7555fdce76127696.svn-base --- a/.svn/pristine/0b/0beba908a316302bd0ca691f7555fdce76127696.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,129 +0,0 @@ ---- -wiki_contents_001: - text: |- - h1. CookBook documentation - - {{child_pages}} - - Some updated [[documentation]] here with gzipped history - updated_on: 2007-03-07 00:10:51 +01:00 - page_id: 1 - id: 1 - version: 3 - author_id: 1 - comments: Gzip compression activated -wiki_contents_002: - text: |- - h1. Another page - - This is a link to a ticket: #2 - And this is an included page: - {{include(Page with an inline image)}} - updated_on: 2007-03-08 00:18:07 +01:00 - page_id: 2 - id: 2 - version: 1 - author_id: 1 - comments: -wiki_contents_003: - text: |- - h1. Start page - - E-commerce web site start page - updated_on: 2007-03-08 00:18:07 +01:00 - page_id: 3 - id: 3 - version: 1 - author_id: 1 - comments: -wiki_contents_004: - text: |- - h1. Page with an inline image - - This is an inline image: - - !logo.gif! - updated_on: 2007-03-08 00:18:07 +01:00 - page_id: 4 - id: 4 - 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: -wiki_contents_007: - text: This is a child page - updated_on: 2007-03-08 00:18:07 +01:00 - page_id: 7 - id: 7 - version: 1 - author_id: 1 - comments: -wiki_contents_008: - text: This is a parent page - updated_on: 2007-03-08 00:18:07 +01:00 - page_id: 8 - id: 8 - version: 1 - author_id: 1 - comments: -wiki_contents_009: - text: This is a child page - updated_on: 2007-03-08 00:18:07 +01:00 - page_id: 9 - id: 9 - version: 1 - author_id: 1 - comments: -wiki_contents_010: - text: Page with cyrillic title - updated_on: 2007-03-08 00:18:07 +01:00 - page_id: 10 - id: 10 - version: 1 - author_id: 1 - comments: -wiki_contents_011: - text: |- - h1. Title - - Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero. - - h2. Heading 1 - - @WHATEVER@ - - Maecenas sed elit sit amet mi accumsan vestibulum non nec velit. Proin porta tincidunt lorem, consequat rhoncus dolor fermentum in. - - Cras ipsum felis, ultrices at porttitor vel, faucibus eu nunc. - - h2. Heading 2 - - Morbi facilisis accumsan orci non pharetra. - updated_on: 2007-03-08 00:18:07 +01:00 - page_id: 11 - id: 11 - version: 3 - author_id: 1 - comments: - diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/0b/0bfbd6508bed6a33543df0df9a316455bb2e521a.svn-base --- a/.svn/pristine/0b/0bfbd6508bed6a33543df0df9a316455bb2e521a.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,24 +0,0 @@ -

<%=h @attachment.filename %>

- -
-

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

-

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

-
-

-<% form_tag({}, :method => 'get') do %> - - <%= select_tag 'type', - options_for_select( - [[l(:label_diff_inline), "inline"], [l(:label_diff_side_by_side), "sbs"]], @diff_type), - :onchange => "if (this.value != '') {this.form.submit()}" %> -<% end %> -

-<%= render :partial => 'common/diff', :locals => {:diff => @diff, :diff_type => @diff_type} %> - -<% html_title @attachment.filename %> - -<% content_for :header_tags do -%> - <%= stylesheet_link_tag "scm" -%> -<% end -%> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/0c/0c07ac7fadf7713b8982de217779de9a2084825c.svn-base --- a/.svn/pristine/0c/0c07ac7fadf7713b8982de217779de9a2084825c.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,70 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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 Group < Principal - has_and_belongs_to_many :users, :after_add => :user_added, - :after_remove => :user_removed - - acts_as_customizable - - validates_presence_of :lastname - validates_uniqueness_of :lastname, :case_sensitive => false - validates_length_of :lastname, :maximum => 30 - - before_destroy :remove_references_before_destroy - - def to_s - lastname.to_s - end - - alias :name :to_s - - def user_added(user) - members.each do |member| - next if member.project.nil? - user_member = Member.find_by_project_id_and_user_id(member.project_id, user.id) || Member.new(:project_id => member.project_id, :user_id => user.id) - member.member_roles.each do |member_role| - user_member.member_roles << MemberRole.new(:role => member_role.role, :inherited_from => member_role.id) - end - user_member.save! - end - end - - def user_removed(user) - members.each do |member| - MemberRole.find(:all, :include => :member, - :conditions => ["#{Member.table_name}.user_id = ? AND #{MemberRole.table_name}.inherited_from IN (?)", user.id, member.member_role_ids]).each(&:destroy) - end - end - - def self.human_attribute_name(attribute_key_name) - attr_name = attribute_key_name - if attr_name == 'lastname' - attr_name = "name" - end - super(attr_name) - end - - private - - # Removes references that are not handled by associations - def remove_references_before_destroy - return if self.id.nil? - - Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id] - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/0c/0c1341a53592418e4a2dd8783234ed2bf47a5fde.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0c/0c1341a53592418e4a2dd8783234ed2bf47a5fde.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,1 @@ +$('#principals_for_new_member').html('<%= escape_javascript(render_principals_for_new_members(@project)) %>'); diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/0c/0c3d44b905a542240e7c616a63f74580e8cbbb9d.svn-base --- a/.svn/pristine/0c/0c3d44b905a542240e7c616a63f74580e8cbbb9d.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,67 +0,0 @@ -<% diff = Redmine::UnifiedDiff.new( - diff, :type => diff_type, - :max_lines => Setting.diff_max_lines_displayed.to_i) -%> - -<% diff.each do |table_file| -%> -
-<% if diff.diff_type == 'sbs' -%> - - - - - - - -<% table_file.each_line do |spacing, line| -%> -<% if spacing -%> - - - -<% end -%> - - - - - - -<% end -%> - -
- <%= h(Redmine::CodesetUtil.to_utf8_by_setting(table_file.file_name)) %> -
......
<%= line.nb_line_left %> -
<%= Redmine::CodesetUtil.to_utf8_by_setting(line.html_line_left) %>
-
<%= line.nb_line_right %> -
<%= Redmine::CodesetUtil.to_utf8_by_setting(line.html_line_right) %>
-
- -<% else -%> - - - - - - - -<% table_file.each_line do |spacing, line| %> -<% if spacing -%> - - - -<% end -%> - - - - - -<% end -%> - -
- <%= h(Redmine::CodesetUtil.to_utf8_by_setting(table_file.file_name)) %> -
......
<%= line.nb_line_left %><%= line.nb_line_right %> -
<%= Redmine::CodesetUtil.to_utf8_by_setting(line.html_line) %>
-
-<% end -%> -
-<% end -%> - -<%= l(:text_diff_truncated) if diff.truncated? %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/0c/0c683f18d0752b5b1c900f3d3bf34e0652a0c13f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0c/0c683f18d0752b5b1c900f3d3bf34e0652a0c13f.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,290 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class Version < ActiveRecord::Base + include Redmine::SafeAttributes + after_update :update_issues_from_sharing_change + belongs_to :project + has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id', :dependent => :nullify + acts_as_customizable + acts_as_attachable :view_permission => :view_files, + :delete_permission => :manage_files + + VERSION_STATUSES = %w(open locked closed) + VERSION_SHARINGS = %w(none descendants hierarchy tree system) + + validates_presence_of :name + validates_uniqueness_of :name, :scope => [:project_id] + validates_length_of :name, :maximum => 60 + validates :effective_date, :date => true + validates_inclusion_of :status, :in => VERSION_STATUSES + validates_inclusion_of :sharing, :in => VERSION_SHARINGS + + scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)} + scope :open, lambda { where(:status => 'open') } + scope :visible, lambda {|*args| + includes(:project).where(Project.allowed_to_condition(args.first || User.current, :view_issues)) + } + + safe_attributes 'name', + 'description', + 'effective_date', + 'due_date', + 'wiki_page_title', + 'status', + 'sharing', + 'custom_field_values', + 'custom_fields' + + # Returns true if +user+ or current user is allowed to view the version + def visible?(user=User.current) + user.allowed_to?(:view_issues, self.project) + end + + # Version files have same visibility as project files + def attachments_visible?(*args) + project.present? && project.attachments_visible?(*args) + end + + def start_date + @start_date ||= fixed_issues.minimum('start_date') + end + + def due_date + effective_date + end + + def due_date=(arg) + self.effective_date=(arg) + end + + # Returns the total estimated time for this version + # (sum of leaves estimated_hours) + def estimated_hours + @estimated_hours ||= fixed_issues.leaves.sum(:estimated_hours).to_f + end + + # Returns the total reported time for this version + def spent_hours + @spent_hours ||= TimeEntry.joins(:issue).where("#{Issue.table_name}.fixed_version_id = ?", id).sum(:hours).to_f + end + + def closed? + status == 'closed' + end + + def open? + status == 'open' + end + + # Returns true if the version is completed: due date reached and no open issues + def completed? + effective_date && (effective_date < Date.today) && (open_issues_count == 0) + end + + def behind_schedule? + if completed_percent == 100 + return false + elsif due_date && start_date + done_date = start_date + ((due_date - start_date+1)* completed_percent/100).floor + return done_date <= Date.today + else + false # No issues so it's not late + end + end + + # Returns the completion percentage of this version based on the amount of open/closed issues + # and the time spent on the open issues. + def completed_percent + if issues_count == 0 + 0 + elsif open_issues_count == 0 + 100 + else + issues_progress(false) + issues_progress(true) + end + end + + # TODO: remove in Redmine 3.0 + def completed_pourcent + ActiveSupport::Deprecation.warn "Version#completed_pourcent is deprecated and will be removed in Redmine 3.0. Please use #completed_percent instead." + completed_percent + end + + # Returns the percentage of issues that have been marked as 'closed'. + def closed_percent + if issues_count == 0 + 0 + else + issues_progress(false) + end + end + + # TODO: remove in Redmine 3.0 + def closed_pourcent + ActiveSupport::Deprecation.warn "Version#closed_pourcent is deprecated and will be removed in Redmine 3.0. Please use #closed_percent instead." + closed_percent + end + + # Returns true if the version is overdue: due date reached and some open issues + def overdue? + effective_date && (effective_date < Date.today) && (open_issues_count > 0) + end + + # Returns assigned issues count + def issues_count + load_issue_counts + @issue_count + end + + # Returns the total amount of open issues for this version. + def open_issues_count + load_issue_counts + @open_issues_count + end + + # Returns the total amount of closed issues for this version. + def closed_issues_count + load_issue_counts + @closed_issues_count + end + + def wiki_page + if project.wiki && !wiki_page_title.blank? + @wiki_page ||= project.wiki.find_page(wiki_page_title) + end + @wiki_page + end + + def to_s; name end + + def to_s_with_project + "#{project} - #{name}" + end + + # Versions are sorted by effective_date and name + # Those with no effective_date are at the end, sorted by name + def <=>(version) + if self.effective_date + if version.effective_date + if self.effective_date == version.effective_date + name == version.name ? id <=> version.id : name <=> version.name + else + self.effective_date <=> version.effective_date + end + else + -1 + end + else + if version.effective_date + 1 + else + name == version.name ? id <=> version.id : name <=> version.name + end + end + end + + def self.fields_for_order_statement(table=nil) + table ||= table_name + ["(CASE WHEN #{table}.effective_date IS NULL THEN 1 ELSE 0 END)", "#{table}.effective_date", "#{table}.name", "#{table}.id"] + end + + scope :sorted, order(fields_for_order_statement) + + # Returns the sharings that +user+ can set the version to + def allowed_sharings(user = User.current) + VERSION_SHARINGS.select do |s| + if sharing == s + true + else + case s + when 'system' + # Only admin users can set a systemwide sharing + user.admin? + when 'hierarchy', 'tree' + # Only users allowed to manage versions of the root project can + # set sharing to hierarchy or tree + project.nil? || user.allowed_to?(:manage_versions, project.root) + else + true + end + end + end + end + + private + + def load_issue_counts + unless @issue_count + @open_issues_count = 0 + @closed_issues_count = 0 + fixed_issues.count(:all, :group => :status).each do |status, count| + if status.is_closed? + @closed_issues_count += count + else + @open_issues_count += count + end + end + @issue_count = @open_issues_count + @closed_issues_count + end + end + + # Update the issue's fixed versions. Used if a version's sharing changes. + def update_issues_from_sharing_change + if sharing_changed? + if VERSION_SHARINGS.index(sharing_was).nil? || + VERSION_SHARINGS.index(sharing).nil? || + VERSION_SHARINGS.index(sharing_was) > VERSION_SHARINGS.index(sharing) + Issue.update_versions_from_sharing_change self + end + end + 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. The returned percentage takes into account + # the amount of estimated time set for this version. + # + # Examples: + # issues_progress(true) => returns the progress percentage for open issues. + # issues_progress(false) => returns the progress percentage for 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.open(open).sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}").to_f + progress = done / (estimated_average * issues_count) + end + progress + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/0c/0ca7bda81025c5f7a9ebad14d8b0c08f3a0b8176.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0c/0ca7bda81025c5f7a9ebad14d8b0c08f3a0b8176.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,35 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class RoutingSysTest < ActionController::IntegrationTest + def test_sys + assert_routing( + { :method => 'get', :path => "/sys/projects" }, + { :controller => 'sys', :action => 'projects' } + ) + assert_routing( + { :method => 'post', :path => "/sys/projects/testid/repository" }, + { :controller => 'sys', :action => 'create_project_repository', :id => 'testid' } + ) + assert_routing( + { :method => 'get', :path => "/sys/fetch_changesets" }, + { :controller => 'sys', :action => 'fetch_changesets' } + ) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/0c/0cc9601ec1a6e3a27ab4a946d394cf4ba82254b4.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0c/0cc9601ec1a6e3a27ab4a946d394cf4ba82254b4.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,22 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 TimeEntryActivityCustomField < CustomField + def type_name + :enumeration_activities + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/0c/0cd8ee527e2f2ff13b3817238dc6eed2ab240c57.svn-base --- a/.svn/pristine/0c/0cd8ee527e2f2ff13b3817238dc6eed2ab240c57.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -class <%= class_name %>Controller < ApplicationController - unloadable - -<% actions.each do |action| -%> - - def <%= action %> - end -<% end -%> -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/0d/0d106c8ee2a99b8daf9a6d87887284bdd099fa19.svn-base --- a/.svn/pristine/0d/0d106c8ee2a99b8daf9a6d87887284bdd099fa19.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) - -class SettingTest < ActiveSupport::TestCase - - def test_read_default - assert_equal "Redmine", Setting.app_title - assert Setting.self_registration? - assert !Setting.login_required? - end - - def test_update - Setting.app_title = "My title" - assert_equal "My title", Setting.app_title - # make sure db has been updated (INSERT) - assert_equal "My title", Setting.find_by_name('app_title').value - - Setting.app_title = "My other title" - assert_equal "My other title", Setting.app_title - # make sure db has been updated (UPDATE) - assert_equal "My other title", Setting.find_by_name('app_title').value - end - - def test_serialized_setting - Setting.notified_events = ['issue_added', 'issue_updated', 'news_added'] - assert_equal ['issue_added', 'issue_updated', 'news_added'], Setting.notified_events - assert_equal ['issue_added', 'issue_updated', 'news_added'], Setting.find_by_name('notified_events').value - end - - def test_setting_should_be_reloaded_after_clear_cache - Setting.app_title = "My title" - assert_equal "My title", Setting.app_title - - s = Setting.find_by_name("app_title") - s.value = 'New title' - s.save! - assert_equal "My title", Setting.app_title - - Setting.clear_cache - assert_equal "New title", Setting.app_title - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/0d/0d50a39088b6bd107e54b315b0bf77a7ffa63596.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0d/0d50a39088b6bd107e54b315b0bf77a7ffa63596.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,110 @@ +### From http://svn.geekdaily.org/public/rails/plugins/generally_useful/tasks/coverage_via_rcov.rake + +namespace :test do + desc 'Measures test coverage' + task :coverage do + rm_f "coverage" + rm_f "coverage.data" + rcov = "rcov --rails --aggregate coverage.data --text-summary -Ilib --html --exclude gems/" + files = %w(unit functional integration).map {|dir| Dir.glob("test/#{dir}/**/*_test.rb")}.flatten.join(" ") + system("#{rcov} #{files}") + end + + desc 'Run unit and functional scm tests' + task :scm do + errors = %w(test:scm:units test:scm:functionals).collect do |task| + begin + Rake::Task[task].invoke + nil + rescue => e + task + end + end.compact + abort "Errors running #{errors.to_sentence(:locale => :en)}!" if errors.any? + end + + 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" + unless File.exists?(repo_path) + system "svnadmin create #{repo_path}" + system "gunzip < test/fixtures/repositories/subversion_repository.dump.gz | svnadmin load #{repo_path}" + end + end + + desc "Creates a test mercurial repository" + task :mercurial => :create_dir do + repo_path = "tmp/test/mercurial_repository" + unless File.exists?(repo_path) + bundle_path = "test/fixtures/repositories/mercurial_repository.hg" + system "hg init #{repo_path}" + system "hg -R #{repo_path} pull #{bundle_path}" + end + end + + (supported_scms - [:subversion, :mercurial]).each do |scm| + desc "Creates a test #{scm} repository" + task scm => :create_dir do + unless File.exists?("tmp/test/#{scm}_repository") + # system "gunzip < test/fixtures/repositories/#{scm}_repository.tar.gz | tar -xv -C tmp/test" + system "tar -xvz -C tmp/test -f test/fixtures/repositories/#{scm}_repository.tar.gz" + end + end + end + + desc "Creates all test repositories" + task :all => supported_scms + end + + desc "Updates installed test repositories" + task :update do + require 'fileutils' + Dir.glob("tmp/test/*_repository").each do |dir| + next unless File.basename(dir) =~ %r{^(.+)_repository$} && File.directory?(dir) + scm = $1 + next unless fixture = Dir.glob("test/fixtures/repositories/#{scm}_repository.*").first + next if File.stat(dir).ctime > File.stat(fixture).mtime + + FileUtils.rm_rf dir + Rake::Task["test:scm:setup:#{scm}"].execute + end + end + + Rake::TestTask.new(:units => "db:test:prepare") do |t| + t.libs << "test" + t.verbose = true + t.test_files = FileList['test/unit/repository*_test.rb'] + FileList['test/unit/lib/redmine/scm/**/*_test.rb'] + end + Rake::Task['test:scm:units'].comment = "Run the scm unit tests" + + Rake::TestTask.new(:functionals => "db:test:prepare") do |t| + t.libs << "test" + t.verbose = true + t.test_files = FileList['test/functional/repositories*_test.rb'] + end + Rake::Task['test:scm:functionals'].comment = "Run the scm functional tests" + end + + Rake::TestTask.new(:rdm_routing) do |t| + t.libs << "test" + t.verbose = true + t.test_files = FileList['test/integration/routing/*_test.rb'] + end + Rake::Task['test:rdm_routing'].comment = "Run the routing tests" + + Rake::TestTask.new(:ui => "db:test:prepare") do |t| + t.libs << "test" + t.verbose = true + t.test_files = FileList['test/ui/**/*_test.rb'] + end + Rake::Task['test:ui'].comment = "Run the UI tests with Capybara (PhantomJS listening on port 4444 is required)" +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/0d/0d581e597497e894acf26342b5cf129e84f325db.svn-base --- a/.svn/pristine/0d/0d581e597497e894acf26342b5cf129e84f325db.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,15 +0,0 @@ -module Redmine - module Info - class << self - def app_name; 'Redmine' end - def url; 'http://www.redmine.org/' end - def help_url; 'http://www.redmine.org/guide' end - def versioned_name; "#{app_name} #{Redmine::VERSION}" end - - # Creates the url string to a specific Redmine issue - def issue(issue_id) - url + 'issues/' + issue_id.to_s - end - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/0d/0d6663388ad2e1df81d9f7719fa0c144a5b76d99.svn-base --- a/.svn/pristine/0d/0d6663388ad2e1df81d9f7719fa0c144a5b76d99.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +0,0 @@ -#!/usr/bin/env ruby -require File.expand_path('../../config/boot', __FILE__) -require 'commands/runner' diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/0d/0da01fc24e804fb2e15e93cbb542b0add78e1cbc.svn-base --- a/.svn/pristine/0d/0da01fc24e804fb2e15e93cbb542b0add78e1cbc.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,13 +0,0 @@ -<%= "#{issue.tracker.name} ##{issue.id}: #{issue.subject}" %> -<%= issue_url %> - -<%=l(:field_author)%>: <%= issue.author %> -<%=l(:field_status)%>: <%= issue.status %> -<%=l(:field_priority)%>: <%= issue.priority %> -<%=l(:field_assigned_to)%>: <%= issue.assigned_to %> -<%=l(:field_category)%>: <%= issue.category %> -<%=l(:field_fixed_version)%>: <%= issue.fixed_version %> -<% issue.custom_field_values.each do |c| %><%= c.custom_field.name %>: <%= show_value(c) %> -<% end %> - -<%= issue.description %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/0d/0dc6f2999aebfb473b433b9a22bddd8917c60616.svn-base --- a/.svn/pristine/0d/0dc6f2999aebfb473b433b9a22bddd8917c60616.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,39 +0,0 @@ -# $Id: ldif.rb 78 2006-04-26 02:57:34Z blackhedd $ -# -# Net::LDIF for Ruby -# -# -# -# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved. -# -# Gmail: garbagecat10 -# -# 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 St, Fifth Floor, Boston, MA 02110-1301 USA -# -# - -# THIS FILE IS A STUB. - -module Net - - class LDIF - - - end # class LDIF - - -end # module Net - - diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/0d/0de0220c6abf526fd90b0775dd5b7d3c897e479b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0d/0de0220c6abf526fd90b0775dd5b7d3c897e479b.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,10 @@ +class AddTrackerPosition < ActiveRecord::Migration + def self.up + add_column :trackers, :position, :integer, :default => 1 + Tracker.all.each_with_index {|tracker, i| tracker.update_attribute(:position, i+1)} + end + + def self.down + remove_column :trackers, :position + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/0e/0e9c8f11b3db41702079f085c94dfa01133abcd9.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0e/0e9c8f11b3db41702079f085c94dfa01133abcd9.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,128 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../../../test_helper', __FILE__) + +class PdfTest < ActiveSupport::TestCase + fixtures :users, :projects, :roles, :members, :member_roles, + :enabled_modules, :issues, :trackers, :attachments + + def test_fix_text_encoding_nil + assert_equal '', Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(nil, "UTF-8") + assert_equal '', Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(nil, "ISO-8859-1") + end + + def test_rdm_pdf_iconv_cannot_convert_ja_cp932 + encoding = ( RUBY_PLATFORM == 'java' ? "SJIS" : "CP932" ) + utf8_txt_1 = "\xe7\x8b\x80\xe6\x85\x8b" + utf8_txt_2 = "\xe7\x8b\x80\xe6\x85\x8b\xe7\x8b\x80" + utf8_txt_3 = "\xe7\x8b\x80\xe7\x8b\x80\xe6\x85\x8b\xe7\x8b\x80" + if utf8_txt_1.respond_to?(:force_encoding) + txt_1 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_1, encoding) + txt_2 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_2, encoding) + txt_3 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_3, encoding) + assert_equal "?\x91\xd4".force_encoding("ASCII-8BIT"), txt_1 + assert_equal "?\x91\xd4?".force_encoding("ASCII-8BIT"), txt_2 + assert_equal "??\x91\xd4?".force_encoding("ASCII-8BIT"), txt_3 + assert_equal "ASCII-8BIT", txt_1.encoding.to_s + assert_equal "ASCII-8BIT", txt_2.encoding.to_s + assert_equal "ASCII-8BIT", txt_3.encoding.to_s + elsif RUBY_PLATFORM == 'java' + assert_equal "??", + Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_1, encoding) + assert_equal "???", + Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_2, encoding) + assert_equal "????", + Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_3, encoding) + else + assert_equal "???\x91\xd4", + Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_1, encoding) + assert_equal "???\x91\xd4???", + Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_2, encoding) + assert_equal "??????\x91\xd4???", + Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_3, encoding) + end + end + + def test_rdm_pdf_iconv_invalid_utf8_should_be_replaced_en + str1 = "Texte encod\xe9 en ISO-8859-1" + str2 = "\xe9a\xe9b\xe9c\xe9d\xe9e test" + str1.force_encoding("UTF-8") if str1.respond_to?(:force_encoding) + str2.force_encoding("ASCII-8BIT") if str2.respond_to?(:force_encoding) + txt_1 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(str1, 'UTF-8') + txt_2 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(str2, 'UTF-8') + if txt_1.respond_to?(:force_encoding) + assert_equal "ASCII-8BIT", txt_1.encoding.to_s + assert_equal "ASCII-8BIT", txt_2.encoding.to_s + end + assert_equal "Texte encod? en ISO-8859-1", txt_1 + assert_equal "?a?b?c?d?e test", txt_2 + end + + def test_rdm_pdf_iconv_invalid_utf8_should_be_replaced_ja + str1 = "Texte encod\xe9 en ISO-8859-1" + str2 = "\xe9a\xe9b\xe9c\xe9d\xe9e test" + str1.force_encoding("UTF-8") if str1.respond_to?(:force_encoding) + str2.force_encoding("ASCII-8BIT") if str2.respond_to?(:force_encoding) + encoding = ( RUBY_PLATFORM == 'java' ? "SJIS" : "CP932" ) + txt_1 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(str1, encoding) + txt_2 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(str2, encoding) + if txt_1.respond_to?(:force_encoding) + assert_equal "ASCII-8BIT", txt_1.encoding.to_s + assert_equal "ASCII-8BIT", txt_2.encoding.to_s + end + assert_equal "Texte encod? en ISO-8859-1", txt_1 + assert_equal "?a?b?c?d?e test", txt_2 + end + + def test_attach + set_fixtures_attachments_directory + + str2 = "\x83e\x83X\x83g" + str2.force_encoding("ASCII-8BIT") if str2.respond_to?(:force_encoding) + encoding = ( RUBY_PLATFORM == 'java' ? "SJIS" : "CP932" ) + + a1 = Attachment.find(17) + a2 = Attachment.find(19) + + User.current = User.find(1) + assert a1.readable? + assert a1.visible? + assert a2.readable? + assert a2.visible? + + aa1 = Redmine::Export::PDF::RDMPdfEncoding::attach(Attachment.all, "Testfile.PNG", "UTF-8") + assert_not_nil aa1 + assert_equal 17, aa1.id + aa2 = Redmine::Export::PDF::RDMPdfEncoding::attach(Attachment.all, "test#{str2}.png", encoding) + assert_not_nil aa2 + assert_equal 19, aa2.id + + User.current = nil + assert a1.readable? + assert (! a1.visible?) + assert a2.readable? + assert (! a2.visible?) + + aa1 = Redmine::Export::PDF::RDMPdfEncoding::attach(Attachment.all, "Testfile.PNG", "UTF-8") + assert_equal nil, aa1 + aa2 = Redmine::Export::PDF::RDMPdfEncoding::attach(Attachment.all, "test#{str2}.png", encoding) + assert_equal nil, aa2 + + set_tmp_attachments_directory + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/0e/0e9e1072226f562ac691c9b0e43850580a4d42a6.svn-base --- a/.svn/pristine/0e/0e9e1072226f562ac691c9b0e43850580a4d42a6.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,26 +0,0 @@ -

<%= l(:label_spent_time) %>

- -<% labelled_tabular_form_for(:time_entry, @time_entry, :url => { - :action => (@time_entry.new_record? ? 'create' : 'update'), - :id => @time_entry, - :project_id => @time_entry.project - }, - :html => {:method => @time_entry.new_record? ? :post : :put}) do |f| %> -<%= error_messages_for 'time_entry' %> -<%= back_url_hidden_field_tag %> - -
-

<%= f.text_field :issue_id, :size => 6 %> <%= h("#{@time_entry.issue.tracker.name} ##{@time_entry.issue.id}: #{@time_entry.issue.subject}") if @time_entry.issue %>

-

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

-

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

-

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

-

<%= f.select :activity_id, activity_collection_for_select_options(@time_entry), :required => true %>

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

<%= custom_field_tag_with_label :time_entry, value %>

-<% end %> -<%= call_hook(:view_timelog_edit_form_bottom, { :time_entry => @time_entry, :form => f }) %> -
- -<%= submit_tag l(:button_save) %> - -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/0e/0eb716c2e3251d0a363271012989e30efeb34f17.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0e/0eb716c2e3251d0a363271012989e30efeb34f17.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,3 @@ +<%= form_tag(signout_path) do %> +

<%= submit_tag l(:label_logout) %>

+<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/0e/0ee15adcf36536c48b6695748041d2c5e43cb26a.svn-base --- a/.svn/pristine/0e/0ee15adcf36536c48b6695748041d2c5e43cb26a.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,255 +0,0 @@ -module CodeRay -module Scanners - - load :java - - # Scanner for Groovy. - class Groovy < Java - - register_for :groovy - - # TODO: check list of keywords - GROOVY_KEYWORDS = %w[ - as assert def in - ] # :nodoc: - KEYWORDS_EXPECTING_VALUE = WordList.new.add %w[ - case instanceof new return throw typeof while as assert in - ] # :nodoc: - GROOVY_MAGIC_VARIABLES = %w[ it ] # :nodoc: - - IDENT_KIND = Java::IDENT_KIND.dup. - add(GROOVY_KEYWORDS, :keyword). - add(GROOVY_MAGIC_VARIABLES, :local_variable) # :nodoc: - - ESCAPE = / [bfnrtv$\n\\'"] | x[a-fA-F0-9]{1,2} | [0-7]{1,3} /x # :nodoc: - UNICODE_ESCAPE = / u[a-fA-F0-9]{4} /x # :nodoc: no 4-byte unicode chars? U[a-fA-F0-9]{8} - REGEXP_ESCAPE = / [bfnrtv\n\\'"] | x[a-fA-F0-9]{1,2} | [0-7]{1,3} | \d | [bBdDsSwW\/] /x # :nodoc: - - # TODO: interpretation inside ', ", / - STRING_CONTENT_PATTERN = { - "'" => /(?>\\[^\\'\n]+|[^\\'\n]+)+/, - '"' => /[^\\$"\n]+/, - "'''" => /(?>[^\\']+|'(?!''))+/, - '"""' => /(?>[^\\$"]+|"(?!""))+/, - '/' => /[^\\$\/\n]+/, - } # :nodoc: - - protected - - def scan_tokens encoder, options - - state = :initial - inline_block_stack = [] - inline_block_paren_depth = nil - string_delimiter = nil - import_clause = class_name_follows = last_token = after_def = false - value_expected = true - - until eos? - - case state - - when :initial - - if match = scan(/ \s+ | \\\n /x) - encoder.text_token match, :space - if match.index ?\n - import_clause = after_def = false - value_expected = true unless value_expected - end - next - - elsif match = scan(%r! // [^\n\\]* (?: \\. [^\n\\]* )* | /\* (?: .*? \*/ | .* ) !mx) - value_expected = true - after_def = false - encoder.text_token match, :comment - - elsif bol? && match = scan(/ \#!.* /x) - encoder.text_token match, :doctype - - elsif import_clause && match = scan(/ (?!as) #{IDENT} (?: \. #{IDENT} )* (?: \.\* )? /ox) - after_def = value_expected = false - encoder.text_token match, :include - - elsif match = scan(/ #{IDENT} | \[\] /ox) - kind = IDENT_KIND[match] - value_expected = (kind == :keyword) && KEYWORDS_EXPECTING_VALUE[match] - if last_token == '.' - kind = :ident - elsif class_name_follows - kind = :class - class_name_follows = false - elsif after_def && check(/\s*[({]/) - kind = :method - after_def = false - elsif kind == :ident && last_token != '?' && check(/:/) - kind = :key - else - class_name_follows = true if match == 'class' || (import_clause && match == 'as') - import_clause = match == 'import' - after_def = true if match == 'def' - end - encoder.text_token match, kind - - elsif match = scan(/;/) - import_clause = after_def = false - value_expected = true - encoder.text_token match, :operator - - elsif match = scan(/\{/) - class_name_follows = after_def = false - value_expected = true - encoder.text_token match, :operator - if !inline_block_stack.empty? - inline_block_paren_depth += 1 - end - - # TODO: ~'...', ~"..." and ~/.../ style regexps - elsif match = scan(/ \.\.] | \+\+ | - && | \|\| | \*\*=? | ==?~ | <=?>? | [-+*%^~&|>=!]=? | <<>>?=? /x) - value_expected = true - value_expected = :regexp if match == '~' - after_def = false - encoder.text_token match, :operator - - elsif match = scan(/ [)\]}] /x) - value_expected = after_def = false - if !inline_block_stack.empty? && match == '}' - inline_block_paren_depth -= 1 - if inline_block_paren_depth == 0 # closing brace of inline block reached - encoder.text_token match, :inline_delimiter - encoder.end_group :inline - state, string_delimiter, inline_block_paren_depth = inline_block_stack.pop - next - end - end - encoder.text_token match, :operator - - elsif check(/[\d.]/) - after_def = value_expected = false - if match = scan(/0[xX][0-9A-Fa-f]+/) - encoder.text_token match, :hex - elsif match = scan(/(?>0[0-7]+)(?![89.eEfF])/) - encoder.text_token match, :octal - elsif match = scan(/\d+[fFdD]|\d*\.\d+(?:[eE][+-]?\d+)?[fFdD]?|\d+[eE][+-]?\d+[fFdD]?/) - encoder.text_token match, :float - elsif match = scan(/\d+[lLgG]?/) - encoder.text_token match, :integer - end - - elsif match = scan(/'''|"""/) - after_def = value_expected = false - state = :multiline_string - encoder.begin_group :string - string_delimiter = match - encoder.text_token match, :delimiter - - # TODO: record.'name' syntax - elsif match = scan(/["']/) - after_def = value_expected = false - state = match == '/' ? :regexp : :string - encoder.begin_group state - string_delimiter = match - encoder.text_token match, :delimiter - - elsif value_expected && match = scan(/\//) - after_def = value_expected = false - encoder.begin_group :regexp - state = :regexp - string_delimiter = '/' - encoder.text_token match, :delimiter - - elsif match = scan(/ @ #{IDENT} /ox) - after_def = value_expected = false - encoder.text_token match, :annotation - - elsif match = scan(/\//) - after_def = false - value_expected = true - encoder.text_token match, :operator - - else - encoder.text_token getch, :error - - end - - when :string, :regexp, :multiline_string - if match = scan(STRING_CONTENT_PATTERN[string_delimiter]) - encoder.text_token match, :content - - elsif match = scan(state == :multiline_string ? /'''|"""/ : /["'\/]/) - encoder.text_token match, :delimiter - if state == :regexp - # TODO: regexp modifiers? s, m, x, i? - modifiers = scan(/[ix]+/) - encoder.text_token modifiers, :modifier if modifiers && !modifiers.empty? - end - state = :string if state == :multiline_string - encoder.end_group state - string_delimiter = nil - after_def = value_expected = false - state = :initial - next - - elsif (state == :string || state == :multiline_string) && - (match = scan(/ \\ (?: #{ESCAPE} | #{UNICODE_ESCAPE} ) /mox)) - if string_delimiter[0] == ?' && !(match == "\\\\" || match == "\\'") - encoder.text_token match, :content - else - encoder.text_token match, :char - end - elsif state == :regexp && match = scan(/ \\ (?: #{REGEXP_ESCAPE} | #{UNICODE_ESCAPE} ) /mox) - encoder.text_token match, :char - - elsif match = scan(/ \$ #{IDENT} /mox) - encoder.begin_group :inline - encoder.text_token '$', :inline_delimiter - match = match[1..-1] - encoder.text_token match, IDENT_KIND[match] - encoder.end_group :inline - next - elsif match = scan(/ \$ \{ /x) - encoder.begin_group :inline - encoder.text_token match, :inline_delimiter - inline_block_stack << [state, string_delimiter, inline_block_paren_depth] - inline_block_paren_depth = 1 - state = :initial - next - - elsif match = scan(/ \$ /mx) - encoder.text_token match, :content - - elsif match = scan(/ \\. /mx) - encoder.text_token match, :content # TODO: Shouldn't this be :error? - - elsif match = scan(/ \\ | \n /x) - encoder.end_group state - encoder.text_token match, :error - after_def = value_expected = false - state = :initial - - else - raise_inspect "else case \" reached; %p not handled." % peek(1), encoder - - end - - else - raise_inspect 'Unknown state', encoder - - end - - last_token = match unless [:space, :comment, :doctype].include? kind - - end - - if [:multiline_string, :string, :regexp].include? state - encoder.end_group state - end - - encoder - end - - end - -end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/0e/0ee222eff2b16f026f0f817fdf32372447c6e7e8.svn-base --- a/.svn/pristine/0e/0ee222eff2b16f026f0f817fdf32372447c6e7e8.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,44 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) - -class ProjectsTest < ActionController::IntegrationTest - fixtures :projects, :users, :members - - def test_archive_project - subproject = Project.find(1).children.first - log_user("admin", "admin") - get "admin/projects" - assert_response :success - assert_template "admin/projects" - post "projects/archive", :id => 1 - assert_redirected_to "/admin/projects" - assert !Project.find(1).active? - - get 'projects/1' - assert_response 403 - get "projects/#{subproject.id}" - assert_response 403 - - post "projects/unarchive", :id => 1 - assert_redirected_to "/admin/projects" - assert Project.find(1).active? - get "projects/1" - assert_response :success - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/0f/0f05e49d9c9481ce6698da320e7a0f815ec9a184.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/0f/0f05e49d9c9481ce6698da320e7a0f815ec9a184.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,456 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../../../test_helper', __FILE__) +require 'digest/md5' + +class Redmine::WikiFormatting::TextileFormatterTest < ActionView::TestCase + + def setup + @formatter = Redmine::WikiFormatting::Textile::Formatter + end + + MODIFIERS = { + "*" => 'strong', # bold + "_" => 'em', # italic + "+" => 'ins', # underline + "-" => 'del', # deleted + "^" => 'sup', # superscript + "~" => 'sub' # subscript + } + + def test_modifiers + assert_html_output( + '*bold*' => 'bold', + 'before *bold*' => 'before bold', + '*bold* after' => 'bold after', + '*two words*' => 'two words', + '*two*words*' => 'two*words', + '*two * words*' => 'two * words', + '*two* *words*' => 'two words', + '*(two)* *(words)*' => '(two) (words)', + # with class + '*(foo)two words*' => 'two words' + ) + end + + def test_modifiers_combination + MODIFIERS.each do |m1, tag1| + MODIFIERS.each do |m2, tag2| + next if m1 == m2 + text = "#{m2}#{m1}Phrase modifiers#{m1}#{m2}" + html = "<#{tag2}><#{tag1}>Phrase modifiers" + assert_html_output text => html + end + end + end + + def test_styles + # single style + assert_html_output({ + 'p{color:red}. text' => '

text

', + 'p{color:red;}. text' => '

text

', + 'p{color: red}. text' => '

text

', + 'p{color:#f00}. text' => '

text

', + 'p{color:#ff0000}. text' => '

text

', + 'p{border:10px}. text' => '

text

', + 'p{border:10}. text' => '

text

', + 'p{border:10%}. text' => '

text

', + 'p{border:10em}. text' => '

text

', + 'p{border:1.5em}. text' => '

text

', + 'p{border-left:1px}. text' => '

text

', + 'p{border-right:1px}. text' => '

text

', + 'p{border-top:1px}. text' => '

text

', + 'p{border-bottom:1px}. text' => '

text

', + }, false) + + # multiple styles + assert_html_output({ + 'p{color:red; border-top:1px}. text' => '

text

', + 'p{color:red ; border-top:1px}. text' => '

text

', + 'p{color:red;border-top:1px}. text' => '

text

', + }, false) + + # styles with multiple values + assert_html_output({ + 'p{border:1px solid red;}. text' => '

text

', + 'p{border-top-left-radius: 10px 5px;}. text' => '

text

', + }, false) + end + + def test_invalid_styles_should_be_filtered + assert_html_output({ + 'p{invalid}. text' => '

text

', + 'p{invalid:red}. text' => '

text

', + 'p{color:(red)}. text' => '

text

', + 'p{color:red;invalid:blue}. text' => '

text

', + 'p{invalid:blue;color:red}. text' => '

text

', + 'p{color:"}. text' => '

p{color:"}. text

', + }, false) + end + + def test_inline_code + assert_html_output( + 'this is @some code@' => 'this is some code', + '@@' => '<Location /redmine>' + ) + end + + def test_nested_lists + raw = <<-RAW +# Item 1 +# Item 2 +** Item 2a +** Item 2b +# Item 3 +** Item 3a +RAW + + expected = <<-EXPECTED +
    +
  1. Item 1
  2. +
  3. Item 2 +
      +
    • Item 2a
    • +
    • Item 2b
    • +
    +
  4. +
  5. Item 3 +
      +
    • Item 3a
    • +
    +
  6. +
+EXPECTED + + assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '') + end + + def test_escaping + assert_html_output( + 'this is a | - \?> - !xi - - HTML_INDICATOR = / ]/i - - IDENTIFIER = /[a-z_\x7f-\xFF][a-z0-9_\x7f-\xFF]*/i - VARIABLE = /\$#{IDENTIFIER}/ - - OPERATOR = / - \.(?!\d)=? | # dot that is not decimal point, string concatenation - && | \|\| | # logic - :: | -> | => | # scope, member, dictionary - \\(?!\n) | # namespace - \+\+ | -- | # increment, decrement - [,;?:()\[\]{}] | # simple delimiters - [-+*\/%&|^]=? | # ordinary math, binary logic, assignment shortcuts - [~$] | # whatever - =& | # reference assignment - [=!]=?=? | <> | # comparison and assignment - <<=? | >>=? | [<>]=? # comparison and shift - /x - - end - - protected - - def scan_tokens encoder, options - - if check(RE::PHP_START) || # starts with #{RE::IDENTIFIER}/o) - encoder.begin_group :inline - encoder.text_token match, :local_variable - encoder.text_token scan(/->/), :operator - encoder.text_token scan(/#{RE::IDENTIFIER}/o), :ident - encoder.end_group :inline - elsif check(/->/) - match << scan(/->/) - encoder.text_token match, :error - else - encoder.text_token match, :local_variable - end - elsif match = scan(/\{/) - if check(/\$/) - encoder.begin_group :inline - states[-1] = [states.last, delimiter] - delimiter = nil - states.push :php - encoder.text_token match, :delimiter - else - encoder.text_token match, :content - end - elsif match = scan(/\$\{#{RE::IDENTIFIER}\}/o) - encoder.text_token match, :local_variable - elsif match = scan(/\$/) - encoder.text_token match, :content - else - states.pop - end - - when :class_expected - if match = scan(/\s+/) - encoder.text_token match, :space - elsif match = scan(/#{RE::IDENTIFIER}/o) - encoder.text_token match, :class - states.pop - else - states.pop - end - - when :function_expected - if match = scan(/\s+/) - encoder.text_token match, :space - elsif match = scan(/&/) - encoder.text_token match, :operator - elsif match = scan(/#{RE::IDENTIFIER}/o) - encoder.text_token match, :function - states.pop - else - states.pop - end - - else - raise_inspect 'Unknown state!', encoder, states - end - - end - - encoder - end - - end - -end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/1d/1d8c43c17d3233bffb7ec0f72aeb2fc2a1c616a9.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/1d/1d8c43c17d3233bffb7ec0f72aeb2fc2a1c616a9.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,39 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class CustomValueTest < ActiveSupport::TestCase + fixtures :custom_fields, :custom_values, :users + + def test_default_value + field = CustomField.find_by_default_value('Default string') + assert_not_nil field + + v = CustomValue.new(:custom_field => field) + assert_equal 'Default string', v.value + + v = CustomValue.new(:custom_field => field, :value => 'Not empty') + assert_equal 'Not empty', v.value + end + + def test_sti_polymorphic_association + # Rails uses top level sti class for polymorphic association. See #3978. + assert !User.find(4).custom_values.empty? + assert !CustomValue.find(2).customized.nil? + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/1e/1e249c4fe7dcd1453c3738c60c059927f1f93080.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/1e/1e249c4fe7dcd1453c3738c60c059927f1f93080.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,96 @@ +source 'https://rubygems.org' + +gem "rails", "3.2.13" +gem "jquery-rails", "~> 2.0.2" +gem "i18n", "~> 0.6.0" +gem "coderay", "~> 1.0.9" +gem "fastercsv", "~> 1.5.0", :platforms => [:mri_18, :mingw_18, :jruby] +gem "builder", "3.0.0" + +# Optional gem for LDAP authentication +group :ldap do + gem "net-ldap", "~> 0.3.1" +end + +# Optional gem for OpenID authentication +group :openid do + gem "ruby-openid", "~> 2.1.4", :require => "openid" + gem "rack-openid" +end + +# Optional gem for exporting the gantt to a PNG file, not supported with jruby +platforms :mri, :mingw do + group :rmagick do + # RMagick 2 supports ruby 1.9 + # RMagick 1 would be fine for ruby 1.8 but Bundler does not support + # different requirements for the same gem on different platforms + gem "rmagick", ">= 2.0.0" + end +end + +platforms :jruby do + # jruby-openssl is bundled with JRuby 1.7.0 + gem "jruby-openssl" if Object.const_defined?(:JRUBY_VERSION) && JRUBY_VERSION < '1.7.0' + gem "activerecord-jdbc-adapter", "1.2.5" +end + +# Include database gems for the adapters found in the database +# configuration file +require 'erb' +require 'yaml' +database_file = File.join(File.dirname(__FILE__), "config/database.yml") +if File.exist?(database_file) + database_config = YAML::load(ERB.new(IO.read(database_file)).result) + adapters = database_config.values.map {|c| c['adapter']}.compact.uniq + if adapters.any? + adapters.each do |adapter| + case adapter + when 'mysql2' + gem "mysql2", "~> 0.3.11", :platforms => [:mri, :mingw] + gem "activerecord-jdbcmysql-adapter", :platforms => :jruby + when 'mysql' + gem "mysql", "~> 2.8.1", :platforms => [:mri, :mingw] + gem "activerecord-jdbcmysql-adapter", :platforms => :jruby + when /postgresql/ + gem "pg", ">= 0.11.0", :platforms => [:mri, :mingw] + gem "activerecord-jdbcpostgresql-adapter", :platforms => :jruby + when /sqlite3/ + gem "sqlite3", :platforms => [:mri, :mingw] + gem "activerecord-jdbcsqlite3-adapter", :platforms => :jruby + when /sqlserver/ + gem "tiny_tds", "~> 0.5.1", :platforms => [:mri, :mingw] + gem "activerecord-sqlserver-adapter", :platforms => [:mri, :mingw] + else + warn("Unknown database adapter `#{adapter}` found in config/database.yml, use Gemfile.local to load your own database gems") + end + end + else + warn("No adapter found in config/database.yml, please configure it first") + end +else + warn("Please configure your config/database.yml first") +end + +group :development do + gem "rdoc", ">= 2.4.2" + gem "yard" +end + +group :test do + gem "shoulda", "~> 3.3.2" + gem "mocha", "~> 0.13.3" + gem 'capybara', '~> 2.0.0' + gem 'nokogiri', '< 1.6.0' +end + +local_gemfile = File.join(File.dirname(__FILE__), "Gemfile.local") +if File.exists?(local_gemfile) + puts "Loading Gemfile.local ..." if $DEBUG # `ruby -d` or `bundle -v` + instance_eval File.read(local_gemfile) +end + +# Load plugins' Gemfiles +Dir.glob File.expand_path("../plugins/*/Gemfile", __FILE__) do |file| + puts "Loading #{file} ..." if $DEBUG # `ruby -d` or `bundle -v` + instance_eval File.read(file) +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/1e/1e483084ac442168a4ba45984a0f145a415a7cad.svn-base --- a/.svn/pristine/1e/1e483084ac442168a4ba45984a0f145a415a7cad.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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 DocumentObserver < ActiveRecord::Observer - def after_create(document) - Mailer.deliver_document_added(document) if Setting.notified_events.include?('document_added') - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/1e/1e54d19d4970142ec949fe4202f5ffe30b1f8dbf.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/1e/1e54d19d4970142ec949fe4202f5ffe30b1f8dbf.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,78 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class Redmine::ApiTest::DisabledRestApiTest < Redmine::ApiTest::Base + fixtures :projects, :trackers, :issue_statuses, :issues, + :enumerations, :users, :issue_categories, + :projects_trackers, + :roles, + :member_roles, + :members, + :enabled_modules + + def setup + Setting.rest_api_enabled = '0' + Setting.login_required = '1' + end + + def teardown + Setting.rest_api_enabled = '1' + Setting.login_required = '0' + end + + def test_with_a_valid_api_token + @user = User.generate! + @token = Token.create!(:user => @user, :action => 'api') + + get "/news.xml?key=#{@token.value}" + assert_response :unauthorized + assert_equal User.anonymous, User.current + + get "/news.json?key=#{@token.value}" + assert_response :unauthorized + assert_equal User.anonymous, User.current + end + + def test_with_valid_username_password_http_authentication + @user = User.generate! do |user| + user.password = 'my_password' + end + + get "/news.xml", nil, credentials(@user.login, 'my_password') + assert_response :unauthorized + assert_equal User.anonymous, User.current + + get "/news.json", nil, credentials(@user.login, 'my_password') + assert_response :unauthorized + assert_equal User.anonymous, User.current + end + + def test_with_valid_token_http_authentication + @user = User.generate! + @token = Token.create!(:user => @user, :action => 'api') + + get "/news.xml", nil, credentials(@token.value, 'X') + assert_response :unauthorized + assert_equal User.anonymous, User.current + + get "/news.json", nil, credentials(@token.value, 'X') + assert_response :unauthorized + assert_equal User.anonymous, User.current + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/1e/1e6be947d792b95e6881bfbd4ea45738a1aaae9e.svn-base --- a/.svn/pristine/1e/1e6be947d792b95e6881bfbd4ea45738a1aaae9e.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +0,0 @@ -# Tests in this file ensure that: -# -# * the application /app/[controllers|helpers|models] and /lib -# paths preceed the corresponding plugin paths -# * the plugin paths are added to $LOAD_PATH in the order in which plugins are -# loaded - -require File.dirname(__FILE__) + '/../test_helper' - -class LoadPathTest < Test::Unit::TestCase - def setup - @load_path = expand_paths($LOAD_PATH) - end - - # Not sure if these test actually make sense as this now essentially tests - # Rails core functionality. On the other hand Engines relies on this to some - # extend so this will choke if something important changes in Rails. - - # the application app/... and lib/ directories should appear - # before any plugin directories - - def test_application_app_libs_should_precede_all_plugin_app_libs - types = %w(app/controllers app/helpers app/models lib) - types.each do |t| - app_index = load_path_index(File.join(RAILS_ROOT, t)) - assert_not_nil app_index, "#{t} is missing in $LOAD_PATH" - Engines.plugins.each do |plugin| - first_plugin_index = load_path_index(File.join(plugin.directory, t)) - assert(app_index < first_plugin_index) unless first_plugin_index.nil? - end - end - end - - # the engine directories should appear in the proper order based on - # the order they were started - - def test_plugin_dirs_should_appear_in_reverse_plugin_loading_order - app_paths = %w(app/controllers/ app app/models app/helpers lib) - app_paths.map { |p| File.join(RAILS_ROOT, p)} - plugin_paths = Engines.plugins.reverse.collect { |plugin| plugin.load_paths.reverse }.flatten - - expected_paths = expand_paths(app_paths + plugin_paths) - # only look at those paths that are also present in expected_paths so - # the only difference would be in the order of the paths - actual_paths = @load_path & expected_paths - - assert_equal expected_paths, actual_paths - end - - protected - def expand_paths(paths) - paths.collect { |p| File.expand_path(p) } - end - - def load_path_index(dir) - @load_path.index(File.expand_path(dir)) - end -end \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/1e/1ee7b5168bf92e876c9f4c10698beed4d4ba3f9d.svn-base --- a/.svn/pristine/1e/1ee7b5168bf92e876c9f4c10698beed4d4ba3f9d.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1011 +0,0 @@ -fa: - # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl) - direction: rtl - date: - formats: - # Use the strftime parameters for formats. - # When no format has been given, it uses default. - # You can provide other formats here if you like! - default: "%Y/%m/%d" - short: "%b %d" - long: "%B %d, %Y" - - day_names: [یک‌شنبه, دوشنبه, سه‌شنبه, چهارشنبه, پنج‌شنبه, آدینه, شنبه] - abbr_day_names: [یک, دو, سه, چهار, پنج, آدینه, شنبه] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, ژانویه, Ùوریه, مارس, آوریل, مه, ژوئن, ژوئیه, اوت, سپتامبر, اکتبر, نوامبر, دسامبر] - abbr_month_names: [~, ژان, Ùور, مار, آور, مه, ژوئن, ژوئیه, اوت, سپت, اکت, نوا, دسا] - # Used in date_select and datime_select. - order: - - :year - - :month - - :day - - time: - formats: - default: "%Y/%m/%d - %H:%M" - time: "%H:%M" - short: "%d %b %H:%M" - long: "%d %B %Y ساعت %H:%M" - am: "صبح" - pm: "عصر" - - datetime: - distance_in_words: - half_a_minute: "نیم دقیقه" - less_than_x_seconds: - one: "کمتر از 1 ثانیه" - other: "کمتر از %{count} ثانیه" - x_seconds: - one: "1 ثانیه" - other: "%{count} ثانیه" - less_than_x_minutes: - one: "کمتر از 1 دقیقه" - other: "کمتر از %{count} دقیقه" - x_minutes: - one: "1 دقیقه" - other: "%{count} دقیقه" - about_x_hours: - one: "نزدیک 1 ساعت" - other: "نزدیک %{count} ساعت" - x_days: - one: "1 روز" - other: "%{count} روز" - about_x_months: - one: "نزدیک 1 ماه" - other: "نزدیک %{count} ماه" - x_months: - one: "1 ماه" - other: "%{count} ماه" - about_x_years: - one: "نزدیک 1 سال" - other: "نزدیک %{count} سال" - over_x_years: - one: "بیش از 1 سال" - other: "بیش از %{count} سال" - almost_x_years: - one: "نزدیک 1 سال" - other: "نزدیک %{count} سال" - - number: - # Default format for numbers - format: - separator: "Ù«" - delimiter: "" - precision: 3 - human: - format: - delimiter: "" - precision: 1 - storage_units: - format: "%n %u" - units: - byte: - one: "بایت" - other: "بایت" - kb: "کیلوبایت" - mb: "مگابایت" - gb: "گیگابایت" - tb: "ترابایت" - - -# Used in array.to_sentence. - support: - array: - sentence_connector: "Ùˆ" - skip_last_comma: false - - activerecord: - errors: - template: - header: - one: "1 ایراد از ذخیره سازی این %{model} جلوگیری کرد" - other: "%{count} ایراد از ذخیره سازی این %{model} جلوگیری کرد" - messages: - inclusion: "در Ùهرست نیامده است" - exclusion: "رزرو شده است" - invalid: "نادرست است" - confirmation: "با بررسی سازگاری ندارد" - accepted: "باید Ù¾Ø°ÛŒØ±ÙØªÙ‡ شود" - empty: "نمی‌تواند تهی باشد" - blank: "نمی‌تواند تهی باشد" - too_long: "خیلی بلند است (بیشترین اندازه %{count} نویسه است)" - too_short: "خیلی کوتاه است (کمترین اندازه %{count} نویسه است)" - wrong_length: "اندازه نادرست است (باید %{count} نویسه باشد)" - taken: "پیش از این Ú¯Ø±ÙØªÙ‡ شده است" - not_a_number: "شماره درستی نیست" - not_a_date: "تاریخ درستی نیست" - greater_than: "باید بزرگتر از %{count} باشد" - greater_than_or_equal_to: "باید بزرگتر از یا برابر با %{count} باشد" - equal_to: "باید برابر با %{count} باشد" - less_than: "باید کمتر از %{count} باشد" - less_than_or_equal_to: "باید کمتر از یا برابر با %{count} باشد" - odd: "باید ÙØ±Ø¯ باشد" - even: "باید زوج باشد" - greater_than_start_date: "باید از تاریخ آغاز بزرگتر باشد" - not_same_project: "به همان پروژه وابسته نیست" - circular_dependency: "این وابستگی یک وابستگی دایره وار خواهد ساخت" - cant_link_an_issue_with_a_descendant: "یک پیامد نمی‌تواند به یکی از زیر کارهایش پیوند بخورد" - - actionview_instancetag_blank_option: گزینش کنید - - general_text_No: 'خیر' - general_text_Yes: 'آری' - general_text_no: 'خیر' - general_text_yes: 'آری' - general_lang_name: 'Persian (پارسی)' - general_csv_separator: ',' - general_csv_decimal_separator: '.' - general_csv_encoding: UTF-8 - general_pdf_encoding: UTF-8 - general_first_day_of_week: '6' - - notice_account_updated: حساب شما بروز شد. - notice_account_invalid_creditentials: نام کاربری یا گذرواژه نادرست است - notice_account_password_updated: گذرواژه بروز شد - notice_account_wrong_password: گذرواژه نادرست است - notice_account_register_done: حساب ساخته شد. برای ÙØ¹Ø§Ù„ نمودن آن، روی پیوندی Ú©Ù‡ به شما ایمیل شده کلیک کنید. - notice_account_unknown_email: کاربر شناخته نشد. - notice_can_t_change_password: این حساب یک روش شناسایی بیرونی را به کار Ú¯Ø±ÙØªÙ‡ است. گذرواژه را نمی‌توان جایگزین کرد. - notice_account_lost_email_sent: یک ایمیل با راهنمایی درباره گزینش گذرواژه تازه برای شما ÙØ±Ø³ØªØ§Ø¯Ù‡ شد. - notice_account_activated: حساب شما ÙØ¹Ø§Ù„ شده است. اکنون می‌توانید وارد شوید. - notice_successful_create: با موÙقیت ساخته شد. - notice_successful_update: با موÙقیت بروز شد. - notice_successful_delete: با موÙقیت برداشته شد. - notice_successful_connection: با موÙقیت متصل شد. - notice_file_not_found: برگه درخواستی شما در دسترس نیست یا پاک شده است. - notice_locking_conflict: داده‌ها را کاربر دیگری بروز کرده است. - notice_not_authorized: شما به این برگه دسترسی ندارید. - notice_not_authorized_archived_project: پروژه درخواستی شما بایگانی شده است. - notice_email_sent: "یک ایمیل به %{value} ÙØ±Ø³ØªØ§Ø¯Ù‡ شد." - notice_email_error: "یک ایراد در ÙØ±Ø³ØªØ§Ø¯Ù† ایمیل پیش آمد (%{value})." - notice_feeds_access_key_reseted: کلید دسترسی RSS شما بازنشانی شد. - notice_api_access_key_reseted: کلید دسترسی API شما بازنشانی شد. - notice_failed_to_save_issues: "ذخیره سازی %{count} پیامد از %{total} پیامد گزینش شده شکست خورد: %{ids}." - notice_failed_to_save_members: "ذخیره سازی اعضا شکست خورد: %{errors}." - notice_no_issue_selected: "هیچ پیامدی برگزیده نشده است! پیامدهایی Ú©Ù‡ می‌خواهید ویرایش کنید را برگزینید." - notice_account_pending: "حساب شما ساخته شد Ùˆ اکنون چشم به راه روادید سرپرست است." - notice_default_data_loaded: پیکربندی پیش‌گزیده با موÙقیت بار شد. - notice_unable_delete_version: نگارش را نمی‌توان پاک کرد. - notice_unable_delete_time_entry: زمان گزارش شده را نمی‌توان پاک کرد. - notice_issue_done_ratios_updated: اندازه انجام شده پیامد بروز شد. - notice_gantt_chart_truncated: "نمودار بریده شد چون از بیشترین شماری Ú©Ù‡ می‌توان نشان داد بزگتر است (%{max})." - - error_can_t_load_default_data: "پیکربندی پیش‌گزیده نمی‌تواند بار شود: %{value}" - error_scm_not_found: "بخش یا نگارش در انباره پیدا نشد." - error_scm_command_failed: "ایرادی در دسترسی به انباره پیش آمد: %{value}" - error_scm_annotate: "بخش پیدا نشد یا نمی‌توان برای آن یادداشت نوشت." - error_issue_not_found_in_project: 'پیامد پیدا نشد یا به این پروژه وابسته نیست.' - error_no_tracker_in_project: 'هیچ پیگردی به این پروژه پیوسته نشده است. پیکربندی پروژه را بررسی کنید.' - error_no_default_issue_status: 'هیچ وضعیت پیامد پیش‌گزیده‌ای مشخص نشده است. پیکربندی را بررسی کنید (به «پیکربندی -> وضعیت‌های پیامد» بروید).' - error_can_not_delete_custom_field: Ùیلد Ø³ÙØ§Ø±Ø´ÛŒ را نمی‌توان پاک کرد. - error_can_not_delete_tracker: "این پیگرد دارای پیامد است Ùˆ نمی‌توان آن را پاک کرد." - error_can_not_remove_role: "این نقش به کار Ú¯Ø±ÙØªÙ‡ شده است Ùˆ نمی‌توان آن را پاک کرد." - error_can_not_reopen_issue_on_closed_version: 'یک پیامد Ú©Ù‡ به یک نگارش بسته شده وابسته است را نمی‌توان باز کرد.' - error_can_not_archive_project: این پروژه را نمی‌توان بایگانی کرد. - error_issue_done_ratios_not_updated: "اندازه انجام شده پیامد بروز نشد." - error_workflow_copy_source: 'یک پیگرد یا نقش منبع را برگزینید.' - error_workflow_copy_target: 'پیگردها یا نقش‌های مقصد را برگزینید.' - error_unable_delete_issue_status: 'وضعیت پیامد را نمی‌توان پاک کرد.' - error_unable_to_connect: "نمی‌توان متصل شد (%{value})" - warning_attachments_not_saved: "%{count} پرونده ذخیره نشد." - - mail_subject_lost_password: "گذرواژه حساب %{value} شما" - mail_body_lost_password: 'برای جایگزینی گذرواژه خود، بر روی پیوند زیر کلیک کنید:' - mail_subject_register: "ÙØ¹Ø§Ù„سازی حساب %{value} شما" - mail_body_register: 'برای ÙØ¹Ø§Ù„سازی حساب خود، بر روی پیوند زیر کلیک کنید:' - mail_body_account_information_external: "شما می‌توانید حساب %{value} خود را برای ورود به کار برید." - mail_body_account_information: داده‌های حساب شما - mail_subject_account_activation_request: "درخواست ÙØ¹Ø§Ù„سازی حساب %{value}" - mail_body_account_activation_request: "یک کاربر تازه (%{value}) نامنویسی کرده است. این حساب چشم به راه روادید شماست:" - mail_subject_reminder: "زمان رسیدگی به %{count} پیامد در %{days} روز آینده سر می‌رسد" - mail_body_reminder: "زمان رسیدگی به %{count} پیامد Ú©Ù‡ به شما واگذار شده است، در %{days} روز آینده سر می‌رسد:" - mail_subject_wiki_content_added: "برگه ویکی «%{id}» Ø§ÙØ²ÙˆØ¯Ù‡ شد" - mail_body_wiki_content_added: "برگه ویکی «%{id}» به دست %{author} Ø§ÙØ²ÙˆØ¯Ù‡ شد." - mail_subject_wiki_content_updated: "برگه ویکی «%{id}» بروز شد" - mail_body_wiki_content_updated: "برگه ویکی «%{id}» به دست %{author} بروز شد." - - gui_validation_error: 1 ایراد - gui_validation_error_plural: "%{count} ایراد" - - 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: عبارت منظم - field_min_length: کمترین اندازه - field_max_length: بیشترین اندازه - field_value: مقدار - field_category: دسته - field_title: عنوان - field_project: پروژه - field_issue: پیامد - field_status: وضعیت - field_notes: یادداشت - field_is_closed: پیامد بسته شده - field_is_default: مقدار پیش‌گزیده - field_tracker: پیگرد - field_subject: موضوع - field_due_date: زمان سررسید - field_assigned_to: واگذار شده به - field_priority: برتری - field_fixed_version: نگارش هد٠- field_user: کاربر - field_principal: دستور دهنده - field_role: نقش - field_homepage: برگه خانه - field_is_public: همگانی - field_parent: پروژه پدر - field_is_in_roadmap: این پیامدها در چشم‌انداز نشان داده شوند - field_login: ورود - field_mail_notification: آگاه سازی‌های ایمیلی - field_admin: سرپرست - field_last_login_on: آخرین ورود - field_language: زبان - field_effective_date: تاریخ - field_password: گذرواژه - field_new_password: گذرواژه تازه - field_password_confirmation: بررسی گذرواژه - field_version: نگارش - field_type: گونه - field_host: میزبان - field_port: درگاه - field_account: حساب - field_base_dn: DN پایه - field_attr_login: نشانه ورود - field_attr_firstname: نشانه نام Ú©ÙˆÚ†Ú© - field_attr_lastname: نشانه نام خانوادگی - field_attr_mail: نشانه ایمیل - field_onthefly: ساخت کاربر بیدرنگ - field_start_date: تاریخ آغاز - field_done_ratio: Ùª انجام شده - field_auth_source: روش شناسایی - field_hide_mail: ایمیل من پنهان شود - field_comments: دیدگاه - field_url: نشانی - field_start_page: برگه آغاز - field_subproject: زیر پروژه - field_hours: ساعت‌ - field_activity: گزارش - field_spent_on: در تاریخ - field_identifier: شناسه - field_is_filter: پالایش پذیر - field_issue_to: پیامد وابسته - field_delay: دیرکرد - field_assignable: پیامدها می‌توانند به این نقش واگذار شوند - field_redirect_existing_links: پیوندهای پیشین به پیوند تازه راهنمایی شوند - field_estimated_hours: زمان برآورد شده - field_column_names: ستون‌ها - field_time_entries: زمان نوشتن - field_time_zone: پهنه زمانی - field_searchable: جستجو پذیر - field_default_value: مقدار پیش‌گزیده - field_comments_sorting: نمایش دیدگاه‌ها - field_parent_title: برگه پدر - field_editable: ویرایش پذیر - field_watcher: دیده‌بان - field_identity_url: نشانی OpenID - field_content: محتوا - field_group_by: دسته بندی با - field_sharing: اشتراک گذاری - field_parent_issue: کار پدر - field_member_of_group: "دسته واگذار شونده" - field_assigned_to_role: "نقش واگذار شونده" - field_text: Ùیلد متنی - field_visible: آشکار - - setting_app_title: نام برنامه - setting_app_subtitle: زیرنام برنامه - setting_welcome_text: نوشتار خوش‌آمد گویی - setting_default_language: زبان پیش‌گزیده - setting_login_required: الزامی بودن ورود - setting_self_registration: خود نام نویسی - setting_attachment_max_size: بیشترین اندازه پیوست - setting_issues_export_limit: کرانه صدور پییامدها - setting_mail_from: نشانی ÙØ±Ø³ØªÙ†Ø¯Ù‡ ایمیل - setting_bcc_recipients: گیرندگان ایمیل دیده نشوند (bcc) - setting_plain_text_mail: ایمیل نوشته ساده (بدون HTML) - setting_host_name: نام میزبان Ùˆ نشانی - setting_text_formatting: قالب بندی نوشته - setting_wiki_compression: ÙØ´Ø±Ø¯Ù‡â€ŒØ³Ø§Ø²ÛŒ پیشینه ویکی - setting_feeds_limit: کرانه محتوای خوراک - setting_default_projects_public: حالت پیش‌گزیده پروژه‌های تازه، همگانی است - setting_autofetch_changesets: Ø¯Ø±ÛŒØ§ÙØª خودکار تغییرات - setting_sys_api_enabled: ÙØ¹Ø§Ù„ سازی وب سرویس برای سرپرستی انباره - setting_commit_ref_keywords: کلیدواژه‌های نشانه - setting_commit_fix_keywords: کلیدواژه‌های انجام - setting_autologin: ورود خودکار - setting_date_format: قالب تاریخ - setting_time_format: قالب زمان - setting_cross_project_issue_relations: توانایی وابستگی میان پروژه‌ای پیامدها - setting_issue_list_default_columns: ستون‌های پیش‌گزیده نمایش داده شده در Ùهرست پیامدها - setting_emails_header: سرنویس ایمیل‌ها - setting_emails_footer: پانویس ایمیل‌ها - setting_protocol: پیوندنامه - setting_per_page_options: گزینه‌های اندازه داده‌های هر برگ - setting_user_format: قالب نمایشی کاربران - setting_activity_days_default: روزهای نمایش داده شده در گزارش پروژه - setting_display_subprojects_issues: پیش‌گزیده نمایش پیامدهای زیرپروژه در پروژه پدر - setting_enabled_scm: ÙØ¹Ø§Ù„سازی SCM - setting_mail_handler_body_delimiters: "بریدن ایمیل‌ها پس از یکی از این ردیÙ‌ها" - setting_mail_handler_api_enabled: ÙØ¹Ø§Ù„سازی وب سرویس برای ایمیل‌های آمده - setting_mail_handler_api_key: کلید API - setting_sequential_project_identifiers: ساخت پشت سر هم شناسه پروژه - setting_gravatar_enabled: کاربرد Gravatar برای عکس کاربر - setting_gravatar_default: عکس Gravatar پیش‌گزیده - setting_diff_max_lines_displayed: بیشترین اندازه ردیÙ‌های ØªÙØ§ÙˆØª نشان داده شده - setting_file_max_size_displayed: بیشترین اندازه پرونده‌های نمایش داده شده درون خطی - setting_repository_log_display_limit: بیشترین شمار نگارش‌های نمایش داده شده در گزارش پرونده - setting_openid: پذیرش ورود Ùˆ نام نویسی با OpenID - setting_password_min_length: کمترین اندازه گذرواژه - setting_new_project_user_role_id: نقش داده شده به کاربری Ú©Ù‡ سرپرست نیست Ùˆ پروژه می‌سازد - setting_default_projects_modules: پیمانه‌های پیش‌گزیده ÙØ¹Ø§Ù„ برای پروژه‌های تازه - setting_issue_done_ratio: برآورد اندازه انجام شده پیامد با - setting_issue_done_ratio_issue_field: کاربرد Ùیلد پیامد - setting_issue_done_ratio_issue_status: کاربرد وضعیت پیامد - setting_start_of_week: آغاز گاهشمار از - setting_rest_api_enabled: ÙØ¹Ø§Ù„سازی وب سرویس‌های REST - setting_cache_formatted_text: نهان سازی نوشته‌های قالب بندی شده - setting_default_notification_option: آگاه سازی پیش‌گزیده - setting_commit_logtime_enabled: ÙØ¹Ø§Ù„سازی زمان گذاشته شده - setting_commit_logtime_activity_id: کار زمان گذاشته شده - setting_gantt_items_limit: بیشترین شمار بخش‌های نمایش داده شده در نمودار گانت - - permission_add_project: ساخت پروژه - permission_add_subprojects: ساخت زیرپروژه - permission_edit_project: ویرایش پروژه - permission_select_project_modules: گزینش پیمانه‌های پروژه - permission_manage_members: سرپرستی اعضا - permission_manage_project_activities: سرپرستی کارهای پروژه - permission_manage_versions: سرپرستی نگارش‌ها - permission_manage_categories: سرپرستی دسته‌های پیامد - permission_view_issues: دیدن پیامدها - 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_delete_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: سرپرستی ویکی - 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: نگه‌داری برگه‌های ویکی - 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: پاک کردن پیام خود - permission_export_wiki_pages: صدور برگه‌های ویکی - permission_manage_subtasks: سرپرستی زیرکارها - - project_module_issue_tracking: پیگیری پیامدها - project_module_time_tracking: پیگیری زمان - project_module_news: رویدادها - project_module_documents: نوشتارها - project_module_files: پرونده‌ها - project_module_wiki: ویکی - project_module_repository: انباره - project_module_boards: انجمن‌ها - project_module_calendar: گاهشمار - project_module_gantt: گانت - - label_user: کاربر - label_user_plural: کاربر - label_user_new: کاربر تازه - label_user_anonymous: ناشناس - label_project: پروژه - label_project_new: پروژه تازه - label_project_plural: پروژه - label_x_projects: - zero: بدون پروژه - one: "1 پروژه" - other: "%{count} پروژه" - label_project_all: همه پروژه‌ها - label_project_latest: آخرین پروژه‌ها - label_issue: پیامد - label_issue_new: پیامد تازه - label_issue_plural: پیامد - label_issue_view_all: دیدن همه پیامدها - label_issues_by: "دسته‌بندی پیامدها با %{value}" - label_issue_added: پیامد Ø§ÙØ²ÙˆØ¯Ù‡ شد - label_issue_updated: پیامد بروز شد - label_document: نوشتار - label_document_new: نوشتار تازه - label_document_plural: نوشتار - label_document_added: نوشتار Ø§ÙØ²ÙˆØ¯Ù‡ شد - label_role: نقش - label_role_plural: نقش - label_role_new: نقش تازه - label_role_and_permissions: نقش‌ها Ùˆ پروانه‌ها - label_member: عضو - label_member_new: عضو تازه - label_member_plural: عضو - label_tracker: پیگرد - label_tracker_plural: پیگرد - label_tracker_new: پیگرد تازه - label_workflow: گردش کار - label_issue_status: وضعیت پیامد - label_issue_status_plural: وضعیت پیامد - label_issue_status_new: وضعیت تازه - label_issue_category: دسته پیامد - label_issue_category_plural: دسته پیامد - label_issue_category_new: دسته تازه - label_custom_field: Ùیلد Ø³ÙØ§Ø±Ø´ÛŒ - label_custom_field_plural: Ùیلد Ø³ÙØ§Ø±Ø´ÛŒ - label_custom_field_new: Ùیلد Ø³ÙØ§Ø±Ø´ÛŒ تازه - label_enumerations: برشمردنی‌ها - label_enumeration_new: مقدار تازه - label_information: داده - label_information_plural: داده - label_please_login: وارد شوید - label_register: نام نویسی کنید - label_login_with_open_id_option: یا با OpenID وارد شوید - label_password_lost: Ø¨Ø§Ø²ÛŒØ§ÙØª گذرواژه - label_home: سرآغاز - label_my_page: برگه من - label_my_account: حساب من - label_my_projects: پروژه‌های من - label_my_page_block: بخش برگه من - label_administration: سرپرستی - label_login: ورود - label_logout: خروج - label_help: راهنما - label_reported_issues: پیامدهای گزارش شده - label_assigned_to_me_issues: پیامدهای واگذار شده به من - label_last_login: آخرین ورود - label_registered_on: نام نویسی شده در - label_activity: گزارش - label_overall_activity: گزارش روی هم Ø±ÙØªÙ‡ - label_user_activity: "گزارش %{value}" - label_new: تازه - label_logged_as: "نام کاربری:" - label_environment: محیط - label_authentication: شناسایی - label_auth_source: روش شناسایی - label_auth_source_new: روش شناسایی تازه - label_auth_source_plural: روش شناسایی - label_subproject_plural: زیرپروژه - label_subproject_new: زیرپروژه تازه - label_and_its_subprojects: "%{value} Ùˆ زیرپروژه‌هایش" - label_min_max_length: کمترین Ùˆ بیشترین اندازه - label_list: Ùهرست - label_date: تاریخ - label_integer: شماره درست - label_float: شماره شناور - label_boolean: درست/نادرست - label_string: نوشته - label_text: نوشته بلند - label_attribute: نشانه - label_attribute_plural: نشانه - label_download: "%{count} بار Ø¯Ø±ÛŒØ§ÙØª شده" - label_download_plural: "%{count} بار Ø¯Ø±ÛŒØ§ÙØª شده" - 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_settings: پیکربندی - label_overview: در یک نگاه - label_version: نگارش - label_version_new: نگارش تازه - label_version_plural: نگارش - label_close_versions: بستن نگارش‌های انجام شده - 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_x_open_issues_abbr_on_total: - zero: 0 باز از %{total} - one: 1 باز از %{total} - other: "%{count} باز از %{total}" - label_x_open_issues_abbr: - zero: 0 باز - one: 1 باز - other: "%{count} باز" - label_x_closed_issues_abbr: - zero: 0 بسته - one: 1 بسته - other: "%{count} بسته" - 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: گانت - label_internal: درونی - label_last_changes: "%{count} تغییر آخر" - label_change_view_all: دیدن همه تغییرات - label_personalize_page: Ø³ÙØ§Ø±Ø´ÛŒ نمودن این برگه - label_comment: دیدگاه - label_comment_plural: دیدگاه - label_x_comments: - zero: بدون دیدگاه - one: 1 دیدگاه - other: "%{count} دیدگاه" - label_comment_add: Ø§ÙØ²ÙˆØ¯Ù† دیدگاه - label_comment_added: دیدگاه Ø§ÙØ²ÙˆØ¯Ù‡ شد - label_comment_delete: پاک کردن دیدگاه‌ها - label_query: پرس‌وجوی Ø³ÙØ§Ø±Ø´ÛŒ - label_query_plural: پرس‌وجوی Ø³ÙØ§Ø±Ø´ÛŒ - label_query_new: پرس‌وجوی تازه - label_filter_add: Ø§ÙØ²ÙˆØ¯Ù† پالایه - label_filter_plural: پالایه - label_equals: برابر است با - label_not_equals: برابر نیست با - label_in_less_than: کمتر است از - label_in_more_than: بیشتر است از - label_greater_or_equal: بیشتر یا برابر است با - label_less_or_equal: کمتر یا برابر است با - label_in: در - label_today: امروز - label_all_time: همیشه - label_yesterday: دیروز - label_this_week: این Ù‡ÙØªÙ‡ - label_last_week: Ù‡ÙØªÙ‡ پیشین - label_last_n_days: "%{count} روز گذشته" - label_this_month: این ماه - label_last_month: ماه پیشین - label_this_year: امسال - label_date_range: بازه تاریخ - label_less_than_ago: کمتر از چند روز پیشین - label_more_than_ago: بیشتر از چند روز پیشین - label_ago: روز پیشین - label_contains: دارد - label_not_contains: ندارد - label_day_plural: روز - label_repository: انباره - label_repository_plural: انباره - label_browse: چریدن - label_modification: "%{count} جایگذاری" - label_modification_plural: "%{count} جایگذاری" - label_branch: شاخه - label_tag: برچسب - label_revision: بازبینی - label_revision_plural: بازبینی - label_revision_id: "بازبینی %{value}" - label_associated_revisions: بازبینی‌های وابسته - label_added: Ø§ÙØ²ÙˆØ¯Ù‡ شده - label_modified: پیراسته شده - label_copied: رونویسی شده - label_renamed: نامگذاری شده - label_deleted: پاکسازی شده - label_latest_revision: آخرین بازبینی - label_latest_revision_plural: آخرین بازبینی - label_view_revisions: دیدن بازبینی‌ها - label_view_all_revisions: دیدن همه بازبینی‌ها - label_max_size: بیشترین اندازه - label_sort_highest: بردن به آغاز - label_sort_higher: بردن به بالا - label_sort_lower: بردن به پایین - label_sort_lowest: بردن به پایان - label_roadmap: چشم‌انداز - label_roadmap_due_in: "سررسید در %{value}" - label_roadmap_overdue: "%{value} دیرکرد" - 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: شاخص بر اساس نام - label_index_by_date: شاخص بر اساس تاریخ - label_current_version: نگارش کنونی - label_preview: پیش‌نمایش - label_feed_plural: خوراک - label_changes_details: ریز همه جایگذاری‌ها - label_issue_tracking: پیگیری پیامد - label_spent_time: زمان گذاشته شده - label_overall_spent_time: زمان گذاشته شده روی هم - label_f_hour: "%{value} ساعت" - label_f_hour_plural: "%{value} ساعت" - label_time_tracking: پیگیری زمان - label_change_plural: جایگذاری - label_statistics: سرشماری - label_commits_per_month: تغییر در هر ماه - label_commits_per_author: تغییر هر نویسنده - label_view_diff: دیدن ØªÙØ§ÙˆØªâ€ŒÙ‡Ø§ - label_diff_inline: همراستا - label_diff_side_by_side: کنار به کنار - label_options: گزینه‌ها - label_copy_workflow_from: رونویسی گردش کار از روی - label_permissions_report: گزارش پروانه‌ها - label_watched_issues: پیامدهای دیده‌بانی شده - label_related_issues: پیامدهای وابسته - label_applied_status: وضعیت به کار Ø±ÙØªÙ‡ - label_loading: بار گذاری... - label_relation_new: وابستگی تازه - label_relation_delete: پاک کردن وابستگی - label_relates_to: وابسته به - label_duplicates: نگارش دیگری از - label_duplicated_by: نگارشی دیگر در - label_blocks: بازداشت‌ها - label_blocked_by: بازداشت به دست - label_precedes: جلوتر است از - label_follows: پستر است از - label_end_to_start: پایان به آغاز - label_end_to_end: پایان به پایان - label_start_to_start: آغاز به آغاز - label_start_to_end: آغاز به پایان - label_stay_logged_in: وارد شده بمانید - label_disabled: ØºÛŒØ±ÙØ¹Ø§Ù„ - label_show_completed_versions: نمایش نگارش‌های انجام شده - label_me: من - label_board: انجمن - label_board_new: انجمن تازه - label_board_plural: انجمن - label_board_locked: Ù‚ÙÙ„ شده - label_board_sticky: چسبناک - label_topic_plural: Ø³Ø±ÙØµÙ„ - label_message_plural: پیام - label_message_last: آخرین پیام - label_message_new: پیام تازه - label_message_posted: پیام Ø§ÙØ²ÙˆØ¯Ù‡ شد - label_reply_plural: پاسخ - label_send_information: ÙØ±Ø³ØªØ§Ø¯Ù† داده‌های حساب به کاربر - label_year: سال - label_month: ماه - label_week: Ù‡ÙØªÙ‡ - label_date_from: از - label_date_to: تا - label_language_based: بر اساس زبان کاربر - label_sort_by: "جور کرد با %{value}" - label_send_test_email: ÙØ±Ø³ØªØ§Ø¯Ù† ایمیل آزمایشی - label_feeds_access_key: کلید دسترسی RSS - label_missing_feeds_access_key: کلید دسترسی RSS در دسترس نیست - label_feeds_access_key_created_on: "کلید دسترسی RSS %{value} پیش ساخته شده است" - label_module_plural: پیمانه - label_added_time_by: "Ø§ÙØ²ÙˆØ¯Ù‡ شده به دست %{author} در %{age} پیش" - label_updated_time_by: "بروز شده به دست %{author} در %{age} پیش" - label_updated_time: "بروز شده در %{value} پیش" - label_jump_to_a_project: پرش به یک پروژه... - label_file_plural: پرونده - label_changeset_plural: تغییر - label_default_columns: ستون‌های پیش‌گزیده - label_no_change_option: (بدون تغییر) - label_bulk_edit_selected_issues: ویرایش دسته‌ای پیامدهای گزینش شده - label_theme: پوسته - label_default: پیش‌گزیده - label_search_titles_only: تنها نام‌ها جستجو شود - label_user_mail_option_all: "برای هر رویداد در همه پروژه‌ها" - label_user_mail_option_selected: "برای هر رویداد تنها در پروژه‌های گزینش شده..." - label_user_mail_option_none: "هیچ رویدادی" - label_user_mail_option_only_my_events: "تنها برای چیزهایی Ú©Ù‡ دیده‌بان هستم یا در آن‌ها درگیر هستم" - label_user_mail_option_only_assigned: "تنها برای چیزهایی Ú©Ù‡ به من واگذار شده" - label_user_mail_option_only_owner: "تنها برای چیزهایی Ú©Ù‡ من دارنده آن‌ها هستم" - label_user_mail_no_self_notified: "نمی‌خواهم از تغییراتی Ú©Ù‡ خودم می‌دهم آگاه شوم" - label_registration_activation_by_email: ÙØ¹Ø§Ù„سازی حساب با ایمیل - label_registration_manual_activation: ÙØ¹Ø§Ù„سازی حساب دستی - label_registration_automatic_activation: ÙØ¹Ø§Ù„سازی حساب خودکار - label_display_per_page: "ردیÙ‌ها در هر برگه: %{value}" - label_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: به ترتیب تاریخ - label_reverse_chronological_order: برعکس ترتیب تاریخ - label_planning: برنامه ریزی - label_incoming_emails: ایمیل‌های آمده - label_generate_key: ساخت کلید - label_issue_watchers: دیده‌بان‌ها - label_example: نمونه - label_display: نمایش - label_sort: جور کرد - label_ascending: Ø§ÙØ²Ø§ÛŒØ´ÛŒ - label_descending: کاهشی - label_date_from_to: از %{start} تا %{end} - label_wiki_content_added: برگه ویکی Ø§ÙØ²ÙˆØ¯Ù‡ شد - label_wiki_content_updated: برگه ویکی بروز شد - label_group: دسته - label_group_plural: دسته - label_group_new: دسته تازه - label_time_entry_plural: زمان گذاشته شده - label_version_sharing_none: بدون اشتراک - label_version_sharing_descendants: با زیر پروژه‌ها - label_version_sharing_hierarchy: با رشته پروژه‌ها - label_version_sharing_tree: با درخت پروژه - label_version_sharing_system: با همه پروژه‌ها - label_update_issue_done_ratios: بروز رسانی اندازه انجام شده پیامد - label_copy_source: منبع - label_copy_target: مقصد - label_copy_same_as_target: مانند مقصد - label_display_used_statuses_only: تنها وضعیت‌هایی نشان داده شوند Ú©Ù‡ در این پیگرد به کار Ø±ÙØªÙ‡â€ŒØ§Ù†Ø¯ - label_api_access_key: کلید دسترسی API - label_missing_api_access_key: کلید دسترسی API در دسترس نیست - label_api_access_key_created_on: "کلید دسترسی API %{value} پیش ساخته شده است" - label_profile: نمایه - label_subtask_plural: زیرکار - label_project_copy_notifications: در هنگام رونویسی پروژه ایمیل‌های آگاه‌سازی را Ø¨ÙØ±Ø³Øª - label_principal_search: "جستجو برای کاربر یا دسته:" - label_user_search: "جستجو برای کاربر:" - - button_login: ورود - button_submit: واگذاری - button_save: نگهداری - button_check_all: گزینش همه - button_uncheck_all: گزینش هیچ - button_delete: پاک - button_create: ساخت - button_create_and_continue: ساخت Ùˆ ادامه - button_test: آزمایش - button_edit: ویرایش - button_edit_associated_wikipage: "ویرایش برگه ویکی وابسته: %{page_title}" - button_add: Ø§ÙØ²ÙˆØ¯Ù† - button_change: ویرایش - button_apply: انجام - button_clear: پاک - button_lock: گذاشتن Ù‚ÙÙ„ - button_unlock: برداشتن Ù‚ÙÙ„ - button_download: Ø¯Ø±ÛŒØ§ÙØª - button_list: Ùهرست - button_view: دیدن - button_move: جابجایی - button_move_and_follow: جابجایی Ùˆ ادامه - button_back: برگشت - button_cancel: بازگشت - button_activate: ÙØ¹Ø§Ù„سازی - button_sort: جور کرد - button_log_time: زمان‌نویسی - button_rollback: برگرد به این نگارش - button_watch: دیده‌بانی - button_unwatch: نا‌دیده‌بانی - button_reply: پاسخ - button_archive: بایگانی - button_unarchive: برگشت از بایگانی - button_reset: بازنشانی - button_rename: نامگذاری - button_change_password: جایگزینی گذرواژه - button_copy: رونوشت - button_copy_and_follow: رونوشت Ùˆ ادامه - button_annotate: یادداشت - button_update: بروز رسانی - button_configure: پیکربندی - button_quote: نقل قول - button_duplicate: نگارش دیگر - button_show: نمایش - - status_active: ÙØ¹Ø§Ù„ - status_registered: نام‌نویسی شده - status_locked: Ù‚ÙÙ„ - - version_status_open: باز - version_status_locked: Ù‚ÙÙ„ - version_status_closed: بسته - - field_active: ÙØ¹Ø§Ù„ - - text_select_mail_notifications: ÙØ±Ù…ان‌هایی Ú©Ù‡ برای آن‌ها باید ایمیل ÙØ±Ø³ØªØ§Ø¯Ù‡ شود را برگزینید. - text_regexp_info: برای نمونه ^[A-Z0-9]+$ - text_min_max_length_info: 0 یعنی بدون کران - text_project_destroy_confirmation: آیا براستی می‌خواهید این پروژه Ùˆ همه داده‌های آن را پاک کنید؟ - text_subprojects_destroy_warning: "زیرپروژه‌های آن: %{value} هم پاک خواهند شد." - text_workflow_edit: یک نقش Ùˆ یک پیگرد را برای ویرایش گردش کار برگزینید - text_are_you_sure: آیا این کار انجام شود؟ - text_are_you_sure_with_children: "آیا پیامد Ùˆ همه زیرپیامدهای آن پاک شوند؟" - text_journal_changed: "«%{label}» از «%{old}» به «%{new}» جایگزین شد" - text_journal_set_to: "«%{label}» به «%{value}» نشانده شد" - text_journal_deleted: "«%{label}» پاک شد (%{old})" - text_journal_added: "«%{label}»، «%{value}» را Ø§ÙØ²ÙˆØ¯" - text_tip_task_begin_day: روز آغاز پیامد - text_tip_task_end_day: روز پایان پیامد - text_tip_task_begin_end_day: روز آغاز Ùˆ پایان پیامد - text_project_identifier_info: 'تنها نویسه‌های Ú©ÙˆÚ†Ú© (a-z)ØŒ شماره‌ها Ùˆ خط تیره Ù¾Ø°ÛŒØ±ÙØªÙ†ÛŒ است.
پس از ذخیره سازی، شناسه نمی‌تواند جایگزین شود.' - text_caracters_maximum: "بیشترین اندازه %{count} است." - text_caracters_minimum: "کمترین اندازه %{count} است." - text_length_between: "باید میان %{min} Ùˆ %{max} نویسه باشد." - text_tracker_no_workflow: هیچ گردش کاری برای این پیگرد مشخص نشده است - text_unallowed_characters: نویسه‌های ناپسند - text_comma_separated: چند مقدار Ù¾Ø°ÛŒØ±ÙØªÙ†ÛŒ است (با «,» از هم جدا شوند). - text_line_separated: چند مقدار Ù¾Ø°ÛŒØ±ÙØªÙ†ÛŒ است (هر مقدار در یک خط). - text_issues_ref_in_commit_messages: نشانه روی Ùˆ بستن پیامدها در پیام‌های انباره - text_issue_added: "پیامد %{id} به دست %{author} گزارش شد." - text_issue_updated: "پیامد %{id} به دست %{author} بروز شد." - text_wiki_destroy_confirmation: آیا براستی می‌خواهید این ویکی Ùˆ همه محتوای آن را پاک کنید؟ - text_issue_category_destroy_question: "برخی پیامدها (%{count}) به این دسته واگذار شده‌اند. می‌خواهید Ú†Ù‡ کنید؟" - text_issue_category_destroy_assignments: پاک کردن واگذاری به دسته - text_issue_category_reassign_to: واگذاری دوباره پیامدها به این دسته - text_user_mail_option: "برای پروژه‌های گزینش نشده، تنها ایمیل‌هایی درباره چیزهایی Ú©Ù‡ دیده‌بان یا درگیر آن‌ها هستید Ø¯Ø±ÛŒØ§ÙØª خواهید کرد (مانند پیامدهایی Ú©Ù‡ نویسنده آن‌ها هستید یا به شما واگذار شده‌اند)." - text_no_configuration_data: "نقش‌ها، پیگردها، وضعیت‌های پیامد Ùˆ گردش کار هنوز پیکربندی نشده‌اند. \nبه سختی پیشنهاد می‌شود Ú©Ù‡ پیکربندی پیش‌گزیده را بار کنید. سپس می‌توانید آن را ویرایش کنید." - text_load_default_configuration: بارگذاری پیکربندی پیش‌گزیده - text_status_changed_by_changeset: "در تغییر %{value} بروز شده است." - text_time_logged_by_changeset: "در تغییر %{value} نوشته شده است." - text_issues_destroy_confirmation: 'آیا براستی می‌خواهید پیامدهای گزینش شده را پاک کنید؟' - text_select_project_modules: 'پیمانه‌هایی Ú©Ù‡ باید برای این پروژه ÙØ¹Ø§Ù„ شوند را برگزینید:' - text_default_administrator_account_changed: حساب سرپرستی پیش‌گزیده جایگزین شد - text_file_repository_writable: پوشه پیوست‌ها نوشتنی است - text_plugin_assets_writable: پوشه دارایی‌های Ø§ÙØ²ÙˆÙ†Ù‡â€ŒÙ‡Ø§ نوشتنی است - text_rmagick_available: RMagick در دسترس است (اختیاری) - text_destroy_time_entries_question: "%{hours} ساعت روی پیامدهایی Ú©Ù‡ می‌خواهید پاک کنید کار گزارش شده است. می‌خواهید Ú†Ù‡ کنید؟" - text_destroy_time_entries: ساعت‌های گزارش شده پاک شوند - text_assign_time_entries_to_project: ساعت‌های گزارش شده به پروژه واگذار شوند - text_reassign_time_entries: 'ساعت‌های گزارش شده به این پیامد واگذار شوند:' - text_user_wrote: "%{value} نوشت:" - text_enumeration_destroy_question: "%{count} داده به این برشمردنی وابسته شده‌اند." - text_enumeration_category_reassign_to: 'به این برشمردنی وابسته شوند:' - text_email_delivery_not_configured: "Ø¯Ø±ÛŒØ§ÙØª ایمیل پیکربندی نشده است Ùˆ آگاه‌سازی‌ها غیر ÙØ¹Ø§Ù„ هستند.\nکارگزار SMTP خود را در config/configuration.yml پیکربندی کنید Ùˆ برنامه را بازنشانی کنید تا ÙØ¹Ø§Ù„ شوند." - text_repository_usernames_mapping: "کاربر Redmine Ú©Ù‡ به هر نام کاربری پیام‌های انباره نگاشت می‌شود را برگزینید.\nکاربرانی Ú©Ù‡ نام کاربری یا ایمیل همسان دارند، خود به خود نگاشت می‌شوند." - text_diff_truncated: '... این ØªÙØ§ÙˆØª بریده شده چون بیشتر از بیشترین اندازه نمایش دادنی است.' - text_custom_field_possible_values_info: 'یک خط برای هر مقدار' - text_wiki_page_destroy_question: "این برگه %{descendants} زیربرگه دارد.می‌خواهید Ú†Ù‡ کنید؟" - text_wiki_page_nullify_children: "زیربرگه‌ها برگه ریشه شوند" - text_wiki_page_destroy_children: "زیربرگه‌ها Ùˆ زیربرگه‌های آن‌ها پاک شوند" - text_wiki_page_reassign_children: "زیربرگه‌ها به زیر این برگه پدر بروند" - text_own_membership_delete_confirmation: "شما دارید برخی یا همه پروانه‌های خود را برمی‌دارید Ùˆ شاید پس از این دیگر نتوانید این پروژه را ویرایش کنید.\nآیا می‌خواهید این کار را بکنید؟" - text_zoom_in: درشتنمایی - text_zoom_out: ریزنمایی - - default_role_manager: سرپرست - default_role_developer: برنامه‌نویس - default_role_reporter: گزارش‌دهنده - default_tracker_bug: ایراد - default_tracker_feature: ویژگی - default_tracker_support: پشتیبانی - default_issue_status_new: تازه - default_issue_status_in_progress: در گردش - default_issue_status_resolved: درست شده - default_issue_status_feedback: بازخورد - default_issue_status_closed: بسته - default_issue_status_rejected: برگشت خورده - default_doc_category_user: نوشتار کاربر - default_doc_category_tech: نوشتار ÙÙ†ÛŒ - default_priority_low: پایین - default_priority_normal: میانه - default_priority_high: بالا - default_priority_urgent: زود - default_priority_immediate: بیدرنگ - default_activity_design: طراحی - default_activity_development: ساخت - - enumeration_issue_priorities: برتری‌های پیامد - enumeration_doc_categories: دسته‌های نوشتار - enumeration_activities: کارها (پیگیری زمان) - enumeration_system_activity: کار سامانه - - text_tip_issue_begin_day: پیامد در این روز آغاز می‌شود - field_warn_on_leaving_unsaved: هنگام ترک برگه‌ای Ú©Ù‡ نوشته‌های آن نگهداری نشده، به من هشدار بده - text_tip_issue_begin_end_day: پیامد در این روز آغاز می‌شود Ùˆ پایان می‌پذیرد - text_tip_issue_end_day: پیامد در این روز پایان می‌پذیرد - text_warn_on_leaving_unsaved: این برگه دارای نوشته‌های نگهداری نشده است Ú©Ù‡ اگر آن را ترک کنید، از میان می‌روند. - label_my_queries: جستارهای Ø³ÙØ§Ø±Ø´ÛŒ من - text_journal_changed_no_detail: "%{label} بروز شد" - label_news_comment_added: دیدگاه به یک رویداد Ø§ÙØ²ÙˆØ¯Ù‡ شد - button_expand_all: باز کردن همه - button_collapse_all: بستن همه - label_additional_workflow_transitions_for_assignee: زمانی Ú©Ù‡ به کاربر واگذار شده، گذارهای بیشتر Ù¾Ø°ÛŒØ±ÙØªÙ‡ می‌شود - label_additional_workflow_transitions_for_author: زمانی Ú©Ù‡ کاربر نویسنده است، گذارهای بیشتر Ù¾Ø°ÛŒØ±ÙØªÙ‡ می‌شود - label_bulk_edit_selected_time_entries: ویرایش دسته‌ای زمان‌های گزارش شده گزینش شده - text_time_entries_destroy_confirmation: آیا می‌خواهید زمان‌های گزارش شده گزینش شده پاک شوند؟ - label_role_anonymous: ناشناس - label_role_non_member: غیر عضو - label_issue_note_added: یادداشت Ø§ÙØ²ÙˆØ¯Ù‡ شد - label_issue_status_updated: وضعیت بروز شد - label_issue_priority_updated: برتری بروز شد - label_issues_visibility_own: Issues created by or assigned to the user - field_issues_visibility: Issues visibility - label_issues_visibility_all: All issues - permission_set_own_issues_private: Set own issues public or private - field_is_private: Private - permission_set_issues_private: Set issues public or private - label_issues_visibility_public: All non private issues - text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). - field_commit_logs_encoding: کدگذاری پیام‌های انباره - field_scm_path_encoding: Path encoding - text_scm_path_encoding_note: "Default: UTF-8" - field_path_to_repository: Path to repository - field_root_directory: Root directory - field_cvs_module: Module - field_cvsroot: CVSROOT - text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) - text_scm_command: Command - text_scm_command_version: Version - label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. - notice_issue_successful_create: Issue %{id} created. - label_between: between - setting_issue_group_assignment: Allow issue assignment to groups - label_diff: diff - text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) - description_query_sort_criteria_direction: Sort direction - description_project_scope: Search scope - description_filter: Filter - description_user_mail_notification: Mail notification settings - description_date_from: Enter start date - description_message_content: Message content - description_available_columns: Available Columns - description_date_range_interval: Choose range by selecting start and end date - description_issue_category_reassign: Choose issue category - description_search: Searchfield - description_notes: Notes - description_date_range_list: Choose range from list - description_choose_project: Projects - description_date_to: Enter end date - description_query_sort_criteria_attribute: Sort attribute - description_wiki_subpages_reassign: Choose new parent page - description_selected_columns: Selected Columns - label_parent_revision: Parent - label_child_revision: Child - error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. - setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues - button_edit_section: Edit this section - setting_repositories_encodings: Attachments and repositories encodings - description_all_columns: All Columns - button_export: Export - label_export_options: "%{export_format} export options" - error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/1e/1eeb2d3cf254cdaa3fced154da1af384e6d301db.svn-base --- a/.svn/pristine/1e/1eeb2d3cf254cdaa3fced154da1af384e6d301db.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,186 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) -require 'workflows_controller' - -# Re-raise errors caught by the controller. -class WorkflowsController; def rescue_action(e) raise e end; end - -class WorkflowsControllerTest < ActionController::TestCase - fixtures :roles, :trackers, :workflows, :users, :issue_statuses - - 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&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 - Workflow.delete_all - Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 2, :new_status_id => 3) - Workflow.create!(:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 5) - - get :edit, :role_id => 2, :tracker_id => 1 - assert_response :success - assert_template 'edit' - - # used status only - assert_not_nil assigns(:statuses) - assert_equal [2, 3, 5], assigns(:statuses).collect(&:id) - - # allowed transitions - assert_tag :tag => 'input', :attributes => { :type => 'checkbox', - :name => 'issue_status[3][5][]', - :value => 'always', - :checked => 'checked' } - # not allowed - assert_tag :tag => 'input', :attributes => { :type => 'checkbox', - :name => 'issue_status[3][2][]', - :value => 'always', - :checked => nil } - # unused - assert_no_tag :tag => 'input', :attributes => { :type => 'checkbox', - :name => 'issue_status[1][1][]' } - end - - def test_get_edit_with_role_and_tracker_and_all_statuses - Workflow.delete_all - - get :edit, :role_id => 2, :tracker_id => 1, :used_statuses_only => '0' - assert_response :success - assert_template 'edit' - - assert_not_nil assigns(:statuses) - assert_equal IssueStatus.count, assigns(:statuses).size - - assert_tag :tag => 'input', :attributes => { :type => 'checkbox', - :name => 'issue_status[1][1][]', - :value => 'always', - :checked => nil } - end - - def test_post_edit - post :edit, :role_id => 2, :tracker_id => 1, - :issue_status => { - '4' => {'5' => ['always']}, - '3' => {'1' => ['always'], '2' => ['always']} - } - 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_post_edit_with_additional_transitions - post :edit, :role_id => 2, :tracker_id => 1, - :issue_status => { - '4' => {'5' => ['always']}, - '3' => {'1' => ['author'], '2' => ['assignee'], '4' => ['author', 'assignee']} - } - assert_redirected_to '/workflows/edit?role_id=2&tracker_id=1' - - assert_equal 4, Workflow.count(:conditions => {:tracker_id => 1, :role_id => 2}) - - w = Workflow.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 4, :new_status_id => 5}) - assert ! w.author - assert ! w.assignee - w = Workflow.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 1}) - assert w.author - assert ! w.assignee - w = Workflow.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 2}) - assert ! w.author - assert w.assignee - w = Workflow.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 4}) - assert w.author - assert w.assignee - 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 - - def test_get_copy - get :copy - assert_response :success - assert_template 'copy' - end - - def test_post_copy_one_to_one - source_transitions = status_transitions(:tracker_id => 1, :role_id => 2) - - post :copy, :source_tracker_id => '1', :source_role_id => '2', - :target_tracker_ids => ['3'], :target_role_ids => ['1'] - assert_response 302 - assert_equal source_transitions, status_transitions(:tracker_id => 3, :role_id => 1) - end - - def test_post_copy_one_to_many - source_transitions = status_transitions(:tracker_id => 1, :role_id => 2) - - post :copy, :source_tracker_id => '1', :source_role_id => '2', - :target_tracker_ids => ['2', '3'], :target_role_ids => ['1', '3'] - assert_response 302 - assert_equal source_transitions, status_transitions(:tracker_id => 2, :role_id => 1) - assert_equal source_transitions, status_transitions(:tracker_id => 3, :role_id => 1) - assert_equal source_transitions, status_transitions(:tracker_id => 2, :role_id => 3) - assert_equal source_transitions, status_transitions(:tracker_id => 3, :role_id => 3) - end - - def test_post_copy_many_to_many - source_t2 = status_transitions(:tracker_id => 2, :role_id => 2) - source_t3 = status_transitions(:tracker_id => 3, :role_id => 2) - - post :copy, :source_tracker_id => 'any', :source_role_id => '2', - :target_tracker_ids => ['2', '3'], :target_role_ids => ['1', '3'] - assert_response 302 - assert_equal source_t2, status_transitions(:tracker_id => 2, :role_id => 1) - assert_equal source_t3, status_transitions(:tracker_id => 3, :role_id => 1) - assert_equal source_t2, status_transitions(:tracker_id => 2, :role_id => 3) - assert_equal source_t3, status_transitions(:tracker_id => 3, :role_id => 3) - end - - # Returns an array of status transitions that can be compared - def status_transitions(conditions) - Workflow.find(:all, :conditions => conditions, - :order => 'tracker_id, role_id, old_status_id, new_status_id').collect {|w| [w.old_status, w.new_status_id]} - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/1e/1ef1c1b3db8e0ec86373a9f847e88d0b2afb5d10.svn-base --- a/.svn/pristine/1e/1ef1c1b3db8e0ec86373a9f847e88d0b2afb5d10.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2 +0,0 @@ -class AssetsController < ApplicationController -end \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/1f/1f0cbd778ac799db40d158f816d90f939c73e314.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/1f/1f0cbd778ac799db40d158f816d90f939c73e314.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,73 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 Helpers + class Diff + include ERB::Util + include ActionView::Helpers::TagHelper + include ActionView::Helpers::TextHelper + attr_reader :diff, :words + + def initialize(content_to, content_from) + @words = content_to.to_s.split(/(\s+)/) + @words = @words.select {|word| word != ' '} + words_from = content_from.to_s.split(/(\s+)/) + words_from = words_from.select {|word| word != ' '} + @diff = words_from.diff @words + end + + def to_html + words = self.words.collect{|word| h(word)} + words_add = 0 + words_del = 0 + dels = 0 + del_off = 0 + diff.diffs.each do |diff| + add_at = nil + add_to = nil + del_at = nil + deleted = "" + diff.each do |change| + pos = change[1] + if change[0] == "+" + add_at = pos + dels unless add_at + add_to = pos + dels + words_add += 1 + else + del_at = pos unless del_at + deleted << ' ' unless deleted.empty? + deleted << h(change[2]) + words_del += 1 + end + end + if add_at + words[add_at] = ''.html_safe + words[add_at] + words[add_to] = words[add_to] + ''.html_safe + end + if del_at + words.insert del_at - del_off + dels + words_add, ''.html_safe + deleted + ''.html_safe + dels += 1 + del_off += words_del + words_del = 0 + end + end + words.join(' ').html_safe + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/1f/1f46b0be9dff718da9b5754753e6d89331b5e22e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/1f/1f46b0be9dff718da9b5754753e6d89331b5e22e.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,24 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +module AuthSourcesHelper + def auth_source_partial_name(auth_source) + "form_#{auth_source.class.name.underscore}" + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/1f/1f572940b49da993424874b4b9bed1d524fcea35.svn-base --- a/.svn/pristine/1f/1f572940b49da993424874b4b9bed1d524fcea35.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,95 +0,0 @@ -module CodeRay - - # A little hack to enable CodeRay highlighting in RedCloth. - # - # Usage: - # require 'coderay' - # require 'coderay/for_redcloth' - # RedCloth.new('@[ruby]puts "Hello, World!"@').to_html - # - # Make sure you have RedCloth 4.0.3 activated, for example by calling - # require 'rubygems' - # before RedCloth is loaded and before calling CodeRay.for_redcloth. - module ForRedCloth - - def self.install - gem 'RedCloth', '>= 4.0.3' if defined? gem - require 'redcloth' - unless RedCloth::VERSION.to_s >= '4.0.3' - if defined? gem - raise 'CodeRay.for_redcloth needs RedCloth version 4.0.3 or later. ' + - "You have #{RedCloth::VERSION}. Please gem install RedCloth." - else - $".delete 'redcloth.rb' # sorry, but it works - require 'rubygems' - return install # retry - end - end - unless RedCloth::VERSION.to_s >= '4.2.2' - warn 'CodeRay.for_redcloth works best with RedCloth version 4.2.2 or later.' - end - RedCloth::TextileDoc.send :include, ForRedCloth::TextileDoc - RedCloth::Formatters::HTML.module_eval do - def unescape(html) # :nodoc: - replacements = { - '&' => '&', - '"' => '"', - '>' => '>', - '<' => '<', - } - html.gsub(/&(?:amp|quot|[gl]t);/) { |entity| replacements[entity] } - end - undef code, bc_open, bc_close, escape_pre - def code(opts) # :nodoc: - opts[:block] = true - if !opts[:lang] && RedCloth::VERSION.to_s >= '4.2.0' - # simulating pre-4.2 behavior - if opts[:text].sub!(/\A\[(\w+)\]/, '') - if CodeRay::Scanners[$1].lang == :text - opts[:text] = $& + opts[:text] - else - opts[:lang] = $1 - end - end - end - if opts[:lang] && !filter_coderay - require 'coderay' - @in_bc ||= nil - format = @in_bc ? :div : :span - opts[:text] = unescape(opts[:text]) unless @in_bc - highlighted_code = CodeRay.encode opts[:text], opts[:lang], format - highlighted_code.sub!(/\A<(span|div)/) { |m| m + pba(@in_bc || opts) } - highlighted_code - else - "#{opts[:text]}" - end - end - def bc_open(opts) # :nodoc: - opts[:block] = true - @in_bc = opts - opts[:lang] ? '' : "" - end - def bc_close(opts) # :nodoc: - opts = @in_bc - @in_bc = nil - opts[:lang] ? '' : "\n" - end - def escape_pre(text) # :nodoc: - if @in_bc ||= nil - text - else - html_esc(text, :html_escape_preformatted) - end - end - end - end - - module TextileDoc # :nodoc: - attr_accessor :filter_coderay - end - - end - -end - -CodeRay::ForRedCloth.install \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/1f/1f78c9cd9879f6fcb3d4c7ae2bf80de5b578d42c.svn-base --- a/.svn/pristine/1f/1f78c9cd9879f6fcb3d4c7ae2bf80de5b578d42c.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,56 +0,0 @@ -<% show_revision_graph = ( @repository.supports_revision_graph? && path.blank? ) %> -<% form_tag({:controller => 'repositories', :action => 'diff', :id => @project, :path => to_path_param(path)}, :method => :get) do %> - - -<% if show_revision_graph %> - -<% end %> - - - - - - - - -<% show_diff = revisions.size > 1 %> -<% line_num = 1 %> -<% revisions.each do |changeset| %> - -<% if show_revision_graph %> - <% if line_num == 1 %> - - <% end %> -<% end %> - - - - - -<% if show_revision_graph %> - -<% else %> - -<% end %> - -<% line_num += 1 %> -<% end %> - -
#<%= l(:label_date) %><%= l(:field_author) %><%= l(:field_comments) %>
- <% href_base = Proc.new {|x| url_for(:controller => 'repositories', - :action => 'revision', - :id => project, - :rev => x) } %> - <%= render :partial => 'revision_graph', - :locals => { - :commits => index_commits( - revisions, - @repository.branches, - href_base - ) - } %> - <%= link_to_revision(changeset, project) %><%= radio_button_tag('rev', changeset.identifier, (line_num==1), :id => "cb-#{line_num}", :onclick => "$('cbto-#{line_num+1}').checked=true;") if show_diff && (line_num < revisions.size) %><%= radio_button_tag('rev_to', changeset.identifier, (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) %><%= format_time(changeset.committed_on) %><%= h truncate(changeset.author.to_s, :length => 30) %> - <%= textilizable(truncate(truncate_at_line_break(changeset.comments, 0), :length => 90)) %> - <%= textilizable(truncate_at_line_break(changeset.comments)) %>
-<%= submit_tag(l(:label_view_diff), :name => nil) if show_diff %> -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/1f/1fa67dac1aa9c162206c0f56eb10970df326cd79.svn-base --- a/.svn/pristine/1f/1fa67dac1aa9c162206c0f56eb10970df326cd79.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,26 +0,0 @@ -
-<%= watcher_tag(@wiki, User.current) %> -
- -

<%= l(:label_index_by_title) %>

- -<% if @pages.empty? %> -

<%= l(:label_no_data) %>

-<% end %> - -<%= render_page_hierarchy(@pages_by_parent_id, nil, :timestamp => true) %> - -<% content_for :sidebar do %> - <%= render :partial => 'sidebar' %> -<% end %> - -<% unless @pages.empty? %> -<% other_formats_links do |f| %> - <%= f.link_to 'Atom', :url => {:controller => 'activities', :action => 'index', :id => @project, :show_wiki_edits => 1, :key => User.current.rss_key} %> - <%= f.link_to('HTML', :url => {:action => 'export'}) if User.current.allowed_to?(:export_wiki_pages, @project) %> -<% end %> -<% end %> - -<% content_for :header_tags do %> -<%= auto_discovery_link_tag(:atom, :controller => 'activities', :action => 'index', :id => @project, :show_wiki_edits => 1, :format => 'atom', :key => User.current.rss_key) %> -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/1f/1fb867d43b925bd44b6fa06a149ecabad4787c36.svn-base --- a/.svn/pristine/1f/1fb867d43b925bd44b6fa06a149ecabad4787c36.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,141 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) -require 'admin_controller' - -# Re-raise errors caught by the controller. -class AdminController; def rescue_action(e) raise e end; end - -class AdminControllerTest < ActionController::TestCase - fixtures :projects, :users, :roles - - def setup - @controller = AdminController.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_no_tag :tag => 'div', - :attributes => { :class => /nodata/ } - end - - def test_index_with_no_configuration_data - delete_configuration_data - get :index - assert_tag :tag => 'div', - :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' - assert_response :redirect - assert_nil flash[:error] - assert IssueStatus.find_by_name('Nouveau') - end - - def test_test_email - get :test_email - 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 - assert_response :success - assert_template 'info' - end - - def test_admin_menu_plugin_extension - Redmine::MenuManager.map :admin_menu do |menu| - menu.push :test_admin_menu_plugin_extension, '/foo/bar', :caption => 'Test' - end - - get :index - assert_response :success - assert_tag :a, :attributes => { :href => '/foo/bar' }, - :content => 'Test' - - Redmine::MenuManager.map :admin_menu do |menu| - menu.delete :test_admin_menu_plugin_extension - end - end - - private - - def delete_configuration_data - Role.delete_all('builtin = 0') - Tracker.delete_all - IssueStatus.delete_all - Enumeration.delete_all - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/1f/1fc4113fdbf747b80464d17633b16c0ed0796efd.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/1f/1fc4113fdbf747b80464d17633b16c0ed0796efd.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,7 @@ +

<%=l(:label_report_plural)%>

+ +

<%=@report_title%>

+<%= render :partial => 'details', :locals => { :data => @data, :field_name => @field, :rows => @rows } %> +
+<%= link_to l(:button_back), project_issues_report_path(@project) %> + diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/1f/1fcb69836707f0f2797567ced5c5da34d64949eb.svn-base --- a/.svn/pristine/1f/1fcb69836707f0f2797567ced5c5da34d64949eb.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +0,0 @@ -
- <%= link_to l(:label_version_new), new_project_version_path(@project), :class => 'icon icon-add' if User.current.allowed_to?(:manage_versions, @project) %> -
- -

<%=l(:label_roadmap)%>

- -<% if @versions.empty? %> -

<%= l(:label_no_data) %>

-<% else %> -
-<% @versions.each do |version| %> -

<%= tag 'a', :name => h(version.name) %><%= link_to_version version %>

- <%= render :partial => 'versions/overview', :locals => {:version => version} %> - <%= render(:partial => "wiki/content", :locals => {:content => version.wiki_page.content}) if version.wiki_page %> - - <% if (issues = @issues_by_version[version]) && issues.size > 0 %> - <% form_tag({}) do -%> - - - <% issues.each do |issue| -%> - - - - - <% end -%> - - <% end %> - <% end %> - <%= call_hook :view_projects_roadmap_version_bottom, :version => version %> -<% end %> -
-<% end %> - -<% content_for :sidebar do %> -<% form_tag({}, :method => :get) do %> -

<%= l(:label_roadmap) %>

-<% @trackers.each do |tracker| %> -
-<% end %> -
- -<% if @project.descendants.active.any? %> - <%= hidden_field_tag 'with_subprojects', 0 %> -
-<% end %> -

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

-<% end %> - -

<%= l(:label_version_plural) %>

-<% @versions.each do |version| %> -<%= link_to format_version_name(version), "##{version.name}" %>
-<% end %> -<% end %> - -<% html_title(l(:label_roadmap)) %> - -<%= context_menu issues_context_menu_path %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/20/202b3d81f943caf51ec23281dde3cf26313ba319.svn-base --- a/.svn/pristine/20/202b3d81f943caf51ec23281dde3cf26313ba319.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,6 +0,0 @@ -

<%= link_to l(@enumeration.option_name), :controller => 'enumerations', :action => 'index' %> » <%=l(:label_enumeration_new)%>

- -<% form_tag({:action => 'create'}, :class => "tabular") do %> - <%= render :partial => 'form' %> - <%= submit_tag l(:button_create) %> -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/20/2087b528196b7332c8234e9ab56b21a9c4114103.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/20/2087b528196b7332c8234e9ab56b21a9c4114103.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,49 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class DefaultDataTest < ActiveSupport::TestCase + include Redmine::I18n + fixtures :roles + + def test_no_data + assert !Redmine::DefaultData::Loader::no_data? + Role.delete_all("builtin = 0") + Tracker.delete_all + IssueStatus.delete_all + Enumeration.delete_all + assert Redmine::DefaultData::Loader::no_data? + end + + def test_load + valid_languages.each do |lang| + begin + Role.delete_all("builtin = 0") + Tracker.delete_all + IssueStatus.delete_all + Enumeration.delete_all + assert Redmine::DefaultData::Loader::load(lang) + assert_not_nil DocumentCategory.first + assert_not_nil IssuePriority.first + assert_not_nil TimeEntryActivity.first + rescue ActiveRecord::RecordInvalid => e + assert false, ":#{lang} default data is invalid (#{e.message})." + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/20/2098d41250dd6484652b4b39b955e37f96aa4bb6.svn-base --- a/.svn/pristine/20/2098d41250dd6484652b4b39b955e37f96aa4bb6.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,114 +0,0 @@ - -require 'active_record' - -module ActiveRecord - class Base - include Redmine::I18n - - # Translate attribute names for validation errors display - def self.human_attribute_name(attr) - l("field_#{attr.to_s.gsub(/_id$/, '')}", :default => attr) - end - end -end - -module ActiveRecord - class Errors - def full_messages(options = {}) - full_messages = [] - - @errors.each_key do |attr| - @errors[attr].each do |message| - next unless message - - if attr == "base" - full_messages << message - elsif attr == "custom_values" - # Replace the generic "custom values is invalid" - # with the errors on custom values - @base.custom_values.each do |value| - value.errors.each do |attr, msg| - full_messages << value.custom_field.name + ' ' + msg - end - end - else - attr_name = @base.class.human_attribute_name(attr) - full_messages << attr_name + ' ' + message.to_s - end - end - end - full_messages - end - end -end - -module ActionView - module Helpers - module DateHelper - # distance_of_time_in_words breaks when difference is greater than 30 years - def distance_of_date_in_words(from_date, to_date = 0, options = {}) - 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 - - I18n.with_options :locale => options[:locale], :scope => :'datetime.distance_in_words' do |locale| - case distance_in_days - when 0..60 then locale.t :x_days, :count => distance_in_days.round - when 61..720 then locale.t :about_x_months, :count => (distance_in_days / 30).round - else locale.t :over_x_years, :count => (distance_in_days / 365).floor - end - end - end - end - end -end - -ActionView::Base.field_error_proc = Proc.new{ |html_tag, instance| "#{html_tag}" } - -module AsynchronousMailer - # Adds :async_smtp and :async_sendmail delivery methods - # to perform email deliveries asynchronously - %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 - - # Adds a delivery method that writes emails in tmp/emails for testing purpose - def perform_delivery_tmp_file(mail) - dest_dir = File.join(Rails.root, 'tmp', 'emails') - Dir.mkdir(dest_dir) unless File.directory?(dest_dir) - File.open(File.join(dest_dir, mail.message_id.gsub(/[<>]/, '') + '.eml'), 'wb') {|f| f.write(mail.encoded) } - end -end - -ActionMailer::Base.send :include, AsynchronousMailer - -module TMail - # TMail::Unquoter.convert_to_with_fallback_on_iso_8859_1 introduced in TMail 1.2.7 - # triggers a test failure in test_add_issue_with_japanese_keywords(MailHandlerTest) - class Unquoter - class << self - alias_method :convert_to, :convert_to_without_fallback_on_iso_8859_1 - end - end - - # Patch for TMail 1.2.7. See http://www.redmine.org/issues/8751 - class Encoder - def puts_meta(str) - add_text str - end - end -end - -module ActionController - module MimeResponds - class Responder - def api(&block) - any(:xml, :json, &block) - end - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/20/20aee30a3886ed67fbcf2637dc7c9a63361c83fd.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/20/20aee30a3886ed67fbcf2637dc7c9a63361c83fd.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,90 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 Group < Principal + include Redmine::SafeAttributes + + has_and_belongs_to_many :users, :after_add => :user_added, + :after_remove => :user_removed + + acts_as_customizable + + validates_presence_of :lastname + validates_uniqueness_of :lastname, :case_sensitive => false + validates_length_of :lastname, :maximum => 255 + + before_destroy :remove_references_before_destroy + + scope :sorted, lambda { order("#{table_name}.lastname ASC") } + scope :named, lambda {|arg| where("LOWER(#{table_name}.lastname) = LOWER(?)", arg.to_s.strip)} + + safe_attributes 'name', + 'user_ids', + 'custom_field_values', + 'custom_fields', + :if => lambda {|group, user| user.admin?} + + def to_s + lastname.to_s + end + + def name + lastname + end + + def name=(arg) + self.lastname = arg + end + + def user_added(user) + members.each do |member| + next if member.project.nil? + user_member = Member.find_by_project_id_and_user_id(member.project_id, user.id) || Member.new(:project_id => member.project_id, :user_id => user.id) + member.member_roles.each do |member_role| + user_member.member_roles << MemberRole.new(:role => member_role.role, :inherited_from => member_role.id) + end + user_member.save! + end + end + + def user_removed(user) + members.each do |member| + MemberRole. + includes(:member). + where("#{Member.table_name}.user_id = ? AND #{MemberRole.table_name}.inherited_from IN (?)", user.id, member.member_role_ids). + all. + each(&:destroy) + end + end + + def self.human_attribute_name(attribute_key_name, *args) + attr_name = attribute_key_name.to_s + if attr_name == 'lastname' + attr_name = "name" + end + super(attr_name, *args) + end + + private + + # Removes references that are not handled by associations + def remove_references_before_destroy + return if self.id.nil? + + Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id] + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/20/20bbceb95e51f40dbb36d0ff4569d0437a37f449.svn-base --- a/.svn/pristine/20/20bbceb95e51f40dbb36d0ff4569d0437a37f449.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,127 +0,0 @@ -// ** I18N - -// Calendar SL language -// Author: Jernej Vidmar, -// 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 -r 0a574315af3e -r 4f746d8966dd .svn/pristine/20/20cfc1def6cf64da0d22e9f4697140fef8698ede.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/20/20cfc1def6cf64da0d22e9f4697140fef8698ede.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,24 @@ +class AddUniqueIndexOnCustomFieldsTrackers < ActiveRecord::Migration + def up + table_name = "#{CustomField.table_name_prefix}custom_fields_trackers#{CustomField.table_name_suffix}" + duplicates = CustomField.connection.select_rows("SELECT custom_field_id, tracker_id FROM #{table_name} GROUP BY custom_field_id, tracker_id HAVING COUNT(*) > 1") + duplicates.each do |custom_field_id, tracker_id| + # Removes duplicate rows + CustomField.connection.execute("DELETE FROM #{table_name} WHERE custom_field_id=#{custom_field_id} AND tracker_id=#{tracker_id}") + # And insert one + CustomField.connection.execute("INSERT INTO #{table_name} (custom_field_id, tracker_id) VALUES (#{custom_field_id}, #{tracker_id})") + end + + if index_exists? :custom_fields_trackers, [:custom_field_id, :tracker_id] + remove_index :custom_fields_trackers, [:custom_field_id, :tracker_id] + end + add_index :custom_fields_trackers, [:custom_field_id, :tracker_id], :unique => true + end + + def down + if index_exists? :custom_fields_trackers, [:custom_field_id, :tracker_id] + remove_index :custom_fields_trackers, [:custom_field_id, :tracker_id] + end + add_index :custom_fields_trackers, [:custom_field_id, :tracker_id] + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/21/210ed78bd5324335e5f6bf0efa5404a9438fd74c.svn-base --- a/.svn/pristine/21/210ed78bd5324335e5f6bf0efa5404a9438fd74c.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ -
-<% i = 0 %> -<% split_on = (@issue.custom_field_values.size / 2.0).ceil - 1 %> -<% @issue.custom_field_values.each do |value| %> -

<%= custom_field_tag_with_label :issue, value %>

-<% if i == split_on -%> -
-<% end -%> -<% i += 1 -%> -<% end -%> -
-
diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/21/21220872c05ed094a0e17dfec9147a1d8f235a7a.svn-base --- a/.svn/pristine/21/21220872c05ed094a0e17dfec9147a1d8f235a7a.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ -class Meeting < ActiveRecord::Base - belongs_to :project - - acts_as_event :title => Proc.new {|o| "#{o.scheduled_on} Meeting"}, - :datetime => :scheduled_on, - :author => nil, - :url => Proc.new {|o| {:controller => 'meetings', :action => 'show', :id => o.id}} - - acts_as_activity_provider :timestamp => 'scheduled_on', - :find_options => { :include => :project } -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/21/213cd47b5ee47b691425e1fcb6dffb207c707d8c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/21/213cd47b5ee47b691425e1fcb6dffb207c707d8c.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,5 @@ +/*! jQuery UI - v1.9.2 - 2012-12-26 +* http://jqueryui.com +* Includes: jquery.ui.core.css, jquery.ui.resizable.css, jquery.ui.selectable.css, jquery.ui.accordion.css, jquery.ui.autocomplete.css, jquery.ui.button.css, jquery.ui.datepicker.css, jquery.ui.dialog.css, jquery.ui.menu.css, jquery.ui.progressbar.css, jquery.ui.slider.css, jquery.ui.spinner.css, jquery.ui.tabs.css, jquery.ui.tooltip.css +* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana%2C%20sans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=759fcf&bgTextureHeader=12_gloss_wave.png&bgImgOpacityHeader=35&borderColorHeader=628db6&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=eeeeee&bgTextureContent=03_highlight_soft.png&bgImgOpacityContent=100&borderColorContent=dddddd&fcContent=333333&iconColorContent=222222&bgColorDefault=f6f6f6&bgTextureDefault=02_glass.png&bgImgOpacityDefault=100&borderColorDefault=cccccc&fcDefault=628db6&iconColorDefault=759fcf&bgColorHover=eef5fd&bgTextureHover=02_glass.png&bgImgOpacityHover=100&borderColorHover=628db6&fcHover=628db6&iconColorHover=759fcf&bgColorActive=ffffff&bgTextureActive=02_glass.png&bgImgOpacityActive=65&borderColorActive=628db6&fcActive=628db6&iconColorActive=759fcf&bgColorHighlight=759fcf&bgTextureHighlight=03_highlight_soft.png&bgImgOpacityHighlight=75&borderColorHighlight=628db6&fcHighlight=363636&iconColorHighlight=759fcf&bgColorError=b81900&bgTextureError=08_diagonals_thick.png&bgImgOpacityError=18&borderColorError=cd0a0a&fcError=ffffff&iconColorError=ffd27a&bgColorOverlay=666666&bgTextureOverlay=08_diagonals_thick.png&bgImgOpacityOverlay=20&opacityOverlay=50&bgColorShadow=000000&bgTextureShadow=01_flat.png&bgImgOpacityShadow=10&opacityShadow=20&thicknessShadow=5px&offsetTopShadow=-5px&offsetLeftShadow=-5px&cornerRadiusShadow=5px +* Copyright (c) 2012 jQuery Foundation and other contributors Licensed MIT */.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table}.ui-helper-clearfix:after{clear:both}.ui-helper-clearfix{zoom:1}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:absolute;top:0;left:0;width:100%;height:100%}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:0.1px;display:block}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-selectable-helper{position:absolute;z-index:100;border:1px dotted black}.ui-accordion .ui-accordion-header{display:block;cursor:pointer;position:relative;margin-top:2px;padding:.5em .5em .5em .7em;zoom:1}.ui-accordion .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-noicons{padding-left:.7em}.ui-accordion .ui-accordion-icons .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-header .ui-accordion-header-icon{position:absolute;left:.5em;top:50%;margin-top:-8px}.ui-accordion .ui-accordion-content{padding:1em 2.2em;border-top:0;overflow:auto;zoom:1}.ui-autocomplete{position:absolute;top:0;left:0;cursor:default}* html .ui-autocomplete{width:1px}.ui-button{display:inline-block;position:relative;padding:0;margin-right:.1em;cursor:pointer;text-align:center;zoom:1;overflow:visible}.ui-button,.ui-button:link,.ui-button:visited,.ui-button:hover,.ui-button:active{text-decoration:none}.ui-button-icon-only{width:2.2em}button.ui-button-icon-only{width:2.4em}.ui-button-icons-only{width:3.4em}button.ui-button-icons-only{width:3.7em}.ui-button .ui-button-text{display:block;line-height:1.4}.ui-button-text-only .ui-button-text{padding:.4em 1em}.ui-button-icon-only .ui-button-text,.ui-button-icons-only .ui-button-text{padding:.4em;text-indent:-9999999px}.ui-button-text-icon-primary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 1em .4em 2.1em}.ui-button-text-icon-secondary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 2.1em .4em 1em}.ui-button-text-icons .ui-button-text{padding-left:2.1em;padding-right:2.1em}input.ui-button{padding:.4em 1em}.ui-button-icon-only .ui-icon,.ui-button-text-icon-primary .ui-icon,.ui-button-text-icon-secondary .ui-icon,.ui-button-text-icons .ui-icon,.ui-button-icons-only .ui-icon{position:absolute;top:50%;margin-top:-8px}.ui-button-icon-only .ui-icon{left:50%;margin-left:-8px}.ui-button-text-icon-primary .ui-button-icon-primary,.ui-button-text-icons .ui-button-icon-primary,.ui-button-icons-only .ui-button-icon-primary{left:.5em}.ui-button-text-icon-secondary .ui-button-icon-secondary,.ui-button-text-icons .ui-button-icon-secondary,.ui-button-icons-only .ui-button-icon-secondary{right:.5em}.ui-button-text-icons .ui-button-icon-secondary,.ui-button-icons-only .ui-button-icon-secondary{right:.5em}.ui-buttonset{margin-right:7px}.ui-buttonset .ui-button{margin-left:0;margin-right:-.3em}button.ui-button::-moz-focus-inner{border:0;padding:0}.ui-datepicker{width:17em;padding:.2em .2em 0;display:none}.ui-datepicker .ui-datepicker-header{position:relative;padding:.2em 0}.ui-datepicker .ui-datepicker-prev,.ui-datepicker .ui-datepicker-next{position:absolute;top:2px;width:1.8em;height:1.8em}.ui-datepicker .ui-datepicker-prev-hover,.ui-datepicker .ui-datepicker-next-hover{top:1px}.ui-datepicker .ui-datepicker-prev{left:2px}.ui-datepicker .ui-datepicker-next{right:2px}.ui-datepicker .ui-datepicker-prev-hover{left:1px}.ui-datepicker .ui-datepicker-next-hover{right:1px}.ui-datepicker .ui-datepicker-prev span,.ui-datepicker .ui-datepicker-next span{display:block;position:absolute;left:50%;margin-left:-8px;top:50%;margin-top:-8px}.ui-datepicker .ui-datepicker-title{margin:0 2.3em;line-height:1.8em;text-align:center}.ui-datepicker .ui-datepicker-title select{font-size:1em;margin:1px 0}.ui-datepicker select.ui-datepicker-month-year{width:100%}.ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{width:49%}.ui-datepicker table{width:100%;font-size:.9em;border-collapse:collapse;margin:0 0 .4em}.ui-datepicker th{padding:.7em .3em;text-align:center;font-weight:bold;border:0}.ui-datepicker td{border:0;padding:1px}.ui-datepicker td span,.ui-datepicker td a{display:block;padding:.2em;text-align:right;text-decoration:none}.ui-datepicker .ui-datepicker-buttonpane{background-image:none;margin:.7em 0 0 0;padding:0 .2em;border-left:0;border-right:0;border-bottom:0}.ui-datepicker .ui-datepicker-buttonpane button{float:right;margin:.5em .2em .4em;cursor:pointer;padding:.2em .6em .3em .6em;width:auto;overflow:visible}.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current{float:left}.ui-datepicker.ui-datepicker-multi{width:auto}.ui-datepicker-multi .ui-datepicker-group{float:left}.ui-datepicker-multi .ui-datepicker-group table{width:95%;margin:0 auto .4em}.ui-datepicker-multi-2 .ui-datepicker-group{width:50%}.ui-datepicker-multi-3 .ui-datepicker-group{width:33.3%}.ui-datepicker-multi-4 .ui-datepicker-group{width:25%}.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header{border-left-width:0}.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header{border-left-width:0}.ui-datepicker-multi .ui-datepicker-buttonpane{clear:left}.ui-datepicker-row-break{clear:both;width:100%;font-size:0em}.ui-datepicker-rtl{direction:rtl}.ui-datepicker-rtl .ui-datepicker-prev{right:2px;left:auto}.ui-datepicker-rtl .ui-datepicker-next{left:2px;right:auto}.ui-datepicker-rtl .ui-datepicker-prev:hover{right:1px;left:auto}.ui-datepicker-rtl .ui-datepicker-next:hover{left:1px;right:auto}.ui-datepicker-rtl .ui-datepicker-buttonpane{clear:right}.ui-datepicker-rtl .ui-datepicker-buttonpane button{float:left}.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current{float:right}.ui-datepicker-rtl .ui-datepicker-group{float:right}.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header{border-right-width:0;border-left-width:1px}.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{border-right-width:0;border-left-width:1px}.ui-datepicker-cover{position:absolute;z-index:-1;filter:mask();top:-4px;left:-4px;width:200px;height:200px}.ui-dialog{position:absolute;top:0;left:0;padding:.2em;width:300px;overflow:hidden}.ui-dialog .ui-dialog-titlebar{padding:.4em 1em;position:relative}.ui-dialog .ui-dialog-title{float:left;margin:.1em 16px .1em 0}.ui-dialog .ui-dialog-titlebar-close{position:absolute;right:.3em;top:50%;width:19px;margin:-10px 0 0 0;padding:1px;height:18px}.ui-dialog .ui-dialog-titlebar-close span{display:block;margin:1px}.ui-dialog .ui-dialog-titlebar-close:hover,.ui-dialog .ui-dialog-titlebar-close:focus{padding:0}.ui-dialog .ui-dialog-content{position:relative;border:0;padding:.5em 1em;background:none;overflow:auto;zoom:1}.ui-dialog .ui-dialog-buttonpane{text-align:left;border-width:1px 0 0 0;background-image:none;margin:.5em 0 0 0;padding:.3em 1em .5em .4em}.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset{float:right}.ui-dialog .ui-dialog-buttonpane button{margin:.5em .4em .5em 0;cursor:pointer}.ui-dialog .ui-resizable-se{width:14px;height:14px;right:3px;bottom:3px}.ui-draggable .ui-dialog-titlebar{cursor:move}.ui-menu{list-style:none;padding:2px;margin:0;display:block;outline:none}.ui-menu .ui-menu{margin-top:-3px;position:absolute}.ui-menu .ui-menu-item{margin:0;padding:0;zoom:1;width:100%}.ui-menu .ui-menu-divider{margin:5px -2px 5px -2px;height:0;font-size:0;line-height:0;border-width:1px 0 0 0}.ui-menu .ui-menu-item a{text-decoration:none;display:block;padding:2px .4em;line-height:1.5;zoom:1;font-weight:normal}.ui-menu .ui-menu-item a.ui-state-focus,.ui-menu .ui-menu-item a.ui-state-active{font-weight:normal;margin:-1px}.ui-menu .ui-state-disabled{font-weight:normal;margin:.4em 0 .2em;line-height:1.5}.ui-menu .ui-state-disabled a{cursor:default}.ui-menu-icons{position:relative}.ui-menu-icons .ui-menu-item a{position:relative;padding-left:2em}.ui-menu .ui-icon{position:absolute;top:.2em;left:.2em}.ui-menu .ui-menu-icon{position:static;float:right}.ui-progressbar{height:2em;text-align:left;overflow:hidden}.ui-progressbar .ui-progressbar-value{margin:-1px;height:100%}.ui-slider{position:relative;text-align:left}.ui-slider .ui-slider-handle{position:absolute;z-index:2;width:1.2em;height:1.2em;cursor:default}.ui-slider .ui-slider-range{position:absolute;z-index:1;font-size:.7em;display:block;border:0;background-position:0 0}.ui-slider-horizontal{height:.8em}.ui-slider-horizontal .ui-slider-handle{top:-.3em;margin-left:-.6em}.ui-slider-horizontal .ui-slider-range{top:0;height:100%}.ui-slider-horizontal .ui-slider-range-min{left:0}.ui-slider-horizontal .ui-slider-range-max{right:0}.ui-slider-vertical{width:.8em;height:100px}.ui-slider-vertical .ui-slider-handle{left:-.3em;margin-left:0;margin-bottom:-.6em}.ui-slider-vertical .ui-slider-range{left:0;width:100%}.ui-slider-vertical .ui-slider-range-min{bottom:0}.ui-slider-vertical .ui-slider-range-max{top:0}.ui-spinner{position:relative;display:inline-block;overflow:hidden;padding:0;vertical-align:middle}.ui-spinner-input{border:none;background:none;padding:0;margin:.2em 0;vertical-align:middle;margin-left:.4em;margin-right:22px}.ui-spinner-button{width:16px;height:50%;font-size:.5em;padding:0;margin:0;text-align:center;position:absolute;cursor:default;display:block;overflow:hidden;right:0}.ui-spinner a.ui-spinner-button{border-top:none;border-bottom:none;border-right:none}.ui-spinner .ui-icon{position:absolute;margin-top:-8px;top:50%;left:0}.ui-spinner-up{top:0}.ui-spinner-down{bottom:0}.ui-spinner .ui-icon-triangle-1-s{background-position:-65px -16px}.ui-tabs{position:relative;padding:.2em;zoom:1}.ui-tabs .ui-tabs-nav{margin:0;padding:.2em .2em 0}.ui-tabs .ui-tabs-nav li{list-style:none;float:left;position:relative;top:0;margin:1px .2em 0 0;border-bottom:0;padding:0;white-space:nowrap}.ui-tabs .ui-tabs-nav li a{float:left;padding:.5em 1em;text-decoration:none}.ui-tabs .ui-tabs-nav li.ui-tabs-active{margin-bottom:-1px;padding-bottom:1px}.ui-tabs .ui-tabs-nav li.ui-tabs-active a,.ui-tabs .ui-tabs-nav li.ui-state-disabled a,.ui-tabs .ui-tabs-nav li.ui-tabs-loading a{cursor:text}.ui-tabs .ui-tabs-nav li a,.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active a{cursor:pointer}.ui-tabs .ui-tabs-panel{display:block;border-width:0;padding:1em 1.4em;background:none}.ui-tooltip{padding:8px;position:absolute;z-index:9999;max-width:300px;-webkit-box-shadow:0 0 5px #aaa;box-shadow:0 0 5px #aaa}* html .ui-tooltip{background-image:none}body .ui-tooltip{border-width:2px}.ui-widget{font-family:Verdana,sans-serif;font-size:1.1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Verdana,sans-serif;font-size:1em}.ui-widget-content{border:1px solid #ddd;background:#eee url(images/ui-bg_highlight-soft_100_eeeeee_1x100.png) 50% top repeat-x;color:#333}.ui-widget-content a{color:#333}.ui-widget-header{border:1px solid #628db6;background:#759fcf url(images/ui-bg_gloss-wave_35_759fcf_500x100.png) 50% 50% repeat-x;color:#fff;font-weight:bold}.ui-widget-header a{color:#fff}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #ccc;background:#f6f6f6 url(images/ui-bg_glass_100_f6f6f6_1x400.png) 50% 50% repeat-x;font-weight:bold;color:#628db6}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#628db6;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus{border:1px solid #628db6;background:#eef5fd url(images/ui-bg_glass_100_eef5fd_1x400.png) 50% 50% repeat-x;font-weight:bold;color:#628db6}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited{color:#628db6;text-decoration:none}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #628db6;background:#fff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x;font-weight:bold;color:#628db6}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#628db6;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #628db6;background:#759fcf url(images/ui-bg_highlight-soft_75_759fcf_1x100.png) 50% top repeat-x;color:#363636}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#363636}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #cd0a0a;background:#b81900 url(images/ui-bg_diagonals-thick_18_b81900_40x40.png) 50% 50% repeat;color:#fff}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#fff}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#fff}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px;background-image:url(images/ui-icons_222222_256x240.png)}.ui-widget-content .ui-icon{background-image:url(images/ui-icons_222222_256x240.png)}.ui-widget-header .ui-icon{background-image:url(images/ui-icons_ffffff_256x240.png)}.ui-state-default .ui-icon{background-image:url(images/ui-icons_759fcf_256x240.png)}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{background-image:url(images/ui-icons_759fcf_256x240.png)}.ui-state-active .ui-icon{background-image:url(images/ui-icons_759fcf_256x240.png)}.ui-state-highlight .ui-icon{background-image:url(images/ui-icons_759fcf_256x240.png)}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url(images/ui-icons_ffd27a_256x240.png)}.ui-icon-carat-1-n{background-position:0 0}.ui-icon-carat-1-ne{background-position:-16px 0}.ui-icon-carat-1-e{background-position:-32px 0}.ui-icon-carat-1-se{background-position:-48px 0}.ui-icon-carat-1-s{background-position:-64px 0}.ui-icon-carat-1-sw{background-position:-80px 0}.ui-icon-carat-1-w{background-position:-96px 0}.ui-icon-carat-1-nw{background-position:-112px 0}.ui-icon-carat-2-n-s{background-position:-128px 0}.ui-icon-carat-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-64px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-64px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:0 -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{-moz-border-radius-topleft:4px;-webkit-border-top-left-radius:4px;-khtml-border-top-left-radius:4px;border-top-left-radius:4px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{-moz-border-radius-topright:4px;-webkit-border-top-right-radius:4px;-khtml-border-top-right-radius:4px;border-top-right-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{-moz-border-radius-bottomleft:4px;-webkit-border-bottom-left-radius:4px;-khtml-border-bottom-left-radius:4px;border-bottom-left-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{-moz-border-radius-bottomright:4px;-webkit-border-bottom-right-radius:4px;-khtml-border-bottom-right-radius:4px;border-bottom-right-radius:4px}.ui-widget-overlay{background:#666 url(images/ui-bg_diagonals-thick_20_666666_40x40.png) 50% 50% repeat;opacity:.5;filter:Alpha(Opacity=50)}.ui-widget-shadow{margin:-5px 0 0 -5px;padding:5px;background:#000 url(images/ui-bg_flat_10_000000_40x100.png) 50% 50% repeat-x;opacity:.2;filter:Alpha(Opacity=20);-moz-border-radius:5px;-khtml-border-radius:5px;-webkit-border-radius:5px;border-radius:5px} diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/21/217d8edc342d8c96ea1e479a7157b0c862da5448.svn-base --- a/.svn/pristine/21/217d8edc342d8c96ea1e479a7157b0c862da5448.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,110 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) -require 'members_controller' - -# Re-raise errors caught by the controller. -class MembersController; def rescue_action(e) raise e end; end - - -class MembersControllerTest < ActionController::TestCase - fixtures :projects, :members, :member_roles, :roles, :users - - def setup - @controller = MembersController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - User.current = nil - @request.session[:user_id] = 2 - end - - def test_create - assert_difference 'Member.count' do - post :new, :id => 1, :member => {:role_ids => [1], :user_id => 7} - end - assert_redirected_to '/projects/ecookbook/settings/members' - assert User.find(7).member_of?(Project.find(1)) - end - - def test_create_multiple - assert_difference 'Member.count', 3 do - post :new, :id => 1, :member => {:role_ids => [1], :user_ids => [7, 8, 9]} - end - assert_redirected_to '/projects/ecookbook/settings/members' - assert User.find(7).member_of?(Project.find(1)) - end - - def test_xhr_create - assert_difference 'Member.count', 3 do - post :new, :format => "js", :id => 1, :member => {:role_ids => [1], :user_ids => [7, 8, 9]} - end - assert_select_rjs :replace_html, 'tab-content-members' - assert User.find(7).member_of?(Project.find(1)) - assert User.find(8).member_of?(Project.find(1)) - assert User.find(9).member_of?(Project.find(1)) - end - - def test_xhr_create_with_failure - assert_no_difference 'Member.count' do - post :new, :format => "js", :id => 1, :member => {:role_ids => [], :user_ids => [7, 8, 9]} - end - assert_select '#tab-content-members', 0 - assert @response.body.match(/alert/i), "Alert message not sent" - end - - def test_edit - assert_no_difference 'Member.count' do - post :edit, :id => 2, :member => {:role_ids => [1], :user_id => 3} - end - assert_redirected_to '/projects/ecookbook/settings/members' - end - - def test_xhr_edit - assert_no_difference 'Member.count' do - xhr :post, :edit, :id => 2, :member => {:role_ids => [1], :user_id => 3} - end - assert_select_rjs :replace_html, 'tab-content-members' - member = Member.find(2) - assert_equal [1], member.role_ids - assert_equal 3, member.user_id - end - - def test_destroy - assert_difference 'Member.count', -1 do - post :destroy, :id => 2 - end - assert_redirected_to '/projects/ecookbook/settings/members' - assert !User.find(3).member_of?(Project.find(1)) - end - - def test_xhr_destroy - assert_difference 'Member.count', -1 do - xhr :post, :destroy, :id => 2 - end - assert_select_rjs :replace_html, 'tab-content-members' - end - - def test_autocomplete_for_member - get :autocomplete_for_member, :id => 1, :q => 'mis' - assert_response :success - assert_template 'autocomplete_for_member' - - assert_tag :label, :content => /User Misc/, - :child => { :tag => 'input', :attributes => { :name => 'member[user_ids][]', :value => '8' } } - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/21/21b08aea1ac3dbc27c974e37d3d3c4f6952cdffd.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/21/21b08aea1ac3dbc27c974e37d3d3c4f6952cdffd.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,83 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 'blankslate' + +module Redmine + module Views + module Builders + class Structure < BlankSlate + attr_accessor :request, :response + + def initialize(request, response) + @struct = [{}] + self.request = request + self.response = response + end + + def array(tag, options={}, &block) + @struct << [] + block.call(self) + ret = @struct.pop + @struct.last[tag] = ret + @struct.last.merge!(options) if options + end + + def method_missing(sym, *args, &block) + if args.any? + if args.first.is_a?(Hash) + if @struct.last.is_a?(Array) + @struct.last << args.first unless block + else + @struct.last[sym] = args.first + end + else + if @struct.last.is_a?(Array) + if args.size == 1 && !block_given? + @struct.last << args.first + else + @struct.last << (args.last || {}).merge(:value => args.first) + end + else + @struct.last[sym] = args.first + end + end + end + + if block + @struct << (args.first.is_a?(Hash) ? args.first : {}) + block.call(self) + ret = @struct.pop + if @struct.last.is_a?(Array) + @struct.last << ret + else + if @struct.last.has_key?(sym) && @struct.last[sym].is_a?(Hash) + @struct.last[sym].merge! ret + else + @struct.last[sym] = ret + end + end + end + end + + def output + raise "Need to implement #{self.class.name}#output" + end + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/21/21bb59308736cf64ef50e8df95a55241d5b3beeb.svn-base --- a/.svn/pristine/21/21bb59308736cf64ef50e8df95a55241d5b3beeb.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +0,0 @@ -
-<%= link_to l(:label_user_new), new_user_path, :class => 'icon icon-add' %> -
- -

<%=l(:label_user_plural)%>

- -<% form_tag({}, :method => :get) do %> -
<%= l(:label_filter_plural) %> - -<%= select_tag 'status', users_status_options_for_select(@status), :class => "small", :onchange => "this.form.submit(); return false;" %> - -<% if @groups.present? %> - -<%= select_tag 'group_id', '' + options_from_collection_for_select(@groups, :id, :name, params[:group_id].to_i), :onchange => "this.form.submit(); return false;" %> -<% end %> - - -<%= text_field_tag 'name', params[:name], :size => 30 %> -<%= submit_tag l(:button_apply), :class => "small", :name => nil %> -<%= link_to l(:button_clear), users_path, :class => 'icon icon-reload' %> -
-<% end %> -  - -
- - - <%= sort_header_tag('login', :caption => l(:field_login)) %> - <%= sort_header_tag('firstname', :caption => l(:field_firstname)) %> - <%= sort_header_tag('lastname', :caption => l(:field_lastname)) %> - <%= sort_header_tag('mail', :caption => l(:field_mail)) %> - <%= sort_header_tag('admin', :caption => l(:field_admin), :default_order => 'desc') %> - <%= sort_header_tag('created_on', :caption => l(:field_created_on), :default_order => 'desc') %> - <%= sort_header_tag('last_login_on', :caption => l(:field_last_login_on), :default_order => 'desc') %> - - - -<% for user in @users -%> - <%= %w(anon active registered locked)[user.status] %>"> - - - - - - - - - -<% end -%> - -
<%= avatar(user, :size => "14") %><%= link_to h(user.login), edit_user_path(user) %><%= h(user.firstname) %><%= h(user.lastname) %><%= checked_image user.admin? %><%= format_time(user.created_on) %> - <%= change_status_link(user) %> - <%= link_to(l(:button_delete), user, :confirm => l(:text_are_you_sure), :method => :delete, :class => 'icon icon-del') unless User.current == user %> -
-
-

<%= pagination_links_full @user_pages, @user_count %>

- -<% html_title(l(:label_user_plural)) -%> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/21/21e12c0e1e71e0c4f94a4c58d91a04399075123a.svn-base --- a/.svn/pristine/21/21e12c0e1e71e0c4f94a4c58d91a04399075123a.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,21 +0,0 @@ -# Settings specified here will take precedence over those in config/environment.rb - -# The production environment is meant for finished, "live" apps. -# Code is not reloaded between requests -config.cache_classes = true - -# Use a different logger for distributed setups -# config.logger = SyslogLogger.new -config.log_level = :info - -# Full error reports are disabled and caching is turned on -config.action_controller.consider_all_requests_local = false -config.action_controller.perform_caching = true - -# Enable serving of images, stylesheets, and javascripts from an asset server -# config.action_controller.asset_host = "http://assets.example.com" - -# Disable mail delivery -config.action_mailer.perform_deliveries = false -config.action_mailer.raise_delivery_errors = false - diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/21/21f67f445a6c5a2512b4712ac66209391d37efdc.svn-base --- a/.svn/pristine/21/21f67f445a6c5a2512b4712ac66209391d37efdc.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,241 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -require 'redmine/scm/adapters/abstract_adapter' -require 'rexml/document' - -module Redmine - module Scm - module Adapters - class DarcsAdapter < AbstractAdapter - # Darcs executable name - DARCS_BIN = Redmine::Configuration['scm_darcs_command'] || "darcs" - - class << self - def client_command - @@bin ||= DARCS_BIN - end - - def sq_bin - @@sq_bin ||= shell_quote_command - end - - def client_version - @@client_version ||= (darcs_binary_version || []) - end - - def client_available - !client_version.empty? - end - - def darcs_binary_version - darcsversion = darcs_binary_version_from_command_line.dup - if darcsversion.respond_to?(:force_encoding) - darcsversion.force_encoding('ASCII-8BIT') - end - if m = darcsversion.match(%r{\A(.*?)((\d+\.)+\d+)}) - m[2].scan(%r{\d+}).collect(&:to_i) - end - end - - def darcs_binary_version_from_command_line - shellout("#{sq_bin} --version") { |io| io.read }.to_s - end - end - - def initialize(url, root_url=nil, login=nil, password=nil, - path_encoding=nil) - @url = url - @root_url = url - end - - def supports_cat? - # cat supported in darcs 2.0.0 and higher - self.class.client_version_above?([2, 0, 0]) - end - - # Get info about the darcs repository - def info - rev = revisions(nil,nil,nil,{:limit => 1}) - rev ? Info.new({:root_url => @url, :lastrev => rev.last}) : nil - end - - # Returns an Entries collection - # or nil if the given path doesn't exist in the repository - def entries(path=nil, identifier=nil, options={}) - path_prefix = (path.blank? ? '' : "#{path}/") - if path.blank? - path = ( self.class.client_version_above?([2, 2, 0]) ? @url : '.' ) - end - entries = Entries.new - cmd = "#{self.class.sq_bin} annotate --repodir #{shell_quote @url} --xml-output" - cmd << " --match #{shell_quote("hash #{identifier}")}" if identifier - cmd << " #{shell_quote path}" - shellout(cmd) do |io| - begin - doc = REXML::Document.new(io) - if doc.root.name == 'directory' - doc.elements.each('directory/*') do |element| - next unless ['file', 'directory'].include? element.name - entries << entry_from_xml(element, path_prefix) - end - elsif doc.root.name == 'file' - entries << entry_from_xml(doc.root, path_prefix) - end - rescue - end - end - return nil if $? && $?.exitstatus != 0 - 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 = "#{self.class.sq_bin} changes --repodir #{shell_quote @url} --xml-output" - 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 - doc = REXML::Document.new(io) - doc.elements.each("changelog/patch") do |patch| - message = patch.elements['name'].text - message << "\n" + patch.elements['comment'].text.gsub(/\*\*\*END OF DESCRIPTION\*\*\*.*\z/m, '') if patch.elements['comment'] - revisions << Revision.new({:identifier => nil, - :author => patch.attributes['author'], - :scmid => patch.attributes['hash'], - :time => Time.parse(patch.attributes['local_date']), - :message => message, - :paths => (options[:with_path] ? get_paths_for_patch(patch.attributes['hash']) : nil) - }) - end - rescue - end - end - return nil if $? && $?.exitstatus != 0 - revisions - end - - def diff(path, identifier_from, identifier_to=nil) - path = '*' if path.blank? - cmd = "#{self.class.sq_bin} diff --repodir #{shell_quote @url}" - if identifier_to.nil? - cmd << " --match #{shell_quote("hash #{identifier_from}")}" - else - cmd << " --to-match #{shell_quote("hash #{identifier_from}")}" - cmd << " --from-match #{shell_quote("hash #{identifier_to}")}" - end - cmd << " -u #{shell_quote path}" - diff = [] - shellout(cmd) do |io| - io.each_line do |line| - diff << line - end - end - return nil if $? && $?.exitstatus != 0 - diff - end - - def cat(path, identifier=nil) - cmd = "#{self.class.sq_bin} show content --repodir #{shell_quote @url}" - cmd << " --match #{shell_quote("hash #{identifier}")}" if identifier - cmd << " #{shell_quote path}" - cat = nil - shellout(cmd) do |io| - io.binmode - cat = io.read - end - return nil if $? && $?.exitstatus != 0 - cat - end - - private - - # 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 => modified_element.elements['patch'].attributes['hash'] - }) - }) - end - - def get_paths_for_patch(hash) - paths = get_paths_for_patch_raw(hash) - if self.class.client_version_above?([2, 4]) - orig_paths = paths - paths = [] - add_paths = [] - add_paths_name = [] - mod_paths = [] - other_paths = [] - orig_paths.each do |path| - if path[:action] == 'A' - add_paths << path - add_paths_name << path[:path] - elsif path[:action] == 'M' - mod_paths << path - else - other_paths << path - end - end - add_paths_name.each do |add_path| - mod_paths.delete_if { |m| m[:path] == add_path } - end - paths.concat add_paths - paths.concat mod_paths - paths.concat other_paths - end - paths - end - - # Retrieve changed paths for a single patch - def get_paths_for_patch_raw(hash) - cmd = "#{self.class.sq_bin} annotate --repodir #{shell_quote @url} --summary --xml-output" - cmd << " --match #{shell_quote("hash #{hash}")} " - paths = [] - shellout(cmd) do |io| - begin - # Darcs xml output has multiple root elements in this case (tested with darcs 1.0.7) - # A root element is added so that REXML doesn't raise an error - doc = REXML::Document.new("" + io.read + "") - doc.elements.each('fake_root/summary/*') do |modif| - paths << {:action => modif.name[0,1].upcase, - :path => "/" + modif.text.chomp.gsub(/^\s*/, '') - } - end - rescue - end - end - paths - rescue CommandFailed - paths - end - end - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/22/2231a90d74735e9a796f1508f0e2f1bfd1774f24.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/22/2231a90d74735e9a796f1508f0e2f1bfd1774f24.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,113 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class Member < ActiveRecord::Base + belongs_to :user + belongs_to :principal, :foreign_key => 'user_id' + has_many :member_roles, :dependent => :destroy + has_many :roles, :through => :member_roles + belongs_to :project + + validates_presence_of :principal, :project + validates_uniqueness_of :user_id, :scope => :project_id + validate :validate_role + + before_destroy :set_issue_category_nil + + def role + end + + def role= + end + + def name + self.user.name + end + + alias :base_role_ids= :role_ids= + def role_ids=(arg) + ids = (arg || []).collect(&:to_i) - [0] + # Keep inherited roles + ids += member_roles.select {|mr| !mr.inherited_from.nil?}.collect(&:role_id) + + new_role_ids = ids - role_ids + # Add new roles + new_role_ids.each {|id| member_roles << MemberRole.new(:role_id => id) } + # Remove roles (Rails' #role_ids= will not trigger MemberRole#on_destroy) + member_roles_to_destroy = member_roles.select {|mr| !ids.include?(mr.role_id)} + if member_roles_to_destroy.any? + member_roles_to_destroy.each(&:destroy) + end + end + + def <=>(member) + a, b = roles.sort.first, member.roles.sort.first + if a == b + if principal + principal <=> member.principal + else + 1 + end + elsif a + a <=> b + else + 1 + end + end + + def deletable? + member_roles.detect {|mr| mr.inherited_from}.nil? + end + + def include?(user) + if principal.is_a?(Group) + !user.nil? && user.groups.include?(principal) + else + self.user == user + end + end + + def set_issue_category_nil + if user + # remove category based auto assignments for this member + IssueCategory.update_all "assigned_to_id = NULL", ["project_id = ? AND assigned_to_id = ?", project.id, user.id] + end + end + + # Find or initilize a Member with an id, attributes, and for a Principal + def self.edit_membership(id, new_attributes, principal=nil) + @membership = id.present? ? Member.find(id) : Member.new(:principal => principal) + @membership.attributes = new_attributes + @membership + end + + # Finds or initilizes a Member for the given project and principal + def self.find_or_new(project, principal) + project_id = project.is_a?(Project) ? project.id : project + principal_id = principal.is_a?(Principal) ? principal.id : principal + + member = Member.find_by_project_id_and_user_id(project_id, principal_id) + member ||= Member.new(:project_id => project_id, :user_id => principal_id) + member + end + + protected + + def validate_role + errors.add_on_empty :role if member_roles.empty? && roles.empty? + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/22/2254c6ada14f9698b9a4320070c88983213e381b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/22/2254c6ada14f9698b9a4320070c88983213e381b.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,32 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class RoutingCalendarsTest < ActionController::IntegrationTest + def test_calendars + assert_routing( + { :method => 'get', :path => "/issues/calendar" }, + { :controller => 'calendars', :action => 'show' } + ) + assert_routing( + { :method => 'get', :path => "/projects/project-name/issues/calendar" }, + { :controller => 'calendars', :action => 'show', + :project_id => 'project-name' } + ) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/22/22629c3ce001e5a68327c24a4b23675476508e16.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/22/22629c3ce001e5a68327c24a4b23675476508e16.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,32 @@ +<%= error_messages_for 'time_entry' %> +<%= back_url_hidden_field_tag %> + +
+ <% if @time_entry.new_record? %> + <% if params[:project_id] || @time_entry.issue %> + <%= f.hidden_field :project_id %> + <% else %> +

<%= f.select :project_id, project_tree_options_for_select(Project.allowed_to(:log_time).all, :selected => @time_entry.project), :required => true %>

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

+ <%= f.text_field :issue_id, :size => 6 %> + <%= h("#{@time_entry.issue.tracker.name} ##{@time_entry.issue.id}: #{@time_entry.issue.subject}") if @time_entry.issue %> +

+

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

+

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

+

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

+

<%= f.select :activity_id, activity_collection_for_select_options(@time_entry), :required => true %>

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

<%= custom_field_tag_with_label :time_entry, value %>

+ <% end %> + <%= call_hook(:view_timelog_edit_form_bottom, { :time_entry => @time_entry, :form => f }) %> +
+ +<%= javascript_tag do %> + observeAutocompleteField('time_entry_issue_id', '<%= escape_javascript auto_complete_issues_path(:project_id => @project, :scope => (@project ? nil : 'all'))%>', { + select: function(event, ui) { + $('#time_entry_issue').text(ui.item.label); + } + }); +<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/22/2271decb00153f4ca80ae30e23c680b8a941ca9b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/22/2271decb00153f4ca80ae30e23c680b8a941ca9b.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,94 @@ +<%= form_tag({:action => 'edit', :tab => 'repositories'}) do %> + +
+<%= l(:setting_enabled_scm) %> +<%= hidden_field_tag 'settings[enabled_scm][]', '' %> + + + + + + + <% Redmine::Scm::Base.all.collect do |choice| %> + <% scm_class = "Repository::#{choice}".constantize %> + <% text, value = (choice.is_a?(Array) ? choice : [choice, choice]) %> + <% setting = :enabled_scm %> + <% enabled = Setting.send(setting).include?(value) %> + + + + + + <% end %> +
<%= l(:text_scm_command) %><%= l(:text_scm_command_version) %>
+ + + <% if enabled %> + <%= + image_tag( + (scm_class.scm_available ? 'true.png' : 'exclamation.png'), + :style => "vertical-align:bottom;" + ) + %> + <%= scm_class.scm_command %> + <% end %> + + <%= scm_class.scm_version_string if enabled %> +
+

<%= l(:text_scm_config) %>

+
+ +
+

<%= setting_check_box :autofetch_changesets %>

+ +

<%= setting_check_box :sys_api_enabled, + :onclick => + "if (this.checked) { $('#settings_sys_api_key').removeAttr('disabled'); } else { $('#settings_sys_api_key').attr('disabled', true); }" %>

+ +

<%= setting_text_field :sys_api_key, + :size => 30, + :id => 'settings_sys_api_key', + :disabled => !Setting.sys_api_enabled?, + :label => :setting_mail_handler_api_key %> + <%= link_to_function l(:label_generate_key), + "if (!$('#settings_sys_api_key').attr('disabled')) { $('#settings_sys_api_key').val(randomKey(20)) }" %> +

+ +

<%= setting_text_field :repository_log_display_limit, :size => 6 %>

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

<%= setting_text_field :commit_ref_keywords, :size => 30 %> +<%= l(:text_comma_separated) %>

+ +

<%= setting_text_field :commit_fix_keywords, :size => 30 %> + <%= l(:label_applied_status) %>: <%= setting_select :commit_fix_status_id, + [["", 0]] + + IssueStatus.sorted.all.collect{ + |status| [status.name, status.id.to_s] + }, + :label => false %> + <%= l(:field_done_ratio) %>: <%= setting_select :commit_fix_done_ratio, + (0..10).to_a.collect {|r| ["#{r*10} %", "#{r*10}"] }, + :blank => :label_no_change_option, + :label => false %> +<%= l(:text_comma_separated) %>

+ +

<%= setting_check_box :commit_cross_project_ref %>

+ +

<%= setting_check_box :commit_logtime_enabled, + :onclick => + "if (this.checked) { $('#settings_commit_logtime_activity_id').removeAttr('disabled'); } else { $('#settings_commit_logtime_activity_id').attr('disabled', true); }"%>

+ +

<%= setting_select :commit_logtime_activity_id, + [[l(:label_default), 0]] + + TimeEntryActivity.shared.active.collect{|activity| [activity.name, activity.id.to_s]}, + :disabled => !Setting.commit_logtime_enabled?%>

+
+ +<%= submit_tag l(:button_save) %> +<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/22/229053adf4d0f0cdc30b283c6478a18c47c6fe1d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/22/229053adf4d0f0cdc30b283c6478a18c47c6fe1d.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,161 @@ +if RUBY_VERSION < '1.9' + require 'iconv' +end + +module Redmine + module CodesetUtil + + def self.replace_invalid_utf8(str) + return str if str.nil? + if str.respond_to?(:force_encoding) + str.force_encoding('UTF-8') + if ! str.valid_encoding? + str = str.encode("US-ASCII", :invalid => :replace, + :undef => :replace, :replace => '?').encode("UTF-8") + end + elsif RUBY_PLATFORM == 'java' + begin + ic = Iconv.new('UTF-8', 'UTF-8') + str = ic.iconv(str) + rescue + str = str.gsub(%r{[^\r\n\t\x20-\x7e]}, '?') + end + else + ic = Iconv.new('UTF-8', 'UTF-8') + txtar = "" + begin + txtar += ic.iconv(str) + rescue Iconv::IllegalSequence + txtar += $!.success + str = '?' + $!.failed[1,$!.failed.length] + retry + rescue + txtar += $!.success + end + str = txtar + end + str + end + + def self.to_utf8(str, encoding) + return str if str.nil? + str.force_encoding("ASCII-8BIT") if str.respond_to?(:force_encoding) + if str.empty? + str.force_encoding("UTF-8") if str.respond_to?(:force_encoding) + return str + end + enc = encoding.blank? ? "UTF-8" : encoding + if str.respond_to?(:force_encoding) + if enc.upcase != "UTF-8" + str.force_encoding(enc) + str = str.encode("UTF-8", :invalid => :replace, + :undef => :replace, :replace => '?') + else + str.force_encoding("UTF-8") + if ! str.valid_encoding? + str = str.encode("US-ASCII", :invalid => :replace, + :undef => :replace, :replace => '?').encode("UTF-8") + end + end + elsif RUBY_PLATFORM == 'java' + begin + ic = Iconv.new('UTF-8', enc) + str = ic.iconv(str) + rescue + str = str.gsub(%r{[^\r\n\t\x20-\x7e]}, '?') + end + else + ic = Iconv.new('UTF-8', enc) + txtar = "" + begin + txtar += ic.iconv(str) + rescue Iconv::IllegalSequence + txtar += $!.success + str = '?' + $!.failed[1,$!.failed.length] + retry + rescue + txtar += $!.success + end + str = txtar + end + str + end + + def self.to_utf8_by_setting(str) + return str if str.nil? + str = self.to_utf8_by_setting_internal(str) + if str.respond_to?(:force_encoding) + str.force_encoding('UTF-8') + end + str + end + + def self.to_utf8_by_setting_internal(str) + return str if str.nil? + if str.respond_to?(:force_encoding) + str.force_encoding('ASCII-8BIT') + end + return str if str.empty? + return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii + if str.respond_to?(:force_encoding) + str.force_encoding('UTF-8') + end + encodings = Setting.repositories_encodings.split(',').collect(&:strip) + encodings.each do |encoding| + if str.respond_to?(:force_encoding) + begin + str.force_encoding(encoding) + utf8 = str.encode('UTF-8') + return utf8 if utf8.valid_encoding? + rescue + # do nothing here and try the next encoding + end + else + begin + return Iconv.conv('UTF-8', encoding, str) + rescue Iconv::Failure + # do nothing here and try the next encoding + end + end + end + str = self.replace_invalid_utf8(str) + if str.respond_to?(:force_encoding) + str.force_encoding('UTF-8') + end + str + end + + def self.from_utf8(str, encoding) + str ||= '' + if str.respond_to?(:force_encoding) + str.force_encoding('UTF-8') + if encoding.upcase != 'UTF-8' + str = str.encode(encoding, :invalid => :replace, + :undef => :replace, :replace => '?') + else + str = self.replace_invalid_utf8(str) + end + elsif RUBY_PLATFORM == 'java' + begin + ic = Iconv.new(encoding, 'UTF-8') + str = ic.iconv(str) + rescue + str = str.gsub(%r{[^\r\n\t\x20-\x7e]}, '?') + end + else + ic = Iconv.new(encoding, 'UTF-8') + txtar = "" + begin + txtar += ic.iconv(str) + rescue Iconv::IllegalSequence + txtar += $!.success + str = '?' + $!.failed[1, $!.failed.length] + retry + rescue + txtar += $!.success + end + str = txtar + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/22/22a7851f8ede366924cba144fbbe8879121ee70d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/22/22a7851f8ede366924cba144fbbe8879121ee70d.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,31 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class RoutingWelcomeTest < ActionController::IntegrationTest + def test_welcome + assert_routing( + { :method => 'get', :path => "/" }, + { :controller => 'welcome', :action => 'index' } + ) + assert_routing( + { :method => 'get', :path => "/robots.txt" }, + { :controller => 'welcome', :action => 'robots' } + ) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/23/2330bc88f1bd2ce1c1b2bfa4d60a6eb823530bd2.svn-base --- a/.svn/pristine/23/2330bc88f1bd2ce1c1b2bfa4d60a6eb823530bd2.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,182 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../../../../test_helper', __FILE__) - -class Redmine::MenuManager::MapperTest < ActiveSupport::TestCase - context "Mapper#initialize" do - should "be tested" - end - - def test_push_onto_root - menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {}) - menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {} - - menu_mapper.exists?(:test_overview) - end - - def test_push_onto_parent - menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {}) - menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {} - menu_mapper.push :test_child, { :controller => 'projects', :action => 'show'}, {:parent => :test_overview} - - assert menu_mapper.exists?(:test_child) - assert_equal :test_child, menu_mapper.find(:test_child).name - end - - def test_push_onto_grandparent - menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {}) - menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {} - menu_mapper.push :test_child, { :controller => 'projects', :action => 'show'}, {:parent => :test_overview} - menu_mapper.push :test_grandchild, { :controller => 'projects', :action => 'show'}, {:parent => :test_child} - - assert menu_mapper.exists?(:test_grandchild) - grandchild = menu_mapper.find(:test_grandchild) - assert_equal :test_grandchild, grandchild.name - assert_equal :test_child, grandchild.parent.name - end - - def test_push_first - menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {}) - menu_mapper.push :test_second, { :controller => 'projects', :action => 'show'}, {} - menu_mapper.push :test_third, { :controller => 'projects', :action => 'show'}, {} - menu_mapper.push :test_fourth, { :controller => 'projects', :action => 'show'}, {} - menu_mapper.push :test_fifth, { :controller => 'projects', :action => 'show'}, {} - menu_mapper.push :test_first, { :controller => 'projects', :action => 'show'}, {:first => true} - - root = menu_mapper.find(:root) - assert_equal 5, root.children.size - {0 => :test_first, 1 => :test_second, 2 => :test_third, 3 => :test_fourth, 4 => :test_fifth}.each do |position, name| - assert_not_nil root.children[position] - assert_equal name, root.children[position].name - end - - end - - def test_push_before - menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {}) - menu_mapper.push :test_first, { :controller => 'projects', :action => 'show'}, {} - menu_mapper.push :test_second, { :controller => 'projects', :action => 'show'}, {} - menu_mapper.push :test_fourth, { :controller => 'projects', :action => 'show'}, {} - menu_mapper.push :test_fifth, { :controller => 'projects', :action => 'show'}, {} - menu_mapper.push :test_third, { :controller => 'projects', :action => 'show'}, {:before => :test_fourth} - - root = menu_mapper.find(:root) - assert_equal 5, root.children.size - {0 => :test_first, 1 => :test_second, 2 => :test_third, 3 => :test_fourth, 4 => :test_fifth}.each do |position, name| - assert_not_nil root.children[position] - assert_equal name, root.children[position].name - end - - end - - def test_push_after - menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {}) - menu_mapper.push :test_first, { :controller => 'projects', :action => 'show'}, {} - menu_mapper.push :test_second, { :controller => 'projects', :action => 'show'}, {} - menu_mapper.push :test_third, { :controller => 'projects', :action => 'show'}, {} - menu_mapper.push :test_fifth, { :controller => 'projects', :action => 'show'}, {} - menu_mapper.push :test_fourth, { :controller => 'projects', :action => 'show'}, {:after => :test_third} - - root = menu_mapper.find(:root) - assert_equal 5, root.children.size - {0 => :test_first, 1 => :test_second, 2 => :test_third, 3 => :test_fourth, 4 => :test_fifth}.each do |position, name| - assert_not_nil root.children[position] - assert_equal name, root.children[position].name - end - - end - - def test_push_last - menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {}) - menu_mapper.push :test_first, { :controller => 'projects', :action => 'show'}, {} - menu_mapper.push :test_second, { :controller => 'projects', :action => 'show'}, {} - menu_mapper.push :test_third, { :controller => 'projects', :action => 'show'}, {} - menu_mapper.push :test_fifth, { :controller => 'projects', :action => 'show'}, {:last => true} - menu_mapper.push :test_fourth, { :controller => 'projects', :action => 'show'}, {} - - root = menu_mapper.find(:root) - assert_equal 5, root.children.size - {0 => :test_first, 1 => :test_second, 2 => :test_third, 3 => :test_fourth, 4 => :test_fifth}.each do |position, name| - assert_not_nil root.children[position] - assert_equal name, root.children[position].name - end - - end - - def test_exists_for_child_node - menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {}) - menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {} - menu_mapper.push :test_child, { :controller => 'projects', :action => 'show'}, {:parent => :test_overview } - - assert menu_mapper.exists?(:test_child) - end - - def test_exists_for_invalid_node - menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {}) - menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {} - - assert !menu_mapper.exists?(:nothing) - end - - def test_find - menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {}) - menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {} - - item = menu_mapper.find(:test_overview) - assert_equal :test_overview, item.name - assert_equal({:controller => 'projects', :action => 'show'}, item.url) - end - - def test_find_missing - menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {}) - menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {} - - item = menu_mapper.find(:nothing) - assert_equal nil, item - end - - def test_delete - menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {}) - menu_mapper.push :test_overview, { :controller => 'projects', :action => 'show'}, {} - assert_not_nil menu_mapper.delete(:test_overview) - - assert_nil menu_mapper.find(:test_overview) - end - - def test_delete_missing - menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {}) - assert_nil menu_mapper.delete(:test_missing) - end - - test 'deleting all items' do - # Exposed by deleting :last items - Redmine::MenuManager.map :test_menu do |menu| - menu.push :not_last, Redmine::Info.help_url - menu.push :administration, { :controller => 'projects', :action => 'show'}, {:last => true} - menu.push :help, Redmine::Info.help_url, :last => true - end - - assert_nothing_raised do - Redmine::MenuManager.map :test_menu do |menu| - menu.delete(:administration) - menu.delete(:help) - menu.push :test_overview, { :controller => 'projects', :action => 'show'}, {} - end - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/23/239f83d29fc7b11e5679dea8261085d9a0c7a621.svn-base --- a/.svn/pristine/23/239f83d29fc7b11e5679dea8261085d9a0c7a621.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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 #:nodoc: - module CoreExtensions #:nodoc: - module String #:nodoc: - # Custom string conversions - module Conversions - # Parses hours format and returns a float - def to_hours - s = self.dup - s.strip! - 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 - s.gsub!(%r{^((\d+)\s*(h|hours?))?\s*((\d+)\s*(m|min)?)?$}) { |m| ($1 || $4) ? ($2.to_i + $5.to_i / 60.0) : m[0] } - end - # 2,5 => 2.5 - s.gsub!(',', '.') - begin; Kernel.Float(s); rescue; nil; end - end - - # Object#to_a removed in ruby1.9 - if RUBY_VERSION > '1.9' - def to_a - [self.dup] - end - end - end - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/23/23b009231ec6aa90fecfe01522aea6ff2ac84fb3.svn-base --- a/.svn/pristine/23/23b009231ec6aa90fecfe01522aea6ff2ac84fb3.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,40 +0,0 @@ -

<%= l(:label_home) %>

- -
- <%= textilizable Setting.welcome_text %> - <% if @news.any? %> -
-

<%=l(:label_news_latest)%>

- <%= render :partial => 'news/news', :collection => @news %> - <%= link_to l(:label_news_view_all), :controller => 'news' %> -
- <% end %> - <%= call_hook(:view_welcome_index_left, :projects => @projects) %> -
- -
- <% if @projects.any? %> -
-

<%=l(:label_project_latest)%>

-
    - <% for project in @projects %> - <% @project = project %> -
  • - <%= link_to_project project %> (<%= format_time(project.created_on) %>) - <%= textilizable project.short_description, :project => project %> -
  • - <% end %> - <% @project = nil %> -
-
- <% end %> - <%= call_hook(:view_welcome_index_right, :projects => @projects) %> -
- -<% content_for :header_tags do %> -<%= stylesheet_link_tag 'scm' %> -<%= auto_discovery_link_tag(:atom, {:controller => 'news', :action => 'index', :key => User.current.rss_key, :format => 'atom'}, - :title => "#{Setting.app_title}: #{l(:label_news_latest)}") %> -<%= auto_discovery_link_tag(:atom, {:controller => 'activities', :action => 'index', :key => User.current.rss_key, :format => 'atom'}, - :title => "#{Setting.app_title}: #{l(:label_activity)}") %> -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/23/23ef48c3079c4ad7ff9c6850f3a7bce6b2c94752.svn-base --- a/.svn/pristine/23/23ef48c3079c4ad7ff9c6850f3a7bce6b2c94752.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,55 +0,0 @@ -<% form_tag({}) do -%> -<%= hidden_field_tag 'back_url', url_for(params) %> -
- - - - -<%= sort_header_tag('spent_on', :caption => l(:label_date), :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') %> - -<%= sort_header_tag('hours', :caption => l(:field_hours)) %> - - - - -<% entries.each do |entry| -%> - hascontextmenu"> - - - - - - - - - - -<% end -%> - -
- <%= link_to image_tag('toggle_check.png'), - {}, - :onclick => 'toggleIssuesSelection(Element.up(this, "form")); return false;', - :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %> -<%= l(:field_comments) %>
<%= check_box_tag("ids[]", entry.id, false, :id => nil) %><%= format_date(entry.spent_on) %><%= link_to_user(entry.user) %><%=h entry.activity %><%= link_to_project(entry.project) %> -<% if entry.issue -%> -<%= entry.issue.visible? ? link_to_issue(entry.issue, :truncate => 50) : "##{entry.issue.id}" -%> -<% end -%> -<%=h entry.comments %><%= html_hours("%.2f" % entry.hours) %> -<% if entry.editable_by?(User.current) -%> - <%= link_to image_tag('edit.png'), {:controller => 'timelog', :action => 'edit', :id => entry, :project_id => nil}, - :title => l(:button_edit) %> - <%= link_to image_tag('delete.png'), {:controller => 'timelog', :action => 'destroy', :id => entry, :project_id => nil}, - :confirm => l(:text_are_you_sure), - :method => :delete, - :title => l(:button_delete) %> -<% end -%> -
-
-<% end -%> - -<%= context_menu time_entries_context_menu_path %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/23/23f046e7217770bca11bfe9989b755a158f48868.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/23/23f046e7217770bca11bfe9989b755a158f48868.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,83 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class Token < ActiveRecord::Base + belongs_to :user + validates_uniqueness_of :value + + before_create :delete_previous_tokens, :generate_new_token + + @@validity_time = 1.day + + def generate_new_token + self.value = Token.generate_token_value + end + + # Return true if token has expired + def expired? + return Time.now > self.created_on + @@validity_time + end + + # Delete all expired tokens + def self.destroy_expired + Token.delete_all ["action NOT IN (?) AND created_on < ?", ['feeds', 'api'], Time.now - @@validity_time] + end + + # Returns the active user who owns the key for the given action + def self.find_active_user(action, key, validity_days=nil) + user = find_user(action, key, validity_days) + if user && user.active? + user + end + end + + # Returns the user who owns the key for the given action + def self.find_user(action, key, validity_days=nil) + token = find_token(action, key, validity_days) + if token + token.user + end + end + + # Returns the token for action and key with an optional + # validity duration (in number of days) + def self.find_token(action, key, validity_days=nil) + action = action.to_s + key = key.to_s + return nil unless action.present? && key =~ /\A[a-z0-9]+\z/i + + token = Token.where(:action => action, :value => key).first + if token && (token.action == action) && (token.value == key) && token.user + if validity_days.nil? || (token.created_on > validity_days.days.ago) + token + end + end + end + + def self.generate_token_value + Redmine::Utils.random_hex(20) + end + + private + + # Removes obsolete tokens (same user and action) + def delete_previous_tokens + if user + Token.delete_all(['user_id = ? AND action = ?', user.id, action]) + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/23/23f71ac728c941d3ffe5e3105dc856980b211079.svn-base --- a/.svn/pristine/23/23f71ac728c941d3ffe5e3105dc856980b211079.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -plugin mail template loaded from application \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/23/23ff873f0bf0f2e45a3709caf12bc84dab7be570.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/23/23ff873f0bf0f2e45a3709caf12bc84dab7be570.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,106 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +module SettingsHelper + 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}, + {:name => 'notifications', :partial => 'settings/notifications', :label => :field_mail_notification}, + {:name => 'mail_handler', :partial => 'settings/mail_handler', :label => :label_incoming_emails}, + {:name => 'repositories', :partial => 'settings/repositories', :label => :label_repository_plural} + ] + end + + def setting_select(setting, choices, options={}) + if blank_text = options.delete(:blank) + choices = [[blank_text.is_a?(Symbol) ? l(blank_text) : blank_text, '']] + choices + end + setting_label(setting, options).html_safe + + select_tag("settings[#{setting}]", + options_for_select(choices, Setting.send(setting).to_s), + options).html_safe + end + + def setting_multiselect(setting, choices, options={}) + setting_values = Setting.send(setting) + setting_values = [] unless setting_values.is_a?(Array) + + content_tag("label", l(options[:label] || "setting_#{setting}")) + + hidden_field_tag("settings[#{setting}][]", '').html_safe + + choices.collect do |choice| + text, value = (choice.is_a?(Array) ? choice : [choice, choice]) + content_tag( + 'label', + check_box_tag( + "settings[#{setting}][]", + value, + setting_values.include?(value), + :id => nil + ) + text.to_s, + :class => (options[:inline] ? 'inline' : 'block') + ) + end.join.html_safe + end + + def setting_text_field(setting, options={}) + setting_label(setting, options).html_safe + + text_field_tag("settings[#{setting}]", Setting.send(setting), options).html_safe + end + + def setting_text_area(setting, options={}) + setting_label(setting, options).html_safe + + text_area_tag("settings[#{setting}]", Setting.send(setting), options).html_safe + end + + def setting_check_box(setting, options={}) + setting_label(setting, options).html_safe + + hidden_field_tag("settings[#{setting}]", 0, :id => nil).html_safe + + check_box_tag("settings[#{setting}]", 1, Setting.send("#{setting}?"), options).html_safe + end + + def setting_label(setting, options={}) + label = options.delete(:label) + label != false ? label_tag("settings_#{setting}", l(label || "setting_#{setting}")).html_safe : '' + end + + # Renders a notification field for a Redmine::Notifiable option + def notification_field(notifiable) + return content_tag(:label, + check_box_tag('settings[notified_events][]', + notifiable.name, + Setting.notified_events.include?(notifiable.name), :id => nil).html_safe + + l_or_humanize(notifiable.name, :prefix => 'label_').html_safe, + :class => notifiable.parent.present? ? "parent" : '').html_safe + end + + def cross_project_subtasks_options + options = [ + [:label_disabled, ''], + [:label_cross_project_system, 'system'], + [:label_cross_project_tree, 'tree'], + [:label_cross_project_hierarchy, 'hierarchy'], + [:label_cross_project_descendants, 'descendants'] + ] + + options.map {|label, value| [l(label), value.to_s]} + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/24/244393349ccf9a5e6e3ea60c976d07e62fd15de2.svn-base --- a/.svn/pristine/24/244393349ccf9a5e6e3ea60c976d07e62fd15de2.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +0,0 @@ -

<%= l(:mail_body_wiki_content_added, :id => link_to(h(@wiki_content.page.pretty_title), @wiki_content_url), - :author => h(@wiki_content.author)) %>
-<%=h @wiki_content.comments %>

diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/24/244c888e0b10e75677f5d843ff3bbb49ca914d36.svn-base --- a/.svn/pristine/24/244c888e0b10e75677f5d843ff3bbb49ca914d36.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1013 +0,0 @@ -# Hebrew translation for Redmine -# Initiated by Dotan Nahum (dipidi@gmail.com) -# Jul 2010 - Updated by Orgad Shaneh (orgads@gmail.com) - -he: - direction: rtl - date: - formats: - default: "%d/%m/%Y" - short: "%d/%m" - long: "%d/%m/%Y" - only_day: "%e" - - day_names: [ר×שון, שני, שלישי, רביעי, חמישי, שישי, שבת] - abbr_day_names: ["×'", "ב'", "×’'", "ד'", "×”'", "ו'", "ש'"] - month_names: [~, ינו×ר, פברו×ר, מרץ, ×פריל, מ××™, יוני, יולי, ×וגוסט, ספטמבר, ×וקטובר, נובמבר, דצמבר] - abbr_month_names: [~, ×™×× , פבר, מרץ, ×פר, מ××™, יונ, יול, ×וג, ספט, ×וק, נוב, דצמ] - order: - - :day - - :month - - :year - - time: - formats: - default: "%a %d/%m/%Y %H:%M:%S" - time: "%H:%M" - short: "%d %b %H:%M" - long: "%B %d, %Y %H:%M" - only_second: "%S" - - datetime: - formats: - default: "%d-%m-%YT%H:%M:%S%Z" - - am: 'am' - pm: 'pm' - - datetime: - distance_in_words: - half_a_minute: 'חצי דקה' - less_than_x_seconds: - zero: 'פחות משניה' - one: 'פחות משניה' - other: 'פחות מ־%{count} שניות' - x_seconds: - one: 'שניה ×חת' - other: '%{count} שניות' - less_than_x_minutes: - zero: 'פחות מדקה ×חת' - one: 'פחות מדקה ×חת' - other: 'פחות מ־%{count} דקות' - x_minutes: - one: 'דקה ×חת' - other: '%{count} דקות' - about_x_hours: - one: 'בערך שעה ×חת' - other: 'בערך %{count} שעות' - x_days: - one: '×™×•× ×חד' - other: '%{count} ימי×' - about_x_months: - one: 'בערך חודש ×חד' - other: 'בערך %{count} חודשי×' - x_months: - one: 'חודש ×חד' - other: '%{count} חודשי×' - about_x_years: - one: 'בערך שנה ×חת' - other: 'בערך %{count} שני×' - over_x_years: - one: 'מעל שנה ×חת' - other: 'מעל %{count} שני×' - almost_x_years: - one: "כמעט שנה" - other: "כמעט %{count} שני×" - - number: - format: - precision: 3 - separator: '.' - delimiter: ',' - currency: - format: - unit: 'ש"×—' - precision: 2 - format: '%u %n' - human: - storage_units: - format: "%n %u" - units: - byte: - one: "בייט" - other: "בתי×" - kb: "KB" - mb: "MB" - gb: "GB" - tb: "TB" - - support: - array: - sentence_connector: "וג×" - skip_last_comma: true - - activerecord: - errors: - template: - header: - one: "1 error prohibited this %{model} from being saved" - other: "%{count} errors prohibited this %{model} from being saved" - messages: - inclusion: "×œ× × ×›×œ×œ ברשימה" - exclusion: "×œ× ×–×ž×™×Ÿ" - invalid: "×œ× ×•×œ×™×“×™" - confirmation: "×œ× ×ª×•×× ×œ×ישור" - accepted: "חייב ב×ישור" - empty: "חייב להכלל" - blank: "חייב להכלל" - too_long: "×רוך מדי (×œ× ×™×•×ª×¨ מ־%{count} תוי×)" - too_short: "קצר מדי (×œ× ×™×•×ª×¨ מ־%{count} תוי×)" - wrong_length: "×œ× ×‘×ורך הנכון (חייב להיות %{count} תוי×)" - taken: "×œ× ×–×ž×™×Ÿ" - not_a_number: "×”×•× ×œ× ×ž×¡×¤×¨" - greater_than: "חייב להיות גדול מ־%{count}" - greater_than_or_equal_to: "חייב להיות גדול ×ו שווה ל־%{count}" - equal_to: "חייב להיות שווה ל־%{count}" - less_than: "חייב להיות קטן מ־%{count}" - less_than_or_equal_to: "חייב להיות קטן ×ו שווה ל־%{count}" - odd: "חייב להיות ××™ זוגי" - even: "חייב להיות זוגי" - greater_than_start_date: "חייב להיות מ×וחר יותר מת×ריך ההתחלה" - not_same_project: "×œ× ×©×™×™×š ל×ותו הפרויקט" - circular_dependency: "קשר ×–×” יצור תלות מעגלית" - cant_link_an_issue_with_a_descendant: "×œ× × ×™×ª×Ÿ לקשר × ×•×©× ×œ×ª×ªÖ¾×ž×©×™×ž×” שלו" - - actionview_instancetag_blank_option: בחר בבקשה - - general_text_No: 'ל×' - general_text_Yes: 'כן' - general_text_no: 'ל×' - general_text_yes: 'כן' - general_lang_name: 'Hebrew (עברית)' - general_csv_separator: ',' - general_csv_decimal_separator: '.' - general_csv_encoding: ISO-8859-8 - general_pdf_encoding: UTF-8 - general_first_day_of_week: '7' - - notice_account_updated: החשבון עודכן בהצלחה! - notice_account_invalid_creditentials: ×©× ×ž×©×ª×ž×© ×ו סיסמה ×©×’×•×™×™× - notice_account_password_updated: הסיסמה עודכנה בהצלחה! - notice_account_wrong_password: סיסמה שגויה - notice_account_register_done: החשבון נוצר בהצלחה. להפעלת החשבון לחץ על הקישור שנשלח לדו×"ל שלך. - notice_account_unknown_email: משתמש ×œ× ×ž×•×›×¨. - notice_can_t_change_password: החשבון ×”×–×” משתמש במקור הזדהות חיצוני. שינוי סיסמה הינו בילתי ×פשר - notice_account_lost_email_sent: דו×"ל ×¢× ×”×•×¨×ות לבחירת סיסמה חדשה נשלח ×ליך. - notice_account_activated: חשבונך הופעל. ×תה יכול להתחבר כעת. - notice_successful_create: יצירה מוצלחת. - notice_successful_update: עידכון מוצלח. - notice_successful_delete: מחיקה מוצלחת. - notice_successful_connection: חיבור מוצלח. - notice_file_not_found: הדף ש×תה מנסה לגשת ×ליו ×ינו ×§×™×™× ×ו שהוסר. - notice_locking_conflict: המידע עודכן על ידי משתמש ×חר. - notice_not_authorized: ×ינך מורשה לר×ות דף ×–×”. - notice_not_authorized_archived_project: הפרויקט ש×תה מנסה לגשת ×ליו × ×ž×¦× ×‘×רכיון. - notice_email_sent: "דו×ל נשלח לכתובת %{value}" - notice_email_error: "×רעה שגי××” בעת שליחת הדו×ל (%{value})" - notice_feeds_access_key_reseted: מפתח ×”Ö¾RSS שלך ×ופס. - notice_api_access_key_reseted: מפתח הגישה שלך ל־API ×ופס. - notice_failed_to_save_issues: "נכשרת בשמירת %{count} נוש××™× ×‘ %{total} נבחרו: %{ids}." - notice_failed_to_save_members: "כשלון בשמירת חבר(×™×): %{errors}." - notice_no_issue_selected: "×œ× × ×‘×—×¨ ××£ נוש×! בחר בבקשה ×ת הנוש××™× ×©×‘×¨×¦×•× ×š לערוך." - notice_account_pending: "החשבון שלך נוצר ועתה מחכה ל×ישור מנהל המערכת." - notice_default_data_loaded: ×פשרויות ברירת מחדל מופעלות. - notice_unable_delete_version: ×œ× × ×™×ª×Ÿ למחוק גירסה - notice_unable_delete_time_entry: ×œ× × ×™×ª×Ÿ למחוק רשומת זמן. - notice_issue_done_ratios_updated: ×חוזי התקדמות ×œ× ×•×©× ×¢×•×“×›× ×•. - - error_can_t_load_default_data: "×פשרויות ברירת המחדל ×œ× ×”×¦×œ×™×—×• להיטען: %{value}" - error_scm_not_found: כניסה ו\×ו מהדורה ××™× × ×§×™×™×ž×™× ×‘×ž×גר. - error_scm_command_failed: "×רעה שגי××” בעת ניסון גישה למ×גר: %{value}" - error_scm_annotate: "הכניסה ×œ× ×§×™×™×ž×ª ×ו ×©×œ× × ×™×ª×Ÿ לת×ר ×ותה." - error_issue_not_found_in_project: 'הנוש××™× ×œ× × ×ž×¦×ו ×ו ××™× × ×©×™×›×™× ×œ×¤×¨×•×™×§×˜' - error_no_tracker_in_project: ×œ× ×”×•×’×“×¨ סיווג לפרויקט ×–×”. × × ×‘×“×•×§ ×ת הגדרות הפרויקט. - error_no_default_issue_status: ×œ× ×ž×•×’×“×¨ מצב ברירת מחדל לנוש××™×. × × ×‘×“×•×§ ×ת התצורה ("ניהול -> מצבי נוש×"). - error_can_not_delete_custom_field: ×œ× × ×™×ª×Ÿ למחוק שדה מות×× ×ישית - error_can_not_delete_tracker: ×§×™×™×ž×™× × ×•×©××™× ×‘×¡×™×•×•×’ ×–×”, ×•×œ× × ×™×ª×Ÿ למחוק ×ותו. - error_can_not_remove_role: תפקיד ×–×” × ×ž×¦× ×‘×©×™×ž×•×©, ×•×œ× × ×™×ª×Ÿ למחוק ×ותו. - error_can_not_reopen_issue_on_closed_version: ×œ× × ×™×ª×Ÿ לפתוח מחדש × ×•×©× ×©×ž×©×•×™×š לגירסה סגורה - error_can_not_archive_project: ×œ× × ×™×ª×Ÿ ל×רכב פרויקט ×–×” - error_issue_done_ratios_not_updated: ×חוז התקדמות ×œ× ×•×©× ×œ× ×¢×•×“×›×Ÿ. - error_workflow_copy_source: × × ×‘×—×¨ סיווג ×ו תפקיד מקור - error_workflow_copy_target: × × ×‘×—×¨ תפקיד(×™×) וסיווג(×™×) - error_unable_delete_issue_status: ×œ× × ×™×ª×Ÿ למחוק מצב × ×•×©× - error_unable_to_connect: ×œ× × ×™×ª×Ÿ להתחבר (%{value}) - warning_attachments_not_saved: "כשלון בשמירת %{count} קבצי×." - - mail_subject_lost_password: "סיסמת ×”Ö¾%{value} שלך" - mail_body_lost_password: 'לשינו סיסמת ×”Ö¾Redmine שלך, לחץ על הקישור הב×:' - mail_subject_register: "הפעלת חשבון %{value}" - mail_body_register: 'להפעלת חשבון ×”Ö¾Redmine שלך, לחץ על הקישור הב×:' - mail_body_account_information_external: "×תה יכול להשתמש בחשבון %{value} כדי להתחבר" - mail_body_account_information: פרטי החשבון שלך - mail_subject_account_activation_request: "בקשת הפעלה לחשבון %{value}" - mail_body_account_activation_request: "משתמש חדש (%{value}) נרש×. החשבון שלו מחכה ל×ישור שלך:" - mail_subject_reminder: "%{count} נוש××™× ×ž×™×•×¢×“×™× ×œ×”×’×©×” ×‘×™×ž×™× ×”×§×¨×•×‘×™× (%{days})" - mail_body_reminder: "%{count} נוש××™× ×©×ž×™×•×¢×“×™× ×ליך ×ž×™×•×¢×“×™× ×œ×”×’×©×” בתוך %{days} ימי×:" - mail_subject_wiki_content_added: "דף ×”Ö¾wiki â€'%{id}' נוסף" - mail_body_wiki_content_added: דף ×”Ö¾wiki â€'%{id}' נוסף ×¢"×™ %{author}. - mail_subject_wiki_content_updated: "דף ×”Ö¾wiki â€'%{id}' עודכן" - mail_body_wiki_content_updated: דף ×”Ö¾wiki â€'%{id}' עודכן ×¢"×™ %{author}. - - gui_validation_error: שגי××” 1 - gui_validation_error_plural: "%{count} שגי×ות" - - 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: ביטוי רגיל - field_min_length: ×ורך מינימ×לי - field_max_length: ×ורך מקסימ×לי - field_value: ערך - field_category: קטגוריה - field_title: כותרת - field_project: פרויקט - field_issue: × ×•×©× - field_status: מצב - field_notes: הערות - field_is_closed: × ×•×©× ×¡×’×•×¨ - field_is_default: ערך ברירת מחדל - field_tracker: סיווג - field_subject: ×©× × ×•×©× - field_due_date: ת×ריך ×¡×™×•× - field_assigned_to: ×חר××™ - field_priority: עדיפות - field_fixed_version: גירסת יעד - field_user: מתשמש - field_principal: מנהל - field_role: תפקיד - field_homepage: דף הבית - field_is_public: פומבי - field_parent: תת פרויקט של - field_is_in_roadmap: נוש××™× ×”×ž×•×¦×’×™× ×‘×ž×¤×ª ×”×“×¨×›×™× - field_login: ×©× ×ž×©×ª×ž×© - field_mail_notification: הודעות דו×"ל - field_admin: ניהול - field_last_login_on: התחברות ×חרונה - field_language: שפה - field_effective_date: ת×ריך - field_password: סיסמה - field_new_password: סיסמה חדשה - field_password_confirmation: ×ישור - field_version: גירסה - field_type: סוג - field_host: שרת - field_port: פורט - field_account: חשבון - field_base_dn: בסיס DN - field_attr_login: תכונת התחברות - field_attr_firstname: תכונת ×©× ×¤×¨×˜×™× - field_attr_lastname: תכונת ×©× ×ž×©×¤×—×” - field_attr_mail: תכונת דו×"ל - field_onthefly: יצירת ×ž×©×ª×ž×©×™× ×–×¨×™×–×” - field_start_date: ת×ריך התחלה - field_done_ratio: "% גמור" - field_auth_source: מקור הזדהות - field_hide_mail: ×”×—×‘× ×ת כתובת הדו×"ל שלי - field_comments: הערות - field_url: URL - field_start_page: דף התחלתי - field_subproject: תת־פרויקט - field_hours: שעות - field_activity: פעילות - field_spent_on: ת×ריך - field_identifier: מזהה - field_is_filter: משמש כמסנן - field_issue_to: נוש××™× ×§×©×•×¨×™× - field_delay: עיקוב - field_assignable: ניתן להקצות נוש××™× ×œ×ª×¤×§×™×“ ×–×” - field_redirect_existing_links: העבר ×§×™×©×•×¨×™× ×§×™×™×ž×™× - field_estimated_hours: זמן משוער - field_column_names: עמודות - field_time_entries: ×¨×™×©×•× ×–×ž× ×™× - field_time_zone: ×יזור זמן - field_searchable: ניתן לחיפוש - field_default_value: ערך ברירת מחדל - field_comments_sorting: הצג הערות - field_parent_title: דף ×ב - field_editable: ניתן לעריכה - field_watcher: צופה - field_identity_url: כתובת OpenID - field_content: תוכן - field_group_by: קבץ ×ת התוצ×ות לפי - field_sharing: שיתוף - field_parent_issue: משימת ×ב - field_text: שדה טקסט - - setting_app_title: כותרת ×™×©×•× - setting_app_subtitle: תת־כותרת ×™×©×•× - setting_welcome_text: טקסט "ברוך הב×" - setting_default_language: שפת ברירת מחדל - setting_login_required: דרושה הזדהות - setting_self_registration: ×פשר הרשמה עצמית - setting_attachment_max_size: גודל דבוקה מקסימ×לי - setting_issues_export_limit: גבול ×™×¦×•× × ×•×©××™× - setting_mail_from: כתובת שליחת דו×"ל - setting_bcc_recipients: מוסתר (bcc) - setting_plain_text_mail: טקסט פשוט בלבד (×œ×œ× HTML) - setting_host_name: ×©× ×©×¨×ª - setting_text_formatting: עיצוב טקסט - setting_wiki_compression: כיווץ היסטורית wiki - setting_feeds_limit: גבול תוכן הזנות - setting_default_projects_public: ×¤×¨×•×™×§×˜×™× ×—×“×©×™× ×”×™× × ×¤×•×ž×‘×™×™× ×›×‘×¨×™×¨×ª מחדל - setting_autofetch_changesets: משיכה ×וטומטית של ×©×™× ×•×™×™× - setting_sys_api_enabled: ×פשר שירות רשת לניהול המ×גר - setting_commit_ref_keywords: מילות מפתח מקשרות - setting_commit_fix_keywords: מילות מפתח מתקנות - setting_autologin: התחברות ×וטומטית - setting_date_format: פורמט ת×ריך - setting_time_format: פורמט זמן - setting_cross_project_issue_relations: הרשה קישור נוש××™× ×‘×™×Ÿ ×¤×¨×•×™×§×˜×™× - setting_issue_list_default_columns: עמודות ברירת מחדל המוצגות ברשימת הנוש××™× - setting_emails_footer: תחתית דו×"ל - setting_protocol: פרוטוקול - setting_per_page_options: ×פשרויות ××•×‘×™×§×˜×™× ×œ×¤×™ דף - setting_user_format: פורמט הצגת ×ž×©×ª×ž×©×™× - setting_activity_days_default: ×™×ž×™× ×”×ž×•×¦×’×™× ×¢×œ פעילות הפרויקט - setting_display_subprojects_issues: הצג נוש××™× ×©×œ ×ª×ª×™Ö¾×¤×¨×•×™×§×˜×™× ×›×‘×¨×™×¨×ª מחדל - setting_enabled_scm: ×פשר ניהול תצורה - setting_mail_handler_body_delimiters: חתוך כתובות דו×ר ×חרי ×חת משורות ×לה - setting_mail_handler_api_enabled: ×פשר שירות רשת לדו×ר נכנס - setting_mail_handler_api_key: מפתח API - setting_sequential_project_identifiers: השתמש ×‘×ž×¡×¤×¨×™× ×¢×•×§×‘×™× ×œ×ž×–×”×™ פרויקט - setting_gravatar_enabled: שימוש בצלמיות ×ž×©×ª×ž×©×™× ×žÖ¾Gravatar - setting_gravatar_default: תמונת Gravatar ברירת מחדל - setting_diff_max_lines_displayed: מספר מירבי של שורות בתצוגת ×©×™× ×•×™×™× - setting_file_max_size_displayed: גודל מירבי של מלל המוצג בתוך השורה - setting_repository_log_display_limit: מספר מירבי של מהדורות המוצגות ביומן קובץ - setting_openid: ×פשר התחברות ×•×¨×™×©×•× ×‘×מצעות OpenID - setting_password_min_length: ×ורך סיסמה מינימ×לי - setting_new_project_user_role_id: התפקיד שמוגדר למשתמש פשוט ×שר יוצר פרויקט - setting_default_projects_modules: ×ž×•×“×•×œ×™× ×ž××•×¤×©×¨×™× ×‘×‘×¨×™×¨×ª מחדל עבור ×¤×¨×•×™×§×˜×™× ×—×“×©×™× - setting_issue_done_ratio: חשב ×חוז התקדמות ×‘× ×•×©× ×¢× - setting_issue_done_ratio_issue_field: השתמש בשדה ×”× ×•×©× - setting_issue_done_ratio_issue_status: השתמש במצב ×”× ×•×©× - setting_start_of_week: השבוע מתחיל ×‘×™×•× - setting_rest_api_enabled: ×פשר שירות רשת REST - setting_cache_formatted_text: שמור טקסט מעוצב במטמון - setting_default_notification_option: ×פשרות התר××” ברירת־מחדל - - permission_add_project: יצירת פרויקט - permission_add_subprojects: יצירת תתי־פרויקט - permission_edit_project: עריכת פרויקט - permission_select_project_modules: בחירת מודולי פרויקט - permission_manage_members: ניהול ×—×‘×¨×™× - permission_manage_project_activities: נהל פעילויות פרויקט - permission_manage_versions: ניהול גירס×ות - permission_manage_categories: ניהול קטגוריות נוש××™× - permission_view_issues: צפיה בנוש××™× - 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_delete_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: מחיקת הודעות של עצמו - permission_export_wiki_pages: ×™×¦× ×“×¤×™ wiki - permission_manage_subtasks: נהל תתי־משימות - - project_module_issue_tracking: מעקב נוש××™× - project_module_time_tracking: מעקב ×חר ×–×ž× ×™× - project_module_news: חדשות - project_module_documents: ×ž×¡×ž×›×™× - project_module_files: ×§×‘×¦×™× - project_module_wiki: Wiki - project_module_repository: מ×גר - project_module_boards: לוחות - project_module_calendar: לוח שנה - project_module_gantt: ×’×נט - - label_user: משתמש - label_user_plural: ×ž×©×ª×ž×©×™× - label_user_new: משתמש חדש - label_user_anonymous: ×למוני - label_project: פרויקט - label_project_new: פרויקט חדש - label_project_plural: ×¤×¨×•×™×§×˜×™× - label_x_projects: - zero: ×œ×œ× ×¤×¨×•×™×§×˜×™× - one: פרויקט ×חד - other: "%{count} פרויקטי×" - label_project_all: כל ×”×¤×¨×•×™×§×˜×™× - label_project_latest: ×”×¤×¨×•×™×§×˜×™× ×”×—×“×©×™× ×‘×™×•×ª×¨ - label_issue: × ×•×©× - label_issue_new: × ×•×©× ×—×“×© - label_issue_plural: נוש××™× - label_issue_view_all: צפה בכל הנוש××™× - label_issues_by: "נוש××™× ×œ×¤×™ %{value}" - label_issue_added: × ×•×©× × ×•×¡×£ - label_issue_updated: × ×•×©× ×¢×•×“×›×Ÿ - label_document: מסמך - label_document_new: מסמך חדש - label_document_plural: ×ž×¡×ž×›×™× - label_document_added: מסמך נוסף - label_role: תפקיד - label_role_plural: ×ª×¤×§×™×“×™× - label_role_new: תפקיד חדש - label_role_and_permissions: ×ª×¤×§×™×“×™× ×•×”×¨×©×ות - label_member: חבר - label_member_new: חבר חדש - label_member_plural: ×—×‘×¨×™× - label_tracker: סיווג - label_tracker_plural: ×¡×™×•×•×’×™× - label_tracker_new: סיווג חדש - label_workflow: זרימת עבודה - label_issue_status: מצב × ×•×©× - label_issue_status_plural: מצבי × ×•×©× - label_issue_status_new: מצב חדש - label_issue_category: קטגורית × ×•×©× - label_issue_category_plural: קטגוריות × ×•×©× - label_issue_category_new: קטגוריה חדשה - label_custom_field: שדה ×ישי - label_custom_field_plural: שדות ××™×©×™×™× - label_custom_field_new: שדה ×ישי חדש - label_enumerations: ×ינומרציות - label_enumeration_new: ערך חדש - label_information: מידע - label_information_plural: מידע - label_please_login: × × ×”×ª×—×‘×¨ - label_register: הרשמה - label_login_with_open_id_option: ×ו התחבר ב×מצעות OpenID - label_password_lost: ×בדה הסיסמה? - label_home: דף הבית - label_my_page: הדף שלי - label_my_account: החשבון שלי - label_my_projects: ×”×¤×¨×•×™×§×˜×™× ×©×œ×™ - label_my_page_block: בלוק הדף שלי - label_administration: ניהול - label_login: התחבר - label_logout: התנתק - label_help: עזרה - label_reported_issues: נוש××™× ×©×“×•×•×—×• - label_assigned_to_me_issues: נוש××™× ×©×”×•×¦×‘×• לי - label_last_login: התחברות ×חרונה - label_registered_on: × ×¨×©× ×‘×ª×ריך - label_activity: פעילות - label_overall_activity: פעילות כוללת - label_user_activity: "הפעילות של %{value}" - label_new: חדש - label_logged_as: מחובר ×› - label_environment: סביבה - label_authentication: הזדהות - label_auth_source: מקור הזדהות - label_auth_source_new: מקור הזדהות חדש - label_auth_source_plural: מקורות הזדהות - label_subproject_plural: ×ª×ªÖ¾×¤×¨×•×™×§×˜×™× - label_subproject_new: תת־פרויקט חדש - label_and_its_subprojects: "%{value} וכל ×ª×ª×™Ö¾×”×¤×¨×•×™×§×˜×™× ×©×œ×•" - label_min_max_length: ×ורך מינימ×לי - מקסימ×לי - label_list: רשימה - label_date: ת×ריך - label_integer: מספר ×©×œ× - label_float: צף - label_boolean: ערך בולי×× ×™ - label_string: טקסט - label_text: טקסט ×רוך - label_attribute: תכונה - label_attribute_plural: תכונות - label_download: "הורדה %{count}" - label_download_plural: "%{count} הורדות" - 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_settings: הגדרות - label_overview: מבט רחב - label_version: גירסה - label_version_new: גירסה חדשה - label_version_plural: גירס×ות - label_close_versions: סגור גירס×ות שהושלמו - 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_x_open_issues_abbr_on_total: - zero: 0 ×¤×ª×•×—×™× / %{total} - one: 1 פתוח / %{total} - other: "%{count} ×¤×ª×•×—×™× / %{total}" - label_x_open_issues_abbr: - zero: 0 ×¤×ª×•×—×™× - one: 1 פתוח - other: "%{count} פתוחי×" - label_x_closed_issues_abbr: - zero: 0 ×¡×’×•×¨×™× - one: 1 סגור - other: "%{count} סגורי×" - 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: ×’×נט - label_internal: פנימי - label_last_changes: "%{count} ×©×™× ×•×™× ×חרוני×" - label_change_view_all: צפה בכל ×”×©×™× ×•×™× - label_personalize_page: הת×× ×ישית דף ×–×” - label_comment: תגובה - label_comment_plural: תגובות - label_x_comments: - zero: ×ין הערות - one: הערה ×חת - other: "%{count} הערות" - label_comment_add: הוסף תגובה - label_comment_added: תגובה נוספה - label_comment_delete: מחק תגובות - label_query: ש×ילתה ×ישית - label_query_plural: ש×ילתות ×ישיות - label_query_new: ש×ילתה חדשה - label_filter_add: הוסף מסנן - label_filter_plural: ×ž×¡× × ×™× - label_equals: ×”×•× - label_not_equals: ×”×•× ×œ× - label_in_less_than: בפחות מ - label_in_more_than: ביותר מ - label_greater_or_equal: ">=" - label_less_or_equal: <= - label_in: ב - label_today: ×”×™×•× - label_all_time: תמיד - label_yesterday: ×תמול - label_this_week: השבוע - label_last_week: השבוע שעבר - label_last_n_days: "ב־%{count} ×™×ž×™× ×חרוני×" - label_this_month: החודש - label_last_month: חודש שעבר - label_this_year: השנה - label_date_range: טווח ת××¨×™×›×™× - label_less_than_ago: פחות מ - label_more_than_ago: יותר מ - label_ago: לפני - label_contains: מכיל - label_not_contains: ×œ× ×ž×›×™×œ - label_day_plural: ×™×ž×™× - label_repository: מ×גר - label_repository_plural: מ××’×¨×™× - label_browse: סייר - label_modification: "שינוי %{count}" - label_modification_plural: "%{count} שינויי×" - label_branch: ×¢× ×£ - label_tag: סימון - label_revision: מהדורה - label_revision_plural: מהדורות - label_revision_id: מהדורה %{value} - label_associated_revisions: מהדורות קשורות - label_added: נוסף - label_modified: שונה - label_copied: הועתק - label_renamed: ×”×©× ×©×•× ×” - label_deleted: נמחק - label_latest_revision: מהדורה ×חרונה - label_latest_revision_plural: מהדורות ×חרונות - label_view_revisions: צפה במהדורות - label_view_all_revisions: צפה בכל המהדורות - label_max_size: גודל מקסימ×לי - label_sort_highest: ×”×–×– לר×שית - label_sort_higher: ×”×–×– למעלה - label_sort_lower: ×”×–×– למטה - label_sort_lowest: ×”×–×– לתחתית - label_roadmap: מפת ×”×“×¨×›×™× - label_roadmap_due_in: "נגמר בעוד %{value}" - label_roadmap_overdue: "%{value} מ×חר" - label_roadmap_no_issues: ×ין נוש××™× ×œ×’×™×¨×¡×” זו - label_search: חפש - label_result_plural: תוצ×ות - label_all_words: כל ×”×ž×™×œ×™× - label_wiki: Wiki - label_wiki_edit: ערוך wiki - label_wiki_edit_plural: עריכות wiki - label_wiki_page: דף Wiki - label_wiki_page_plural: דפי wiki - label_index_by_title: סדר על פי כותרת - label_index_by_date: סדר על פי ת×ריך - label_current_version: גירסה נוכחית - label_preview: תצוגה מקדימה - label_feed_plural: הזנות - label_changes_details: פירוט כל ×”×©×™× ×•×™×™× - label_issue_tracking: מעקב ×חר נוש××™× - label_spent_time: זמן שהושקע - label_overall_spent_time: זמן שהושקע סה"×› - label_f_hour: "%{value} שעה" - label_f_hour_plural: "%{value} שעות" - label_time_tracking: מעקב ×–×ž× ×™× - label_change_plural: ×©×™× ×•×™×™× - label_statistics: סטטיסטיקות - label_commits_per_month: הפקדות לפי חודש - label_commits_per_author: הפקדות לפי כותב - label_view_diff: צפה ×‘×©×™× ×•×™×™× - label_diff_inline: בתוך השורה - label_diff_side_by_side: צד לצד - label_options: ×פשרויות - label_copy_workflow_from: העתק זירמת עבודה מ - label_permissions_report: דו"×— הרש×ות - label_watched_issues: נוש××™× ×©× ×¦×¤×• - label_related_issues: נוש××™× ×§×©×•×¨×™× - label_applied_status: מצב מוחל - label_loading: טוען... - label_relation_new: קשר חדש - label_relation_delete: מחק קשר - label_relates_to: קשור ל - label_duplicates: מכפיל ×ת - label_duplicated_by: שוכפל ×¢"×™ - label_blocks: ×—×•×¡× ×ת - label_blocked_by: ×—×¡×•× ×¢"×™ - label_precedes: ×ž×§×“×™× ×ת - label_follows: עוקב ×חרי - label_end_to_start: מהתחלה לסוף - label_end_to_end: מהסוף לסוף - label_start_to_start: מהתחלה להתחלה - label_start_to_end: מהתחלה לסוף - label_stay_logged_in: הש×ר מחובר - label_disabled: מבוטל - label_show_completed_versions: הצג גירס×ות גמורות - label_me: ×× ×™ - label_board: ×¤×•×¨×•× - label_board_new: ×¤×•×¨×•× ×—×“×© - label_board_plural: ×¤×•×¨×•×ž×™× - label_board_locked: נעול - label_board_sticky: דביק - label_topic_plural: נוש××™× - label_message_plural: הודעות - label_message_last: הודעה ×חרונה - label_message_new: הודעה חדשה - label_message_posted: הודעה נוספה - label_reply_plural: השבות - label_send_information: שלח מידע על חשבון למשתמש - label_year: שנה - label_month: חודש - label_week: שבוע - label_date_from: מת×ריך - label_date_to: עד - label_language_based: מבוסס שפה - label_sort_by: "מיין לפי %{value}" - label_send_test_email: שלח דו×"ל בדיקה - label_feeds_access_key: מפתח גישה ל־RSS - label_missing_feeds_access_key: חסר מפתח גישה ל־RSS - label_feeds_access_key_created_on: "מפתח הזנת RSS נוצר לפני%{value}" - label_module_plural: ×ž×•×“×•×œ×™× - label_added_time_by: 'נוסף ×¢"×™ %{author} לפני %{age}' - label_updated_time_by: 'עודכן ×¢"×™ %{author} לפני %{age}' - label_updated_time: "עודכן לפני %{value} " - label_jump_to_a_project: קפוץ לפרויקט... - label_file_plural: ×§×‘×¦×™× - label_changeset_plural: סדרות ×©×™× ×•×™×™× - label_default_columns: עמודת ברירת מחדל - label_no_change_option: (×ין שינוי×) - label_bulk_edit_selected_issues: ערוך ×ת הנוש××™× ×”×ž×¡×•×ž× ×™× - label_theme: ערכת × ×•×©× - label_default: ברירת מחדל - label_search_titles_only: חפש בכותרות בלבד - label_user_mail_option_all: "לכל ×ירוע בכל ×”×¤×¨×•×™×§×˜×™× ×©×œ×™" - label_user_mail_option_selected: "לכל ×ירוע ×‘×¤×¨×•×™×§×˜×™× ×©×‘×—×¨×ª×™ בלבד..." - label_user_mail_option_only_my_events: עבור ×“×‘×¨×™× ×©×× ×™ צופה ×ו מעורב ×‘×”× ×‘×œ×‘×“ - label_user_mail_option_only_assigned: עבור ×“×‘×¨×™× ×©×× ×™ ×חר××™ ×¢×œ×™×”× ×‘×œ×‘×“ - label_user_mail_option_only_owner: עבור ×“×‘×¨×™× ×©×× ×™ ×”×‘×¢×œ×™× ×©×œ×”× ×‘×œ×‘×“ - label_user_mail_no_self_notified: "×× ×™ ×œ× ×¨×•×¦×” שיודיעו לי על ×©×™× ×•×™×™× ×©×× ×™ מבצע" - label_registration_activation_by_email: הפעל חשבון ב×מצעות דו×"ל - label_registration_manual_activation: הפעלת חשבון ידנית - label_registration_automatic_activation: הפעלת חשבון ×וטומטית - label_display_per_page: "בכל דף: %{value} תוצ×ות" - label_age: גיל - label_change_properties: שנה מ××¤×™×™× ×™× - label_general: כללי - label_more: עוד - label_scm: מערכת ניהול תצורה - label_plugins: ×ª×•×¡×¤×™× - label_ldap_authentication: הזדהות LDAP - label_downloads_abbr: D/L - label_optional_description: תי×ור רשות - label_add_another_file: הוסף עוד קובץ - label_preferences: העדפות - label_chronological_order: בסדר כרונולוגי - label_reverse_chronological_order: בסדר כרונולוגי הפוך - label_planning: תכנון - label_incoming_emails: דו×"ל נכנס - label_generate_key: צור מפתח - label_issue_watchers: ×¦×•×¤×™× - label_example: ×“×•×’×ž× - label_display: תצוגה - label_sort: מיון - label_ascending: בסדר עולה - label_descending: בסדר יורד - label_date_from_to: 'מת×ריך %{start} ועד ת×ריך %{end}' - label_wiki_content_added: נוסף דף ל־wiki - label_wiki_content_updated: דף wiki עודכן - label_group: קבוצה - label_group_plural: קבוצות - label_group_new: קבוצה חדשה - label_time_entry_plural: זמן שהושקע - label_version_sharing_none: ×œ× ×ž×©×•×ª×£ - label_version_sharing_descendants: ×¢× ×¤×¨×•×™×§×˜×™× ×‘× ×™× - label_version_sharing_hierarchy: ×¢× ×”×™×¨×¨×›×™×ª ×”×¤×¨×•×™×§×˜×™× - label_version_sharing_tree: ×¢× ×¢×¥ הפרויקט - label_version_sharing_system: ×¢× ×›×œ ×”×¤×¨×•×™×§×˜×™× - label_update_issue_done_ratios: עדכן ×חוז התקדמות ×œ× ×•×©× - label_copy_source: מקור - label_copy_target: יעד - label_copy_same_as_target: ×–×”×” ליעד - label_display_used_statuses_only: הצג רק ×ת ×”×ž×¦×‘×™× ×‘×©×™×ž×•×© לסיווג ×–×” - label_api_access_key: מפתח גישה ל־API - label_missing_api_access_key: חסר מפתח גישה ל־API - label_api_access_key_created_on: 'מפתח גישה ל־API נוצר לפני %{value}' - label_profile: פרופיל - label_subtask_plural: תתי־משימות - label_project_copy_notifications: שלח התר×ות דו×ר במהלך העתקת הפרויקט - - button_login: התחבר - button_submit: ×שר - button_save: שמור - button_check_all: בחר הכל - button_uncheck_all: בחר ×›×œ×•× - button_delete: מחק - button_create: צור - button_create_and_continue: צור ופתח חדש - button_test: בדוק - button_edit: ערוך - button_edit_associated_wikipage: "ערוך דף wiki מקושר: %{page_title}" - button_add: הוסף - button_change: שנה - button_apply: החל - button_clear: × ×§×” - button_lock: נעל - button_unlock: בטל נעילה - button_download: הורד - button_list: רשימה - button_view: צפה - button_move: ×”×–×– - button_move_and_follow: העבר ועקוב - button_back: ×”×§×•×“× - button_cancel: בטל - button_activate: הפעל - button_sort: מיין - button_log_time: ×¨×™×©×•× ×–×ž× ×™× - button_rollback: חזור למהדורה זו - button_watch: צפה - button_unwatch: בטל צפיה - button_reply: השב - button_archive: ×רכיון - button_unarchive: ×”×•×¦× ×ž×”×רכיון - button_reset: ×פס - button_rename: שנה ×©× - button_change_password: שנה סיסמה - button_copy: העתק - button_copy_and_follow: העתק ועקוב - button_annotate: הוסף תי×ור מסגרת - button_update: עדכן - button_configure: ×פשרויות - button_quote: צטט - button_duplicate: שכפל - button_show: הצג - - status_active: פעיל - status_registered: ×¨×©×•× - status_locked: נעול - - version_status_open: פתוח - version_status_locked: נעול - version_status_closed: סגור - - field_active: פעיל - - text_select_mail_notifications: בחר פעולת שבגללן ישלח דו×"ל. - text_regexp_info: כגון. ^[A-Z0-9]+$ - text_min_max_length_info: 0 משמעו ×œ×œ× ×”×’×‘×œ×•×ª - text_project_destroy_confirmation: ×”×× ×תה בטוח שברצונך למחוק ×ת הפרויקט ו×ת כל המידע הקשור ×ליו? - text_subprojects_destroy_warning: "תת־הפרויקטי×: %{value} ימחקו ×’× ×›×Ÿ." - text_workflow_edit: בחר תפקיד וסיווג כדי לערוך ×ת זרימת העבודה - text_are_you_sure: ×”×× ×תה בטוח? - text_are_you_sure_with_children: ×”×× ×œ×ž×—×•×§ ×ת ×”× ×•×©× ×•×ת כל בניו? - text_journal_changed: "%{label} השתנה מ%{old} ל%{new}" - text_journal_set_to: "%{label} נקבע ל%{value}" - text_journal_deleted: "%{label} נמחק (%{old})" - text_journal_added: "%{label} %{value} נוסף" - text_tip_issue_begin_day: מטלה המתחילה ×”×™×•× - text_tip_issue_end_day: מטלה המסתיימת ×”×™×•× - text_tip_issue_begin_end_day: מטלה המתחילה ומסתיימת ×”×™×•× - text_project_identifier_info: '×ותיות לטיניות (a-z), ×ž×¡×¤×¨×™× ×•×ž×§×¤×™×.
ברגע שנשמר, ×œ× × ×™×ª×Ÿ לשנות ×ת המזהה.' - text_caracters_maximum: "×ž×§×¡×™×ž×•× %{count} תווי×." - text_caracters_minimum: "חייב להיות לפחות ב×ורך של %{count} תווי×." - text_length_between: "×ורך בין %{min} ל %{max} תווי×." - text_tracker_no_workflow: זרימת עבודה ×œ× ×”×•×’×“×¨×” עבור סיווג ×–×” - text_unallowed_characters: ×ª×•×•×™× ×œ× ×ž×•×¨×©×™× - text_comma_separated: הכנסת ×¢×¨×›×™× ×ž×¨×•×‘×™× ×ž×•×ª×¨×ª (×ž×•×¤×¨×“×™× ×‘×¤×¡×™×§×™×). - text_line_separated: ניתן להזין מספר ×¢×¨×›×™× (שורה ×חת לכל ערך). - text_issues_ref_in_commit_messages: קישור ×•×ª×™×§×•× × ×•×©××™× ×‘×”×•×“×¢×•×ª הפקדה - text_issue_added: "×”× ×•×©× %{id} דווח (בידי %{author})." - text_issue_updated: "×”× ×•×©× %{id} עודכן (בידי %{author})." - text_wiki_destroy_confirmation: ×”×× ×תה בטוח שברצונך למחוק ×ת ×”WIKI ×”×–×” ו×ת כל תוכנו? - text_issue_category_destroy_question: "כמה נוש××™× (%{count}) ×ž×•×¦×‘×™× ×œ×§×˜×’×•×¨×™×” הזו. מה ברצונך לעשות?" - text_issue_category_destroy_assignments: הסר הצבת קטגוריה - text_issue_category_reassign_to: הצב מחדש ×ת הקטגוריה לנוש××™× - text_user_mail_option: "×‘×¤×¨×•×™×§×˜×™× ×©×œ× ×‘×—×¨×ª, ×תה רק תקבל התרעות על ש×תה צופה ×ו קשור ××œ×™×”× (לדוגמ×:נוש××™× ×©×תה היוצר ×©×œ×”× ×ו ×חר××™ עליה×)." - text_no_configuration_data: "×œ× ×”×•×’×“×¨×” תצורה עבור תפקידי×, סיווגי×, מצבי × ×•×©× ×•×–×¨×™×ž×ª עבודה.\nמומלץ מ×ד לטעון ×ת תצורת ברירת המחדל. תוכל לשנותה מ×וחר יותר." - text_load_default_configuration: טען ×ת ×פשרויות ברירת המחדל - text_status_changed_by_changeset: "הוחל בסדרת ×”×©×™× ×•×™×™× %{value}." - text_issues_destroy_confirmation: '×”×× ×תה בטוח שברצונך למחוק ×ת הנוש××™×?' - text_select_project_modules: 'בחר ×ž×•×“×•×œ×™× ×œ×”×—×™×œ על פרויקט ×–×”:' - text_default_administrator_account_changed: מנהל המערכת ברירת המחדל שונה - text_file_repository_writable: מ×גר ×”×§×‘×¦×™× × ×™×ª×Ÿ לכתיבה - text_plugin_assets_writable: ספרית נכסי ×ª×•×¡×¤×™× × ×™×ª× ×ª לכתיבה - text_rmagick_available: RMagick זמין (רשות) - text_destroy_time_entries_question: "%{hours} שעות דווחו על הנוש××™× ×©×תה עומד למחוק. מה ברצונך לעשות?" - text_destroy_time_entries: מחק שעות שדווחו - text_assign_time_entries_to_project: הצב שעות שדווחו לפרויקט ×”×–×” - text_reassign_time_entries: 'הצב מחדש שעות שדווחו לפרויקט ×”×–×”:' - text_user_wrote: "%{value} כתב:" - text_enumeration_destroy_question: "%{count} ××•×‘×™×§×˜×™× ×ž×•×¦×‘×™× ×œ×¢×¨×š ×–×”." - text_enumeration_category_reassign_to: 'הצב מחדש לערך ×”×–×”:' - text_email_delivery_not_configured: '×œ× × ×§×‘×¢×” תצורה לשליחת דו×ר, וההתר×ות כבויות.\nקבע ×ת תצורת שרת ×”Ö¾SMTP בקובץ /etc/redmine/<instance>/configuration.yml והתחל ×ת ×”×פליקציה מחדש ×¢"מ ל×פשר ×ות×.' - text_repository_usernames_mapping: "בחר ×ו עדכן ×ת משתמש Redmine הממופה לכל ×©× ×ž×©×ª×ž×© ביומן המ×גר.\n×ž×©×ª×ž×©×™× ×‘×¢×œ×™ ×©× ×ו כתובת דו×ר ×–×”×” ב־Redmine ובמ×גר ×ž×ž×•×¤×™× ×‘×ופן ×וטומטי." - text_diff_truncated: '... ×”×©×™× ×•×™×™× ×¢×•×‘×¨×™× ×ת מספר השורות המירבי לתצוגה, ולכן ×”× ×§×•×¦×¦×•.' - text_custom_field_possible_values_info: שורה ×חת לכל ערך - text_wiki_page_destroy_question: לדף ×–×” יש %{descendants} ×“×¤×™× ×‘× ×™× ×•×ª×œ×•×™×™×. מה ברצונך לעשות? - text_wiki_page_nullify_children: הש×ר ×“×¤×™× ×‘× ×™× ×›×“×¤×™× ×¨××©×™×™× - text_wiki_page_destroy_children: מחק ×ת ×”×“×¤×™× ×”×‘× ×™× ×•×ת כל ×”×ª×œ×•×™×™× ×‘×”× - text_wiki_page_reassign_children: הצב מחדש ×“×¤×™× ×‘× ×™× ×œ×“×£ ×”×ב הנוכחי - text_own_membership_delete_confirmation: |- - בכוונתך למחוק חלק ×ו ×ת כל ההרש×ות שלך. ל×חר מכן ×œ× ×ª×•×›×œ יותר לערוך פרויקט ×–×”. - ×”×× ×תה בטוח שברצונך להמשיך? - text_zoom_in: התקרב - text_zoom_out: התרחק - - default_role_manager: מנהל - default_role_developer: מפתח - default_role_reporter: מדווח - default_tracker_bug: תקלה - default_tracker_feature: יכולת - default_tracker_support: תמיכה - default_issue_status_new: חדש - default_issue_status_in_progress: בעבודה - default_issue_status_resolved: נפתר - default_issue_status_feedback: משוב - default_issue_status_closed: סגור - default_issue_status_rejected: נדחה - default_doc_category_user: תיעוד משתמש - default_doc_category_tech: תיעוד טכני - default_priority_low: נמוכה - default_priority_normal: רגילה - default_priority_high: גבוהה - default_priority_urgent: דחופה - default_priority_immediate: מידית - default_activity_design: עיצוב - default_activity_development: פיתוח - - enumeration_issue_priorities: עדיפות נוש××™× - enumeration_doc_categories: קטגוריות ×ž×¡×ž×›×™× - enumeration_activities: פעילויות (מעקב ×חר זמני×) - enumeration_system_activity: פעילות מערכת - label_user_mail_option_none: No events - field_member_of_group: Assignee's group - field_assigned_to_role: Assignee's role - label_principal_search: "Search for user or group:" - label_user_search: "Search for user:" - field_visible: Visible - setting_emails_header: Emails header - setting_commit_logtime_activity_id: Activity for logged time - text_time_logged_by_changeset: Applied in changeset %{value}. - setting_commit_logtime_enabled: Enable time logging - notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) - setting_gantt_items_limit: Maximum number of items displayed on the gantt chart - field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text - text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. - label_my_queries: My custom queries - text_journal_changed_no_detail: "%{label} updated" - label_news_comment_added: Comment added to a news - button_expand_all: Expand all - button_collapse_all: Collapse all - label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee - label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author - label_bulk_edit_selected_time_entries: Bulk edit selected time entries - text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? - label_role_anonymous: Anonymous - label_role_non_member: Non member - label_issue_note_added: Note added - label_issue_status_updated: Status updated - label_issue_priority_updated: Priority updated - label_issues_visibility_own: Issues created by or assigned to the user - field_issues_visibility: Issues visibility - label_issues_visibility_all: All issues - permission_set_own_issues_private: Set own issues public or private - field_is_private: Private - permission_set_issues_private: Set issues public or private - label_issues_visibility_public: All non private issues - text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). - field_commit_logs_encoding: קידוד הודעות הפקדה - field_scm_path_encoding: Path encoding - text_scm_path_encoding_note: "Default: UTF-8" - field_path_to_repository: Path to repository - field_root_directory: Root directory - field_cvs_module: Module - field_cvsroot: CVSROOT - text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) - text_scm_command: Command - text_scm_command_version: Version - label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. - notice_issue_successful_create: Issue %{id} created. - label_between: between - setting_issue_group_assignment: Allow issue assignment to groups - label_diff: diff - text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) - description_query_sort_criteria_direction: Sort direction - description_project_scope: Search scope - description_filter: Filter - description_user_mail_notification: Mail notification settings - description_date_from: Enter start date - description_message_content: Message content - description_available_columns: Available Columns - description_date_range_interval: Choose range by selecting start and end date - description_issue_category_reassign: Choose issue category - description_search: Searchfield - description_notes: Notes - description_date_range_list: Choose range from list - description_choose_project: Projects - description_date_to: Enter end date - description_query_sort_criteria_attribute: Sort attribute - description_wiki_subpages_reassign: Choose new parent page - description_selected_columns: Selected Columns - label_parent_revision: Parent - label_child_revision: Child - error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. - setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues - button_edit_section: Edit this section - setting_repositories_encodings: Attachments and repositories encodings - description_all_columns: All Columns - button_export: Export - label_export_options: "%{export_format} export options" - error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/24/246272ecf77e9e146775d6173c7c910bea9ac736.svn-base --- a/.svn/pristine/24/246272ecf77e9e146775d6173c7c910bea9ac736.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,95 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class ReportsController < ApplicationController - menu_item :issues - before_filter :find_project, :authorize, :find_issue_statuses - - def issue_report - @trackers = @project.trackers - @versions = @project.shared_versions.sort - @priorities = IssuePriority.all - @categories = @project.issue_categories - @assignees = (Setting.issue_group_assignment? ? @project.principals : @project.users).sort - @authors = @project.users.sort - @subprojects = @project.descendants.visible - - @issues_by_tracker = Issue.by_tracker(@project) - @issues_by_version = Issue.by_version(@project) - @issues_by_priority = Issue.by_priority(@project) - @issues_by_category = Issue.by_category(@project) - @issues_by_assigned_to = Issue.by_assigned_to(@project) - @issues_by_author = Issue.by_author(@project) - @issues_by_subproject = Issue.by_subproject(@project) || [] - - render :template => "reports/issue_report" - end - - def issue_report_details - case params[:detail] - when "tracker" - @field = "tracker_id" - @rows = @project.trackers - @data = Issue.by_tracker(@project) - @report_title = l(:field_tracker) - when "version" - @field = "fixed_version_id" - @rows = @project.shared_versions.sort - @data = Issue.by_version(@project) - @report_title = l(:field_version) - when "priority" - @field = "priority_id" - @rows = IssuePriority.all - @data = Issue.by_priority(@project) - @report_title = l(:field_priority) - when "category" - @field = "category_id" - @rows = @project.issue_categories - @data = Issue.by_category(@project) - @report_title = l(:field_category) - when "assigned_to" - @field = "assigned_to_id" - @rows = (Setting.issue_group_assignment? ? @project.principals : @project.users).sort - @data = Issue.by_assigned_to(@project) - @report_title = l(:field_assigned_to) - when "author" - @field = "author_id" - @rows = @project.users.sort - @data = Issue.by_author(@project) - @report_title = l(:field_author) - when "subproject" - @field = "project_id" - @rows = @project.descendants.visible - @data = Issue.by_subproject(@project) || [] - @report_title = l(:field_subproject) - end - - respond_to do |format| - if @field - format.html {} - else - format.html { redirect_to :action => 'issue_report', :id => @project } - end - end - end - - private - - def find_issue_statuses - @statuses = IssueStatus.find(:all, :order => 'position') - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/24/246874006236597325557c0bf816ad53110aa1a7.svn-base --- a/.svn/pristine/24/246874006236597325557c0bf816ad53110aa1a7.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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 DocumentCategoryCustomField < CustomField - def type_name - :enumeration_doc_categories - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/24/2471e93efea942bf9d4041debddb77820f273e27.svn-base --- a/.svn/pristine/24/2471e93efea942bf9d4041debddb77820f273e27.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,36 +0,0 @@ ---- -journal_details_001: - old_value: "1" - property: attr - id: 1 - value: "2" - prop_key: status_id - journal_id: 1 -journal_details_002: - old_value: "40" - property: attr - id: 2 - value: "30" - prop_key: done_ratio - journal_id: 1 -journal_details_003: - old_value: nil - property: attr - id: 3 - value: "6" - prop_key: fixed_version_id - journal_id: 4 -journal_details_004: - old_value: "This word was removed and an other was" - property: attr - id: 4 - value: "This word was and an other was added" - prop_key: description - journal_id: 3 -journal_details_005: - old_value: Old value - property: cf - id: 5 - value: New value - prop_key: 2 - journal_id: 3 diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/24/247b283cdc31d8c730597eba0ebe99d82df6440a.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/24/247b283cdc31d8c730597eba0ebe99d82df6440a.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,122 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class GanttsControllerTest < ActionController::TestCase + fixtures :projects, :trackers, :issue_statuses, :issues, + :enumerations, :users, :issue_categories, + :projects_trackers, + :roles, + :member_roles, + :members, + :enabled_modules, + :versions + + def test_gantt_should_work + i2 = Issue.find(2) + i2.update_attribute(:due_date, 1.month.from_now) + get :show, :project_id => 1 + assert_response :success + assert_template 'gantts/show' + assert_not_nil assigns(:gantt) + # Issue with start and due dates + i = Issue.find(1) + assert_not_nil i.due_date + assert_select "div a.issue", /##{i.id}/ + # Issue with on a targeted version should not be in the events but loaded in the html + i = Issue.find(2) + assert_select "div a.issue", /##{i.id}/ + end + + def test_gantt_should_work_without_issue_due_dates + Issue.update_all("due_date = NULL") + get :show, :project_id => 1 + assert_response :success + assert_template 'gantts/show' + assert_not_nil assigns(:gantt) + end + + def test_gantt_should_work_without_issue_and_version_due_dates + Issue.update_all("due_date = NULL") + Version.update_all("effective_date = NULL") + get :show, :project_id => 1 + assert_response :success + assert_template 'gantts/show' + assert_not_nil assigns(:gantt) + end + + def test_gantt_should_work_cross_project + get :show + assert_response :success + assert_template 'gantts/show' + assert_not_nil assigns(:gantt) + assert_not_nil assigns(:gantt).query + assert_nil assigns(:gantt).project + end + + def test_gantt_should_not_disclose_private_projects + get :show + assert_response :success + assert_template 'gantts/show' + assert_tag 'a', :content => /eCookbook/ + # Root private project + assert_no_tag 'a', {:content => /OnlineStore/} + # Private children of a public project + assert_no_tag 'a', :content => /Private child of eCookbook/ + end + + def test_gantt_should_display_relations + IssueRelation.delete_all + issue1 = Issue.generate!(:start_date => 1.day.from_now, :due_date => 3.day.from_now) + issue2 = Issue.generate!(:start_date => 1.day.from_now, :due_date => 3.day.from_now) + IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => 'precedes') + + get :show + assert_response :success + + relations = assigns(:gantt).relations + assert_kind_of Hash, relations + assert relations.present? + assert_select 'div.task_todo[id=?][data-rels*=?]', "task-todo-issue-#{issue1.id}", issue2.id.to_s + assert_select 'div.task_todo[id=?]:not([data-rels])', "task-todo-issue-#{issue2.id}" + end + + def test_gantt_should_export_to_pdf + get :show, :project_id => 1, :format => 'pdf' + assert_response :success + assert_equal 'application/pdf', @response.content_type + assert @response.body.starts_with?('%PDF') + assert_not_nil assigns(:gantt) + end + + def test_gantt_should_export_to_pdf_cross_project + get :show, :format => 'pdf' + assert_response :success + assert_equal 'application/pdf', @response.content_type + assert @response.body.starts_with?('%PDF') + assert_not_nil assigns(:gantt) + end + + if Object.const_defined?(:Magick) + def test_gantt_should_export_to_png + get :show, :project_id => 1, :format => 'png' + assert_response :success + assert_equal 'image/png', @response.content_type + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/24/24b4bc532dbfbdbadb24d22bdcba74f2493b0b5a.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/24/24b4bc532dbfbdbadb24d22bdcba74f2493b0b5a.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,119 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class RoutingVersionsTest < ActionController::IntegrationTest + def test_roadmap + # /projects/foo/versions is /projects/foo/roadmap + assert_routing( + { :method => 'get', :path => "/projects/33/roadmap" }, + { :controller => 'versions', :action => 'index', :project_id => '33' } + ) + end + + def test_versions_scoped_under_project + assert_routing( + { :method => 'put', :path => "/projects/foo/versions/close_completed" }, + { :controller => 'versions', :action => 'close_completed', + :project_id => 'foo' } + ) + assert_routing( + { :method => 'get', :path => "/projects/foo/versions.xml" }, + { :controller => 'versions', :action => 'index', + :project_id => 'foo', :format => 'xml' } + ) + assert_routing( + { :method => 'get', :path => "/projects/foo/versions.json" }, + { :controller => 'versions', :action => 'index', + :project_id => 'foo', :format => 'json' } + ) + assert_routing( + { :method => 'get', :path => "/projects/foo/versions/new" }, + { :controller => 'versions', :action => 'new', + :project_id => 'foo' } + ) + assert_routing( + { :method => 'post', :path => "/projects/foo/versions" }, + { :controller => 'versions', :action => 'create', + :project_id => 'foo' } + ) + assert_routing( + { :method => 'post', :path => "/projects/foo/versions.xml" }, + { :controller => 'versions', :action => 'create', + :project_id => 'foo', :format => 'xml' } + ) + assert_routing( + { :method => 'post', :path => "/projects/foo/versions.json" }, + { :controller => 'versions', :action => 'create', + :project_id => 'foo', :format => 'json' } + ) + end + + def test_versions + assert_routing( + { :method => 'get', :path => "/versions/1" }, + { :controller => 'versions', :action => 'show', :id => '1' } + ) + assert_routing( + { :method => 'get', :path => "/versions/1.xml" }, + { :controller => 'versions', :action => 'show', :id => '1', + :format => 'xml' } + ) + assert_routing( + { :method => 'get', :path => "/versions/1.json" }, + { :controller => 'versions', :action => 'show', :id => '1', + :format => 'json' } + ) + assert_routing( + { :method => 'get', :path => "/versions/1/edit" }, + { :controller => 'versions', :action => 'edit', :id => '1' } + ) + assert_routing( + { :method => 'put', :path => "/versions/1" }, + { :controller => 'versions', :action => 'update', :id => '1' } + ) + assert_routing( + { :method => 'put', :path => "/versions/1.xml" }, + { :controller => 'versions', :action => 'update', :id => '1', + :format => 'xml' } + ) + assert_routing( + { :method => 'put', :path => "/versions/1.json" }, + { :controller => 'versions', :action => 'update', :id => '1', + :format => 'json' } + ) + assert_routing( + { :method => 'delete', :path => "/versions/1" }, + { :controller => 'versions', :action => 'destroy', :id => '1' } + ) + assert_routing( + { :method => 'delete', :path => "/versions/1.xml" }, + { :controller => 'versions', :action => 'destroy', :id => '1', + :format => 'xml' } + ) + assert_routing( + { :method => 'delete', :path => "/versions/1.json" }, + { :controller => 'versions', :action => 'destroy', :id => '1', + :format => 'json' } + ) + assert_routing( + { :method => 'post', :path => "/versions/1/status_by" }, + { :controller => 'versions', :action => 'status_by', :id => '1' } + ) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/24/24c62162ea3371ca1be4a955c3f0b6fc0dac2c81.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/24/24c62162ea3371ca1be4a955c3f0b6fc0dac2c81.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,167 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class AdminControllerTest < ActionController::TestCase + fixtures :projects, :users, :roles + + def setup + User.current = nil + @request.session[:user_id] = 1 # admin + end + + def test_index + get :index + assert_select 'div.nodata', 0 + end + + def test_index_with_no_configuration_data + delete_configuration_data + get :index + assert_select 'div.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_status_filter + get :projects, :status => 1 + 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' + assert_response :redirect + assert_nil flash[:error] + assert IssueStatus.find_by_name('Nouveau') + end + + def test_load_default_configuration_data_should_rescue_error + delete_configuration_data + Redmine::DefaultData::Loader.stubs(:load).raises(Exception.new("Something went wrong")) + post :default_configuration, :lang => 'fr' + assert_response :redirect + assert_not_nil flash[:error] + assert_match /Something went wrong/, flash[:error] + end + + def test_test_email + user = User.find(1) + user.pref[:no_self_notified] = '1' + user.pref.save! + ActionMailer::Base.deliveries.clear + + get :test_email + assert_redirected_to '/settings?tab=notifications' + mail = ActionMailer::Base.deliveries.last + assert_not_nil mail + user = User.find(1) + assert_equal [user.mail], mail.bcc + end + + def test_test_email_failure_should_display_the_error + Mailer.stubs(:test_email).raises(Exception, 'Some error message') + get :test_email + assert_redirected_to '/settings?tab=notifications' + assert_match /Some error message/, flash[:error] + 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_select 'tr#plugin-foo' do + assert_select 'td span.name', :text => 'Foo plugin' + assert_select 'td.configure a[href=/settings/plugin/foo]' + end + assert_select 'tr#plugin-bar' do + assert_select 'td span.name', :text => 'Bar' + assert_select 'td.configure a', 0 + end + end + + def test_info + get :info + assert_response :success + assert_template 'info' + end + + def test_admin_menu_plugin_extension + Redmine::MenuManager.map :admin_menu do |menu| + menu.push :test_admin_menu_plugin_extension, '/foo/bar', :caption => 'Test' + end + + get :index + assert_response :success + assert_select 'div#admin-menu a[href=/foo/bar]', :text => 'Test' + + Redmine::MenuManager.map :admin_menu do |menu| + menu.delete :test_admin_menu_plugin_extension + end + end + + private + + def delete_configuration_data + Role.delete_all('builtin = 0') + Tracker.delete_all + IssueStatus.delete_all + Enumeration.delete_all + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/24/24ebd90070f1307a8f6d2f2f35671e06e60bb2b7.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/24/24ebd90070f1307a8f6d2f2f35671e06e60bb2b7.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,184 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class MessageTest < ActiveSupport::TestCase + fixtures :projects, :roles, :members, :member_roles, :boards, :messages, + :users, :watchers, :enabled_modules + + def setup + @board = Board.find(1) + @user = User.find(1) + end + + def test_create + topics_count = @board.topics_count + messages_count = @board.messages_count + + message = Message.new(:board => @board, :subject => 'Test message', + :content => 'Test message content', + :author => @user) + assert message.save + @board.reload + # topics count incremented + assert_equal topics_count + 1, @board[:topics_count] + # messages count incremented + assert_equal messages_count + 1, @board[:messages_count] + assert_equal message, @board.last_message + # author should be watching the message + assert message.watched_by?(@user) + end + + def test_reply + topics_count = @board.topics_count + messages_count = @board.messages_count + message = Message.find(1) + replies_count = message.replies_count + + reply_author = User.find(2) + reply = Message.new(:board => @board, :subject => 'Test reply', + :content => 'Test reply content', + :parent => message, :author => reply_author) + assert reply.save + @board.reload + # same topics count + assert_equal topics_count, @board[:topics_count] + # messages count incremented + assert_equal messages_count+1, @board[:messages_count] + assert_equal reply, @board.last_message + message.reload + # replies count incremented + assert_equal replies_count+1, message[:replies_count] + assert_equal reply, message.last_reply + # author should be watching the message + assert message.watched_by?(reply_author) + end + + def test_cannot_reply_to_locked_topic + topics_count = @board.topics_count + messages_count = @board.messages_count + message = Message.find(1) + replies_count = message.replies_count + assert_equal false, message.locked + message.locked = true + assert message.save + assert_equal true, message.locked + + reply_author = User.find(2) + reply = Message.new(:board => @board, :subject => 'Test reply', + :content => 'Test reply content', + :parent => message, :author => reply_author) + reply.save + assert_equal 1, reply.errors.count + end + + def test_moving_message_should_update_counters + message = Message.find(1) + assert_no_difference 'Message.count' do + # Previous board + assert_difference 'Board.find(1).topics_count', -1 do + assert_difference 'Board.find(1).messages_count', -(1 + message.replies_count) do + # New board + assert_difference 'Board.find(2).topics_count' do + assert_difference 'Board.find(2).messages_count', (1 + message.replies_count) do + message.update_attributes(:board_id => 2) + end + end + end + end + end + end + + def test_destroy_topic + message = Message.find(1) + board = message.board + topics_count, messages_count = board.topics_count, board.messages_count + + assert_difference('Watcher.count', -1) do + assert message.destroy + end + board.reload + + # Replies deleted + assert Message.find_all_by_parent_id(1).empty? + # Checks counters + assert_equal topics_count - 1, board.topics_count + assert_equal messages_count - 3, board.messages_count + # Watchers removed + end + + def test_destroy_reply + message = Message.find(5) + board = message.board + topics_count, messages_count = board.topics_count, board.messages_count + assert message.destroy + board.reload + + # Checks counters + assert_equal topics_count, board.topics_count + assert_equal messages_count - 1, board.messages_count + end + + def test_destroying_last_reply_should_update_topic_last_reply_id + topic = Message.find(4) + assert_equal 6, topic.last_reply_id + + assert_difference 'Message.count', -1 do + Message.find(6).destroy + end + assert_equal 5, topic.reload.last_reply_id + + assert_difference 'Message.count', -1 do + Message.find(5).destroy + end + assert_nil topic.reload.last_reply_id + end + + def test_editable_by + message = Message.find(6) + author = message.author + assert message.editable_by?(author) + + author.roles_for_project(message.project).first.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.roles_for_project(message.project).first.remove_permission!(:delete_own_messages) + assert !message.reload.destroyable_by?(author.reload) + end + + def test_set_sticky + message = Message.new + assert_equal 0, message.sticky + message.sticky = nil + assert_equal 0, message.sticky + message.sticky = false + assert_equal 0, message.sticky + message.sticky = true + assert_equal 1, message.sticky + message.sticky = '0' + assert_equal 0, message.sticky + message.sticky = '1' + assert_equal 1, message.sticky + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/24/24f7f0414301f421feaefeee3ae3bf564c315361.svn-base --- a/.svn/pristine/24/24f7f0414301f421feaefeee3ae3bf564c315361.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,217 +0,0 @@ -# encoding: utf-8 -module CodeRay - module Scanners - - # Clojure scanner by Licenser. - class Clojure < Scanner - - register_for :clojure - file_extension 'clj' - - SPECIAL_FORMS = %w[ - def if do let quote var fn loop recur throw try catch monitor-enter monitor-exit . - new - ] # :nodoc: - - CORE_FORMS = %w[ - + - -> ->> .. / * <= < = == >= > accessor aclone add-classpath add-watch - agent agent-error agent-errors aget alength alias all-ns alter alter-meta! - alter-var-root amap ancestors and apply areduce array-map aset aset-boolean - aset-byte aset-char aset-double aset-float aset-int aset-long aset-short - assert assoc assoc! assoc-in associative? atom await await-for bases bean - bigdec bigint binding bit-and bit-and-not bit-clear bit-flip bit-not bit-or - bit-set bit-shift-left bit-shift-right bit-test bit-xor boolean boolean-array - booleans bound-fn bound-fn* bound? butlast byte byte-array bytes case cast char - char-array char-escape-string char-name-string char? chars class class? - clear-agent-errors clojure-version coll? comment commute comp comparator - compare compare-and-set! compile complement concat cond condp conj conj! - cons constantly construct-proxy contains? count counted? create-ns - create-struct cycle dec decimal? declare definline defmacro defmethod defmulti - defn defn- defonce defprotocol defrecord defstruct deftype delay delay? - deliver denominator deref derive descendants disj disj! dissoc dissoc! - distinct distinct? doall doc dorun doseq dosync dotimes doto double - double-array doubles drop drop-last drop-while empty empty? ensure - enumeration-seq error-handler error-mode eval even? every? extend - extend-protocol extend-type extenders extends? false? ffirst file-seq - filter find find-doc find-ns find-var first float float-array float? - floats flush fn fn? fnext for force format future future-call future-cancel - future-cancelled? future-done? future? gen-class gen-interface gensym get - get-in get-method get-proxy-class get-thread-bindings get-validator hash - hash-map hash-set identical? identity if-let if-not ifn? import in-ns - inc init-proxy instance? int int-array integer? interleave intern - interpose into into-array ints io! isa? iterate iterator-seq juxt key - keys keyword keyword? last lazy-cat lazy-seq let letfn line-seq list list* - list? load load-file load-reader load-string loaded-libs locking long - long-array longs loop macroexpand macroexpand-1 make-array make-hierarchy - map map? mapcat max max-key memfn memoize merge merge-with meta methods - min min-key mod name namespace neg? newline next nfirst nil? nnext not - not-any? not-empty not-every? not= ns ns-aliases ns-imports ns-interns - ns-map ns-name ns-publics ns-refers ns-resolve ns-unalias ns-unmap nth - nthnext num number? numerator object-array odd? or parents partial - partition pcalls peek persistent! pmap pop pop! pop-thread-bindings - pos? pr pr-str prefer-method prefers print print-namespace-doc - print-str printf println println-str prn prn-str promise proxy - proxy-mappings proxy-super push-thread-bindings pvalues quot rand - rand-int range ratio? rationalize re-find re-groups re-matcher - re-matches re-pattern re-seq read read-line read-string reduce ref - ref-history-count ref-max-history ref-min-history ref-set refer - refer-clojure reify release-pending-sends rem remove remove-all-methods - remove-method remove-ns remove-watch repeat repeatedly replace replicate - require reset! reset-meta! resolve rest restart-agent resultset-seq - reverse reversible? rseq rsubseq satisfies? second select-keys send - send-off seq seq? seque sequence sequential? set set-error-handler! - set-error-mode! set-validator! set? short short-array shorts - shutdown-agents slurp some sort sort-by sorted-map sorted-map-by - sorted-set sorted-set-by sorted? special-form-anchor special-symbol? - split-at split-with str string? struct struct-map subs subseq subvec - supers swap! symbol symbol? sync syntax-symbol-anchor take take-last - take-nth take-while test the-ns thread-bound? time to-array to-array-2d - trampoline transient tree-seq true? type unchecked-add unchecked-dec - unchecked-divide unchecked-inc unchecked-multiply unchecked-negate - unchecked-remainder unchecked-subtract underive update-in update-proxy - use val vals var-get var-set var? vary-meta vec vector vector-of vector? - when when-first when-let when-not while with-bindings with-bindings* - with-in-str with-local-vars with-meta with-open with-out-str - with-precision xml-seq zero? zipmap - ] # :nodoc: - - PREDEFINED_CONSTANTS = %w[ - true false nil *1 *2 *3 *agent* *clojure-version* *command-line-args* - *compile-files* *compile-path* *e *err* *file* *flush-on-newline* - *in* *ns* *out* *print-dup* *print-length* *print-level* *print-meta* - *print-readably* *read-eval* *warn-on-reflection* - ] # :nodoc: - - IDENT_KIND = WordList.new(:ident). - add(SPECIAL_FORMS, :keyword). - add(CORE_FORMS, :keyword). - add(PREDEFINED_CONSTANTS, :predefined_constant) - - KEYWORD_NEXT_TOKEN_KIND = WordList.new(nil). - add(%w[ def defn defn- definline defmacro defmulti defmethod defstruct defonce declare ], :function). - add(%w[ ns ], :namespace). - add(%w[ defprotocol defrecord ], :class) - - BASIC_IDENTIFIER = /[a-zA-Z$%*\/_+!?&<>\-=]=?[a-zA-Z0-9$&*+!\/_?<>\-\#]*/ - IDENTIFIER = /(?!-\d)(?:(?:#{BASIC_IDENTIFIER}\.)*#{BASIC_IDENTIFIER}(?:\/#{BASIC_IDENTIFIER})?\.?)|\.\.?/ - SYMBOL = /::?#{IDENTIFIER}/o - DIGIT = /\d/ - DIGIT10 = DIGIT - DIGIT16 = /[0-9a-f]/i - DIGIT8 = /[0-7]/ - DIGIT2 = /[01]/ - RADIX16 = /\#x/i - RADIX8 = /\#o/i - RADIX2 = /\#b/i - RADIX10 = /\#d/i - EXACTNESS = /#i|#e/i - SIGN = /[\+-]?/ - EXP_MARK = /[esfdl]/i - EXP = /#{EXP_MARK}#{SIGN}#{DIGIT}+/ - SUFFIX = /#{EXP}?/ - PREFIX10 = /#{RADIX10}?#{EXACTNESS}?|#{EXACTNESS}?#{RADIX10}?/ - PREFIX16 = /#{RADIX16}#{EXACTNESS}?|#{EXACTNESS}?#{RADIX16}/ - PREFIX8 = /#{RADIX8}#{EXACTNESS}?|#{EXACTNESS}?#{RADIX8}/ - PREFIX2 = /#{RADIX2}#{EXACTNESS}?|#{EXACTNESS}?#{RADIX2}/ - UINT10 = /#{DIGIT10}+#*/ - UINT16 = /#{DIGIT16}+#*/ - UINT8 = /#{DIGIT8}+#*/ - UINT2 = /#{DIGIT2}+#*/ - DECIMAL = /#{DIGIT10}+#+\.#*#{SUFFIX}|#{DIGIT10}+\.#{DIGIT10}*#*#{SUFFIX}|\.#{DIGIT10}+#*#{SUFFIX}|#{UINT10}#{EXP}/ - UREAL10 = /#{UINT10}\/#{UINT10}|#{DECIMAL}|#{UINT10}/ - UREAL16 = /#{UINT16}\/#{UINT16}|#{UINT16}/ - UREAL8 = /#{UINT8}\/#{UINT8}|#{UINT8}/ - UREAL2 = /#{UINT2}\/#{UINT2}|#{UINT2}/ - REAL10 = /#{SIGN}#{UREAL10}/ - REAL16 = /#{SIGN}#{UREAL16}/ - REAL8 = /#{SIGN}#{UREAL8}/ - REAL2 = /#{SIGN}#{UREAL2}/ - IMAG10 = /i|#{UREAL10}i/ - IMAG16 = /i|#{UREAL16}i/ - IMAG8 = /i|#{UREAL8}i/ - IMAG2 = /i|#{UREAL2}i/ - COMPLEX10 = /#{REAL10}@#{REAL10}|#{REAL10}\+#{IMAG10}|#{REAL10}-#{IMAG10}|\+#{IMAG10}|-#{IMAG10}|#{REAL10}/ - COMPLEX16 = /#{REAL16}@#{REAL16}|#{REAL16}\+#{IMAG16}|#{REAL16}-#{IMAG16}|\+#{IMAG16}|-#{IMAG16}|#{REAL16}/ - COMPLEX8 = /#{REAL8}@#{REAL8}|#{REAL8}\+#{IMAG8}|#{REAL8}-#{IMAG8}|\+#{IMAG8}|-#{IMAG8}|#{REAL8}/ - COMPLEX2 = /#{REAL2}@#{REAL2}|#{REAL2}\+#{IMAG2}|#{REAL2}-#{IMAG2}|\+#{IMAG2}|-#{IMAG2}|#{REAL2}/ - NUM10 = /#{PREFIX10}?#{COMPLEX10}/ - NUM16 = /#{PREFIX16}#{COMPLEX16}/ - NUM8 = /#{PREFIX8}#{COMPLEX8}/ - NUM2 = /#{PREFIX2}#{COMPLEX2}/ - NUM = /#{NUM10}|#{NUM16}|#{NUM8}|#{NUM2}/ - - protected - - def scan_tokens encoder, options - - state = :initial - kind = nil - - until eos? - - case state - when :initial - if match = scan(/ \s+ | \\\n | , /x) - encoder.text_token match, :space - elsif match = scan(/['`\(\[\)\]\{\}]|\#[({]|~@?|[@\^]/) - encoder.text_token match, :operator - elsif match = scan(/;.*/) - encoder.text_token match, :comment # TODO: recognize (comment ...) too - elsif match = scan(/\#?\\(?:newline|space|.?)/) - encoder.text_token match, :char - elsif match = scan(/\#[ft]/) - encoder.text_token match, :predefined_constant - elsif match = scan(/#{IDENTIFIER}/o) - kind = IDENT_KIND[match] - encoder.text_token match, kind - if rest? && kind == :keyword - if kind = KEYWORD_NEXT_TOKEN_KIND[match] - encoder.text_token match, :space if match = scan(/\s+/o) - encoder.text_token match, kind if match = scan(/#{IDENTIFIER}/o) - end - end - elsif match = scan(/#{SYMBOL}/o) - encoder.text_token match, :symbol - elsif match = scan(/\./) - encoder.text_token match, :operator - elsif match = scan(/ \# \^ #{IDENTIFIER} /ox) - encoder.text_token match, :type - elsif match = scan(/ (\#)? " /x) - state = self[1] ? :regexp : :string - encoder.begin_group state - encoder.text_token match, :delimiter - elsif match = scan(/#{NUM}/o) and not matched.empty? - encoder.text_token match, match[/[.e\/]/i] ? :float : :integer - else - encoder.text_token getch, :error - end - - when :string, :regexp - if match = scan(/[^"\\]+|\\.?/) - encoder.text_token match, :content - elsif match = scan(/"/) - encoder.text_token match, :delimiter - encoder.end_group state - state = :initial - else - raise_inspect "else case \" reached; %p not handled." % peek(1), - encoder, state - end - - else - raise 'else case reached' - - end - - end - - if [:string, :regexp].include? state - encoder.end_group state - end - - encoder - - end - end - end -end \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/25/25113ec5e4b14337a78af5200fc4c9451ad1548e.svn-base --- a/.svn/pristine/25/25113ec5e4b14337a78af5200fc4c9451ad1548e.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,39 +0,0 @@ -<% @entries.each do |entry| %> -<% tr_id = Digest::MD5.hexdigest(entry.path) - depth = params[:depth].to_i %> -<% ent_path = Redmine::CodesetUtil.replace_invalid_utf8(entry.path) %> -<% ent_name = Redmine::CodesetUtil.replace_invalid_utf8(entry.name) %> - -";> -<% if entry.is_dir? %> - "scmEntryClick('#{tr_id}')" - ) %>">  -<% end %> -<%= link_to h(ent_name), - {:action => (entry.is_dir? ? 'show' : 'changes'), :id => @project, :path => to_path_param(ent_path), :rev => @rev}, - :class => (entry.is_dir? ? 'icon icon-folder' : "icon icon-file #{Redmine::MimeType.css_class_of(ent_name)}")%> - -<%= (entry.size ? number_to_human_size(entry.size) : "?") unless entry.is_dir? %> -<% changeset = @project.repository.find_changeset_by_name(entry.lastrev.identifier) if entry.lastrev && entry.lastrev.identifier %> -<% if @repository.report_last_commit %> -<%= link_to_revision(changeset, @project) if changeset %> -<%= distance_of_time_in_words(entry.lastrev.time, Time.now) if entry.lastrev && entry.lastrev.time %> -<%= changeset.nil? ? h(Redmine::CodesetUtil.replace_invalid_utf8(entry.lastrev.author.to_s.split('<').first)) : h(changeset.author) if entry.lastrev %> -<%=h truncate(changeset.comments, :length => 50) unless changeset.nil? %> -<% end %> - -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/25/252339adc97bdbe7fac1c5b16dba024f1d817a65.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/25/252339adc97bdbe7fac1c5b16dba024f1d817a65.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,290 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class QueriesControllerTest < ActionController::TestCase + fixtures :projects, :users, :members, :member_roles, :roles, :trackers, :issue_statuses, :issue_categories, :enumerations, :issues, :custom_fields, :custom_values, :queries, :enabled_modules + + def setup + User.current = nil + end + + def test_index + get :index + # HTML response not implemented + assert_response 406 + end + + def test_new_project_query + @request.session[:user_id] = 2 + get :new, :project_id => 1 + assert_response :success + assert_template 'new' + assert_tag :tag => 'input', :attributes => { :type => 'checkbox', + :name => 'query[is_public]', + :checked => nil } + assert_tag :tag => 'input', :attributes => { :type => 'checkbox', + :name => 'query_is_for_all', + :checked => nil, + :disabled => nil } + assert_select 'select[name=?]', 'c[]' do + assert_select 'option[value=tracker]' + assert_select 'option[value=subject]' + end + end + + def test_new_global_query + @request.session[:user_id] = 2 + get :new + assert_response :success + assert_template 'new' + assert_no_tag :tag => 'input', :attributes => { :type => 'checkbox', + :name => 'query[is_public]' } + assert_tag :tag => 'input', :attributes => { :type => 'checkbox', + :name => 'query_is_for_all', + :checked => 'checked', + :disabled => nil } + end + + def test_new_on_invalid_project + @request.session[:user_id] = 2 + get :new, :project_id => 'invalid' + assert_response 404 + end + + def test_create_project_public_query + @request.session[:user_id] = 2 + post :create, + :project_id => 'ecookbook', + :default_columns => '1', + :f => ["status_id", "assigned_to_id"], + :op => {"assigned_to_id" => "=", "status_id" => "o"}, + :v => { "assigned_to_id" => ["1"], "status_id" => ["1"]}, + :query => {"name" => "test_new_project_public_query", "is_public" => "1"} + + q = Query.find_by_name('test_new_project_public_query') + assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook', :query_id => q + assert q.is_public? + assert q.has_default_columns? + assert q.valid? + end + + def test_create_project_private_query + @request.session[:user_id] = 3 + post :create, + :project_id => 'ecookbook', + :default_columns => '1', + :fields => ["status_id", "assigned_to_id"], + :operators => {"assigned_to_id" => "=", "status_id" => "o"}, + :values => { "assigned_to_id" => ["1"], "status_id" => ["1"]}, + :query => {"name" => "test_new_project_private_query", "is_public" => "1"} + + q = Query.find_by_name('test_new_project_private_query') + assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook', :query_id => q + assert !q.is_public? + assert q.has_default_columns? + assert q.valid? + end + + def test_create_global_private_query_with_custom_columns + @request.session[:user_id] = 3 + post :create, + :fields => ["status_id", "assigned_to_id"], + :operators => {"assigned_to_id" => "=", "status_id" => "o"}, + :values => { "assigned_to_id" => ["me"], "status_id" => ["1"]}, + :query => {"name" => "test_new_global_private_query", "is_public" => "1"}, + :c => ["", "tracker", "subject", "priority", "category"] + + q = Query.find_by_name('test_new_global_private_query') + assert_redirected_to :controller => 'issues', :action => 'index', :project_id => nil, :query_id => q + assert !q.is_public? + assert !q.has_default_columns? + assert_equal [:id, :tracker, :subject, :priority, :category], q.columns.collect {|c| c.name} + assert q.valid? + end + + def test_create_global_query_with_custom_filters + @request.session[:user_id] = 3 + post :create, + :fields => ["assigned_to_id"], + :operators => {"assigned_to_id" => "="}, + :values => { "assigned_to_id" => ["me"]}, + :query => {"name" => "test_new_global_query"} + + q = Query.find_by_name('test_new_global_query') + assert_redirected_to :controller => 'issues', :action => 'index', :project_id => nil, :query_id => q + assert !q.has_filter?(:status_id) + assert_equal ['assigned_to_id'], q.filters.keys + assert q.valid? + end + + def test_create_with_sort + @request.session[:user_id] = 1 + post :create, + :default_columns => '1', + :operators => {"status_id" => "o"}, + :values => {"status_id" => ["1"]}, + :query => {:name => "test_new_with_sort", + :is_public => "1", + :sort_criteria => {"0" => ["due_date", "desc"], "1" => ["tracker", ""]}} + + query = Query.find_by_name("test_new_with_sort") + assert_not_nil query + assert_equal [['due_date', 'desc'], ['tracker', 'asc']], query.sort_criteria + end + + def test_create_with_failure + @request.session[:user_id] = 2 + assert_no_difference '::Query.count' do + post :create, :project_id => 'ecookbook', :query => {:name => ''} + end + assert_response :success + assert_template 'new' + assert_select 'input[name=?]', 'query[name]' + end + + def test_edit_global_public_query + @request.session[:user_id] = 1 + get :edit, :id => 4 + assert_response :success + assert_template 'edit' + assert_tag :tag => 'input', :attributes => { :type => 'checkbox', + :name => 'query[is_public]', + :checked => 'checked' } + assert_tag :tag => 'input', :attributes => { :type => 'checkbox', + :name => 'query_is_for_all', + :checked => 'checked', + :disabled => 'disabled' } + end + + def test_edit_global_private_query + @request.session[:user_id] = 3 + get :edit, :id => 3 + assert_response :success + assert_template 'edit' + assert_no_tag :tag => 'input', :attributes => { :type => 'checkbox', + :name => 'query[is_public]' } + assert_tag :tag => 'input', :attributes => { :type => 'checkbox', + :name => 'query_is_for_all', + :checked => 'checked', + :disabled => 'disabled' } + end + + def test_edit_project_private_query + @request.session[:user_id] = 3 + get :edit, :id => 2 + assert_response :success + assert_template 'edit' + assert_no_tag :tag => 'input', :attributes => { :type => 'checkbox', + :name => 'query[is_public]' } + assert_tag :tag => 'input', :attributes => { :type => 'checkbox', + :name => 'query_is_for_all', + :checked => nil, + :disabled => nil } + end + + def test_edit_project_public_query + @request.session[:user_id] = 2 + get :edit, :id => 1 + assert_response :success + assert_template 'edit' + assert_tag :tag => 'input', :attributes => { :type => 'checkbox', + :name => 'query[is_public]', + :checked => 'checked' + } + assert_tag :tag => 'input', :attributes => { :type => 'checkbox', + :name => 'query_is_for_all', + :checked => nil, + :disabled => 'disabled' } + end + + def test_edit_sort_criteria + @request.session[:user_id] = 1 + get :edit, :id => 5 + assert_response :success + assert_template 'edit' + assert_tag :tag => 'select', :attributes => { :name => 'query[sort_criteria][0][]' }, + :child => { :tag => 'option', :attributes => { :value => 'priority', + :selected => 'selected' } } + assert_tag :tag => 'select', :attributes => { :name => 'query[sort_criteria][0][]' }, + :child => { :tag => 'option', :attributes => { :value => 'desc', + :selected => 'selected' } } + end + + def test_edit_invalid_query + @request.session[:user_id] = 2 + get :edit, :id => 99 + assert_response 404 + end + + def test_udpate_global_private_query + @request.session[:user_id] = 3 + put :update, + :id => 3, + :default_columns => '1', + :fields => ["status_id", "assigned_to_id"], + :operators => {"assigned_to_id" => "=", "status_id" => "o"}, + :values => { "assigned_to_id" => ["me"], "status_id" => ["1"]}, + :query => {"name" => "test_edit_global_private_query", "is_public" => "1"} + + assert_redirected_to :controller => 'issues', :action => 'index', :query_id => 3 + q = Query.find_by_name('test_edit_global_private_query') + assert !q.is_public? + assert q.has_default_columns? + assert q.valid? + end + + def test_update_global_public_query + @request.session[:user_id] = 1 + put :update, + :id => 4, + :default_columns => '1', + :fields => ["status_id", "assigned_to_id"], + :operators => {"assigned_to_id" => "=", "status_id" => "o"}, + :values => { "assigned_to_id" => ["1"], "status_id" => ["1"]}, + :query => {"name" => "test_edit_global_public_query", "is_public" => "1"} + + assert_redirected_to :controller => 'issues', :action => 'index', :query_id => 4 + q = Query.find_by_name('test_edit_global_public_query') + assert q.is_public? + assert q.has_default_columns? + assert q.valid? + end + + def test_update_with_failure + @request.session[:user_id] = 1 + put :update, :id => 4, :query => {:name => ''} + assert_response :success + assert_template 'edit' + end + + def test_destroy + @request.session[:user_id] = 2 + delete :destroy, :id => 1 + assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook', :set_filter => 1, :query_id => nil + assert_nil Query.find_by_id(1) + end + + def test_backslash_should_be_escaped_in_filters + @request.session[:user_id] = 2 + get :new, :subject => 'foo/bar' + assert_response :success + assert_template 'new' + assert_include 'addFilter("subject", "=", ["foo\/bar"]);', response.body + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/25/25a75439387b5e7c9157d74c8baba2d41c8319c0.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/25/25a75439387b5e7c9157d74c8baba2d41c8319c0.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,48 @@ +
+<%= link_to l(:button_log_time), + {:controller => 'timelog', :action => 'new', :project_id => @project, :issue_id => @issue}, + :class => 'icon icon-time-add' if User.current.allowed_to?(:log_time, @project, :global => true) %> +
+ +<%= render_timelog_breadcrumb %> + +

<%= l(:label_spent_time) %>

+ +<%= form_tag({:controller => 'timelog', :action => 'index', :project_id => @project, :issue_id => @issue}, :method => :get, :id => 'query_form') do %> +<%= render :partial => 'date_range' %> +<% end %> + +
+

<%= l(:label_total_time) %>: <%= html_hours(l_hours(@total_hours)) %>

+
+ +<% unless @entries.empty? %> +<%= render :partial => 'list', :locals => { :entries => @entries }%> +

<%= pagination_links_full @entry_pages, @entry_count %>

+ +<% 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, :onclick => "showModal('csv-export-options', '330px'); return false;" %> +<% end %> + + +<% end %> + +<% html_title l(:label_spent_time), l(:label_details) %> + +<% content_for :header_tags do %> + <%= auto_discovery_link_tag(:atom, {:issue_id => @issue, :format => 'atom', :key => User.current.rss_key}, :title => l(:label_spent_time)) %> +<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/25/25e20e75adcb2337c7b7589449cc334604a57456.svn-base --- a/.svn/pristine/25/25e20e75adcb2337c7b7589449cc334604a57456.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,64 +0,0 @@ -# $Id: psw.rb 73 2006-04-24 21:59:35Z blackhedd $ -# -# -#---------------------------------------------------------------------------- -# -# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved. -# -# Gmail: garbagecat10 -# -# 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 St, Fifth Floor, Boston, MA 02110-1301 USA -# -#--------------------------------------------------------------------------- -# -# - - -module Net -class LDAP - - -class Password - class << self - - # Generate a password-hash suitable for inclusion in an LDAP attribute. - # Pass a hash type (currently supported: :md5 and :sha) and a plaintext - # password. This function will return a hashed representation. - # STUB: This is here to fulfill the requirements of an RFC, which one? - # TODO, gotta do salted-sha and (maybe) salted-md5. - # Should we provide sha1 as a synonym for sha1? I vote no because then - # should you also provide ssha1 for symmetry? - def generate( type, str ) - case type - when :md5 - require 'md5' - "{MD5}#{ [MD5.new( str.to_s ).digest].pack("m").chomp }" - when :sha - require 'sha1' - "{SHA}#{ [SHA1.new( str.to_s ).digest].pack("m").chomp }" - # when ssha - else - raise Net::LDAP::LdapError.new( "unsupported password-hash type (#{type})" ) - end - end - - end -end - - -end # class LDAP -end # module Net - - diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/25/25e2b9b227cb6f18e371c2b9f54071b5c31827f3.svn-base --- a/.svn/pristine/25/25e2b9b227cb6f18e371c2b9f54071b5c31827f3.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,106 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../../../test_helper', __FILE__) - -class Redmine::CipheringTest < ActiveSupport::TestCase - - def test_password_should_be_encrypted - Redmine::Configuration.with 'database_cipher_key' => 'secret' do - r = Repository::Subversion.generate!(:password => 'foo') - assert_equal 'foo', r.password - assert r.read_attribute(:password).match(/\Aaes-256-cbc:.+\Z/) - end - end - - def test_password_should_be_clear_with_blank_key - Redmine::Configuration.with 'database_cipher_key' => '' do - r = Repository::Subversion.generate!(:password => 'foo') - assert_equal 'foo', r.password - assert_equal 'foo', r.read_attribute(:password) - end - end - - def test_password_should_be_clear_with_nil_key - Redmine::Configuration.with 'database_cipher_key' => nil do - r = Repository::Subversion.generate!(:password => 'foo') - assert_equal 'foo', r.password - assert_equal 'foo', r.read_attribute(:password) - end - end - - def test_blank_password_should_be_clear - Redmine::Configuration.with 'database_cipher_key' => 'secret' do - r = Repository::Subversion.generate!(:password => '') - assert_equal '', r.password - assert_equal '', r.read_attribute(:password) - end - end - - def test_unciphered_password_should_be_readable - Redmine::Configuration.with 'database_cipher_key' => nil do - r = Repository::Subversion.generate!(:password => 'clear') - end - - Redmine::Configuration.with 'database_cipher_key' => 'secret' do - r = Repository.first(:order => 'id DESC') - assert_equal 'clear', r.password - end - end - - def test_ciphered_password_with_no_cipher_key_configured_should_be_returned_ciphered - Redmine::Configuration.with 'database_cipher_key' => 'secret' do - r = Repository::Subversion.generate!(:password => 'clear') - end - - Redmine::Configuration.with 'database_cipher_key' => '' do - r = Repository.first(:order => 'id DESC') - # password can not be deciphered - assert_nothing_raised do - assert r.password.match(/\Aaes-256-cbc:.+\Z/) - end - end - end - - def test_encrypt_all - Repository.delete_all - Redmine::Configuration.with 'database_cipher_key' => nil do - Repository::Subversion.generate!(:password => 'foo') - Repository::Subversion.generate!(:password => 'bar') - end - - Redmine::Configuration.with 'database_cipher_key' => 'secret' do - assert Repository.encrypt_all(:password) - r = Repository.first(:order => 'id DESC') - assert_equal 'bar', r.password - assert r.read_attribute(:password).match(/\Aaes-256-cbc:.+\Z/) - end - end - - def test_decrypt_all - Repository.delete_all - Redmine::Configuration.with 'database_cipher_key' => 'secret' do - Repository::Subversion.generate!(:password => 'foo') - Repository::Subversion.generate!(:password => 'bar') - - assert Repository.decrypt_all(:password) - r = Repository.first(:order => 'id DESC') - assert_equal 'bar', r.password - assert_equal 'bar', r.read_attribute(:password) - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/25/25f1da6f66db9d07326385e2039be298e7f220f8.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/25/25f1da6f66db9d07326385e2039be298e7f220f8.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,61 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../../test_helper', __FILE__) + +class Redmine::ConfigurationTest < ActiveSupport::TestCase + def setup + @conf = Redmine::Configuration + end + + def test_empty + assert_kind_of Hash, load_conf('empty.yml', 'test') + end + + def test_default + assert_kind_of Hash, load_conf('default.yml', 'test') + assert_equal 'foo', @conf['somesetting'] + end + + def test_no_default + assert_kind_of Hash, load_conf('no_default.yml', 'test') + assert_equal 'foo', @conf['somesetting'] + end + + def test_overrides + assert_kind_of Hash, load_conf('overrides.yml', 'test') + assert_equal 'bar', @conf['somesetting'] + end + + def test_with + load_conf('default.yml', 'test') + assert_equal 'foo', @conf['somesetting'] + @conf.with 'somesetting' => 'bar' do + assert_equal 'bar', @conf['somesetting'] + end + assert_equal 'foo', @conf['somesetting'] + end + + private + + def load_conf(file, env) + @conf.load( + :file => File.join(Rails.root, 'test', 'fixtures', 'configuration', file), + :env => env + ) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/26/2671f6e5821e0f33ebda94781641aa4c51e44a69.svn-base --- a/.svn/pristine/26/2671f6e5821e0f33ebda94781641aa4c51e44a69.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ -<%= wiki_page_breadcrumb(@page) %> - -

<%=h @original_title %>

- -<%= error_messages_for 'page' %> - -<% labelled_tabular_form_for :wiki_page, @page, :url => { :action => 'rename' } do |f| %> -
-

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

-

<%= f.check_box :redirect_existing_links %>

-

<%= f.select :parent_id, "" + wiki_page_options_for_select(@wiki.pages.all(:include => :parent) - @page.self_and_descendants, @page.parent), :label => :field_parent_title %>

-
-<%= submit_tag l(:button_rename) %> -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/26/2675c54fd1e1031cc73ed11da9cb75c92aebe12b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/26/2675c54fd1e1031cc73ed11da9cb75c92aebe12b.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,1089 @@ +sl: + # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl) + direction: ltr + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%Y-%m-%d" + short: "%b %d" + long: "%B %d, %Y" + + day_names: [Nedelja, Ponedeljek, Torek, Sreda, ÄŒetrtek, Petek, Sobota] + abbr_day_names: [Ned, Pon, To, Sr, ÄŒet, Pet, Sob] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, Januar, Februar, Marec, April, Maj, Junij, Julij, Avgust, September, Oktober, November, December] + abbr_month_names: [~, Jan, Feb, Mar, Apr, Maj, Jun, Jul, Aug, Sep, Okt, Nov, Dec] + # Used in date_select and datime_select. + order: + - :year + - :month + - :day + + time: + formats: + default: "%a, %d %b %Y %H:%M:%S %z" + time: "%H:%M" + short: "%d %b %H:%M" + long: "%B %d, %Y %H:%M" + am: "am" + pm: "pm" + + datetime: + distance_in_words: + half_a_minute: "pol minute" + less_than_x_seconds: + one: "manj kot 1. sekundo" + other: "manj kot %{count} sekund" + x_seconds: + one: "1. sekunda" + other: "%{count} sekund" + less_than_x_minutes: + one: "manj kot minuto" + other: "manj kot %{count} minut" + x_minutes: + one: "1 minuta" + other: "%{count} minut" + about_x_hours: + one: "okrog 1. ure" + other: "okrog %{count} ur" + x_hours: + one: "1 ura" + other: "%{count} ur" + x_days: + one: "1 dan" + other: "%{count} dni" + about_x_months: + one: "okrog 1. mesec" + other: "okrog %{count} mesecev" + x_months: + one: "1 mesec" + other: "%{count} mesecev" + about_x_years: + one: "okrog 1. leto" + other: "okrog %{count} let" + over_x_years: + one: "veÄ kot 1. leto" + other: "veÄ kot %{count} let" + almost_x_years: + one: "skoraj 1. leto" + other: "skoraj %{count} let" + + number: + format: + separator: "," + delimiter: "." + precision: 3 + human: + format: + precision: 3 + delimiter: "" + storage_units: + format: "%n %u" + units: + kb: KB + tb: TB + gb: GB + byte: + one: Byte + other: Bytes + mb: MB + +# Used in array.to_sentence. + support: + array: + sentence_connector: "in" + skip_last_comma: false + + activerecord: + errors: + template: + header: + one: "1. napaka je prepreÄila temu %{model} da bi se shranil" + other: "%{count} napak je prepreÄilo temu %{model} da bi se shranil" + messages: + inclusion: "ni vkljuÄen na seznamu" + exclusion: "je rezerviran" + invalid: "je napaÄen" + confirmation: "ne ustreza potrdilu" + accepted: "mora biti sprejet" + empty: "ne sme biti prazen" + blank: "ne sme biti neizpolnjen" + too_long: "je predolg" + too_short: "je prekratek" + wrong_length: "je napaÄne dolžine" + taken: "je že zaseden" + not_a_number: "ni Å¡tevilo" + not_a_date: "ni veljaven datum" + greater_than: "mora biti veÄji kot %{count}" + greater_than_or_equal_to: "mora biti veÄji ali enak kot %{count}" + equal_to: "mora biti enak kot %{count}" + less_than: "mora biti manjÅ¡i kot %{count}" + less_than_or_equal_to: "mora biti manjÅ¡i ali enak kot %{count}" + odd: "mora biti sodo" + even: "mora biti liho" + greater_than_start_date: "mora biti kasnejÅ¡i kot zaÄetni datum" + not_same_project: "ne pripada istemu projektu" + circular_dependency: "Ta odnos bi povzroÄil krožno odvisnost" + cant_link_an_issue_with_a_descendant: "Zahtevek ne more biti povezan s svojo podnalogo" + + actionview_instancetag_blank_option: Prosimo izberite + + 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: UTF-8 + general_pdf_encoding: UTF-8 + 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: Ustvarjanje 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 %{value}" + notice_email_error: "Ob poÅ¡iljanju e-sporoÄila je priÅ¡lo do napake (%{value})" + notice_feeds_access_key_reseted: VaÅ¡ RSS dostopni kljuÄ je bil ponastavljen. + notice_failed_to_save_issues: "Neuspelo shranjevanje %{count} zahtevka na %{total} izbranem: %{ids}." + 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: %{value}" + 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: %{value}" + 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 %{value} geslo" + mail_body_lost_password: 'Za spremembo glesla kliknite na naslednjo povezavo:' + mail_subject_register: "Aktivacija %{value} 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Å¡ %{value} raÄun." + mail_body_account_information: Informacije o vaÅ¡em raÄunu + mail_subject_account_activation_request: "%{value} zahtevek za aktivacijo raÄuna" + mail_body_account_activation_request: "Registriral se je nov uporabnik (%{value}). RaÄun Äaka na vaÅ¡o odobritev:" + mail_subject_reminder: "%{count} zahtevek(zahtevki) zapadejo v naslednjih %{days} dneh" + mail_body_reminder: "%{count} zahtevek(zahtevki), ki so vam dodeljeni bodo zapadli v naslednjih %{days} dneh:" + + + 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_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: 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_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_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_x_projects: + zero: ni projektov + one: 1 projekt + other: "%{count} projektov" + 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 %{value}" + 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_registered_on: Registriran + label_activity: Aktivnost + label_overall_activity: Celotna aktivnost + label_user_activity: "Aktivnost %{value}" + 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: "%{value} 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_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_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: odprt zahtevek + label_open_issues_plural: odprti zahtevki + label_closed_issues: zaprt zahtevek + label_closed_issues_plural: zaprti zahtevki + label_x_open_issues_abbr_on_total: + zero: 0 odprtih / %{total} + one: 1 odprt / %{total} + other: "%{count} odprtih / %{total}" + label_x_open_issues_abbr: + zero: 0 odprtih + one: 1 odprt + other: "%{count} odprtih" + label_x_closed_issues_abbr: + zero: 0 zaprtih + one: 1 zaprt + other: "%{count} zaprtih" + 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: Gantogram + label_internal: Notranji + label_last_changes: "zadnjih %{count} sprememb" + label_change_view_all: Poglej vse spremembe + label_personalize_page: Individualiziraj to stran + label_comment: Komentar + label_comment_plural: Komentarji + label_x_comments: + zero: ni komentarjev + one: 1 komentar + other: "%{count} komentarjev" + 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 %{count} 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_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_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 %{value}" + label_roadmap_overdue: "%{value} 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: "%{value} ura" + label_f_hour_plural: "%{value} 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: Do + label_date_to: Do + label_language_based: Glede na uporabnikov jezik + label_sort_by: "Razporedi po %{value}" + label_send_test_email: PoÅ¡lji testno e-pismo + label_feeds_access_key_created_on: "RSS dostopni kljuÄ narejen %{value} nazaj" + label_module_plural: Moduli + label_added_time_by: "Dodan %{author} %{age} nazaj" + label_updated_time_by: "Posodobljen od %{author} %{age} nazaj" + label_updated_time: "Posodobljen %{value} 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_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: %{value}" + 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: Izberi 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): %{value} bodo prav tako izbrisani." + text_workflow_edit: Izberite vlogo in zahtevek za urejanje poteka dela + text_are_you_sure: Ali ste prepriÄani? + text_tip_issue_begin_day: naloga z zaÄetkom na ta dan + text_tip_issue_end_day: naloga z zakljuÄkom na ta dan + text_tip_issue_begin_end_day: naloga ki se zaÄne in konÄa ta dan + text_caracters_maximum: "najveÄ %{count} znakov." + text_caracters_minimum: "Mora biti vsaj dolg vsaj %{count} znakov." + text_length_between: "Dolžina med %{min} in %{max} znakov." + 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 %{id} je sporoÄil(a) %{author}." + text_issue_updated: "Zahtevek %{id} je posodobil(a) %{author}." + text_wiki_destroy_confirmation: Ali ste prepriÄani da želite izbrisati ta wiki in vso njegovo vsebino? + text_issue_category_destroy_question: "Nekateri zahtevki (%{count}) 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 %{value}." + 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: "%{hours} 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: "%{value} je napisal(a):" + text_enumeration_destroy_question: "%{count} 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/configuration.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_developer: 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_in_progress: V teku + 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) + warning_attachments_not_saved: "%{count} datotek(e) ni bilo mogoÄe shraniti." + field_editable: Uredljivo + text_plugin_assets_writable: Zapisljiva mapa za vtiÄnike + label_display: Prikaz + button_create_and_continue: Ustvari in nadaljuj + text_custom_field_possible_values_info: 'Ena vrstica za vsako vrednost' + setting_repository_log_display_limit: NajveÄje Å¡tevilo prikazanih revizij v log datoteki + setting_file_max_size_displayed: NajveÄja velikost besedilnih datotek v vkljuÄenem prikazu + field_watcher: Opazovalec + setting_openid: Dovoli OpenID prijavo in registracijo + field_identity_url: OpenID URL + label_login_with_open_id_option: ali se prijavi z OpenID + field_content: Vsebina + label_descending: PadajoÄe + label_sort: Razvrsti + label_ascending: NaraÅ¡ÄajoÄe + label_date_from_to: Od %{start} do %{end} + label_greater_or_equal: ">=" + label_less_or_equal: <= + text_wiki_page_destroy_question: Ta stran ima %{descendants} podstran(i) in naslednik(ov). Kaj želite storiti? + text_wiki_page_reassign_children: Znova dodeli podstrani tej glavni strani + text_wiki_page_nullify_children: Obdrži podstrani kot glavne strani + text_wiki_page_destroy_children: IzbriÅ¡i podstrani in vse njihove naslednike + setting_password_min_length: Minimalna dolžina gesla + field_group_by: Združi rezultate po + mail_subject_wiki_content_updated: "'%{id}' wiki stran je bila posodobljena" + label_wiki_content_added: Wiki stran dodana + mail_subject_wiki_content_added: "'%{id}' wiki stran je bila dodana" + mail_body_wiki_content_added: "%{author} je dodal '%{id}' wiki stran" + label_wiki_content_updated: Wiki stran posodobljena + mail_body_wiki_content_updated: "%{author} je posodobil '%{id}' wiki stran." + permission_add_project: Ustvari projekt + setting_new_project_user_role_id: Vloga, dodeljena neadministratorskemu uporabniku, ki je ustvaril projekt + label_view_all_revisions: Poglej vse revizije + label_tag: Oznaka + label_branch: Veja + error_no_tracker_in_project: Noben sledilnik ni povezan s tem projektom. Prosimo preverite nastavitve projekta. + error_no_default_issue_status: Privzeti zahtevek ni definiran. Prosimo preverite svoje nastavitve (Pojdite na "Administracija -> Stanje zahtevkov"). + text_journal_changed: "%{label} se je spremenilo iz %{old} v %{new}" + text_journal_set_to: "%{label} nastavljeno na %{value}" + text_journal_deleted: "%{label} izbrisan (%{old})" + label_group_plural: Skupine + label_group: Skupina + label_group_new: Nova skupina + label_time_entry_plural: Porabljen Äas + text_journal_added: "%{label} %{value} dodan" + field_active: Aktiven + enumeration_system_activity: Sistemska aktivnost + permission_delete_issue_watchers: IzbriÅ¡i opazovalce + version_status_closed: zaprt + version_status_locked: zaklenjen + version_status_open: odprt + error_can_not_reopen_issue_on_closed_version: Zahtevek dodeljen zaprti verziji ne more biti ponovno odprt + label_user_anonymous: Anonimni + button_move_and_follow: Premakni in sledi + setting_default_projects_modules: Privzeti moduli za nove projekte + setting_gravatar_default: Privzeta Gravatar slika + field_sharing: Deljenje + label_version_sharing_hierarchy: S projektno hierarhijo + label_version_sharing_system: Z vsemi projekti + label_version_sharing_descendants: S podprojekti + label_version_sharing_tree: Z drevesom projekta + label_version_sharing_none: Ni deljeno + error_can_not_archive_project: Ta projekt ne more biti arhiviran + button_duplicate: Podvoji + button_copy_and_follow: Kopiraj in sledi + label_copy_source: Vir + setting_issue_done_ratio: IzraÄunaj razmerje opravljenega zahtevka z + setting_issue_done_ratio_issue_status: Uporabi stanje zahtevka + error_issue_done_ratios_not_updated: Razmerje opravljenega zahtevka ni bilo posodobljeno. + error_workflow_copy_target: Prosimo izberite ciljni(e) sledilnik(e) in vlogo(e) + setting_issue_done_ratio_issue_field: Uporabi polje zahtevka + label_copy_same_as_target: Enako kot cilj + label_copy_target: Cilj + notice_issue_done_ratios_updated: Razmerje opravljenega zahtevka posodobljeno. + error_workflow_copy_source: Prosimo izberite vir zahtevka ali vlogo + label_update_issue_done_ratios: Posodobi razmerje opravljenega zahtevka + setting_start_of_week: ZaÄni koledarje z + permission_view_issues: Poglej zahtevke + label_display_used_statuses_only: Prikaži samo stanja ki uporabljajo ta sledilnik + label_revision_id: Revizija %{value} + label_api_access_key: API dostopni kljuÄ + label_api_access_key_created_on: API dostopni kljuÄ ustvarjen pred %{value} + label_feeds_access_key: RSS dostopni kljuÄ + notice_api_access_key_reseted: VaÅ¡ API dostopni kljuÄ je bil ponastavljen. + setting_rest_api_enabled: OmogoÄi REST spletni servis + label_missing_api_access_key: ManjkajoÄ API dostopni kljuÄ + label_missing_feeds_access_key: ManjkajoÄ RSS dostopni kljuÄ + button_show: Prikaži + text_line_separated: Dovoljenih veÄ vrednosti (ena vrstica za vsako vrednost). + setting_mail_handler_body_delimiters: Odreži e-poÅ¡to po eni od teh vrstic + permission_add_subprojects: Ustvari podprojekte + label_subproject_new: Nov podprojekt + text_own_membership_delete_confirmation: |- + Odstranili boste nekatere ali vse od dovoljenj zaradi Äesar morda ne boste mogli veÄ urejati tega projekta. + Ali ste prepriÄani, da želite nadaljevati? + label_close_versions: Zapri dokonÄane verzije + label_board_sticky: Lepljivo + label_board_locked: Zaklenjeno + permission_export_wiki_pages: Izvozi wiki strani + setting_cache_formatted_text: Predpomni oblikovano besedilo + permission_manage_project_activities: Uredi aktivnosti projekta + error_unable_delete_issue_status: Stanja zahtevka ni bilo možno spremeniti + label_profile: Profil + permission_manage_subtasks: Uredi podnaloge + field_parent_issue: Nadrejena naloga + label_subtask_plural: Podnaloge + label_project_copy_notifications: Med kopiranjem projekta poÅ¡lji e-poÅ¡tno sporoÄilo + error_can_not_delete_custom_field: Polja po meri ni mogoÄe izbrisati + error_unable_to_connect: Povezava ni mogoÄa (%{value}) + error_can_not_remove_role: Ta vloga je v uporabi in je ni mogoÄe izbrisati. + error_can_not_delete_tracker: Ta sledilnik vsebuje zahtevke in se ga ne more izbrisati. + field_principal: Upravnik varnosti + label_my_page_block: Moj gradnik strani + notice_failed_to_save_members: "Shranjevanje uporabnika(ov) ni uspelo: %{errors}." + text_zoom_out: Približaj + text_zoom_in: Oddalji + notice_unable_delete_time_entry: Brisanje dnevnika porabljenaga Äasa ni mogoÄe. + label_overall_spent_time: Skupni porabljeni Äas + field_time_entries: Beleži porabljeni Äas + project_module_gantt: Gantogram + project_module_calendar: Koledear + button_edit_associated_wikipage: "Uredi povezano Wiki stran: %{page_title}" + field_text: Besedilno polje + label_user_mail_option_only_owner: Samo za stvari katerih lastnik sem + setting_default_notification_option: Privzeta možnost obveÅ¡Äanja + label_user_mail_option_only_my_events: Samo za stvari, ki jih opazujem ali sem v njih vpleten + label_user_mail_option_only_assigned: Samo za stvari, ki smo mi dodeljene + label_user_mail_option_none: Noben dogodek + field_member_of_group: PooblaÅ¡ÄenÄeva skupina + field_assigned_to_role: PooblaÅ¡ÄenÄeva vloga + notice_not_authorized_archived_project: Projekt, do katerega poskuÅ¡ate dostopati, je bil arhiviran. + label_principal_search: "PoiÅ¡Äi uporabnika ali skupino:" + label_user_search: "PoiÅ¡Äi uporabnikia:" + field_visible: Viden + setting_emails_header: Glava e-poÅ¡te + setting_commit_logtime_activity_id: Aktivnost zabeleženega Äasa + text_time_logged_by_changeset: Uporabljeno v spremembi %{value}. + setting_commit_logtime_enabled: OmogoÄi beleženje Äasa + notice_gantt_chart_truncated: Graf je bil odrezan, ker je prekoraÄil najveÄje dovoljeno Å¡tevilo elementov, ki se jih lahko prikaže (%{max}) + setting_gantt_items_limit: NajveÄje Å¡tevilo elementov prikazano na gantogramu + field_warn_on_leaving_unsaved: Opozori me, kadar zapuÅ¡Äam stran z neshranjenim besedilom + text_warn_on_leaving_unsaved: Trenutna stran vsebuje neshranjeno besedilo ki bo izgubljeno, Äe zapustite to stran. + label_my_queries: Moje poizvedbe po meri + text_journal_changed_no_detail: "%{label} posodobljen" + label_news_comment_added: Komentar dodan novici + button_expand_all: RazÅ¡iri vse + button_collapse_all: SkrÄi vse + label_additional_workflow_transitions_for_assignee: Dovoljeni dodatni prehodi kadar je uporabnik pooblaÅ¡Äenec + label_additional_workflow_transitions_for_author: Dovoljeni dodatni prehodi kadar je uporabnik avtor + label_bulk_edit_selected_time_entries: Skupinsko urejanje izbranih Äasovnih zapisov + text_time_entries_destroy_confirmation: Ali ste prepriÄani, da želite izbristai izbran(e) Äasovn(i/e) zapis(e)? + label_role_anonymous: Anonimni + label_role_non_member: NeÄlan + label_issue_note_added: Dodan zaznamek + label_issue_status_updated: Status posodobljen + label_issue_priority_updated: Prioriteta posodobljena + label_issues_visibility_own: Zahtevek ustvarjen s strani uporabnika ali dodeljen uporabniku + field_issues_visibility: Vidljivost zahtevkov + label_issues_visibility_all: Vsi zahtevki + permission_set_own_issues_private: Nastavi lastne zahtevke kot javne ali zasebne + field_is_private: Zaseben + permission_set_issues_private: Nastavi zahtevke kot javne ali zasebne + label_issues_visibility_public: Vsi nezasebni zahtevki + text_issues_destroy_descendants_confirmation: To bo izbrisalo tudi %{count} podnalog(o). + field_commit_logs_encoding: Kodiranje sporoÄil ob predaji + field_scm_path_encoding: Pot do kodiranja + text_scm_path_encoding_note: "Privzeto: UTF-8" + field_path_to_repository: Pot do shrambe + field_root_directory: Korenska mapa + field_cvs_module: Modul + field_cvsroot: CVSROOT + text_mercurial_repository_note: Lokalna shramba (npr. /hgrepo, c:\hgrepo) + text_scm_command: Ukaz + text_scm_command_version: Verzija + label_git_report_last_commit: SporoÄi zadnje uveljavljanje datotek in map + text_scm_config: Svoje SCM ukaze lahko nastavite v datoteki config/configuration.yml. Po urejanju prosimo ponovno zaženite aplikacijo. + text_scm_command_not_available: SCM ukaz ni na voljo. Prosimo preverite nastavitve v upravljalskem podoknu. + + text_git_repository_note: Shramba je prazna in lokalna (npr. /gitrepo, c:\gitrepo) + + notice_issue_successful_create: Ustvarjen zahtevek %{id}. + label_between: med + setting_issue_group_assignment: Dovoli dodeljevanje zahtevka skupinam + label_diff: diff + + description_query_sort_criteria_direction: Sort direction + description_project_scope: Search scope + description_filter: Filter + description_user_mail_notification: Mail notification settings + description_date_from: Enter start date + description_message_content: Message content + description_available_columns: Available Columns + description_date_range_interval: Choose range by selecting start and end date + description_issue_category_reassign: Choose issue category + description_search: Searchfield + description_notes: Notes + description_date_range_list: Choose range from list + description_choose_project: Projects + description_date_to: Enter end date + description_query_sort_criteria_attribute: Sort attribute + description_wiki_subpages_reassign: Choose new parent page + description_selected_columns: Selected Columns + label_parent_revision: Parent + label_child_revision: Child + error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. + setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues + button_edit_section: Edit this section + setting_repositories_encodings: Attachments and repositories encodings + description_all_columns: All Columns + button_export: Export + label_export_options: "%{export_format} export options" + error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) + notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." + label_x_issues: + zero: 0 zahtevek + one: 1 zahtevek + other: "%{count} zahtevki" + label_repository_new: New repository + field_repository_is_default: Main repository + label_copy_attachments: Copy attachments + label_item_position: "%{position}/%{count}" + label_completed_versions: Completed versions + text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
Once saved, the identifier cannot be changed. + field_multiple: Multiple values + setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed + text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes + text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) + notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. + text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} + permission_manage_related_issues: Manage related issues + field_auth_source_ldap_filter: LDAP filter + label_search_for_watchers: Search for watchers to add + notice_account_deleted: Your account has been permanently deleted. + setting_unsubscribe: Allow users to delete their own account + button_delete_my_account: Delete my account + text_account_destroy_confirmation: |- + Are you sure you want to proceed? + Your account will be permanently deleted, with no way to reactivate it. + error_session_expired: Your session has expired. Please login again. + text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." + setting_session_lifetime: Session maximum lifetime + setting_session_timeout: Session inactivity timeout + label_session_expiration: Session expiration + permission_close_project: Close / reopen the project + label_show_closed_projects: View closed projects + button_close: Close + button_reopen: Reopen + project_status_active: active + project_status_closed: closed + project_status_archived: archived + text_project_closed: This project is closed and read-only. + notice_user_successful_create: User %{id} created. + field_core_fields: Standard fields + field_timeout: Timeout (in seconds) + setting_thumbnails_enabled: Display attachment thumbnails + setting_thumbnails_size: Thumbnails size (in pixels) + label_status_transitions: Status transitions + label_fields_permissions: Fields permissions + label_readonly: Read-only + label_required: Required + text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
Once saved, the identifier cannot be changed. + field_board_parent: Parent forum + label_attribute_of_project: Project's %{name} + label_attribute_of_author: Author's %{name} + label_attribute_of_assigned_to: Assignee's %{name} + label_attribute_of_fixed_version: Target version's %{name} + label_copy_subtasks: Copy subtasks + label_copied_to: copied to + label_copied_from: copied from + label_any_issues_in_project: any issues in project + label_any_issues_not_in_project: any issues not in project + field_private_notes: Private notes + permission_view_private_notes: View private notes + permission_set_notes_private: Set notes as private + label_no_issues_in_project: no issues in project + label_any: vsi + label_last_n_weeks: last %{count} weeks + setting_cross_project_subtasks: Allow cross-project subtasks + label_cross_project_descendants: S podprojekti + label_cross_project_tree: Z drevesom projekta + label_cross_project_hierarchy: S projektno hierarhijo + label_cross_project_system: Z vsemi projekti + button_hide: Hide + setting_non_working_week_days: Non-working days + label_in_the_next_days: in the next + label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Skupaj diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/26/269ba2765067adff083d346c53c1c4aac3a7ed83.svn-base --- a/.svn/pristine/26/269ba2765067adff083d346c53c1c4aac3a7ed83.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,90 +0,0 @@ -module CodeRay - - # A Hash of all known token kinds and their associated CSS classes. - TokenKinds = Hash.new do |h, k| - warn 'Undefined Token kind: %p' % [k] if $CODERAY_DEBUG - false - end - - # speedup - TokenKinds.compare_by_identity if TokenKinds.respond_to? :compare_by_identity - - TokenKinds.update( # :nodoc: - :annotation => 'annotation', - :attribute_name => 'attribute-name', - :attribute_value => 'attribute-value', - :binary => 'bin', - :char => 'char', - :class => 'class', - :class_variable => 'class-variable', - :color => 'color', - :comment => 'comment', - :complex => 'complex', - :constant => 'constant', - :content => 'content', - :debug => 'debug', - :decorator => 'decorator', - :definition => 'definition', - :delimiter => 'delimiter', - :directive => 'directive', - :doc => 'doc', - :doctype => 'doctype', - :doc_string => 'doc-string', - :entity => 'entity', - :error => 'error', - :escape => 'escape', - :exception => 'exception', - :filename => 'filename', - :float => 'float', - :function => 'function', - :global_variable => 'global-variable', - :hex => 'hex', - :imaginary => 'imaginary', - :important => 'important', - :include => 'include', - :inline => 'inline', - :inline_delimiter => 'inline-delimiter', - :instance_variable => 'instance-variable', - :integer => 'integer', - :key => 'key', - :keyword => 'keyword', - :label => 'label', - :local_variable => 'local-variable', - :modifier => 'modifier', - :namespace => 'namespace', - :octal => 'octal', - :predefined => 'predefined', - :predefined_constant => 'predefined-constant', - :predefined_type => 'predefined-type', - :preprocessor => 'preprocessor', - :pseudo_class => 'pseudo-class', - :regexp => 'regexp', - :reserved => 'reserved', - :shell => 'shell', - :string => 'string', - :symbol => 'symbol', - :tag => 'tag', - :type => 'type', - :value => 'value', - :variable => 'variable', - - :change => 'change', - :delete => 'delete', - :head => 'head', - :insert => 'insert', - - :eyecatcher => 'eyecatcher', - - :ident => false, - :operator => false, - - :space => false, - :plain => false - ) - - TokenKinds[:method] = TokenKinds[:function] - TokenKinds[:escape] = TokenKinds[:delimiter] - TokenKinds[:docstring] = TokenKinds[:comment] - - TokenKinds.freeze -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/26/26d9747ae706cea5f68ea0007e9fb7e1bf0c8d18.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/26/26d9747ae706cea5f68ea0007e9fb7e1bf0c8d18.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,76 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class CustomFieldVersionFormatTest < ActiveSupport::TestCase + fixtures :custom_fields, :projects, :members, :users, :member_roles, :trackers, :issues, :versions + + def setup + @field = IssueCustomField.create!(:name => 'Tester', :field_format => 'version') + end + + def test_possible_values_with_no_arguments + assert_equal [], @field.possible_values + assert_equal [], @field.possible_values(nil) + end + + def test_possible_values_with_project_resource + project = Project.find(1) + possible_values = @field.possible_values(project.issues.first) + assert possible_values.any? + assert_equal project.shared_versions.sort.collect(&:id).map(&:to_s), possible_values + end + + def test_possible_values_with_nil_project_resource + assert_equal [], @field.possible_values(Issue.new) + end + + def test_possible_values_options_with_no_arguments + assert_equal [], @field.possible_values_options + assert_equal [], @field.possible_values_options(nil) + end + + def test_possible_values_options_with_project_resource + project = Project.find(1) + possible_values_options = @field.possible_values_options(project.issues.first) + assert possible_values_options.any? + assert_equal project.shared_versions.sort.map {|u| [u.name, u.id.to_s]}, possible_values_options + end + + def test_possible_values_options_with_array + projects = Project.find([1, 2]) + possible_values_options = @field.possible_values_options(projects) + assert possible_values_options.any? + assert_equal (projects.first.shared_versions & projects.last.shared_versions).sort.map {|u| [u.name, u.id.to_s]}, possible_values_options + end + + def test_cast_blank_value + assert_equal nil, @field.cast_value(nil) + assert_equal nil, @field.cast_value("") + end + + def test_cast_valid_value + version = @field.cast_value("2") + assert_kind_of Version, version + assert_equal Version.find(2), version + end + + def test_cast_invalid_value + assert_equal nil, @field.cast_value("187") + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/26/26f2b8dec33ec3e8eb2d5b55094ac09d33160993.svn-base --- a/.svn/pristine/26/26f2b8dec33ec3e8eb2d5b55094ac09d33160993.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,603 +0,0 @@ -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 -r 0a574315af3e -r 4f746d8966dd .svn/pristine/27/270c9debef33419abbdab93ac142c4646e6e19cf.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/27/270c9debef33419abbdab93ac142c4646e6e19cf.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,67 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class ReportsControllerTest < ActionController::TestCase + fixtures :projects, :trackers, :issue_statuses, :issues, + :enumerations, :users, :issue_categories, + :projects_trackers, + :roles, + :member_roles, + :members, + :enabled_modules, + :versions + + def test_get_issue_report + get :issue_report, :id => 1 + + assert_response :success + assert_template 'issue_report' + + [:issues_by_tracker, :issues_by_version, :issues_by_category, :issues_by_assigned_to, + :issues_by_author, :issues_by_subproject, :issues_by_priority].each do |ivar| + assert_not_nil assigns(ivar) + end + + assert_equal IssuePriority.all.reverse, assigns(:priorities) + end + + def test_get_issue_report_details + %w(tracker version priority category assigned_to author subproject).each do |detail| + get :issue_report_details, :id => 1, :detail => detail + + assert_response :success + assert_template 'issue_report_details' + assert_not_nil assigns(:field) + assert_not_nil assigns(:rows) + assert_not_nil assigns(:data) + assert_not_nil assigns(:report_title) + end + end + + def test_get_issue_report_details_by_priority + get :issue_report_details, :id => 1, :detail => 'priority' + assert_equal IssuePriority.all.reverse, assigns(:rows) + end + + def test_get_issue_report_details_with_an_invalid_detail + get :issue_report_details, :id => 1, :detail => 'invalid' + + assert_redirected_to '/projects/ecookbook/issues/report' + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/27/273ac825eb891aaec6af1c5e2e94f14d0663c303.svn-base --- a/.svn/pristine/27/273ac825eb891aaec6af1c5e2e94f14d0663c303.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,61 +0,0 @@ -module CodeRay -module Encoders - - # = Debug Encoder - # - # Fast encoder producing simple debug output. - # - # It is readable and diff-able and is used for testing. - # - # You cannot fully restore the tokens information from the - # output, because consecutive :space tokens are merged. - # Use Tokens#dump for caching purposes. - # - # See also: Scanners::Debug - class Debug < Encoder - - register_for :debug - - FILE_EXTENSION = 'raydebug' - - def initialize options = {} - super - @opened = [] - end - - def text_token text, kind - if kind == :space - @out << text - else - # TODO: Escape ( - text = text.gsub(/[)\\]/, '\\\\\0') # escape ) and \ - @out << kind.to_s << '(' << text << ')' - end - end - - def begin_group kind - @opened << kind - @out << kind.to_s << '<' - end - - def end_group kind - if @opened.last != kind - puts @out - raise "we are inside #{@opened.inspect}, not #{kind}" - end - @opened.pop - @out << '>' - end - - def begin_line kind - @out << kind.to_s << '[' - end - - def end_line kind - @out << ']' - end - - end - -end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/27/2745a50d7301c30263d5450e181481b6eeb6b406.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/27/2745a50d7301c30263d5450e181481b6eeb6b406.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,106 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../../test_helper', __FILE__) + +class Redmine::CipheringTest < ActiveSupport::TestCase + + def test_password_should_be_encrypted + Redmine::Configuration.with 'database_cipher_key' => 'secret' do + r = Repository::Subversion.create!(:password => 'foo', :url => 'file:///tmp', :identifier => 'svn') + assert_equal 'foo', r.password + assert r.read_attribute(:password).match(/\Aaes-256-cbc:.+\Z/) + end + end + + def test_password_should_be_clear_with_blank_key + Redmine::Configuration.with 'database_cipher_key' => '' do + r = Repository::Subversion.create!(:password => 'foo', :url => 'file:///tmp', :identifier => 'svn') + assert_equal 'foo', r.password + assert_equal 'foo', r.read_attribute(:password) + end + end + + def test_password_should_be_clear_with_nil_key + Redmine::Configuration.with 'database_cipher_key' => nil do + r = Repository::Subversion.create!(:password => 'foo', :url => 'file:///tmp', :identifier => 'svn') + assert_equal 'foo', r.password + assert_equal 'foo', r.read_attribute(:password) + end + end + + def test_blank_password_should_be_clear + Redmine::Configuration.with 'database_cipher_key' => 'secret' do + r = Repository::Subversion.create!(:password => '', :url => 'file:///tmp', :identifier => 'svn') + assert_equal '', r.password + assert_equal '', r.read_attribute(:password) + end + end + + def test_unciphered_password_should_be_readable + Redmine::Configuration.with 'database_cipher_key' => nil do + r = Repository::Subversion.create!(:password => 'clear', :url => 'file:///tmp', :identifier => 'svn') + end + + Redmine::Configuration.with 'database_cipher_key' => 'secret' do + r = Repository.first(:order => 'id DESC') + assert_equal 'clear', r.password + end + end + + def test_ciphered_password_with_no_cipher_key_configured_should_be_returned_ciphered + Redmine::Configuration.with 'database_cipher_key' => 'secret' do + r = Repository::Subversion.create!(:password => 'clear', :url => 'file:///tmp', :identifier => 'svn') + end + + Redmine::Configuration.with 'database_cipher_key' => '' do + r = Repository.first(:order => 'id DESC') + # password can not be deciphered + assert_nothing_raised do + assert r.password.match(/\Aaes-256-cbc:.+\Z/) + end + end + end + + def test_encrypt_all + Repository.delete_all + Redmine::Configuration.with 'database_cipher_key' => nil do + Repository::Subversion.create!(:password => 'foo', :url => 'file:///tmp', :identifier => 'foo') + Repository::Subversion.create!(:password => 'bar', :url => 'file:///tmp', :identifier => 'bar') + end + + Redmine::Configuration.with 'database_cipher_key' => 'secret' do + assert Repository.encrypt_all(:password) + r = Repository.first(:order => 'id DESC') + assert_equal 'bar', r.password + assert r.read_attribute(:password).match(/\Aaes-256-cbc:.+\Z/) + end + end + + def test_decrypt_all + Repository.delete_all + Redmine::Configuration.with 'database_cipher_key' => 'secret' do + Repository::Subversion.create!(:password => 'foo', :url => 'file:///tmp', :identifier => 'foo') + Repository::Subversion.create!(:password => 'bar', :url => 'file:///tmp', :identifier => 'bar') + + assert Repository.decrypt_all(:password) + r = Repository.first(:order => 'id DESC') + assert_equal 'bar', r.password + assert_equal 'bar', r.read_attribute(:password) + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/27/274f3b80c3fec339ad5a9fd2fdcdbd871a6ae2d1.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/27/274f3b80c3fec339ad5a9fd2fdcdbd871a6ae2d1.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,69 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../../../../test_helper', __FILE__) +begin + require 'mocha' + + class DarcsAdapterTest < ActiveSupport::TestCase + REPOSITORY_PATH = Rails.root.join('tmp/test/darcs_repository').to_s + + if File.directory?(REPOSITORY_PATH) + def setup + @adapter = Redmine::Scm::Adapters::DarcsAdapter.new(REPOSITORY_PATH) + end + + def test_darcsversion + to_test = { "1.0.9 (release)\n" => [1,0,9] , + "2.2.0 (release)\n" => [2,2,0] } + to_test.each do |s, v| + test_darcsversion_for(s, v) + end + end + + def test_revisions + id1 = '20080308225258-98289-761f654d669045eabee90b91b53a21ce5593cadf.gz' + revs = @adapter.revisions('', nil, nil, {:with_path => true}) + assert_equal 6, revs.size + assert_equal id1, revs[5].scmid + paths = revs[5].paths + assert_equal 5, paths.size + assert_equal 'A', paths[0][:action] + assert_equal '/README', paths[0][:path] + assert_equal 'A', paths[1][:action] + assert_equal '/images', paths[1][:path] + end + + private + + def test_darcsversion_for(darcsversion, version) + @adapter.class.expects(:darcs_binary_version_from_command_line).returns(darcsversion) + assert_equal version, @adapter.class.darcs_binary_version + end + + else + puts "Darcs test repository NOT FOUND. Skipping unit tests !!!" + def test_fake; assert true end + end + end + +rescue LoadError + class DarcsMochaFake < ActiveSupport::TestCase + def test_fake; assert(false, "Requires mocha to run those tests") end + end +end + diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/27/275a63d95374044297ed252103e82089ce4c9a74.svn-base --- a/.svn/pristine/27/275a63d95374044297ed252103e82089ce4c9a74.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,86 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../../test_helper', __FILE__) - -class ApiTest::AttachmentsTest < ActionController::IntegrationTest - fixtures :projects, :trackers, :issue_statuses, :issues, - :enumerations, :users, :issue_categories, - :projects_trackers, - :roles, - :member_roles, - :members, - :enabled_modules, - :workflows, - :attachments - - def setup - Setting.rest_api_enabled = '1' - Attachment.storage_path = "#{Rails.root}/test/fixtures/files" - end - - context "/attachments/:id" do - context "GET" do - should "return the attachment" do - get '/attachments/7.xml', {}, :authorization => credentials('jsmith') - assert_response :success - assert_equal 'application/xml', @response.content_type - assert_tag :tag => 'attachment', - :child => { - :tag => 'id', - :content => '7', - :sibling => { - :tag => 'filename', - :content => 'archive.zip', - :sibling => { - :tag => 'content_url', - :content => 'http://www.example.com/attachments/download/7/archive.zip' - } - } - } - end - - should "deny access without credentials" do - get '/attachments/7.xml' - assert_response 401 - set_tmp_attachments_directory - end - end - end - - context "/attachments/download/:id/:filename" do - context "GET" do - should "return the attachment content" do - get '/attachments/download/7/archive.zip', - {}, :authorization => credentials('jsmith') - assert_response :success - assert_equal 'application/octet-stream', @response.content_type - set_tmp_attachments_directory - end - - should "deny access without credentials" do - get '/attachments/download/7/archive.zip' - assert_response 302 - set_tmp_attachments_directory - end - end - end - - def credentials(user, password=nil) - ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user) - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/27/27952988c7b2ad95ea1ef729436ace0c3896dd74.svn-base --- a/.svn/pristine/27/27952988c7b2ad95ea1ef729436ace0c3896dd74.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,10 +0,0 @@ -#!/usr/bin/env ruby - -require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT) - -# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like: -# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired -require "dispatcher" - -ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun) -Dispatcher.dispatch \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/27/2798cf8bdb60e5a48dc833a97dea5cc70e29382c.svn-base --- a/.svn/pristine/27/2798cf8bdb60e5a48dc833a97dea5cc70e29382c.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,76 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../../test_helper', __FILE__) - -class ProjectsHelperTest < ActionView::TestCase - include ApplicationHelper - include ProjectsHelper - - fixtures :projects, :trackers, :issue_statuses, :issues, - :enumerations, :users, :issue_categories, - :versions, - :projects_trackers, - :member_roles, - :members, - :groups_users, - :enabled_modules, - :workflows - - def setup - super - set_language_if_valid('en') - User.current = nil - end - - def test_link_to_version_within_project - @project = Project.find(2) - User.current = User.find(1) - assert_equal 'Alpha', link_to_version(Version.find(5)) - end - - def test_link_to_version - User.current = User.find(1) - assert_equal 'OnlineStore - Alpha', link_to_version(Version.find(5)) - end - - def test_link_to_private_version - assert_equal 'OnlineStore - Alpha', link_to_version(Version.find(5)) - end - - def test_link_to_version_invalid_version - assert_equal '', link_to_version(Object) - end - - def test_format_version_name_within_project - @project = Project.find(1) - assert_equal "0.1", format_version_name(Version.find(1)) - end - - def test_format_version_name - assert_equal "eCookbook - 0.1", format_version_name(Version.find(1)) - end - - def test_format_version_name_for_system_version - assert_equal "OnlineStore - Systemwide visible version", format_version_name(Version.find(7)) - end - - def test_version_options_for_select_with_no_versions - assert_equal '', version_options_for_select([]) - assert_equal '', version_options_for_select([], Version.find(1)) - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/27/27ca0e4d3dcbae405bcd6b8ff364b70b5542b197.svn-base --- a/.svn/pristine/27/27ca0e4d3dcbae405bcd6b8ff364b70b5542b197.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,46 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../../test_helper', __FILE__) - -class CustomFieldsHelperTest < ActionView::TestCase - include CustomFieldsHelper - include Redmine::I18n - - def test_format_boolean_value - I18n.locale = 'en' - assert_equal 'Yes', format_value('1', 'bool') - assert_equal 'No', format_value('0', 'bool') - end - - def test_unknow_field_format_should_be_edited_as_string - field = CustomField.new(:field_format => 'foo') - value = CustomValue.new(:value => 'bar', :custom_field => field) - field.id = 52 - - assert_equal '', - custom_field_tag('object', value) - end - - def test_unknow_field_format_should_be_bulk_edited_as_string - field = CustomField.new(:field_format => 'foo') - field.id = 52 - - assert_equal '', - custom_field_tag_for_bulk_edit('object', field) - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/27/27ce56818e709e285d3d63b2d5d116ba119243bf.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/27/27ce56818e709e285d3d63b2d5d116ba119243bf.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,32 @@ +class ChangeChangesetsRevisionToString < ActiveRecord::Migration + def self.up + # Some backends (eg. SQLServer 2012) do not support changing the type + # of an indexed column so the index needs to be dropped first + # BUT this index is renamed with some backends (at least SQLite3) for + # some (unknown) reasons, thus we check for the other name as well + # so we don't end up with 2 identical indexes + if index_exists? :changesets, [:repository_id, :revision], :name => :changesets_repos_rev + remove_index :changesets, :name => :changesets_repos_rev + end + if index_exists? :changesets, [:repository_id, :revision], :name => :altered_changesets_repos_rev + remove_index :changesets, :name => :altered_changesets_repos_rev + end + + change_column :changesets, :revision, :string, :null => false + + add_index :changesets, [:repository_id, :revision], :unique => true, :name => :changesets_repos_rev + end + + def self.down + if index_exists? :changesets, :changesets_repos_rev + remove_index :changesets, :name => :changesets_repos_rev + end + if index_exists? :changesets, [:repository_id, :revision], :name => :altered_changesets_repos_rev + remove_index :changesets, :name => :altered_changesets_repos_rev + end + + change_column :changesets, :revision, :integer, :null => false + + add_index :changesets, [:repository_id, :revision], :unique => true, :name => :changesets_repos_rev + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/27/27ff0c8aa6059bb3d3244c9053898a1f8e9c2b1e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/27/27ff0c8aa6059bb3d3244c9053898a1f8e9c2b1e.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,32 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 + module MyPage + module Block + def self.additional_blocks + @@additional_blocks ||= Dir.glob("#{Redmine::Plugin.directory}/*/app/views/my/blocks/_*.{rhtml,erb}").inject({}) do |h,file| + name = File.basename(file).split('.').first.gsub(/^_/, '') + h[name] = name.to_sym + h + end + end + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/28/2804d830457d070d543c18e9bbae7cbb0206aa55.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/28/2804d830457d070d543c18e9bbae7cbb0206aa55.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,188 @@ +/* Redmine - project management software + Copyright (C) 2006-2013 Jean-Philippe Lang */ + +function addFile(inputEl, file, eagerUpload) { + + if ($('#attachments_fields').children().length < 10) { + + var attachmentId = addFile.nextAttachmentId++; + + var fileSpan = $('', { id: 'attachments_' + attachmentId }); + + fileSpan.append( + $('', { type: 'text', 'class': 'filename readonly', name: 'attachments[' + attachmentId + '][filename]', readonly: 'readonly'} ).val(file.name), + $('', { type: 'text', 'class': 'description', name: 'attachments[' + attachmentId + '][description]', maxlength: 255, placeholder: $(inputEl).data('description-placeholder') } ).toggle(!eagerUpload), + $(' ').attr({ href: "#", 'class': 'remove-upload' }).click(removeFile).toggle(!eagerUpload) + ).appendTo('#attachments_fields'); + + if(eagerUpload) { + ajaxUpload(file, attachmentId, fileSpan, inputEl); + } + + return attachmentId; + } + return null; +} + +addFile.nextAttachmentId = 1; + +function ajaxUpload(file, attachmentId, fileSpan, inputEl) { + + function onLoadstart(e) { + fileSpan.removeClass('ajax-waiting'); + fileSpan.addClass('ajax-loading'); + $('input:submit', $(this).parents('form')).attr('disabled', 'disabled'); + } + + function onProgress(e) { + if(e.lengthComputable) { + this.progressbar( 'value', e.loaded * 100 / e.total ); + } + } + + function actualUpload(file, attachmentId, fileSpan, inputEl) { + + ajaxUpload.uploading++; + + uploadBlob(file, $(inputEl).data('upload-path'), attachmentId, { + loadstartEventHandler: onLoadstart.bind(progressSpan), + progressEventHandler: onProgress.bind(progressSpan) + }) + .done(function(result) { + progressSpan.progressbar( 'value', 100 ).remove(); + fileSpan.find('input.description, a').css('display', 'inline-block'); + }) + .fail(function(result) { + progressSpan.text(result.statusText); + }).always(function() { + ajaxUpload.uploading--; + fileSpan.removeClass('ajax-loading'); + var form = fileSpan.parents('form'); + if (form.queue('upload').length == 0 && ajaxUpload.uploading == 0) { + $('input:submit', form).removeAttr('disabled'); + } + form.dequeue('upload'); + }); + } + + var progressSpan = $('
').insertAfter(fileSpan.find('input.filename')); + progressSpan.progressbar(); + fileSpan.addClass('ajax-waiting'); + + var maxSyncUpload = $(inputEl).data('max-concurrent-uploads'); + + if(maxSyncUpload == null || maxSyncUpload <= 0 || ajaxUpload.uploading < maxSyncUpload) + actualUpload(file, attachmentId, fileSpan, inputEl); + else + $(inputEl).parents('form').queue('upload', actualUpload.bind(this, file, attachmentId, fileSpan, inputEl)); +} + +ajaxUpload.uploading = 0; + +function removeFile() { + $(this).parent('span').remove(); + return false; +} + +function uploadBlob(blob, uploadUrl, attachmentId, options) { + + var actualOptions = $.extend({ + loadstartEventHandler: $.noop, + progressEventHandler: $.noop + }, options); + + uploadUrl = uploadUrl + '?attachment_id=' + attachmentId; + if (blob instanceof window.File) { + uploadUrl += '&filename=' + encodeURIComponent(blob.name); + } + + return $.ajax(uploadUrl, { + type: 'POST', + contentType: 'application/octet-stream', + beforeSend: function(jqXhr) { + jqXhr.setRequestHeader('Accept', 'application/js'); + }, + xhr: function() { + var xhr = $.ajaxSettings.xhr(); + xhr.upload.onloadstart = actualOptions.loadstartEventHandler; + xhr.upload.onprogress = actualOptions.progressEventHandler; + return xhr; + }, + data: blob, + cache: false, + processData: false + }); +} + +function addInputFiles(inputEl) { + var clearedFileInput = $(inputEl).clone().val(''); + + if (inputEl.files) { + // upload files using ajax + uploadAndAttachFiles(inputEl.files, inputEl); + $(inputEl).remove(); + } else { + // browser not supporting the file API, upload on form submission + var attachmentId; + var aFilename = inputEl.value.split(/\/|\\/); + attachmentId = addFile(inputEl, { name: aFilename[ aFilename.length - 1 ] }, false); + if (attachmentId) { + $(inputEl).attr({ name: 'attachments[' + attachmentId + '][file]', style: 'display:none;' }).appendTo('#attachments_' + attachmentId); + } + } + + clearedFileInput.insertAfter('#attachments_fields'); +} + +function uploadAndAttachFiles(files, inputEl) { + + var maxFileSize = $(inputEl).data('max-file-size'); + var maxFileSizeExceeded = $(inputEl).data('max-file-size-message'); + + var sizeExceeded = false; + $.each(files, function() { + if (this.size && maxFileSize && this.size > parseInt(maxFileSize)) {sizeExceeded=true;} + }); + if (sizeExceeded) { + window.alert(maxFileSizeExceeded); + } else { + $.each(files, function() {addFile(inputEl, this, true);}); + } +} + +function handleFileDropEvent(e) { + + $(this).removeClass('fileover'); + blockEventPropagation(e); + + if ($.inArray('Files', e.dataTransfer.types) > -1) { + uploadAndAttachFiles(e.dataTransfer.files, $('input:file.file_selector')); + } +} + +function dragOverHandler(e) { + $(this).addClass('fileover'); + blockEventPropagation(e); +} + +function dragOutHandler(e) { + $(this).removeClass('fileover'); + blockEventPropagation(e); +} + +function setupFileDrop() { + if (window.File && window.FileList && window.ProgressEvent && window.FormData) { + + $.event.fixHooks.drop = { props: [ 'dataTransfer' ] }; + + $('form div.box').has('input:file').each(function() { + $(this).on({ + dragover: dragOverHandler, + dragleave: dragOutHandler, + drop: handleFileDropEvent + }); + }); + } +} + +$(document).ready(setupFileDrop); diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/28/2806c2e1c64e92f9ca9d69815ab933298ecbeb25.svn-base --- a/.svn/pristine/28/2806c2e1c64e92f9ca9d69815ab933298ecbeb25.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -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 -r 0a574315af3e -r 4f746d8966dd .svn/pristine/28/2840fb98174d2a4d326c6f441324c06bb7ec4f82.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/28/2840fb98174d2a4d326c6f441324c06bb7ec4f82.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,326 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require "digest/md5" +require "fileutils" + +class Attachment < ActiveRecord::Base + belongs_to :container, :polymorphic => true + belongs_to :author, :class_name => "User", :foreign_key => "author_id" + + validates_presence_of :filename, :author + validates_length_of :filename, :maximum => 255 + validates_length_of :disk_filename, :maximum => 255 + validates_length_of :description, :maximum => 255 + validate :validate_max_file_size + + acts_as_event :title => :filename, + :url => Proc.new {|o| {:controller => 'attachments', :action => 'download', :id => o.id, :filename => o.filename}} + + 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 OR ( #{Attachment.table_name}.container_type='Project' AND #{Attachment.table_name}.container_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"} + + cattr_accessor :storage_path + @@storage_path = Redmine::Configuration['attachments_storage_path'] || File.join(Rails.root, "files") + + cattr_accessor :thumbnails_storage_path + @@thumbnails_storage_path = File.join(Rails.root, "tmp", "thumbnails") + + before_save :files_to_final_location + after_destroy :delete_from_disk + + # Returns an unsaved copy of the attachment + def copy(attributes=nil) + copy = self.class.new + copy.attributes = self.attributes.dup.except("id", "downloads") + copy.attributes = attributes if attributes + copy + end + + def validate_max_file_size + if @temp_file && self.filesize > Setting.attachment_max_size.to_i.kilobytes + errors.add(:base, l(:error_attachment_too_big, :max_size => Setting.attachment_max_size.to_i.kilobytes)) + end + end + + def file=(incoming_file) + unless incoming_file.nil? + @temp_file = incoming_file + if @temp_file.size > 0 + if @temp_file.respond_to?(:original_filename) + self.filename = @temp_file.original_filename + self.filename.force_encoding("UTF-8") if filename.respond_to?(:force_encoding) + end + if @temp_file.respond_to?(:content_type) + self.content_type = @temp_file.content_type.to_s.chomp + end + if content_type.blank? && filename.present? + self.content_type = Redmine::MimeType.of(filename) + end + self.filesize = @temp_file.size + end + end + end + + def file + nil + end + + def filename=(arg) + write_attribute :filename, sanitize_filename(arg.to_s) + filename + end + + # Copies the temporary file to its final location + # and computes its MD5 hash + def files_to_final_location + if @temp_file && (@temp_file.size > 0) + self.disk_directory = target_directory + self.disk_filename = Attachment.disk_filename(filename, disk_directory) + logger.info("Saving attachment '#{self.diskfile}' (#{@temp_file.size} bytes)") + path = File.dirname(diskfile) + unless File.directory?(path) + FileUtils.mkdir_p(path) + end + md5 = Digest::MD5.new + File.open(diskfile, "wb") do |f| + if @temp_file.respond_to?(:read) + buffer = "" + while (buffer = @temp_file.read(8192)) + f.write(buffer) + md5.update(buffer) + end + else + f.write(@temp_file) + md5.update(@temp_file) + end + end + self.digest = md5.hexdigest + end + @temp_file = nil + # Don't save the content type if it's longer than the authorized length + if self.content_type && self.content_type.length > 255 + self.content_type = nil + end + end + + # Deletes the file from the file system if it's not referenced by other attachments + def delete_from_disk + if Attachment.where("disk_filename = ? AND id <> ?", disk_filename, id).empty? + delete_from_disk! + end + end + + # Returns file's location on disk + def diskfile + File.join(self.class.storage_path, disk_directory.to_s, disk_filename.to_s) + end + + def title + title = filename.to_s + if description.present? + title << " (#{description})" + end + title + end + + def increment_download + increment!(:downloads) + end + + def project + container.try(:project) + end + + def visible?(user=User.current) + if container_id + container && container.attachments_visible?(user) + else + author == user + end + end + + def deletable?(user=User.current) + if container_id + container && container.attachments_deletable?(user) + else + author == user + end + end + + def image? + !!(self.filename =~ /\.(bmp|gif|jpg|jpe|jpeg|png)$/i) + end + + def thumbnailable? + image? + end + + # Returns the full path the attachment thumbnail, or nil + # if the thumbnail cannot be generated. + def thumbnail(options={}) + if thumbnailable? && readable? + size = options[:size].to_i + if size > 0 + # Limit the number of thumbnails per image + size = (size / 50) * 50 + # Maximum thumbnail size + size = 800 if size > 800 + else + size = Setting.thumbnails_size.to_i + end + size = 100 unless size > 0 + target = File.join(self.class.thumbnails_storage_path, "#{id}_#{digest}_#{size}.thumb") + + begin + Redmine::Thumbnail.generate(self.diskfile, target, size) + rescue => e + logger.error "An error occured while generating thumbnail for #{disk_filename} to #{target}\nException was: #{e.message}" if logger + return nil + end + end + end + + # Deletes all thumbnails + def self.clear_thumbnails + Dir.glob(File.join(thumbnails_storage_path, "*.thumb")).each do |file| + File.delete file + end + end + + def is_text? + Redmine::MimeType.is_type?('text', filename) + end + + def is_diff? + self.filename =~ /\.(patch|diff)$/i + end + + # Returns true if the file is readable + def readable? + File.readable?(diskfile) + end + + # Returns the attachment token + def token + "#{id}.#{digest}" + end + + # Finds an attachment that matches the given token and that has no container + def self.find_by_token(token) + if token.to_s =~ /^(\d+)\.([0-9a-f]+)$/ + attachment_id, attachment_digest = $1, $2 + attachment = Attachment.where(:id => attachment_id, :digest => attachment_digest).first + if attachment && attachment.container.nil? + attachment + end + end + end + + # Bulk attaches a set of files to an object + # + # Returns a Hash of the results: + # :files => array of the attached files + # :unsaved => array of the files that could not be attached + def self.attach_files(obj, attachments) + result = obj.save_attachments(attachments, User.current) + obj.attach_saved_attachments + result + end + + def self.latest_attach(attachments, filename) + attachments.sort_by(&:created_on).reverse.detect { + |att| att.filename.downcase == filename.downcase + } + end + + def self.prune(age=1.day) + Attachment.where("created_on < ? AND (container_type IS NULL OR container_type = '')", Time.now - age).destroy_all + end + + # Moves an existing attachment to its target directory + def move_to_target_directory! + if !new_record? & readable? + src = diskfile + self.disk_directory = target_directory + dest = diskfile + if src != dest && FileUtils.mkdir_p(File.dirname(dest)) && FileUtils.mv(src, dest) + update_column :disk_directory, disk_directory + end + end + end + + # Moves existing attachments that are stored at the root of the files + # directory (ie. created before Redmine 2.3) to their target subdirectories + def self.move_from_root_to_target_directory + Attachment.where("disk_directory IS NULL OR disk_directory = ''").find_each do |attachment| + attachment.move_to_target_directory! + end + end + + private + + # Physically deletes the file from the file system + def delete_from_disk! + if disk_filename.present? && File.exist?(diskfile) + File.delete(diskfile) + end + end + + def sanitize_filename(value) + # get only the filename, not the whole path + just_filename = value.gsub(/^.*(\\|\/)/, '') + + # Finally, replace invalid characters with underscore + @filename = just_filename.gsub(/[\/\?\%\*\:\|\"\'<>]+/, '_') + end + + # Returns the subdirectory in which the attachment will be saved + def target_directory + time = created_on || DateTime.now + time.strftime("%Y/%m") + end + + # Returns an ASCII or hashed filename that do not + # exists yet in the given subdirectory + def self.disk_filename(filename, directory=nil) + timestamp = DateTime.now.strftime("%y%m%d%H%M%S") + ascii = '' + if filename =~ %r{^[a-zA-Z0-9_\.\-]*$} + ascii = filename + else + ascii = Digest::MD5.hexdigest(filename) + # keep the extension if any + ascii << $1 if filename =~ %r{(\.[a-zA-Z0-9]+)$} + end + while File.exist?(File.join(storage_path, directory.to_s, "#{timestamp}_#{ascii}")) + timestamp.succ! + end + "#{timestamp}_#{ascii}" + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/28/28420e6f2a289bd07da6ea940b845a9449d5bc41.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/28/28420e6f2a289bd07da6ea940b845a9449d5bc41.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,35 @@ +api.array :issues, api_meta(:total_count => @issue_count, :offset => @offset, :limit => @limit) do + @issues.each do |issue| + api.issue do + api.id issue.id + api.project(:id => issue.project_id, :name => issue.project.name) unless issue.project.nil? + api.tracker(:id => issue.tracker_id, :name => issue.tracker.name) unless issue.tracker.nil? + api.status(:id => issue.status_id, :name => issue.status.name) unless issue.status.nil? + api.priority(:id => issue.priority_id, :name => issue.priority.name) unless issue.priority.nil? + api.author(:id => issue.author_id, :name => issue.author.name) unless issue.author.nil? + api.assigned_to(:id => issue.assigned_to_id, :name => issue.assigned_to.name) unless issue.assigned_to.nil? + api.category(:id => issue.category_id, :name => issue.category.name) unless issue.category.nil? + api.fixed_version(:id => issue.fixed_version_id, :name => issue.fixed_version.name) unless issue.fixed_version.nil? + api.parent(:id => issue.parent_id) unless issue.parent.nil? + + api.subject issue.subject + api.description issue.description + api.start_date issue.start_date + api.due_date issue.due_date + api.done_ratio issue.done_ratio + api.estimated_hours issue.estimated_hours + + render_api_custom_values issue.custom_field_values, api + + api.created_on issue.created_on + api.updated_on issue.updated_on + api.closed_on issue.closed_on + + api.array :relations do + issue.relations.each do |relation| + api.relation(:id => relation.id, :issue_id => relation.issue_from_id, :issue_to_id => relation.issue_to_id, :relation_type => relation.relation_type, :delay => relation.delay) + end + end if include_in_api_response?('relations') + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/28/2864783005a4ba0460d387fef08dd617a059d205.svn-base --- a/.svn/pristine/28/2864783005a4ba0460d387fef08dd617a059d205.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,61 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../../../test_helper', __FILE__) - -class Redmine::ConfigurationTest < ActiveSupport::TestCase - def setup - @conf = Redmine::Configuration - end - - def test_empty - assert_kind_of Hash, load_conf('empty.yml', 'test') - end - - def test_default - assert_kind_of Hash, load_conf('default.yml', 'test') - assert_equal 'foo', @conf['somesetting'] - end - - def test_no_default - assert_kind_of Hash, load_conf('no_default.yml', 'test') - assert_equal 'foo', @conf['somesetting'] - end - - def test_overrides - assert_kind_of Hash, load_conf('overrides.yml', 'test') - assert_equal 'bar', @conf['somesetting'] - end - - def test_with - load_conf('default.yml', 'test') - assert_equal 'foo', @conf['somesetting'] - @conf.with 'somesetting' => 'bar' do - assert_equal 'bar', @conf['somesetting'] - end - assert_equal 'foo', @conf['somesetting'] - end - - private - - def load_conf(file, env) - @conf.load( - :file => File.join(Rails.root, 'test', 'fixtures', 'configuration', file), - :env => env - ) - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/28/28ac0f519eb9b5a961e5bcb8825c18def371a932.svn-base --- a/.svn/pristine/28/28ac0f519eb9b5a961e5bcb8825c18def371a932.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -<%= @note %> (from application) \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/28/28e4e2427d2a8e8bd98db1e2c7317b6c662eb82a.svn-base --- a/.svn/pristine/28/28e4e2427d2a8e8bd98db1e2c7317b6c662eb82a.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,17 +0,0 @@ -

<%=l(:label_news)%>

- -<% labelled_tabular_form_for @news, :html => { :id => 'news-form', :method => :put } do |f| %> -<%= render :partial => 'form', :locals => { :f => f } %> -<%= submit_tag l(:button_save) %> -<%= link_to_remote l(:label_preview), - { :url => preview_news_path(:project_id => @project), - :method => 'get', - :update => 'preview', - :with => "Form.serialize('news-form')" - }, :accesskey => accesskey(:preview) %> -<% end %> -
- -<% content_for :header_tags do %> - <%= stylesheet_link_tag 'scm' %> -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/28/28f61d6f82da16a79bf305217d0869e0a3170794.svn-base --- a/.svn/pristine/28/28f61d6f82da16a79bf305217d0869e0a3170794.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,39 +0,0 @@ -
-<%= link_to_if_authorized l(:label_document_new), - {:controller => 'documents', :action => 'new', :project_id => @project}, - :class => 'icon icon-add', - :onclick => 'Element.show("add-document"); Form.Element.focus("document_title"); return false;' %> -
- - - -

<%=l(:label_document_plural)%>

- -<% if @grouped.empty? %>

<%= l(:label_no_data) %>

<% end %> - -<% @grouped.keys.sort.each do |group| %> -

<%= group %>

- <%= render :partial => 'documents/document', :collection => @grouped[group] %> -<% end %> - -<% content_for :sidebar do %> -

<%= l(:label_sort_by, '') %>

- <% form_tag({}, :method => :get) do %> -
-
-
- - <% end %> -<% end %> - -<% html_title(l(:label_document_plural)) -%> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/29/2916f748eef73ff691bcfc74e7c1ac23960d090b.svn-base --- a/.svn/pristine/29/2916f748eef73ff691bcfc74e7c1ac23960d090b.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,294 +0,0 @@ -# $Id: ber.rb 142 2006-07-26 12:20:33Z blackhedd $ -# -# NET::BER -# Mixes ASN.1/BER convenience methods into several standard classes. -# Also provides BER parsing functionality. -# -#---------------------------------------------------------------------------- -# -# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved. -# -# Gmail: garbagecat10 -# -# 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 St, Fifth Floor, Boston, MA 02110-1301 USA -# -#--------------------------------------------------------------------------- -# -# - - - - -module Net - - module BER - - class BerError < Exception; end - - - # This module is for mixing into IO and IO-like objects. - module BERParser - - # The order of these follows the class-codes in BER. - # Maybe this should have been a hash. - TagClasses = [:universal, :application, :context_specific, :private] - - BuiltinSyntax = { - :universal => { - :primitive => { - 1 => :boolean, - 2 => :integer, - 4 => :string, - 10 => :integer, - }, - :constructed => { - 16 => :array, - 17 => :array - } - } - } - - # - # read_ber - # TODO: clean this up so it works properly with partial - # packets coming from streams that don't block when - # we ask for more data (like StringIOs). At it is, - # this can throw TypeErrors and other nasties. - # - def read_ber syntax=nil - return nil if (StringIO == self.class) and eof? - - id = getc # don't trash this value, we'll use it later - tag = id & 31 - tag < 31 or raise BerError.new( "unsupported tag encoding: #{id}" ) - tagclass = TagClasses[ id >> 6 ] - encoding = (id & 0x20 != 0) ? :constructed : :primitive - - n = getc - lengthlength,contentlength = if n <= 127 - [1,n] - else - j = (0...(n & 127)).inject(0) {|mem,x| mem = (mem << 8) + getc} - [1 + (n & 127), j] - end - - newobj = read contentlength - - objtype = nil - [syntax, BuiltinSyntax].each {|syn| - if syn && (ot = syn[tagclass]) && (ot = ot[encoding]) && ot[tag] - objtype = ot[tag] - break - end - } - - obj = case objtype - when :boolean - newobj != "\000" - when :string - (newobj || "").dup - when :integer - j = 0 - newobj.each_byte {|b| j = (j << 8) + b} - j - when :array - seq = [] - sio = StringIO.new( newobj || "" ) - # Interpret the subobject, but note how the loop - # is built: nil ends the loop, but false (a valid - # BER value) does not! - while (e = sio.read_ber(syntax)) != nil - seq << e - end - seq - else - raise BerError.new( "unsupported object type: class=#{tagclass}, encoding=#{encoding}, tag=#{tag}" ) - end - - # Add the identifier bits into the object if it's a String or an Array. - # We can't add extra stuff to Fixnums and booleans, not that it makes much sense anyway. - obj and ([String,Array].include? obj.class) and obj.instance_eval "def ber_identifier; #{id}; end" - obj - - end - - end # module BERParser - end # module BER - -end # module Net - - -class IO - include Net::BER::BERParser -end - -require "stringio" -class StringIO - include Net::BER::BERParser -end - -begin - require 'openssl' - class OpenSSL::SSL::SSLSocket - include Net::BER::BERParser - end -rescue LoadError -# Ignore LoadError. -# DON'T ignore NameError, which means the SSLSocket class -# is somehow unavailable on this implementation of Ruby's openssl. -# This may be WRONG, however, because we don't yet know how Ruby's -# openssl behaves on machines with no OpenSSL library. I suppose -# it's possible they do not fail to require 'openssl' but do not -# create the classes. So this code is provisional. -# Also, you might think that OpenSSL::SSL::SSLSocket inherits from -# IO so we'd pick it up above. But you'd be wrong. -end - -class String - def read_ber syntax=nil - StringIO.new(self).read_ber(syntax) - end -end - - - -#---------------------------------------------- - - -class FalseClass - # - # to_ber - # - def to_ber - "\001\001\000" - end -end - - -class TrueClass - # - # to_ber - # - def to_ber - "\001\001\001" - end -end - - - -class Fixnum - # - # to_ber - # - def to_ber - i = [self].pack('w') - [2, i.length].pack("CC") + i - end - - # - # to_ber_enumerated - # - def to_ber_enumerated - i = [self].pack('w') - [10, i.length].pack("CC") + i - end - - # - # to_ber_length_encoding - # - def to_ber_length_encoding - if self <= 127 - [self].pack('C') - else - i = [self].pack('N').sub(/^[\0]+/,"") - [0x80 + i.length].pack('C') + i - end - end - -end # class Fixnum - - -class Bignum - - def to_ber - i = [self].pack('w') - i.length > 126 and raise Net::BER::BerError.new( "range error in bignum" ) - [2, i.length].pack("CC") + i - end - -end - - - -class String - # - # to_ber - # A universal octet-string is tag number 4, - # but others are possible depending on the context, so we - # let the caller give us one. - # The preferred way to do this in user code is via to_ber_application_sring - # and to_ber_contextspecific. - # - def to_ber code = 4 - [code].pack('C') + length.to_ber_length_encoding + self - end - - # - # to_ber_application_string - # - def to_ber_application_string code - to_ber( 0x40 + code ) - end - - # - # to_ber_contextspecific - # - def to_ber_contextspecific code - to_ber( 0x80 + code ) - end - -end # class String - - - -class Array - # - # to_ber_appsequence - # An application-specific sequence usually gets assigned - # a tag that is meaningful to the particular protocol being used. - # This is different from the universal sequence, which usually - # gets a tag value of 16. - # Now here's an interesting thing: We're adding the X.690 - # "application constructed" code at the top of the tag byte (0x60), - # but some clients, notably ldapsearch, send "context-specific - # constructed" (0xA0). The latter would appear to violate RFC-1777, - # but what do I know? We may need to change this. - # - - def to_ber id = 0; to_ber_seq_internal( 0x30 + id ); end - def to_ber_set id = 0; to_ber_seq_internal( 0x31 + id ); end - def to_ber_sequence id = 0; to_ber_seq_internal( 0x30 + id ); end - def to_ber_appsequence id = 0; to_ber_seq_internal( 0x60 + id ); end - def to_ber_contextspecific id = 0; to_ber_seq_internal( 0xA0 + id ); end - - private - def to_ber_seq_internal code - s = self.to_s - [code].pack('C') + s.length.to_ber_length_encoding + s - end - -end # class Array - - diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/29/299dc6b637e0f5b3775b92631d240971508c0684.svn-base --- a/.svn/pristine/29/299dc6b637e0f5b3775b92631d240971508c0684.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,61 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../../../test_helper', __FILE__) - -class Redmine::MimeTypeTest < ActiveSupport::TestCase - - def test_of - to_test = {'test.unk' => nil, - 'test.txt' => 'text/plain', - 'test.c' => 'text/x-c', - } - to_test.each do |name, expected| - assert_equal expected, Redmine::MimeType.of(name) - end - end - - def test_css_class_of - to_test = {'test.unk' => nil, - 'test.txt' => 'text-plain', - 'test.c' => 'text-x-c', - } - to_test.each do |name, expected| - assert_equal expected, Redmine::MimeType.css_class_of(name) - end - end - - def test_main_mimetype_of - to_test = {'test.unk' => nil, - 'test.txt' => 'text', - 'test.c' => 'text', - } - to_test.each do |name, expected| - assert_equal expected, Redmine::MimeType.main_mimetype_of(name) - end - end - - def test_is_type - to_test = {['text', 'test.unk'] => false, - ['text', 'test.txt'] => true, - ['text', 'test.c'] => true, - } - to_test.each do |args, expected| - assert_equal expected, Redmine::MimeType.is_type?(*args) - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/29/29d1b7a44a44f4040dd2b8d4fe4b9b40b3383592.svn-base --- a/.svn/pristine/29/29d1b7a44a44f4040dd2b8d4fe4b9b40b3383592.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,84 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class TrackersController < ApplicationController - layout 'admin' - - before_filter :require_admin, :except => :index - before_filter :require_admin_or_api_request, :only => :index - accept_api_auth :index - - def index - respond_to do |format| - format.html { - @tracker_pages, @trackers = paginate :trackers, :per_page => 10, :order => 'position' - render :action => "index", :layout => false if request.xhr? - } - format.api { - @trackers = Tracker.all - } - end - end - - def new - @tracker ||= Tracker.new(params[:tracker]) - @trackers = Tracker.find :all, :order => 'position' - @projects = Project.find(:all) - end - - def create - @tracker = Tracker.new(params[:tracker]) - if request.post? and @tracker.save - # workflow copy - if !params[:copy_workflow_from].blank? && (copy_from = Tracker.find_by_id(params[:copy_workflow_from])) - @tracker.workflows.copy(copy_from) - end - flash[:notice] = l(:notice_successful_create) - redirect_to :action => 'index' - return - end - new - render :action => 'new' - end - - def edit - @tracker ||= Tracker.find(params[:id]) - @projects = Project.find(:all) - end - - def update - @tracker = Tracker.find(params[:id]) - if request.put? and @tracker.update_attributes(params[:tracker]) - flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'index' - return - end - edit - render :action => 'edit' - end - - verify :method => :delete, :only => :destroy, :redirect_to => { :action => :index } - def destroy - @tracker = Tracker.find(params[:id]) - unless @tracker.issues.empty? - flash[:error] = l(:error_can_not_delete_tracker) - else - @tracker.destroy - end - redirect_to :action => 'index' - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/29/29e480c531a7477e290d81b189c3105531d0d4db.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/29/29e480c531a7477e290d81b189c3105531d0d4db.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,217 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../base', __FILE__) + +class Redmine::UiTest::IssuesTest < Redmine::UiTest::Base + fixtures :projects, :users, :roles, :members, :member_roles, + :trackers, :projects_trackers, :enabled_modules, :issue_statuses, :issues, + :enumerations, :custom_fields, :custom_values, :custom_fields_trackers, + :watchers + + def test_create_issue + log_user('jsmith', 'jsmith') + visit '/projects/ecookbook/issues/new' + within('form#issue-form') do + select 'Bug', :from => 'Tracker' + select 'Low', :from => 'Priority' + fill_in 'Subject', :with => 'new test issue' + fill_in 'Description', :with => 'new issue' + select '0 %', :from => 'Done' + fill_in 'Due date', :with => '' + select '', :from => 'Assignee' + fill_in 'Searchable field', :with => 'Value for field 2' + # click_button 'Create' would match both 'Create' and 'Create and continue' buttons + find('input[name=commit]').click + end + + # find created issue + issue = Issue.find_by_subject("new test issue") + assert_kind_of Issue, issue + + # check redirection + find 'div#flash_notice', :visible => true, :text => "Issue \##{issue.id} created." + assert_equal issue_path(:id => issue), current_path + + # check issue attributes + assert_equal 'jsmith', issue.author.login + assert_equal 1, issue.project.id + assert_equal IssueStatus.find_by_name('New'), issue.status + assert_equal Tracker.find_by_name('Bug'), issue.tracker + assert_equal IssuePriority.find_by_name('Low'), issue.priority + assert_equal 'Value for field 2', issue.custom_field_value(CustomField.find_by_name('Searchable field')) + end + + def test_create_issue_with_form_update + field1 = IssueCustomField.create!( + :field_format => 'string', + :name => 'Field1', + :is_for_all => true, + :trackers => Tracker.find_all_by_id([1, 2]) + ) + field2 = IssueCustomField.create!( + :field_format => 'string', + :name => 'Field2', + :is_for_all => true, + :trackers => Tracker.find_all_by_id(2) + ) + + Role.non_member.add_permission! :add_issues + Role.non_member.remove_permission! :edit_issues, :add_issue_notes + + log_user('someone', 'foo') + visit '/projects/ecookbook/issues/new' + assert page.has_no_content?(field2.name) + assert page.has_content?(field1.name) + + fill_in 'Subject', :with => 'New test issue' + fill_in 'Description', :with => 'New test issue description' + fill_in field1.name, :with => 'CF1 value' + select 'Low', :from => 'Priority' + + # field2 should show up when changing tracker + select 'Feature request', :from => 'Tracker' + assert page.has_content?(field2.name) + assert page.has_content?(field1.name) + + fill_in field2.name, :with => 'CF2 value' + assert_difference 'Issue.count' do + page.first(:button, 'Create').click + end + + issue = Issue.order('id desc').first + assert_equal 'New test issue', issue.subject + assert_equal 'New test issue description', issue.description + assert_equal 'Low', issue.priority.name + assert_equal 'CF1 value', issue.custom_field_value(field1) + assert_equal 'CF2 value', issue.custom_field_value(field2) + end + + def test_create_issue_with_watchers + User.generate!(:firstname => 'Some', :lastname => 'Watcher') + + log_user('jsmith', 'jsmith') + visit '/projects/ecookbook/issues/new' + fill_in 'Subject', :with => 'Issue with watchers' + # Add a project member as watcher + check 'Dave Lopper' + # Search for another user + click_link 'Search for watchers to add' + within('form#new-watcher-form') do + assert page.has_content?('Some One') + fill_in 'user_search', :with => 'watch' + assert page.has_no_content?('Some One') + check 'Some Watcher' + click_button 'Add' + end + assert_difference 'Issue.count' do + find('input[name=commit]').click + end + + issue = Issue.order('id desc').first + assert_equal ['Dave Lopper', 'Some Watcher'], issue.watcher_users.map(&:name).sort + end + + def test_preview_issue_description + log_user('jsmith', 'jsmith') + visit '/projects/ecookbook/issues/new' + within('form#issue-form') do + fill_in 'Subject', :with => 'new issue subject' + fill_in 'Description', :with => 'new issue description' + click_link 'Preview' + end + find 'div#preview fieldset', :visible => true, :text => 'new issue description' + assert_difference 'Issue.count' do + find('input[name=commit]').click + end + + issue = Issue.order('id desc').first + assert_equal 'new issue description', issue.description + end + + def test_update_issue_with_form_update + field = IssueCustomField.create!( + :field_format => 'string', + :name => 'Form update CF', + :is_for_all => true, + :trackers => Tracker.find_all_by_name('Feature request') + ) + + Role.non_member.add_permission! :edit_issues + Role.non_member.remove_permission! :add_issues, :add_issue_notes + + log_user('someone', 'foo') + visit '/issues/1' + assert page.has_no_content?('Form update CF') + + page.first(:link, 'Update').click + # the custom field should show up when changing tracker + select 'Feature request', :from => 'Tracker' + assert page.has_content?('Form update CF') + + fill_in 'Form update', :with => 'CF value' + assert_no_difference 'Issue.count' do + page.first(:button, 'Submit').click + end + + issue = Issue.find(1) + assert_equal 'CF value', issue.custom_field_value(field) + end + + def test_remove_issue_watcher_from_sidebar + user = User.find(3) + Watcher.create!(:watchable => Issue.find(1), :user => user) + + log_user('jsmith', 'jsmith') + visit '/issues/1' + assert page.first('#sidebar').has_content?('Watchers (1)') + assert page.first('#sidebar').has_content?(user.name) + assert_difference 'Watcher.count', -1 do + page.first('ul.watchers .user-3 a.delete').click + end + assert page.first('#sidebar').has_content?('Watchers (0)') + assert page.first('#sidebar').has_no_content?(user.name) + end + + def test_watch_issue_via_context_menu + log_user('jsmith', 'jsmith') + visit '/issues' + find('tr#issue-1 td.updated_on').click + page.execute_script "$('tr#issue-1 td.updated_on').trigger('contextmenu');" + assert_difference 'Watcher.count' do + within('#context-menu') do + click_link 'Watch' + end + end + assert Issue.find(1).watched_by?(User.find_by_login('jsmith')) + end + + def test_bulk_watch_issues_via_context_menu + log_user('jsmith', 'jsmith') + visit '/issues' + find('tr#issue-1 input[type=checkbox]').click + find('tr#issue-4 input[type=checkbox]').click + page.execute_script "$('tr#issue-1 td.updated_on').trigger('contextmenu');" + assert_difference 'Watcher.count', 2 do + within('#context-menu') do + click_link 'Watch' + end + end + assert Issue.find(1).watched_by?(User.find_by_login('jsmith')) + assert Issue.find(4).watched_by?(User.find_by_login('jsmith')) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/2a/2a26987d3e76c57b6a5ef62d32f7ea30699c2a7e.svn-base --- a/.svn/pristine/2a/2a26987d3e76c57b6a5ef62d32f7ea30699c2a7e.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +0,0 @@ -class AlphaPluginLibModel < ActiveRecord::Base - def self.report_location; TestHelper::report_location(__FILE__); end -end \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/2a/2a729c60b4face5ae26610fc483b9a2b1f7c2f71.svn-base --- a/.svn/pristine/2a/2a729c60b4face5ae26610fc483b9a2b1f7c2f71.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,102 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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 - layout 'admin' - - before_filter :require_admin - before_filter :find_roles - before_filter :find_trackers - - 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 { |status_id, transitions| - transitions.each { |new_status_id, options| - author = options.is_a?(Array) && options.include?('author') && !options.include?('always') - assignee = options.is_a?(Array) && options.include?('assignee') && !options.include?('always') - @role.workflows.build(:tracker_id => @tracker.id, :old_status_id => status_id, :new_status_id => new_status_id, :author => author, :assignee => assignee) - } - } - if @role.save - flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'edit', :role_id => @role, :tracker_id => @tracker - return - end - end - - @used_statuses_only = (params[:used_statuses_only] == '0' ? false : true) - if @tracker && @used_statuses_only && @tracker.issue_statuses.any? - @statuses = @tracker.issue_statuses - end - @statuses ||= IssueStatus.find(:all, :order => 'position') - - if @tracker && @role && @statuses.any? - workflows = Workflow.all(:conditions => {:role_id => @role.id, :tracker_id => @tracker.id}) - @workflows = {} - @workflows['always'] = workflows.select {|w| !w.author && !w.assignee} - @workflows['author'] = workflows.select {|w| w.author} - @workflows['assignee'] = workflows.select {|w| w.assignee} - end - end - - def copy - - if params[:source_tracker_id].blank? || params[:source_tracker_id] == 'any' - @source_tracker = nil - else - @source_tracker = Tracker.find_by_id(params[:source_tracker_id].to_i) - end - if params[:source_role_id].blank? || params[:source_role_id] == 'any' - @source_role = nil - else - @source_role = Role.find_by_id(params[:source_role_id].to_i) - end - - @target_trackers = params[:target_tracker_ids].blank? ? nil : Tracker.find_all_by_id(params[:target_tracker_ids]) - @target_roles = params[:target_role_ids].blank? ? nil : Role.find_all_by_id(params[:target_role_ids]) - - if request.post? - if params[:source_tracker_id].blank? || params[:source_role_id].blank? || (@source_tracker.nil? && @source_role.nil?) - flash.now[:error] = l(:error_workflow_copy_source) - elsif @target_trackers.nil? || @target_roles.nil? - flash.now[:error] = l(:error_workflow_copy_target) - else - Workflow.copy(@source_tracker, @source_role, @target_trackers, @target_roles) - flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'copy', :source_tracker_id => @source_tracker, :source_role_id => @source_role - end - end - end - - private - - def find_roles - @roles = Role.find(:all, :order => 'builtin, position') - end - - def find_trackers - @trackers = Tracker.find(:all, :order => 'position') - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/2a/2a8a6f95df9c9b5ea1480ac33b5f365836080244.svn-base --- a/.svn/pristine/2a/2a8a6f95df9c9b5ea1480ac33b5f365836080244.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,178 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class Setting < ActiveRecord::Base - - DATE_FORMATS = [ - '%Y-%m-%d', - '%d/%m/%Y', - '%d.%m.%Y', - '%d-%m-%Y', - '%m/%d/%Y', - '%d %b %Y', - '%d %B %Y', - '%b %d, %Y', - '%B %d, %Y' - ] - - TIME_FORMATS = [ - '%H:%M', - '%I:%M %p' - ] - - ENCODINGS = %w(US-ASCII - windows-1250 - windows-1251 - windows-1252 - windows-1253 - windows-1254 - windows-1255 - windows-1256 - windows-1257 - windows-1258 - windows-31j - ISO-2022-JP - ISO-2022-KR - ISO-8859-1 - ISO-8859-2 - ISO-8859-3 - ISO-8859-4 - ISO-8859-5 - ISO-8859-6 - ISO-8859-7 - ISO-8859-8 - ISO-8859-9 - ISO-8859-13 - ISO-8859-15 - KOI8-R - UTF-8 - UTF-16 - UTF-16BE - UTF-16LE - EUC-JP - Shift_JIS - CP932 - GB18030 - GBK - ISCII91 - EUC-KR - Big5 - Big5-HKSCS - TIS-620) - - cattr_accessor :available_settings - @@available_settings = YAML::load(File.open("#{Rails.root}/config/settings.yml")) - Redmine::Plugin.all.each do |plugin| - next unless plugin.settings - @@available_settings["plugin_#{plugin.id}"] = {'default' => plugin.settings[:default], 'serialized' => true} - end - - validates_uniqueness_of :name - validates_inclusion_of :name, :in => @@available_settings.keys - validates_numericality_of :value, :only_integer => true, :if => Proc.new { |setting| @@available_settings[setting.name]['format'] == 'int' } - - # Hash used to cache setting values - @cached_settings = {} - @cached_cleared_on = Time.now - - def value - v = read_attribute(:value) - # Unserialize serialized settings - v = YAML::load(v) if @@available_settings[name]['serialized'] && v.is_a?(String) - v = v.to_sym if @@available_settings[name]['format'] == 'symbol' && !v.blank? - v - end - - def value=(v) - v = v.to_yaml if v && @@available_settings[name] && @@available_settings[name]['serialized'] - write_attribute(:value, v.to_s) - end - - # Returns the value of the setting named name - def self.[](name) - v = @cached_settings[name] - v ? v : (@cached_settings[name] = find_or_default(name).value) - end - - def self.[]=(name, v) - setting = find_or_default(name) - setting.value = (v ? v : "") - @cached_settings[name] = nil - setting.save - setting.value - end - - # Defines getter and setter for each setting - # Then setting values can be read using: Setting.some_setting_name - # or set using Setting.some_setting_name = "some value" - @@available_settings.each do |name, params| - src = <<-END_SRC - def self.#{name} - self[:#{name}] - end - - def self.#{name}? - self[:#{name}].to_i > 0 - end - - def self.#{name}=(value) - self[:#{name}] = value - end - END_SRC - class_eval src, __FILE__, __LINE__ - end - - # Helper that returns an array based on per_page_options setting - def self.per_page_options_array - 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_i > 0 - end - - # Checks if settings have changed since the values were read - # and clears the cache hash if it's the case - # Called once per request - def self.check_cache - settings_updated_on = Setting.maximum(:updated_on) - if settings_updated_on && @cached_cleared_on <= settings_updated_on - clear_cache - end - end - - # Clears the settings cache - def self.clear_cache - @cached_settings.clear - @cached_cleared_on = Time.now - logger.info "Settings cache cleared." if logger - end - -private - # Returns the Setting instance for the setting named name - # (record found in database or new record with default value) - def self.find_or_default(name) - name = name.to_s - raise "There's no setting named #{name}" unless @@available_settings.has_key?(name) - setting = find_by_name(name) - unless setting - setting = new(:name => name) - setting.value = @@available_settings[name]['default'] - end - setting - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/2b/2b0a9f999f6063790f2925cfcbf5bec32cb0108e.svn-base --- a/.svn/pristine/2b/2b0a9f999f6063790f2925cfcbf5bec32cb0108e.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,583 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../../test_helper', __FILE__) - -class ApiTest::IssuesTest < ActionController::IntegrationTest - fixtures :projects, - :users, - :roles, - :members, - :member_roles, - :issues, - :issue_statuses, - :versions, - :trackers, - :projects_trackers, - :issue_categories, - :enabled_modules, - :enumerations, - :attachments, - :workflows, - :custom_fields, - :custom_values, - :custom_fields_projects, - :custom_fields_trackers, - :time_entries, - :journals, - :journal_details, - :queries, - :attachments - - def setup - Setting.rest_api_enabled = '1' - end - - context "/issues" do - # Use a private project to make sure auth is really working and not just - # only showing public issues. - should_allow_api_authentication(:get, "/projects/private-child/issues.xml") - - should "contain metadata" do - get '/issues.xml' - - assert_tag :tag => 'issues', - :attributes => { - :type => 'array', - :total_count => assigns(:issue_count), - :limit => 25, - :offset => 0 - } - end - - context "with offset and limit" do - should "use the params" do - get '/issues.xml?offset=2&limit=3' - - assert_equal 3, assigns(:limit) - assert_equal 2, assigns(:offset) - assert_tag :tag => 'issues', :children => {:count => 3, :only => {:tag => 'issue'}} - end - end - - context "with nometa param" do - should "not contain metadata" do - get '/issues.xml?nometa=1' - - assert_tag :tag => 'issues', - :attributes => { - :type => 'array', - :total_count => nil, - :limit => nil, - :offset => nil - } - end - end - - context "with nometa header" do - should "not contain metadata" do - get '/issues.xml', {}, {'X-Redmine-Nometa' => '1'} - - assert_tag :tag => 'issues', - :attributes => { - :type => 'array', - :total_count => nil, - :limit => nil, - :offset => nil - } - end - end - - context "with relations" do - should "display relations" do - get '/issues.xml?include=relations' - - assert_response :success - assert_equal 'application/xml', @response.content_type - assert_tag 'relations', - :parent => {:tag => 'issue', :child => {:tag => 'id', :content => '3'}}, - :children => {:count => 1}, - :child => { - :tag => 'relation', - :attributes => {:id => '2', :issue_id => '2', :issue_to_id => '3', :relation_type => 'relates'} - } - assert_tag 'relations', - :parent => {:tag => 'issue', :child => {:tag => 'id', :content => '1'}}, - :children => {:count => 0} - end - end - - context "with invalid query params" do - should "return errors" do - get '/issues.xml', {:f => ['start_date'], :op => {:start_date => '='}} - - assert_response :unprocessable_entity - assert_equal 'application/xml', @response.content_type - assert_tag 'errors', :child => {:tag => 'error', :content => "Start date can't be blank"} - end - end - - context "with custom field filter" do - should "show only issues with the custom field value" do - get '/issues.xml', { :set_filter => 1, :f => ['cf_1'], :op => {:cf_1 => '='}, :v => {:cf_1 => ['MySQL']}} - - expected_ids = Issue.visible.all( - :include => :custom_values, - :conditions => {:custom_values => {:custom_field_id => 1, :value => 'MySQL'}}).map(&:id) - - assert_select 'issues > issue > id', :count => expected_ids.count do |ids| - ids.each { |id| assert expected_ids.delete(id.children.first.content.to_i) } - end - end - end - - context "with custom field filter (shorthand method)" do - should "show only issues with the custom field value" do - get '/issues.xml', { :cf_1 => 'MySQL' } - - expected_ids = Issue.visible.all( - :include => :custom_values, - :conditions => {:custom_values => {:custom_field_id => 1, :value => 'MySQL'}}).map(&:id) - - assert_select 'issues > issue > id', :count => expected_ids.count do |ids| - ids.each { |id| assert expected_ids.delete(id.children.first.content.to_i) } - end - end - end - end - - context "/index.json" do - should_allow_api_authentication(:get, "/projects/private-child/issues.json") - end - - context "/index.xml with filter" do - should "show only issues with the status_id" do - get '/issues.xml?status_id=5' - - expected_ids = Issue.visible.all(:conditions => {:status_id => 5}).map(&:id) - - assert_select 'issues > issue > id', :count => expected_ids.count do |ids| - ids.each { |id| assert expected_ids.delete(id.children.first.content.to_i) } - end - end - end - - context "/index.json with filter" do - should "show only issues with the status_id" do - get '/issues.json?status_id=5' - - json = ActiveSupport::JSON.decode(response.body) - status_ids_used = json['issues'].collect {|j| j['status']['id'] } - assert_equal 3, status_ids_used.length - assert status_ids_used.all? {|id| id == 5 } - end - - end - - # Issue 6 is on a private project - context "/issues/6.xml" do - should_allow_api_authentication(:get, "/issues/6.xml") - end - - context "/issues/6.json" do - should_allow_api_authentication(:get, "/issues/6.json") - end - - context "GET /issues/:id" do - context "with journals" do - context ".xml" do - should "display journals" do - get '/issues/1.xml?include=journals' - - assert_tag :tag => 'issue', - :child => { - :tag => 'journals', - :attributes => { :type => 'array' }, - :child => { - :tag => 'journal', - :attributes => { :id => '1'}, - :child => { - :tag => 'details', - :attributes => { :type => 'array' }, - :child => { - :tag => 'detail', - :attributes => { :name => 'status_id' }, - :child => { - :tag => 'old_value', - :content => '1', - :sibling => { - :tag => 'new_value', - :content => '2' - } - } - } - } - } - } - end - end - end - - context "with custom fields" do - context ".xml" do - should "display custom fields" do - get '/issues/3.xml' - - assert_tag :tag => 'issue', - :child => { - :tag => 'custom_fields', - :attributes => { :type => 'array' }, - :child => { - :tag => 'custom_field', - :attributes => { :id => '1'}, - :child => { - :tag => 'value', - :content => 'MySQL' - } - } - } - - assert_nothing_raised do - Hash.from_xml(response.body).to_xml - end - end - end - end - - context "with attachments" do - context ".xml" do - should "display attachments" do - get '/issues/3.xml?include=attachments' - - assert_tag :tag => 'issue', - :child => { - :tag => 'attachments', - :children => {:count => 5}, - :child => { - :tag => 'attachment', - :child => { - :tag => 'filename', - :content => 'source.rb', - :sibling => { - :tag => 'content_url', - :content => 'http://www.example.com/attachments/download/4/source.rb' - } - } - } - } - end - end - end - - context "with subtasks" do - setup do - @c1 = Issue.generate!(:status_id => 1, :subject => "child c1", :tracker_id => 1, :project_id => 1, :parent_issue_id => 1) - @c2 = Issue.generate!(:status_id => 1, :subject => "child c2", :tracker_id => 1, :project_id => 1, :parent_issue_id => 1) - @c3 = Issue.generate!(:status_id => 1, :subject => "child c3", :tracker_id => 1, :project_id => 1, :parent_issue_id => @c1.id) - end - - context ".xml" do - should "display children" do - get '/issues/1.xml?include=children' - - assert_tag :tag => 'issue', - :child => { - :tag => 'children', - :children => {:count => 2}, - :child => { - :tag => 'issue', - :attributes => {:id => @c1.id.to_s}, - :child => { - :tag => 'subject', - :content => 'child c1', - :sibling => { - :tag => 'children', - :children => {:count => 1}, - :child => { - :tag => 'issue', - :attributes => {:id => @c3.id.to_s} - } - } - } - } - } - end - - context ".json" do - should "display children" do - get '/issues/1.json?include=children' - - json = ActiveSupport::JSON.decode(response.body) - assert_equal([ - { - 'id' => @c1.id, 'subject' => 'child c1', 'tracker' => {'id' => 1, 'name' => 'Bug'}, - 'children' => [{ 'id' => @c3.id, 'subject' => 'child c3', 'tracker' => {'id' => 1, 'name' => 'Bug'} }] - }, - { 'id' => @c2.id, 'subject' => 'child c2', 'tracker' => {'id' => 1, 'name' => 'Bug'} } - ], - json['issue']['children']) - end - end - end - end - end - - context "POST /issues.xml" do - should_allow_api_authentication(:post, - '/issues.xml', - {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}}, - {:success_code => :created}) - - should "create an issue with the attributes" do - assert_difference('Issue.count') do - post '/issues.xml', {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}}, :authorization => credentials('jsmith') - end - - issue = Issue.first(:order => 'id DESC') - assert_equal 1, issue.project_id - assert_equal 2, issue.tracker_id - assert_equal 3, issue.status_id - assert_equal 'API test', issue.subject - - assert_response :created - assert_equal 'application/xml', @response.content_type - assert_tag 'issue', :child => {:tag => 'id', :content => issue.id.to_s} - end - end - - context "POST /issues.xml with failure" do - should "have an errors tag" do - assert_no_difference('Issue.count') do - post '/issues.xml', {:issue => {:project_id => 1}}, :authorization => credentials('jsmith') - end - - assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"} - end - end - - context "POST /issues.json" do - should_allow_api_authentication(:post, - '/issues.json', - {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}}, - {:success_code => :created}) - - should "create an issue with the attributes" do - assert_difference('Issue.count') do - post '/issues.json', {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}}, :authorization => credentials('jsmith') - end - - issue = Issue.first(:order => 'id DESC') - assert_equal 1, issue.project_id - assert_equal 2, issue.tracker_id - assert_equal 3, issue.status_id - assert_equal 'API test', issue.subject - end - - end - - context "POST /issues.json with failure" do - should "have an errors element" do - assert_no_difference('Issue.count') do - post '/issues.json', {:issue => {:project_id => 1}}, :authorization => credentials('jsmith') - end - - json = ActiveSupport::JSON.decode(response.body) - assert json['errors'].include?(['subject', "can't be blank"]) - end - end - - # Issue 6 is on a private project - context "PUT /issues/6.xml" do - setup do - @parameters = {:issue => {:subject => 'API update', :notes => 'A new note'}} - @headers = { :authorization => credentials('jsmith') } - end - - should_allow_api_authentication(:put, - '/issues/6.xml', - {:issue => {:subject => 'API update', :notes => 'A new note'}}, - {:success_code => :ok}) - - should "not create a new issue" do - assert_no_difference('Issue.count') do - put '/issues/6.xml', @parameters, @headers - end - end - - should "create a new journal" do - assert_difference('Journal.count') do - put '/issues/6.xml', @parameters, @headers - end - end - - should "add the note to the journal" do - put '/issues/6.xml', @parameters, @headers - - journal = Journal.last - assert_equal "A new note", journal.notes - end - - should "update the issue" do - put '/issues/6.xml', @parameters, @headers - - issue = Issue.find(6) - assert_equal "API update", issue.subject - end - - end - - context "PUT /issues/3.xml with custom fields" do - setup do - @parameters = {:issue => {:custom_fields => [{'id' => '1', 'value' => 'PostgreSQL' }, {'id' => '2', 'value' => '150'}]}} - @headers = { :authorization => credentials('jsmith') } - end - - should "update custom fields" do - assert_no_difference('Issue.count') do - put '/issues/3.xml', @parameters, @headers - end - - issue = Issue.find(3) - assert_equal '150', issue.custom_value_for(2).value - assert_equal 'PostgreSQL', issue.custom_value_for(1).value - end - end - - context "PUT /issues/6.xml with failed update" do - setup do - @parameters = {:issue => {:subject => ''}} - @headers = { :authorization => credentials('jsmith') } - end - - should "not create a new issue" do - assert_no_difference('Issue.count') do - put '/issues/6.xml', @parameters, @headers - end - end - - should "not create a new journal" do - assert_no_difference('Journal.count') do - put '/issues/6.xml', @parameters, @headers - end - end - - should "have an errors tag" do - put '/issues/6.xml', @parameters, @headers - - assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"} - end - end - - context "PUT /issues/6.json" do - setup do - @parameters = {:issue => {:subject => 'API update', :notes => 'A new note'}} - @headers = { :authorization => credentials('jsmith') } - end - - should_allow_api_authentication(:put, - '/issues/6.json', - {:issue => {:subject => 'API update', :notes => 'A new note'}}, - {:success_code => :ok}) - - should "not create a new issue" do - assert_no_difference('Issue.count') do - put '/issues/6.json', @parameters, @headers - end - end - - should "create a new journal" do - assert_difference('Journal.count') do - put '/issues/6.json', @parameters, @headers - end - end - - should "add the note to the journal" do - put '/issues/6.json', @parameters, @headers - - journal = Journal.last - assert_equal "A new note", journal.notes - end - - should "update the issue" do - put '/issues/6.json', @parameters, @headers - - issue = Issue.find(6) - assert_equal "API update", issue.subject - end - - end - - context "PUT /issues/6.json with failed update" do - setup do - @parameters = {:issue => {:subject => ''}} - @headers = { :authorization => credentials('jsmith') } - end - - should "not create a new issue" do - assert_no_difference('Issue.count') do - put '/issues/6.json', @parameters, @headers - end - end - - should "not create a new journal" do - assert_no_difference('Journal.count') do - put '/issues/6.json', @parameters, @headers - end - end - - should "have an errors attribute" do - put '/issues/6.json', @parameters, @headers - - json = ActiveSupport::JSON.decode(response.body) - assert json['errors'].include?(['subject', "can't be blank"]) - end - end - - context "DELETE /issues/1.xml" do - should_allow_api_authentication(:delete, - '/issues/6.xml', - {}, - {:success_code => :ok}) - - should "delete the issue" do - assert_difference('Issue.count',-1) do - delete '/issues/6.xml', {}, :authorization => credentials('jsmith') - end - - assert_nil Issue.find_by_id(6) - end - end - - context "DELETE /issues/1.json" do - should_allow_api_authentication(:delete, - '/issues/6.json', - {}, - {:success_code => :ok}) - - should "delete the issue" do - assert_difference('Issue.count',-1) do - delete '/issues/6.json', {}, :authorization => credentials('jsmith') - end - - assert_nil Issue.find_by_id(6) - end - end - - def credentials(user, password=nil) - ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user) - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/2b/2b22d90c6bc0187f71e0f86b9bffc12dccfc3f03.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2b/2b22d90c6bc0187f71e0f86b9bffc12dccfc3f03.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,337 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class ProjectCopyTest < ActiveSupport::TestCase + fixtures :projects, :trackers, :issue_statuses, :issues, + :journals, :journal_details, + :enumerations, :users, :issue_categories, + :projects_trackers, + :custom_fields, + :custom_fields_projects, + :custom_fields_trackers, + :custom_values, + :roles, + :member_roles, + :members, + :enabled_modules, + :versions, + :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions, + :groups_users, + :boards, :messages, + :repositories, + :news, :comments, + :documents + + def setup + ProjectCustomField.destroy_all + @source_project = Project.find(2) + @project = Project.new(:name => 'Copy Test', :identifier => 'copy-test') + @project.trackers = @source_project.trackers + @project.enabled_module_names = @source_project.enabled_modules.collect(&:name) + end + + test "#copy should copy issues" do + @source_project.issues << Issue.generate!(:status => IssueStatus.find_by_name('Closed'), + :subject => "copy issue status", + :tracker_id => 1, + :assigned_to_id => 2, + :project_id => @source_project.id) + assert @project.valid? + assert @project.issues.empty? + assert @project.copy(@source_project) + + assert_equal @source_project.issues.size, @project.issues.size + @project.issues.each do |issue| + assert issue.valid? + assert ! issue.assigned_to.blank? + assert_equal @project, issue.project + end + + copied_issue = @project.issues.first(:conditions => {:subject => "copy issue status"}) + assert copied_issue + assert copied_issue.status + assert_equal "Closed", copied_issue.status.name + end + + test "#copy should copy issues custom values" do + field = IssueCustomField.generate!(:is_for_all => true, :trackers => Tracker.all) + issue = Issue.generate!(:project => @source_project, :subject => 'Custom field copy') + issue.custom_field_values = {field.id => 'custom'} + issue.save! + assert_equal 'custom', issue.reload.custom_field_value(field) + + assert @project.copy(@source_project) + copy = @project.issues.find_by_subject('Custom field copy') + assert copy + assert_equal 'custom', copy.reload.custom_field_value(field) + end + + test "#copy should copy issues assigned to a locked version" do + User.current = User.find(1) + assigned_version = Version.generate!(:name => "Assigned Issues") + @source_project.versions << assigned_version + Issue.generate!(:project => @source_project, + :fixed_version_id => assigned_version.id, + :subject => "copy issues assigned to a locked version") + assigned_version.update_attribute :status, 'locked' + + assert @project.copy(@source_project) + @project.reload + copied_issue = @project.issues.first(:conditions => {:subject => "copy issues assigned to a locked version"}) + + assert copied_issue + assert copied_issue.fixed_version + assert_equal "Assigned Issues", copied_issue.fixed_version.name # Same name + assert_equal 'locked', copied_issue.fixed_version.status + end + + test "#copy should change the new issues to use the copied version" do + User.current = User.find(1) + assigned_version = Version.generate!(:name => "Assigned Issues", :status => 'open') + @source_project.versions << assigned_version + assert_equal 3, @source_project.versions.size + Issue.generate!(:project => @source_project, + :fixed_version_id => assigned_version.id, + :subject => "change the new issues to use the copied version") + + assert @project.copy(@source_project) + @project.reload + copied_issue = @project.issues.first(:conditions => {:subject => "change the new issues to use the copied version"}) + + assert copied_issue + assert copied_issue.fixed_version + assert_equal "Assigned Issues", copied_issue.fixed_version.name # Same name + assert_not_equal assigned_version.id, copied_issue.fixed_version.id # Different record + end + + test "#copy should keep target shared versions from other project" do + assigned_version = Version.generate!(:name => "Assigned Issues", :status => 'open', :project_id => 1, :sharing => 'system') + issue = Issue.generate!(:project => @source_project, + :fixed_version => assigned_version, + :subject => "keep target shared versions") + + assert @project.copy(@source_project) + @project.reload + copied_issue = @project.issues.first(:conditions => {:subject => "keep target shared versions"}) + + assert copied_issue + assert_equal assigned_version, copied_issue.fixed_version + end + + test "#copy should copy issue relations" do + Setting.cross_project_issue_relations = '1' + + second_issue = Issue.generate!(:status_id => 5, + :subject => "copy issue relation", + :tracker_id => 1, + :assigned_to_id => 2, + :project_id => @source_project.id) + source_relation = IssueRelation.create!(:issue_from => Issue.find(4), + :issue_to => second_issue, + :relation_type => "relates") + source_relation_cross_project = IssueRelation.create!(:issue_from => Issue.find(1), + :issue_to => second_issue, + :relation_type => "duplicates") + + assert @project.copy(@source_project) + assert_equal @source_project.issues.count, @project.issues.count + copied_issue = @project.issues.find_by_subject("Issue on project 2") # Was #4 + copied_second_issue = @project.issues.find_by_subject("copy issue relation") + + # First issue with a relation on project + assert_equal 1, copied_issue.relations.size, "Relation not copied" + copied_relation = copied_issue.relations.first + assert_equal "relates", copied_relation.relation_type + assert_equal copied_second_issue.id, copied_relation.issue_to_id + assert_not_equal source_relation.id, copied_relation.id + + # Second issue with a cross project relation + assert_equal 2, copied_second_issue.relations.size, "Relation not copied" + copied_relation = copied_second_issue.relations.select {|r| r.relation_type == 'duplicates'}.first + assert_equal "duplicates", copied_relation.relation_type + assert_equal 1, copied_relation.issue_from_id, "Cross project relation not kept" + assert_not_equal source_relation_cross_project.id, copied_relation.id + end + + test "#copy should copy issue attachments" do + issue = Issue.generate!(:subject => "copy with attachment", :tracker_id => 1, :project_id => @source_project.id) + Attachment.create!(:container => issue, :file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 1) + @source_project.issues << issue + assert @project.copy(@source_project) + + copied_issue = @project.issues.first(:conditions => {:subject => "copy with attachment"}) + assert_not_nil copied_issue + assert_equal 1, copied_issue.attachments.count, "Attachment not copied" + assert_equal "testfile.txt", copied_issue.attachments.first.filename + end + + test "#copy should copy memberships" do + assert @project.valid? + assert @project.members.empty? + assert @project.copy(@source_project) + + assert_equal @source_project.memberships.size, @project.memberships.size + @project.memberships.each do |membership| + assert membership + assert_equal @project, membership.project + end + end + + test "#copy should copy memberships with groups and additional roles" do + group = Group.create!(:lastname => "Copy group") + user = User.find(7) + group.users << user + # group role + Member.create!(:project_id => @source_project.id, :principal => group, :role_ids => [2]) + member = Member.find_by_user_id_and_project_id(user.id, @source_project.id) + # additional role + member.role_ids = [1] + + assert @project.copy(@source_project) + member = Member.find_by_user_id_and_project_id(user.id, @project.id) + assert_not_nil member + assert_equal [1, 2], member.role_ids.sort + end + + test "#copy should copy project specific queries" do + assert @project.valid? + assert @project.queries.empty? + assert @project.copy(@source_project) + + assert_equal @source_project.queries.size, @project.queries.size + @project.queries.each do |query| + assert query + assert_equal @project, query.project + end + assert_equal @source_project.queries.map(&:user_id).sort, @project.queries.map(&:user_id).sort + end + + test "#copy should copy versions" do + @source_project.versions << Version.generate! + @source_project.versions << Version.generate! + + assert @project.versions.empty? + assert @project.copy(@source_project) + + assert_equal @source_project.versions.size, @project.versions.size + @project.versions.each do |version| + assert version + assert_equal @project, version.project + end + end + + test "#copy should copy wiki" do + assert_difference 'Wiki.count' do + assert @project.copy(@source_project) + end + + assert @project.wiki + assert_not_equal @source_project.wiki, @project.wiki + assert_equal "Start page", @project.wiki.start_page + end + + test "#copy should copy wiki without wiki module" do + project = Project.new(:name => 'Copy Test', :identifier => 'copy-test', :enabled_module_names => []) + assert_difference 'Wiki.count' do + assert project.copy(@source_project) + end + + assert project.wiki + end + + test "#copy should copy wiki pages and content with hierarchy" do + assert_difference 'WikiPage.count', @source_project.wiki.pages.size do + assert @project.copy(@source_project) + end + + assert @project.wiki + assert_equal @source_project.wiki.pages.size, @project.wiki.pages.size + + @project.wiki.pages.each do |wiki_page| + assert wiki_page.content + assert !@source_project.wiki.pages.include?(wiki_page) + end + + parent = @project.wiki.find_page('Parent_page') + child1 = @project.wiki.find_page('Child_page_1') + child2 = @project.wiki.find_page('Child_page_2') + assert_equal parent, child1.parent + assert_equal parent, child2.parent + end + + test "#copy should copy issue categories" do + assert @project.copy(@source_project) + + assert_equal 2, @project.issue_categories.size + @project.issue_categories.each do |issue_category| + assert !@source_project.issue_categories.include?(issue_category) + end + end + + test "#copy should copy boards" do + assert @project.copy(@source_project) + + assert_equal 1, @project.boards.size + @project.boards.each do |board| + assert !@source_project.boards.include?(board) + end + end + + test "#copy should change the new issues to use the copied issue categories" do + issue = Issue.find(4) + issue.update_attribute(:category_id, 3) + + assert @project.copy(@source_project) + + @project.issues.each do |issue| + assert issue.category + assert_equal "Stock management", issue.category.name # Same name + assert_not_equal IssueCategory.find(3), issue.category # Different record + end + end + + test "#copy should limit copy with :only option" do + assert @project.members.empty? + assert @project.issue_categories.empty? + assert @source_project.issues.any? + + assert @project.copy(@source_project, :only => ['members', 'issue_categories']) + + assert @project.members.any? + assert @project.issue_categories.any? + assert @project.issues.empty? + end + + test "#copy should copy subtasks" do + source = Project.generate!(:tracker_ids => [1]) + issue = Issue.generate_with_descendants!(:project => source) + project = Project.new(:name => 'Copy', :identifier => 'copy', :tracker_ids => [1]) + + assert_difference 'Project.count' do + assert_difference 'Issue.count', 1+issue.descendants.count do + assert project.copy(source.reload) + end + end + copy = Issue.where(:parent_id => nil).order("id DESC").first + assert_equal project, copy.project + assert_equal issue.descendants.count, copy.descendants.count + child_copy = copy.children.detect {|c| c.subject == 'Child1'} + assert child_copy.descendants.any? + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/2b/2b52b333ebdd9a328bfca27bab6d9bb1f5c45425.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2b/2b52b333ebdd9a328bfca27bab6d9bb1f5c45425.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,75 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 ActivitiesController < ApplicationController + menu_item :activity + before_filter :find_optional_project + accept_rss_auth :index + + def index + @days = Setting.activity_days_default.to_i + + if params[:from] + 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, + :author => @author) + @activity.scope_select {|t| !params["show_#{t}"].nil?} + @activity.scope = (@author.nil? ? :default : :all) if @activity.scope.empty? + + events = @activity.events(@date_from, @date_to) + + if events.empty? || stale?(:etag => [@activity.scope, @date_to, @date_from, @with_subprojects, @author, events.first, events.size, User.current, current_language]) + respond_to do |format| + format.html { + @events_by_day = events.group_by {|event| User.current.time_to_date(event.event_datetime)} + render :layout => false if request.xhr? + } + format.atom { + 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 + end + + rescue ActiveRecord::RecordNotFound + render_404 + end + + private + + # TODO: refactor, duplicated in projects_controller + def find_optional_project + return true unless params[:id] + @project = Project.find(params[:id]) + authorize + rescue ActiveRecord::RecordNotFound + render_404 + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/2b/2b8d3c9c3c263ae7cd6f97cc6dd5f192b92ebd2b.svn-base --- a/.svn/pristine/2b/2b8d3c9c3c263ae7cd6f97cc6dd5f192b92ebd2b.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,69 +0,0 @@ -# The MIT License -# -# 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. -# -# This implements native php methods used by tcpdf, which have had to be -# reimplemented within Ruby. - -module RFPDF - - # http://uk2.php.net/getimagesize - def getimagesize(filename) - image = Magick::ImageList.new(filename) - - out = Hash.new - out[0] = image.columns - out[1] = image.rows - - # These are actually meant to return integer values But I couldn't seem to find anything saying what those values are. - # So for now they return strings. The only place that uses this at the moment is the parsejpeg method, so I've changed that too. - case image.mime_type - when "image/gif" - out[2] = "GIF" - when "image/jpeg" - out[2] = "JPEG" - when "image/png" - out[2] = "PNG" - when " image/vnd.wap.wbmp" - out[2] = "WBMP" - when "image/x-xpixmap" - out[2] = "XPM" - end - out[3] = "height=\"#{image.rows}\" width=\"#{image.columns}\"" - out['mime'] = image.mime_type - - # This needs work to cover more situations - # I can't see how to just list the number of channels with ImageMagick / rmagick - if image.colorspace.to_s == "CMYKColorspace" - out['channels'] = 4 - elsif image.colorspace.to_s == "RGBColorspace" - out['channels'] = 3 - end - - out['bits'] = image.channel_depth - File.open( TCPDF.k_path_cache + File::basename(filename), 'w'){|f| - f.binmode - f.print image.to_blob - f.close - } - - out - end - -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/2b/2b9b5932a0933b4096addb68309527dfbf45da2c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2b/2b9b5932a0933b4096addb68309527dfbf45da2c.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,32 @@ +<% if version.completed? %> +

<%= format_date(version.effective_date) %>

+<% elsif version.effective_date %> +

<%= due_date_distance_in_words(version.effective_date) %> (<%= format_date(version.effective_date) %>)

+<% end %> + +

<%=h version.description %>

+<% if version.custom_field_values.any? %> +
    + <% version.custom_field_values.each do |custom_value| %> + <% if custom_value.value.present? %> +
  • <%=h custom_value.custom_field.name %>: <%=h show_value(custom_value) %>
  • + <% end %> + <% end %> +
+<% end %> + +<% if version.issues_count > 0 %> + <%= progress_bar([version.closed_percent, version.completed_percent], :width => '40em', :legend => ('%0.0f%' % version.completed_percent)) %> +

+ <%= link_to(l(:label_x_issues, :count => version.issues_count), + project_issues_path(version.project, :status_id => '*', :fixed_version_id => version, :set_filter => 1)) %> +   + (<%= link_to_if(version.closed_issues_count > 0, l(:label_x_closed_issues_abbr, :count => version.closed_issues_count), + project_issues_path(version.project, :status_id => 'c', :fixed_version_id => version, :set_filter => 1)) %> + — + <%= link_to_if(version.open_issues_count > 0, l(:label_x_open_issues_abbr, :count => version.open_issues_count), + project_issues_path(version.project, :status_id => 'o', :fixed_version_id => version, :set_filter => 1)) %>) +

+<% else %> +

<%= l(:label_roadmap_no_issues) %>

+<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/2c/2cde7dc8fe86b1b79ab2ee40c054579ef58ee63c.svn-base --- a/.svn/pristine/2c/2cde7dc8fe86b1b79ab2ee40c054579ef58ee63c.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1015 +0,0 @@ -# Croatian translations for Ruby on Rails -# by Helix d.o.o. (info@helix.hr) - -hr: - direction: ltr - date: - formats: - # Use the strftime parameters for formats. - # When no format has been given, it uses default. - # You can provide other formats here if you like! - default: "%m/%d/%Y" - short: "%b %d" - long: "%B %d, %Y" - - day_names: [Ponedjeljak, Utorak, Srijeda, ÄŒetvrtak, Petak, Subota, Nedjelja] - abbr_day_names: [Ned, Pon, Uto, Sri, ÄŒet, Pet, Sub] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, Sijecanj, Veljaca, Ožujak, Travanj, Svibanj, Lipanj, Srpanj, Kolovoz, Rujan, Listopad, Studeni, Prosinac] - abbr_month_names: [~, Sij, Velj, Ožu, Tra, Svi, Lip, Srp, Kol, Ruj, List, Stu, Pro] - # Used in date_select and datime_select. - order: - - :year - - :month - - :day - - time: - formats: - default: "%m/%d/%Y %I:%M %p" - time: "%I:%M %p" - short: "%d %b %H:%M" - long: "%B %d, %Y %H:%M" - am: "am" - pm: "pm" - - datetime: - distance_in_words: - half_a_minute: "pola minute" - less_than_x_seconds: - one: "manje od sekunde" - other: "manje od %{count} sekundi" - x_seconds: - one: "1 sekunda" - other: "%{count} sekundi" - less_than_x_minutes: - one: "manje od minute" - other: "manje od %{count} minuta" - x_minutes: - one: "1 minuta" - other: "%{count} minuta" - about_x_hours: - one: "oko sat vremena" - other: "oko %{count} sati" - x_days: - one: "1 dan" - other: "%{count} dana" - about_x_months: - one: "oko 1 mjesec" - other: "oko %{count} mjeseci" - x_months: - one: "mjesec" - other: "%{count} mjeseci" - about_x_years: - one: "1 godina" - other: "%{count} godina" - over_x_years: - one: "preko 1 godine" - other: "preko %{count} godina" - - number: - format: - separator: "." - delimiter: "" - precision: 3 - human: - format: - delimiter: "" - precision: 1 - storage_units: - format: "%n %u" - units: - byte: - one: "Byte" - other: "Bytes" - kb: "KB" - mb: "MB" - gb: "GB" - tb: "TB" - - -# Used in array.to_sentence. - support: - array: - sentence_connector: "i" - skip_last_comma: false - - activerecord: - errors: - template: - header: - one: "1 error prohibited this %{model} from being saved" - other: "%{count} errors prohibited this %{model} from being saved" - messages: - inclusion: "nije ukljuceno u listu" - exclusion: "je rezervirano" - invalid: "nije ispravno" - confirmation: "ne odgovara za potvrdu" - accepted: "mora biti prihvaćen" - empty: "ne može biti prazno" - blank: "ne može biti razmaka" - too_long: "je predug (maximum is %{count} characters)" - too_short: "je prekratak (minimum is %{count} characters)" - wrong_length: "je pogreÅ¡ne dužine (should be %{count} characters)" - taken: "već je zauzeto" - not_a_number: "nije broj" - not_a_date: "nije ispravan datum" - greater_than: "mora biti veći od %{count}" - greater_than_or_equal_to: "mora biti veći ili jednak %{count}" - equal_to: "mora biti jednak %{count}" - less_than: "mora biti manji od %{count}" - less_than_or_equal_to: "mora bit manji ili jednak%{count}" - odd: "mora biti neparan" - even: "mora biti paran" - greater_than_start_date: "mora biti veci nego pocetni datum" - not_same_project: "ne pripada istom projektu" - circular_dependency: "Ovaj relacija stvara kružnu ovisnost" - cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" - - actionview_instancetag_blank_option: Molimo odaberite - - general_text_No: 'Ne' - general_text_Yes: 'Da' - general_text_no: 'ne' - general_text_yes: 'da' - general_lang_name: 'Hrvatski' - general_csv_separator: ',' - general_csv_decimal_separator: '.' - general_csv_encoding: UTF-8 - general_pdf_encoding: UTF-8 - general_first_day_of_week: '7' - - notice_account_updated: VaÅ¡ profil je uspjeÅ¡no promijenjen. - notice_account_invalid_creditentials: Neispravno korisniÄko ime ili zaporka. - notice_account_password_updated: Zaporka je uspjeÅ¡no promijenjena. - notice_account_wrong_password: PogreÅ¡na zaporka - notice_account_register_done: Racun je uspjeÅ¡no napravljen. Da biste aktivirali svoj raÄun, kliknite na link koji vam je poslan na e-mail. - notice_account_unknown_email: Nepoznati korisnik. - notice_can_t_change_password: Ovaj raÄun koristi eksterni izvor prijavljivanja. Nemoguće je promijeniti zaporku. - notice_account_lost_email_sent: E-mail s uputama kako bi odabrali novu zaporku je poslan na na vaÅ¡u e-mail adresu. - notice_account_activated: VaÅ¡ racun je aktiviran. Možete se prijaviti. - notice_successful_create: UspjeÅ¡no napravljeno. - notice_successful_update: UspjeÅ¡na promjena. - notice_successful_delete: UspjeÅ¡no brisanje. - notice_successful_connection: UspjeÅ¡na veza. - notice_file_not_found: Stranica kojoj ste pokuÅ¡ali pristupiti ne postoji ili je uklonjena. - notice_locking_conflict: Podataci su ažurirani od strane drugog korisnika. - notice_not_authorized: Niste ovlaÅ¡teni za pristup ovoj stranici. - notice_email_sent: E-mail je poslan %{value}" - notice_email_error: Dogodila se pogreÅ¡ka tijekom slanja E-maila (%{value})" - notice_feeds_access_key_reseted: VaÅ¡ RSS pristup je resetovan. - notice_api_access_key_reseted: VaÅ¡ API pristup je resetovan. - notice_failed_to_save_issues: "Neuspjelo spremanje %{count} predmeta na %{total} odabrane: %{ids}." - notice_no_issue_selected: "Niti jedan predmet nije odabran! Molim, odaberite predmete koje želite urediti." - notice_account_pending: "VaÅ¡ korisnicki raÄun je otvoren, Äeka odobrenje administratora." - notice_default_data_loaded: Konfiguracija je uspjeÅ¡no uÄitana. - notice_unable_delete_version: Nije moguće izbrisati verziju. - notice_issue_done_ratios_updated: Issue done ratios updated. - - error_can_t_load_default_data: "Zadanu konfiguracija nije uÄitana: %{value}" - error_scm_not_found: "Unos i/ili revizija nije pronaÄ‘en." - error_scm_command_failed: "Dogodila se pogreÅ¡ka prilikom pokuÅ¡aja pristupa: %{value}" - error_scm_annotate: "Ne postoji ili ne može biti obilježen." - error_issue_not_found_in_project: 'Nije pronaÄ‘en ili ne pripada u ovaj projekt' - error_no_tracker_in_project: 'No tracker is associated to this project. Please check the Project settings.' - error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").' - error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version can not be reopened' - error_can_not_archive_project: This project can not be archived - error_issue_done_ratios_not_updated: "Issue done ratios not updated." - error_workflow_copy_source: 'Please select a source tracker or role' - error_workflow_copy_target: 'Please select target tracker(s) and role(s)' - - warning_attachments_not_saved: "%{count} Datoteka/e nije mogla biti spremljena." - - mail_subject_lost_password: "VaÅ¡a %{value} zaporka" - mail_body_lost_password: 'Kako biste promijenili VaÅ¡u zaporku slijedite poveznicu:' - mail_subject_register: "Aktivacija korisniÄog raÄuna %{value}" - mail_body_register: 'Da biste aktivirali svoj raÄun, kliknite na sljedeci link:' - mail_body_account_information_external: "Možete koristiti vaÅ¡ raÄun %{value} za prijavu." - mail_body_account_information: VaÅ¡i korisniÄki podaci - mail_subject_account_activation_request: "%{value} predmet za aktivaciju korisniÄkog raÄuna" - mail_body_account_activation_request: "Novi korisnik (%{value}) je registriran. Njegov korisniÄki raÄun Äeka vaÅ¡e odobrenje:" - mail_subject_reminder: "%{count} predmet(a) dospijeva sljedećih %{days} dana" - mail_body_reminder: "%{count} vama dodijeljen(ih) predmet(a) dospijeva u sljedećih %{days} dana:" - mail_subject_wiki_content_added: "'%{id}' wiki page has been added" - mail_body_wiki_content_added: "The '%{id}' wiki page has been added by %{author}." - mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated" - mail_body_wiki_content_updated: "The '%{id}' wiki page has been updated by %{author}." - - gui_validation_error: 1 pogreÅ¡ka - gui_validation_error_plural: "%{count} pogreÅ¡aka" - - field_name: Ime - field_description: Opis - field_summary: Sažetak - field_is_required: Obavezno - field_firstname: Ime - field_lastname: Prezime - field_mail: E-poÅ¡ta - field_filename: Datoteka - field_filesize: VeliÄina - field_downloads: Preuzimanja - field_author: Autor - field_created_on: Napravljen - field_updated_on: Promijenjen - field_field_format: Format - field_is_for_all: Za sve projekte - field_possible_values: Moguće vrijednosti - field_regexp: Regularni izraz - field_min_length: Minimalna dužina - field_max_length: Maksimalna dužina - field_value: Vrijednost - field_category: Kategorija - field_title: Naslov - field_project: Projekt - field_issue: Predmet - field_status: Status - field_notes: Napomene - field_is_closed: Predmet je zatvoren - field_is_default: Zadana vrijednost - field_tracker: Tracker - field_subject: Predmet - field_due_date: Do datuma - field_assigned_to: Dodijeljeno - field_priority: Prioritet - field_fixed_version: Verzija - field_user: Korisnik - field_role: Uloga - field_homepage: Naslovnica - field_is_public: Javni projekt - field_parent: Potprojekt od - field_is_in_chlog: Predmeti se prikazuju u dnevniku promjena - field_is_in_roadmap: Predmeti se prikazuju u Putokazu - field_login: KorisniÄko ime - field_mail_notification: Obavijest putem e-poÅ¡te - field_admin: Administrator - field_last_login_on: Zadnja prijava - field_language: Primarni jezik - field_effective_date: Datum - field_password: Zaporka - field_new_password: Nova zaporka - field_password_confirmation: Potvrda zaporke - field_version: Verzija - field_type: Tip - field_host: Host - field_port: Port - field_account: Racun - field_base_dn: Osnovni DN - field_attr_login: Login atribut - field_attr_firstname: Atribut imena - field_attr_lastname: Atribut prezimena - field_attr_mail: Atribut e-poÅ¡te - field_onthefly: "Izrada korisnika \"u hodu\"" - field_start_date: Pocetak - field_done_ratio: "% UÄinjeno" - field_auth_source: Vrsta prijavljivanja - field_hide_mail: Sakrij moju adresu e-poÅ¡te - field_comments: Komentar - field_url: URL - field_start_page: PoÄetna stranica - field_subproject: Potprojekt - field_hours: Sati - field_activity: Aktivnost - field_spent_on: Datum - field_identifier: Identifikator - field_is_filter: KoriÅ¡teno kao filtar - field_issue_to_id: Povezano s predmetom - field_delay: Odgodeno - field_assignable: Predmeti mogu biti dodijeljeni ovoj ulozi - field_redirect_existing_links: Preusmjeravanje postojećih linkova - field_estimated_hours: Procijenjeno vrijeme - field_column_names: Stupci - field_time_zone: Vremenska zona - field_searchable: Pretraživo - field_default_value: Zadana vrijednost - field_comments_sorting: Prikaz komentara - field_parent_title: Parent page - field_editable: Editable - field_watcher: Watcher - field_identity_url: OpenID URL - field_content: Content - field_group_by: Group results by - - setting_app_title: Naziv aplikacije - setting_app_subtitle: Podnaslov aplikacije - setting_welcome_text: Tekst dobrodoÅ¡lice - setting_default_language: Zadani jezik - setting_login_required: Potrebna je prijava - setting_self_registration: Samoregistracija je dozvoljena - setting_attachment_max_size: Maksimalna veliÄina privitka - setting_issues_export_limit: OgraniÄenje izvoza predmeta - setting_mail_from: Izvorna adresa e-poÅ¡te - setting_bcc_recipients: Blind carbon copy primatelja (bcc) - setting_plain_text_mail: obiÄni tekst poÅ¡te (bez HTML-a) - setting_host_name: Naziv domaćina (host) - setting_text_formatting: Oblikovanje teksta - setting_wiki_compression: Sažimanje - setting_feeds_limit: Ogranicenje unosa sadržaja - setting_default_projects_public: Novi projekti su javni po defaultu - setting_autofetch_changesets: Autofetch commits - setting_sys_api_enabled: Omogući WS za upravljanje skladiÅ¡tem - setting_commit_ref_keywords: Referentne kljuÄne rijeÄi - setting_commit_fix_keywords: Fiksne kljuÄne rijeÄi - setting_autologin: Automatska prijava - setting_date_format: Format datuma - setting_time_format: Format vremena - setting_cross_project_issue_relations: Dozvoli povezivanje predmeta izmedu razliÄitih projekata - setting_issue_list_default_columns: Stupci prikazani na listi predmeta - setting_emails_footer: Zaglavlje e-poÅ¡te - setting_protocol: Protokol - setting_per_page_options: Objekata po stranici opcija - setting_user_format: Oblik prikaza korisnika - setting_activity_days_default: Dani prikazane aktivnosti na projektu - setting_display_subprojects_issues: Prikaz predmeta potprojekta na glavnom projektu po defaultu - setting_enabled_scm: Omogućen SCM - setting_mail_handler_body_delimiters: "Truncate emails after one of these lines" - setting_mail_handler_api_enabled: Omoguci WS za dolaznu e-poÅ¡tu - setting_mail_handler_api_key: API kljuÄ - setting_sequential_project_identifiers: Generiraj slijedne identifikatore projekta - setting_gravatar_enabled: Koristi Gravatar korisniÄke ikone - setting_gravatar_default: Default Gravatar image - setting_diff_max_lines_displayed: Maksimalni broj diff linija za prikazati - setting_file_max_size_displayed: Max size of text files displayed inline - setting_repository_log_display_limit: Maximum number of revisions displayed on file log - setting_openid: Allow OpenID login and registration - setting_password_min_length: Minimum password length - setting_new_project_user_role_id: Role given to a non-admin user who creates a project - setting_default_projects_modules: Default enabled modules for new projects - setting_issue_done_ratio: Calculate the issue done ratio with - setting_issue_done_ratio_issue_field: Use the issue field - setting_issue_done_ratio_issue_status: Use the issue status - setting_start_of_week: Start calendars on - setting_rest_api_enabled: Enable REST web service - - permission_add_project: Dodaj projekt - permission_add_subprojects: Dodaj potprojekt - permission_edit_project: Uredi projekt - permission_select_project_modules: Odaberi projektne module - permission_manage_members: Upravljaj Älanovima - permission_manage_versions: Upravljaj verzijama - permission_manage_categories: Upravljaj kategorijama predmeta - permission_view_issues: Pregledaj zahtjeve - permission_add_issues: Dodaj predmete - permission_edit_issues: Uredi predmete - permission_manage_issue_relations: Upravljaj relacijama predmeta - permission_add_issue_notes: Dodaj biljeÅ¡ke - permission_edit_issue_notes: Uredi biljeÅ¡ke - permission_edit_own_issue_notes: Uredi vlastite biljeÅ¡ke - permission_move_issues: Premjesti predmete - permission_delete_issues: Brisanje predmeta - permission_manage_public_queries: Upravljaj javnim upitima - permission_save_queries: Spremi upite - permission_view_gantt: Pregledaj gantt grafikon - permission_view_calendar: Pregledaj kalendar - permission_view_issue_watchers: Pregledaj listu promatraca - permission_add_issue_watchers: Dodaj promatraÄa - permission_delete_issue_watchers: Delete watchers - permission_log_time: Dnevnik utroÅ¡enog vremena - permission_view_time_entries: Pregledaj utroÅ¡eno vrijeme - permission_edit_time_entries: Uredi vremenske dnevnike - permission_edit_own_time_entries: Edit own time logs - permission_manage_news: Upravljaj novostima - permission_comment_news: Komentiraj novosti - permission_manage_documents: Upravljaj dokumentima - permission_view_documents: Pregledaj dokumente - permission_manage_files: Upravljaj datotekama - permission_view_files: Pregledaj datoteke - permission_manage_wiki: Upravljaj wikijem - permission_rename_wiki_pages: Promijeni ime wiki stranicama - permission_delete_wiki_pages: ObriÅ¡i wiki stranice - permission_view_wiki_pages: Pregledaj wiki - permission_view_wiki_edits: Pregledaj povijest wikija - permission_edit_wiki_pages: Uredi wiki stranice - permission_delete_wiki_pages_attachments: ObriÅ¡i privitke - permission_protect_wiki_pages: ZaÅ¡titi wiki stranice - permission_manage_repository: Upravljaj skladiÅ¡tem - permission_browse_repository: Browse repository - permission_view_changesets: View changesets - permission_commit_access: Mogućnost pohranjivanja - permission_manage_boards: Manage boards - permission_view_messages: Pregledaj poruke - permission_add_messages: Objavi poruke - permission_edit_messages: Uredi poruke - permission_edit_own_messages: Uredi vlastite poruke - permission_delete_messages: ObriÅ¡i poruke - permission_delete_own_messages: ObriÅ¡i vlastite poruke - - project_module_issue_tracking: Praćenje predmeta - project_module_time_tracking: Praćenje vremena - project_module_news: Novosti - project_module_documents: Dokumenti - project_module_files: Datoteke - project_module_wiki: Wiki - project_module_repository: SkladiÅ¡te - project_module_boards: Boards - - label_user: Korisnik - label_user_plural: Korisnici - label_user_new: Novi korisnik - label_user_anonymous: Anonymous - label_project: Projekt - label_project_new: Novi projekt - label_project_plural: Projekti - label_x_projects: - zero: no projects - one: 1 project - other: "%{count} projects" - label_project_all: Svi Projekti - label_project_latest: Najnoviji projekt - label_issue: Predmet - label_issue_new: Novi predmet - label_issue_plural: Predmeti - label_issue_view_all: Pregled svih predmeta - label_issues_by: "Predmeti od %{value}" - label_issue_added: Predmet dodan - label_issue_updated: Predmet promijenjen - label_document: Dokument - label_document_new: Novi dokument - label_document_plural: Dokumenti - label_document_added: Dokument dodan - label_role: Uloga - label_role_plural: Uloge - label_role_new: Nova uloga - label_role_and_permissions: Uloge i ovlasti - label_member: ÄŒlan - label_member_new: Novi Älan - label_member_plural: ÄŒlanovi - label_tracker: Vrsta - label_tracker_plural: Vrste predmeta - label_tracker_new: Nova vrsta - label_workflow: Tijek rada - label_issue_status: Status predmeta - label_issue_status_plural: Status predmeta - label_issue_status_new: Novi status - label_issue_category: Kategorija predmeta - label_issue_category_plural: Kategorije predmeta - label_issue_category_new: Nova kategorija - label_custom_field: KorisniÄki definirano polje - label_custom_field_plural: KorisniÄki definirana polja - label_custom_field_new: Novo korisniÄki definirano polje - label_enumerations: Pobrojenice - label_enumeration_new: Nova vrijednost - label_information: Informacija - label_information_plural: Informacije - label_please_login: Molim prijavite se - label_register: Registracija - label_login_with_open_id_option: or login with OpenID - label_password_lost: Izgubljena zaporka - label_home: PoÄetna stranica - label_my_page: Moja stranica - label_my_account: Moj profil - label_my_projects: Moji projekti - label_administration: Administracija - label_login: Korisnik - label_logout: Odjava - label_help: Pomoć - label_reported_issues: Prijavljeni predmeti - label_assigned_to_me_issues: Moji predmeti - label_last_login: Last connection - label_registered_on: Registrirano - label_activity: Aktivnosti - label_overall_activity: Aktivnosti - label_user_activity: "%{value} ova/ina aktivnost" - label_new: Novi - label_logged_as: Prijavljeni ste kao - label_environment: Okolina - label_authentication: Autentikacija - label_auth_source: NaÄin prijavljivanja - label_auth_source_new: Novi naÄin prijavljivanja - label_auth_source_plural: NaÄini prijavljivanja - label_subproject_plural: Potprojekti - label_subproject_new: Novi potprojekt - label_and_its_subprojects: "%{value} i njegovi potprojekti" - label_min_max_length: Min - Maks veliÄina - label_list: Liste - label_date: Datum - label_integer: Integer - label_float: Float - label_boolean: Boolean - label_string: Text - label_text: Long text - label_attribute: Atribut - label_attribute_plural: Atributi - label_download: "%{count} Download" - label_download_plural: "%{count} Downloads" - label_no_data: Nema podataka za prikaz - label_change_status: Promjena statusa - label_history: Povijest - label_attachment: Datoteka - label_attachment_new: Nova datoteka - label_attachment_delete: Brisanje datoteke - label_attachment_plural: Datoteke - label_file_added: Datoteka dodana - label_report: Izvješće - label_report_plural: Izvješća - label_news: Novosti - label_news_new: Dodaj novost - label_news_plural: Novosti - label_news_latest: Novosti - label_news_view_all: Pregled svih novosti - label_news_added: Novosti dodane - label_change_log: Dnevnik promjena - label_settings: Postavke - label_overview: Pregled - label_version: Verzija - label_version_new: Nova verzija - label_version_plural: Verzije - label_confirmation: Potvrda - label_export_to: 'Izvoz u:' - label_read: ÄŒitaj... - label_public_projects: Javni projekti - label_open_issues: Otvoren - label_open_issues_plural: Otvoreno - label_closed_issues: Zatvoren - label_closed_issues_plural: Zatvoreno - label_x_open_issues_abbr_on_total: - zero: 0 open / %{total} - one: 1 open / %{total} - other: "%{count} open / %{total}" - label_x_open_issues_abbr: - zero: 0 open - one: 1 open - other: "%{count} open" - label_x_closed_issues_abbr: - zero: 0 closed - one: 1 closed - other: "%{count} closed" - label_total: Ukupno - label_permissions: Dozvole - label_current_status: Trenutni status - label_new_statuses_allowed: Novi status je dozvoljen - label_all: Svi - label_none: nema - label_nobody: nitko - label_next: Naredni - label_previous: Prethodni - label_used_by: KoriÅ¡ten od - label_details: Detalji - label_add_note: Dodaj napomenu - label_per_page: Po stranici - label_calendar: Kalendar - label_months_from: Mjeseci od - label_gantt: Gantt - label_internal: Interno - label_last_changes: "Posljednjih %{count} promjena" - label_change_view_all: Prikaz svih promjena - label_personalize_page: Prilagodite ovu stranicu - label_comment: Komentar - label_comment_plural: Komentari - label_x_comments: - zero: no comments - one: 1 comment - other: "%{count} comments" - label_comment_add: Dodaj komentar - label_comment_added: Komentar dodan - label_comment_delete: Brisanje komentara - label_query: KorisniÄki upit - label_query_plural: KorisniÄki upiti - label_query_new: Novi upit - label_filter_add: Dodaj filtar - label_filter_plural: Filtri - label_equals: je - label_not_equals: nije - label_in_less_than: za manje od - label_in_more_than: za viÅ¡e od - label_greater_or_equal: '>=' - label_less_or_equal: '<=' - label_in: za toÄno - label_today: danas - label_all_time: sva vremena - label_yesterday: juÄer - label_this_week: ovog tjedna - label_last_week: proÅ¡log tjedna - label_last_n_days: "zadnjih %{count} dana" - label_this_month: ovog mjeseca - label_last_month: proÅ¡log mjeseca - label_this_year: ove godine - label_date_range: vremenski raspon - label_less_than_ago: manje od - label_more_than_ago: viÅ¡e od - label_ago: prije - label_contains: Sadrži - label_not_contains: ne sadrži - label_day_plural: dana - label_repository: SkladiÅ¡te - label_repository_plural: SkladiÅ¡ta - label_browse: Pregled - label_modification: "%{count} promjena" - label_modification_plural: "%{count} promjena" - label_branch: Branch - label_tag: Tag - label_revision: Revizija - label_revision_plural: Revizije - label_revision_id: "Revision %{value}" - label_associated_revisions: Dodijeljene revizije - label_added: dodano - label_modified: promijenjen - label_copied: kopirano - label_renamed: preimenovano - label_deleted: obrisano - label_latest_revision: Posljednja revizija - label_latest_revision_plural: Posljednje revizije - label_view_revisions: Pregled revizija - label_view_all_revisions: View all revisions - label_max_size: Maksimalna veliÄina - label_sort_highest: Premjesti na vrh - label_sort_higher: Premjesti prema gore - label_sort_lower: Premjesti prema dolje - label_sort_lowest: Premjesti na dno - label_roadmap: Putokaz - label_roadmap_due_in: "ZavrÅ¡ava se za %{value}" - label_roadmap_overdue: "%{value} kasni" - label_roadmap_no_issues: Nema predmeta za ovu verziju - label_search: Traži - label_result_plural: Rezultati - label_all_words: Sve rijeÄi - label_wiki: Wiki - label_wiki_edit: Wiki promjena - label_wiki_edit_plural: Wiki promjene - label_wiki_page: Wiki stranica - label_wiki_page_plural: Wiki stranice - label_index_by_title: Indeks po naslovima - 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 promjena - label_issue_tracking: Praćenje predmeta - label_spent_time: UtroÅ¡eno vrijeme - label_f_hour: "%{value} sata" - label_f_hour_plural: "%{value} sati" - label_time_tracking: Praćenje vremena - label_change_plural: Promjene - label_statistics: Statistika - label_commits_per_month: Pohrana po mjesecu - label_commits_per_author: Pohrana po autoru - label_view_diff: Pregled razlika - label_diff_inline: uvuÄeno - label_diff_side_by_side: paralelno - label_options: Opcije - label_copy_workflow_from: Kopiraj tijek rada od - label_permissions_report: Izvješće o dozvolama - label_watched_issues: Praćeni predmeti - label_related_issues: Povezani predmeti - label_applied_status: Primijenjen status - label_loading: UÄitavam... - label_relation_new: Nova relacija - label_relation_delete: Brisanje relacije - label_relates_to: u relaciji sa - label_duplicates: Duplira - label_duplicated_by: ponovljen kao - label_blocks: blokira - label_blocked_by: blokiran od strane - label_precedes: prethodi - label_follows: slijedi - label_end_to_start: od kraja do poÄetka - label_end_to_end: od kraja do kraja - label_end_to_start: od kraja do poÄetka - label_end_to_end: od kraja do kraja - label_stay_logged_in: Ostanite prijavljeni - label_disabled: IskljuÄen - label_show_completed_versions: Prikaži zavrÅ¡ene verzije - label_me: ja - label_board: Forum - label_board_new: Novi forum - label_board_plural: Forumi - label_topic_plural: Teme - label_message_plural: Poruke - label_message_last: Posljednja poruka - label_message_new: Nova poruka - label_message_posted: Poruka dodana - label_reply_plural: Odgovori - label_send_information: PoÅ¡alji korisniku informaciju o profilu - label_year: Godina - label_month: Mjesec - label_week: Tjedan - label_date_from: Od - label_date_to: Do - label_language_based: Zasnovano na jeziku - label_sort_by: "Uredi po %{value}" - label_send_test_email: PoÅ¡alji testno E-pismo - label_feeds_access_key: RSS access key - label_missing_feeds_access_key: Missing a RSS access key - label_feeds_access_key_created_on: "RSS kljuc za pristup je napravljen prije %{value}" - label_module_plural: Moduli - label_added_time_by: "Promijenio %{author} prije %{age}" - label_updated_time_by: "Dodao/la %{author} prije %{age}" - label_updated_time: "Promijenjeno prije %{value}" - label_jump_to_a_project: Prebaci se na projekt... - label_file_plural: Datoteke - label_changeset_plural: Promjene - label_default_columns: Zadani stupci - label_no_change_option: (Bez promjene) - label_bulk_edit_selected_issues: ZajedniÄka promjena izabranih predmeta - label_theme: Tema - label_default: Zadana - label_search_titles_only: Pretraživanje samo naslova - label_user_mail_option_all: "Za bilo koji dogaÄ‘aj na svim mojim projektima" - label_user_mail_option_selected: "Za bilo koji dogaÄ‘aj samo za izabrane projekte..." - label_user_mail_no_self_notified: "Ne želim primati obavijesti o promjenama koje sam napravim" - label_registration_activation_by_email: aktivacija putem e-poÅ¡te - label_registration_manual_activation: ruÄna aktivacija - label_registration_automatic_activation: automatska aktivacija - label_display_per_page: "Po stranici: %{value}" - label_age: Starost - label_change_properties: Promijeni svojstva - label_general: Općenito - label_more: JoÅ¡ - label_scm: SCM - label_plugins: Plugins - label_ldap_authentication: LDAP autentikacija - label_downloads_abbr: D/L - label_optional_description: Opcije - label_add_another_file: Dodaj joÅ¡ jednu datoteku - label_preferences: Preferences - label_chronological_order: U kronoloÅ¡kom redoslijedu - label_reverse_chronological_order: U obrnutom kronoloÅ¡kom redoslijedu - label_planning: Planiranje - label_incoming_emails: Dolazne poruke e-poÅ¡te - label_generate_key: Generiraj kljuÄ - label_issue_watchers: PromatraÄi - label_example: Primjer - label_display: Display - label_sort: Sort - label_ascending: Ascending - label_descending: Descending - label_date_from_to: From %{start} to %{end} - label_wiki_content_added: Wiki page added - label_wiki_content_updated: Wiki page updated - label_group: Group - label_group_plural: Grupe - label_group_new: Nova grupa - label_time_entry_plural: Spent time - label_version_sharing_none: Not shared - label_version_sharing_descendants: With subprojects - label_version_sharing_hierarchy: With project hierarchy - label_version_sharing_tree: With project tree - label_version_sharing_system: With all projects - label_update_issue_done_ratios: Update issue done ratios - label_copy_source: Source - label_copy_target: Target - label_copy_same_as_target: Same as target - label_display_used_statuses_only: Only display statuses that are used by this tracker - label_api_access_key: API access key - label_missing_api_access_key: Missing an API access key - label_api_access_key_created_on: "API access key created %{value} ago" - - button_login: Prijavi - button_submit: PoÅ¡alji - button_save: Spremi - button_check_all: OznaÄi sve - button_uncheck_all: IskljuÄi sve - button_delete: ObriÅ¡i - button_create: Napravi - button_create_and_continue: Napravi i nastavi - button_test: Test - button_edit: Uredi - button_add: Dodaj - button_change: Promijeni - button_apply: Primijeni - button_clear: Ukloni - button_lock: ZakljuÄaj - button_unlock: OtkljuÄaj - button_download: Preuzmi - button_list: Spisak - button_view: Pregled - button_move: Premjesti - button_move_and_follow: Move and follow - button_back: Nazad - button_cancel: Odustani - button_activate: Aktiviraj - button_sort: Redoslijed - button_log_time: ZapiÅ¡i vrijeme - button_rollback: IzvrÅ¡i rollback na ovu verziju - button_watch: Prati - button_unwatch: Prekini pracenje - button_reply: Odgovori - button_archive: Arhiviraj - button_rollback: Dearhiviraj - button_reset: PoniÅ¡ti - button_rename: Promijeni ime - button_change_password: Promjena zaporke - button_copy: Kopiraj - button_copy_and_follow: Copy and follow - button_annotate: Annotate - button_update: Promijeni - button_configure: Konfiguracija - button_quote: Navod - button_duplicate: Duplicate - button_show: Show - - status_active: aktivan - status_registered: Registriran - status_locked: zakljuÄan - - version_status_open: open - version_status_locked: locked - version_status_closed: closed - - field_active: Active - - text_select_mail_notifications: Izbor akcija za koje će biti poslana obavijest e-poÅ¡tom. - text_regexp_info: eg. ^[A-Z0-9]+$ - text_min_max_length_info: 0 znaÄi bez ograniÄenja - text_project_destroy_confirmation: Da li ste sigurni da želite izbrisati ovaj projekt i sve njegove podatke? - text_subprojects_destroy_warning: "Njegov(i) potprojekt(i): %{value} će takoÄ‘er biti obrisan." - text_workflow_edit: Select a role and a tracker to edit the workflow - text_are_you_sure: Da li ste sigurni? - text_journal_changed: "%{label} promijenjen iz %{old} u %{new}" - text_journal_set_to: "%{label} postavi na %{value}" - text_journal_deleted: "%{label} izbrisano (%{old})" - text_journal_added: "%{label} %{value} added" - text_tip_issue_begin_day: Zadaci koji poÄinju ovog dana - text_tip_issue_end_day: zadaci koji se zavrÅ¡avaju ovog dana - text_tip_issue_begin_end_day: Zadaci koji poÄinju i zavrÅ¡avaju se ovog dana - text_project_identifier_info: 'mala slova (a-z), brojevi i crtice su dozvoljeni.
Jednom snimljen identifikator se ne može mijenjati!' - text_caracters_maximum: "NajviÅ¡e %{count} znakova." - text_caracters_minimum: "Mora biti dugaÄko najmanje %{count} znakova." - text_length_between: "Dužina izmedu %{min} i %{max} znakova." - text_tracker_no_workflow: Tijek rada nije definiran za ovaj tracker - text_unallowed_characters: Nedozvoljeni znakovi - text_comma_separated: ViÅ¡estruke vrijednosti su dozvoljene (razdvojene zarezom). - text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages - text_tracker_no_workflow: No workflow defined for this tracker - text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages - text_issue_added: "Predmet %{id} je prijavljen (prijavio %{author})." - text_issue_updated: "Predmet %{id} je promijenjen %{author})." - text_wiki_destroy_confirmation: Da li ste sigurni da želite izbrisati ovaj wiki i njegov sadržaj? - text_issue_category_destroy_question: "Neke predmeti (%{count}) su dodijeljeni ovoj kategoriji. Å to želite uraditi?" - text_issue_category_destroy_assignments: Ukloni dodjeljivanje kategorija - text_issue_category_reassign_to: Ponovo dodijeli predmete ovoj kategoriji - text_user_mail_option: "Za neizabrane projekte, primit ćete obavjesti samo o stvarima koje pratite ili u kojima sudjelujete (npr. predmete koje ste vi napravili ili koje su vama dodjeljeni)." - 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: UÄitaj poÄetnu konfiguraciju - text_status_changed_by_changeset: "Applied in changeset %{value}." - text_issues_destroy_confirmation: 'Jeste li sigurni da želite obrisati izabrani/e predmet(e)?' - text_select_project_modules: 'Odaberite module koji će biti omogućeni za ovaj projekt:' - text_default_administrator_account_changed: Default administrator account changed - text_file_repository_writable: Dozvoljeno pisanje u direktorij za privitke - text_plugin_assets_writable: Plugin assets directory writable - text_rmagick_available: RMagick dostupan (nije obavezno) - text_destroy_time_entries_question: "%{hours} sati je prijavljeno za predmete koje želite obrisati. Å to ćete uÄiniti?" - text_destroy_time_entries: ObriÅ¡i prijavljene sate - text_assign_time_entries_to_project: Pridruži prijavljene sate projektu - text_reassign_time_entries: 'Premjesti prijavljene sate ovom predmetu:' - text_user_wrote: "%{value} je napisao/la:" - text_enumeration_destroy_question: "%{count} objekata je pridruženo toj vrijednosti." - text_enumeration_category_reassign_to: 'Premjesti ih ovoj vrijednosti:' - text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/configuration.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: '... Ovaj diff je odrezan zato Å¡to prelazi maksimalnu veliÄinu koja može biti prikazana.' - text_custom_field_possible_values_info: 'One line for each value' - text_wiki_page_destroy_question: "This page has %{descendants} child page(s) and descendant(s). What do you want to do?" - text_wiki_page_nullify_children: "Keep child pages as root pages" - text_wiki_page_destroy_children: "Delete child pages and all their descendants" - text_wiki_page_reassign_children: "Reassign child pages to this parent page" - default_role_manager: Upravitelj - default_role_developer: Razvojni inženjer - default_role_reporter: Korisnik - default_tracker_bug: PogreÅ¡ka - default_tracker_feature: Funkcionalnost - default_tracker_support: PodrÅ¡ka - default_issue_status_new: Novo - default_issue_status_assigned: Dodijeljeno - default_issue_status_resolved: RijeÅ¡eno - default_issue_status_feedback: Povratna informacija - default_issue_status_closed: Zatvoreno - default_issue_status_rejected: Odbaceno - default_doc_category_user: KorisniÄka dokumentacija - default_doc_category_tech: TehniÄka dokumentacija - default_priority_low: Nizak - default_priority_normal: Redovan - default_priority_high: Visok - default_priority_urgent: Hitan - default_priority_immediate: Odmah - default_activity_design: Dizajn - default_activity_development: Razvoj - enumeration_issue_priorities: Prioriteti predmeta - enumeration_doc_categories: Kategorija dokumenata - enumeration_activities: Aktivnosti (po vremenu) - enumeration_system_activity: System Activity - field_sharing: Sharing - text_line_separated: Multiple values allowed (one line for each value). - label_close_versions: Close completed versions - button_unarchive: Unarchive - label_start_to_end: start to end - label_start_to_start: start to start - field_issue_to: Related issue - default_issue_status_in_progress: In Progress - text_own_membership_delete_confirmation: |- - You are about to remove some or all of your permissions and may no longer be able to edit this project after that. - Are you sure you want to continue? - label_board_sticky: Sticky - label_board_locked: Locked - permission_export_wiki_pages: Export wiki pages - setting_cache_formatted_text: Cache formatted text - permission_manage_project_activities: Manage project activities - error_unable_delete_issue_status: Unable to delete issue status - label_profile: Profile - permission_manage_subtasks: Manage subtasks - field_parent_issue: Parent task - label_subtask_plural: Subtasks - label_project_copy_notifications: Send email notifications during the project copy - error_can_not_delete_custom_field: Unable to delete custom field - error_unable_to_connect: Unable to connect (%{value}) - error_can_not_remove_role: This role is in use and can not be deleted. - error_can_not_delete_tracker: This tracker contains issues and can't be deleted. - field_principal: Principal - label_my_page_block: My page block - notice_failed_to_save_members: "Failed to save member(s): %{errors}." - text_zoom_out: Zoom out - text_zoom_in: Zoom in - notice_unable_delete_time_entry: Unable to delete time log entry. - label_overall_spent_time: Overall spent time - field_time_entries: Log time - project_module_gantt: Gantt - project_module_calendar: Calendar - button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" - text_are_you_sure_with_children: Delete issue and all child issues? - field_text: Text field - label_user_mail_option_only_owner: Only for things I am the owner of - setting_default_notification_option: Default notification option - label_user_mail_option_only_my_events: Only for things I watch or I'm involved in - label_user_mail_option_only_assigned: Only for things I am assigned to - label_user_mail_option_none: No events - field_member_of_group: Assignee's group - field_assigned_to_role: Assignee's role - notice_not_authorized_archived_project: The project you're trying to access has been archived. - label_principal_search: "Search for user or group:" - label_user_search: "Search for user:" - field_visible: Visible - setting_emails_header: Emails header - setting_commit_logtime_activity_id: Activity for logged time - text_time_logged_by_changeset: Applied in changeset %{value}. - setting_commit_logtime_enabled: Enable time logging - notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) - setting_gantt_items_limit: Maximum number of items displayed on the gantt chart - field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text - text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. - label_my_queries: My custom queries - text_journal_changed_no_detail: "%{label} updated" - label_news_comment_added: Comment added to a news - button_expand_all: Expand all - button_collapse_all: Collapse all - label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee - label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author - label_bulk_edit_selected_time_entries: Bulk edit selected time entries - text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? - label_role_anonymous: Anonymous - label_role_non_member: Non member - label_issue_note_added: Note added - label_issue_status_updated: Status updated - label_issue_priority_updated: Priority updated - label_issues_visibility_own: Issues created by or assigned to the user - field_issues_visibility: Issues visibility - label_issues_visibility_all: All issues - permission_set_own_issues_private: Set own issues public or private - field_is_private: Private - permission_set_issues_private: Set issues public or private - label_issues_visibility_public: All non private issues - text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). - field_commit_logs_encoding: Commit messages encoding - field_scm_path_encoding: Path encoding - text_scm_path_encoding_note: "Default: UTF-8" - field_path_to_repository: Path to repository - field_root_directory: Root directory - field_cvs_module: Module - field_cvsroot: CVSROOT - text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) - text_scm_command: Command - text_scm_command_version: Version - label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. - notice_issue_successful_create: Issue %{id} created. - label_between: between - setting_issue_group_assignment: Allow issue assignment to groups - label_diff: diff - text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) - description_query_sort_criteria_direction: Sort direction - description_project_scope: Search scope - description_filter: Filter - description_user_mail_notification: Mail notification settings - description_date_from: Enter start date - description_message_content: Message content - description_available_columns: Available Columns - description_date_range_interval: Choose range by selecting start and end date - description_issue_category_reassign: Choose issue category - description_search: Searchfield - description_notes: Notes - description_date_range_list: Choose range from list - description_choose_project: Projects - description_date_to: Enter end date - description_query_sort_criteria_attribute: Sort attribute - description_wiki_subpages_reassign: Choose new parent page - description_selected_columns: Selected Columns - label_parent_revision: Parent - label_child_revision: Child - error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. - setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues - button_edit_section: Edit this section - setting_repositories_encodings: Attachments and repositories encodings - description_all_columns: All Columns - button_export: Export - label_export_options: "%{export_format} export options" - error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/2c/2cfeab9b9366f48fd720b77fb1dc5f6f878ef869.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2c/2cfeab9b9366f48fd720b77fb1dc5f6f878ef869.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,284 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require 'redmine/core_ext' + +begin + require 'RMagick' unless Object.const_defined?(:Magick) +rescue LoadError + # RMagick is not available +end + +require 'redmine/scm/base' +require 'redmine/access_control' +require 'redmine/access_keys' +require 'redmine/activity' +require 'redmine/activity/fetcher' +require 'redmine/ciphering' +require 'redmine/codeset_util' +require 'redmine/custom_field_format' +require 'redmine/i18n' +require 'redmine/menu_manager' +require 'redmine/notifiable' +require 'redmine/platform' +require 'redmine/mime_type' +require 'redmine/notifiable' +require 'redmine/search' +require 'redmine/syntax_highlighting' +require 'redmine/thumbnail' +require 'redmine/unified_diff' +require 'redmine/utils' +require 'redmine/version' +require 'redmine/wiki_formatting' + +require 'redmine/default_data/loader' +require 'redmine/helpers/calendar' +require 'redmine/helpers/diff' +require 'redmine/helpers/gantt' +require 'redmine/helpers/time_report' +require 'redmine/views/other_formats_builder' +require 'redmine/views/labelled_form_builder' +require 'redmine/views/builders' + +require 'redmine/themes' +require 'redmine/hook' +require 'redmine/plugin' + +if RUBY_VERSION < '1.9' + require 'fastercsv' +else + require 'csv' + FCSV = CSV +end + +Redmine::Scm::Base.add "Subversion" +Redmine::Scm::Base.add "Darcs" +Redmine::Scm::Base.add "Mercurial" +Redmine::Scm::Base.add "Cvs" +Redmine::Scm::Base.add "Bazaar" +Redmine::Scm::Base.add "Git" +Redmine::Scm::Base.add "Filesystem" + +Redmine::CustomFieldFormat.map do |fields| + fields.register 'string' + fields.register 'text' + fields.register 'int', :label => :label_integer + fields.register 'float' + fields.register 'list' + fields.register 'date' + fields.register 'bool', :label => :label_boolean + fields.register 'user', :only => %w(Issue TimeEntry Version Project), :edit_as => 'list' + fields.register 'version', :only => %w(Issue TimeEntry Version Project), :edit_as => 'list' +end + +# Permissions +Redmine::AccessControl.map do |map| + map.permission :view_project, {:projects => [:show], :activities => [:index]}, :public => true, :read => true + map.permission :search_project, {:search => :index}, :public => true, :read => true + map.permission :add_project, {:projects => [:new, :create]}, :require => :loggedin + map.permission :edit_project, {:projects => [:settings, :edit, :update]}, :require => :member + map.permission :close_project, {:projects => [:close, :reopen]}, :require => :member, :read => true + map.permission :select_project_modules, {:projects => :modules}, :require => :member + map.permission :manage_members, {:projects => :settings, :members => [:index, :show, :create, :update, :destroy, :autocomplete]}, :require => :member + map.permission :manage_versions, {:projects => :settings, :versions => [:new, :create, :edit, :update, :close_completed, :destroy]}, :require => :member + map.permission :add_subprojects, {:projects => [:new, :create]}, :require => :member + + map.project_module :issue_tracking do |map| + # Issue categories + map.permission :manage_categories, {:projects => :settings, :issue_categories => [:index, :show, :new, :create, :edit, :update, :destroy]}, :require => :member + # Issues + map.permission :view_issues, {:issues => [:index, :show], + :auto_complete => [:issues], + :context_menus => [:issues], + :versions => [:index, :show, :status_by], + :journals => [:index, :diff], + :queries => :index, + :reports => [:issue_report, :issue_report_details]}, + :read => true + map.permission :add_issues, {:issues => [:new, :create, :update_form], :attachments => :upload} + map.permission :edit_issues, {:issues => [:edit, :update, :bulk_edit, :bulk_update, :update_form], :journals => [:new], :attachments => :upload} + map.permission :manage_issue_relations, {:issue_relations => [:index, :show, :create, :destroy]} + map.permission :manage_subtasks, {} + map.permission :set_issues_private, {} + map.permission :set_own_issues_private, {}, :require => :loggedin + map.permission :add_issue_notes, {:issues => [:edit, :update, :update_form], :journals => [:new], :attachments => :upload} + map.permission :edit_issue_notes, {:journals => :edit}, :require => :loggedin + map.permission :edit_own_issue_notes, {:journals => :edit}, :require => :loggedin + map.permission :view_private_notes, {}, :read => true, :require => :member + map.permission :set_notes_private, {}, :require => :member + map.permission :move_issues, {:issues => [:bulk_edit, :bulk_update]}, :require => :loggedin + map.permission :delete_issues, {:issues => :destroy}, :require => :member + # Queries + map.permission :manage_public_queries, {:queries => [:new, :create, :edit, :update, :destroy]}, :require => :member + map.permission :save_queries, {:queries => [:new, :create, :edit, :update, :destroy]}, :require => :loggedin + # Watchers + map.permission :view_issue_watchers, {}, :read => true + map.permission :add_issue_watchers, {:watchers => [:new, :create, :append, :autocomplete_for_user]} + map.permission :delete_issue_watchers, {:watchers => :destroy} + end + + map.project_module :time_tracking do |map| + map.permission :log_time, {:timelog => [:new, :create]}, :require => :loggedin + map.permission :view_time_entries, {:timelog => [:index, :report, :show]}, :read => true + map.permission :edit_time_entries, {:timelog => [:edit, :update, :destroy, :bulk_edit, :bulk_update]}, :require => :member + map.permission :edit_own_time_entries, {:timelog => [:edit, :update, :destroy,:bulk_edit, :bulk_update]}, :require => :loggedin + map.permission :manage_project_activities, {:project_enumerations => [:update, :destroy]}, :require => :member + end + + map.project_module :news do |map| + map.permission :manage_news, {:news => [:new, :create, :edit, :update, :destroy], :comments => [:destroy]}, :require => :member + map.permission :view_news, {:news => [:index, :show]}, :public => true, :read => true + map.permission :comment_news, {:comments => :create} + end + + map.project_module :documents do |map| + map.permission :add_documents, {:documents => [:new, :create, :add_attachment]}, :require => :loggedin + map.permission :edit_documents, {:documents => [:edit, :update, :add_attachment]}, :require => :loggedin + map.permission :delete_documents, {:documents => [:destroy]}, :require => :loggedin + map.permission :view_documents, {:documents => [:index, :show, :download]}, :read => true + end + + map.project_module :files do |map| + map.permission :manage_files, {:files => [:new, :create]}, :require => :loggedin + map.permission :view_files, {:files => :index, :versions => :download}, :read => true + end + + map.project_module :wiki do |map| + map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member + map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member + map.permission :delete_wiki_pages, {:wiki => [:destroy, :destroy_version]}, :require => :member + map.permission :view_wiki_pages, {:wiki => [:index, :show, :special, :date_index]}, :read => true + map.permission :export_wiki_pages, {:wiki => [:export]}, :read => true + map.permission :view_wiki_edits, {:wiki => [:history, :diff, :annotate]}, :read => true + map.permission :edit_wiki_pages, :wiki => [:edit, :update, :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 => [:new, :create, :edit, :update, :committers, :destroy]}, :require => :member + map.permission :browse_repository, {:repositories => [:show, :browse, :entry, :raw, :annotate, :changes, :diff, :stats, :graph]}, :read => true + map.permission :view_changesets, {:repositories => [:show, :revisions, :revision]}, :read => true + map.permission :commit_access, {} + map.permission :manage_related_issues, {:repositories => [:add_related_issue, :remove_related_issue]} + end + + map.project_module :boards do |map| + map.permission :manage_boards, {:boards => [:new, :create, :edit, :update, :destroy]}, :require => :member + map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true, :read => 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 + + map.project_module :calendar do |map| + map.permission :view_calendar, {:calendars => [:show, :update]}, :read => true + end + + map.project_module :gantt do |map| + map.permission :view_gantt, {:gantts => [:show, :update]}, :read => true + end +end + +Redmine::MenuManager.map :top_menu do |menu| + 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, :if => Proc.new { !User.current.logged? } + menu.push :register, :register_path, :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, :html => {:method => 'post'}, :if => Proc.new { User.current.logged? } +end + +Redmine::MenuManager.map :application_menu do |menu| + # Empty +end + +Redmine::MenuManager.map :admin_menu do |menu| + menu.push :projects, {:controller => 'admin', :action => 'projects'}, :caption => :label_project_plural + menu.push :users, {:controller => 'users'}, :caption => :label_user_plural + menu.push :groups, {:controller => 'groups'}, :caption => :label_group_plural + menu.push :roles, {:controller => 'roles'}, :caption => :label_role_and_permissions + menu.push :trackers, {:controller => 'trackers'}, :caption => :label_tracker_plural + menu.push :issue_statuses, {:controller => 'issue_statuses'}, :caption => :label_issue_status_plural, + :html => {:class => 'issue_statuses'} + menu.push :workflows, {:controller => 'workflows', :action => 'edit'}, :caption => :label_workflow + menu.push :custom_fields, {:controller => 'custom_fields'}, :caption => :label_custom_field_plural, + :html => {:class => 'custom_fields'} + menu.push :enumerations, {:controller => 'enumerations'} + menu.push :settings, {:controller => 'settings'} + menu.push :ldap_authentication, {:controller => 'auth_sources', :action => 'index'}, + :html => {:class => 'server_authentication'} + menu.push :plugins, {:controller => 'admin', :action => 'plugins'}, :last => true + menu.push :info, {:controller => 'admin', :action => 'info'}, :caption => :label_information_plural, :last => true +end + +Redmine::MenuManager.map :project_menu do |menu| + menu.push :overview, { :controller => 'projects', :action => 'show' } + menu.push :activity, { :controller => 'activities', :action => 'index' } + menu.push :roadmap, { :controller => 'versions', :action => 'index' }, :param => :project_id, + :if => Proc.new { |p| p.shared_versions.any? } + menu.push :issues, { :controller => 'issues', :action => 'index' }, :param => :project_id, :caption => :label_issue_plural + menu.push :new_issue, { :controller => 'issues', :action => 'new', :copy_from => nil }, :param => :project_id, :caption => :label_issue_new, + :html => { :accesskey => Redmine::AccessKeys.key_for(:new_issue) } + menu.push :gantt, { :controller => 'gantts', :action => 'show' }, :param => :project_id, :caption => :label_gantt + menu.push :calendar, { :controller => 'calendars', :action => 'show' }, :param => :project_id, :caption => :label_calendar + menu.push :news, { :controller => 'news', :action => 'index' }, :param => :project_id, :caption => :label_news_plural + menu.push :documents, { :controller => 'documents', :action => 'index' }, :param => :project_id, :caption => :label_document_plural + menu.push :wiki, { :controller => 'wiki', :action => 'show', :id => nil }, :param => :project_id, + :if => Proc.new { |p| p.wiki && !p.wiki.new_record? } + menu.push :boards, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id, + :if => Proc.new { |p| p.boards.any? }, :caption => :label_board_plural + menu.push :files, { :controller => 'files', :action => 'index' }, :caption => :label_file_plural, :param => :project_id + menu.push :repository, { :controller => 'repositories', :action => 'show', :repository_id => nil, :path => nil, :rev => nil }, + :if => Proc.new { |p| p.repository && !p.repository.new_record? } + menu.push :settings, { :controller => 'projects', :action => 'settings' }, :last => true +end + +Redmine::Activity.map do |activity| + activity.register :issues, :class_name => %w(Issue Journal) + activity.register :changesets + activity.register :news + activity.register :documents, :class_name => %w(Document Attachment) + activity.register :files, :class_name => 'Attachment' + activity.register :wiki_edits, :class_name => 'WikiContent::Version', :default => false + activity.register :messages, :default => false + activity.register :time_entries, :default => false +end + +Redmine::Search.map do |search| + search.register :issues + search.register :news + search.register :documents + search.register :changesets + search.register :wiki_pages + search.register :messages + search.register :projects +end + +Redmine::WikiFormatting.map do |format| + format.register :textile, Redmine::WikiFormatting::Textile::Formatter, Redmine::WikiFormatting::Textile::Helper +end + +ActionView::Template.register_template_handler :rsb, Redmine::Views::ApiTemplateHandler diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/2d/2d1b6f7772fdd853c88983e5f12b084f72e3159d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2d/2d1b6f7772fdd853c88983e5f12b084f72e3159d.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,13 @@ +class AddCommitAccessPermission < ActiveRecord::Migration + def self.up + Role.all.select { |r| not r.builtin? }.each do |r| + r.add_permission!(:commit_access) + end + end + + def self.down + Role.all.select { |r| not r.builtin? }.each do |r| + r.remove_permission!(:commit_access) + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/2d/2d2fc73f05e5360a853859d4a6d6261e16112400.svn-base --- a/.svn/pristine/2d/2d2fc73f05e5360a853859d4a6d6261e16112400.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ -
-<%= link_to l(:label_profile), user_path(@user), :class => 'icon icon-user' %> -<%= change_status_link(@user) %> -<%= link_to(l(:button_delete), @user, :confirm => l(:text_are_you_sure), :method => :delete, :class => 'icon icon-del') if User.current != @user %> -
- -

<%= link_to l(:label_user_plural), users_path %> » <%=h @user.login %>

- -<%= render_tabs user_settings_tabs %> - -<% html_title(l(:label_user), @user.login, l(:label_administration)) -%> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/2d/2d3d652686003e0e75f2451ee9050de946a60dda.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2d/2d3d652686003e0e75f2451ee9050de946a60dda.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,92 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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. + +# Generic exception for when the AuthSource can not be reached +# (eg. can not connect to the LDAP) +class AuthSourceException < Exception; end +class AuthSourceTimeoutException < AuthSourceException; end + +class AuthSource < ActiveRecord::Base + include Redmine::SubclassFactory + include Redmine::Ciphering + + has_many :users + + validates_presence_of :name + validates_uniqueness_of :name + validates_length_of :name, :maximum => 60 + + def authenticate(login, password) + end + + def test_connection + end + + def auth_method_name + "Abstract" + end + + def account_password + read_ciphered_attribute(:account_password) + end + + def account_password=(arg) + write_ciphered_attribute(:account_password, arg) + end + + def searchable? + false + end + + def self.search(q) + results = [] + AuthSource.all.each do |source| + begin + if source.searchable? + results += source.search(q) + end + rescue AuthSourceException => e + logger.error "Error while searching users in #{source.name}: #{e.message}" + end + end + results + end + + def allow_password_changes? + self.class.allow_password_changes? + end + + # Does this auth source backend allow password changes? + def self.allow_password_changes? + false + end + + # Try to authenticate a user not yet registered against available sources + def self.authenticate(login, password) + AuthSource.where(:onthefly_register => true).all.each do |source| + begin + logger.debug "Authenticating '#{login}' against '#{source.name}'" if logger && logger.debug? + attrs = source.authenticate(login, password) + rescue => e + logger.error "Error during authentication: #{e.message}" + attrs = nil + end + return attrs if attrs + end + return nil + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/2d/2d611b348b4b0790a149102dbbb27f7bf647e472.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2d/2d611b348b4b0790a149102dbbb27f7bf647e472.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,176 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../../test_helper', __FILE__) + +class Redmine::PluginTest < ActiveSupport::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_installed + @klass.register(:foo) {} + + assert_equal true, @klass.installed?(:foo) + assert_equal false, @klass.installed?(:bar) + end + + def test_menu + assert_difference 'Redmine::MenuManager.items(:project_menu).size' do + @klass.register :foo do + menu :project_menu, :foo_menu_item, '/foo', :caption => 'Foo' + end + end + menu_item = Redmine::MenuManager.items(:project_menu).detect {|i| i.name == :foo_menu_item} + assert_not_nil menu_item + assert_equal 'Foo', menu_item.caption + assert_equal '/foo', menu_item.url + end + + def test_delete_menu_item + Redmine::MenuManager.map(:project_menu).push(:foo_menu_item, '/foo', :caption => 'Foo') + + assert_difference 'Redmine::MenuManager.items(:project_menu).size', -1 do + @klass.register :foo do + delete_menu_item :project_menu, :foo_menu_item + end + end + assert_nil Redmine::MenuManager.items(:project_menu).detect {|i| i.name == :foo_menu_item} + end + + def test_requires_redmine + plugin = Redmine::Plugin.register(:foo) {} + Redmine::VERSION.stubs(:to_a).returns([2, 1, 3, "stable", 10817]) + + # Specific version without hash + assert plugin.requires_redmine('2.1.3') + assert plugin.requires_redmine('2.1') + assert_raise Redmine::PluginRequirementError do + plugin.requires_redmine('2.1.4') + end + assert_raise Redmine::PluginRequirementError do + plugin.requires_redmine('2.2') + end + + # Specific version + assert plugin.requires_redmine(:version => '2.1.3') + assert plugin.requires_redmine(:version => ['2.1.3', '2.2.0']) + assert plugin.requires_redmine(:version => '2.1') + assert_raise Redmine::PluginRequirementError do + plugin.requires_redmine(:version => '2.2.0') + end + assert_raise Redmine::PluginRequirementError do + plugin.requires_redmine(:version => ['2.1.4', '2.2.0']) + end + assert_raise Redmine::PluginRequirementError do + plugin.requires_redmine(:version => '2.2') + end + + # Version range + assert plugin.requires_redmine(:version => '2.0.0'..'2.2.4') + assert plugin.requires_redmine(:version => '2.1.3'..'2.2.4') + assert plugin.requires_redmine(:version => '2.0.0'..'2.1.3') + assert plugin.requires_redmine(:version => '2.0'..'2.2') + assert plugin.requires_redmine(:version => '2.1'..'2.2') + assert plugin.requires_redmine(:version => '2.0'..'2.1') + assert_raise Redmine::PluginRequirementError do + plugin.requires_redmine(:version => '2.1.4'..'2.2.4') + end + + + # Version or higher + assert plugin.requires_redmine(:version_or_higher => '0.1.0') + assert plugin.requires_redmine(:version_or_higher => '2.1.3') + assert plugin.requires_redmine(:version_or_higher => '2.1') + assert_raise Redmine::PluginRequirementError do + plugin.requires_redmine(:version_or_higher => '2.2.0') + end + assert_raise Redmine::PluginRequirementError do + plugin.requires_redmine(:version_or_higher => '2.2') + end + end + + def test_requires_redmine_plugin + test = self + other_version = '0.5.0' + + @klass.register :other do + name 'Other' + version other_version + end + + @klass.register :foo do + test.assert requires_redmine_plugin(:other, :version_or_higher => '0.1.0') + test.assert requires_redmine_plugin(:other, :version_or_higher => other_version) + test.assert requires_redmine_plugin(:other, other_version) + test.assert_raise Redmine::PluginRequirementError do + requires_redmine_plugin(:other, :version_or_higher => '99.0.0') + end + + test.assert requires_redmine_plugin(:other, :version => other_version) + test.assert requires_redmine_plugin(:other, :version => [other_version, '99.0.0']) + test.assert_raise Redmine::PluginRequirementError do + requires_redmine_plugin(:other, :version => '99.0.0') + end + test.assert_raise Redmine::PluginRequirementError do + requires_redmine_plugin(:other, :version => ['98.0.0', '99.0.0']) + end + # Missing plugin + test.assert_raise Redmine::PluginNotFound do + requires_redmine_plugin(:missing, :version_or_higher => '0.1.0') + end + test.assert_raise Redmine::PluginNotFound do + requires_redmine_plugin(:missing, '0.1.0') + end + test.assert_raise Redmine::PluginNotFound do + requires_redmine_plugin(:missing, :version => '0.1.0') + end + + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/2d/2d85d0783f59c365e1251c736bcbc44da38bc043.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2d/2d85d0783f59c365e1251c736bcbc44da38bc043.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,163 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class WikiPageTest < ActiveSupport::TestCase + fixtures :projects, :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions + + def setup + @wiki = Wiki.find(1) + @page = @wiki.pages.first + end + + def test_create + page = WikiPage.new(:wiki => @wiki) + assert !page.save + assert_equal 1, page.errors.count + + page.title = "Page" + assert page.save + page.reload + assert !page.protected? + + @wiki.reload + assert @wiki.pages.include?(page) + end + + def test_sidebar_should_be_protected_by_default + page = @wiki.find_or_new_page('sidebar') + assert page.new_record? + assert page.protected? + end + + def test_find_or_new_page + page = @wiki.find_or_new_page("CookBook documentation") + assert_kind_of WikiPage, page + assert !page.new_record? + + page = @wiki.find_or_new_page("Non existing page") + assert_kind_of WikiPage, page + assert page.new_record? + end + + def test_parent_title + page = WikiPage.find_by_title('Another_page') + assert_nil page.parent_title + + page = WikiPage.find_by_title('Page_with_an_inline_image') + assert_equal 'CookBook documentation', page.parent_title + end + + def test_assign_parent + page = WikiPage.find_by_title('Another_page') + page.parent_title = 'CookBook documentation' + assert page.save + page.reload + assert_equal WikiPage.find_by_title('CookBook_documentation'), page.parent + end + + def test_unassign_parent + page = WikiPage.find_by_title('Page_with_an_inline_image') + page.parent_title = '' + assert page.save + page.reload + assert_nil page.parent + end + + def test_parent_validation + page = WikiPage.find_by_title('CookBook_documentation') + + # A page that doesn't exist + page.parent_title = 'Unknown title' + assert !page.save + assert_include I18n.translate('activerecord.errors.messages.invalid'), + page.errors[:parent_title] + # A child page + page.parent_title = 'Page_with_an_inline_image' + assert !page.save + assert_include I18n.translate('activerecord.errors.messages.circular_dependency'), + page.errors[:parent_title] + # The page itself + page.parent_title = 'CookBook_documentation' + assert !page.save + assert_include I18n.translate('activerecord.errors.messages.circular_dependency'), + page.errors[:parent_title] + page.parent_title = 'Another_page' + assert page.save + end + + def test_destroy + page = WikiPage.find(1) + page.destroy + assert_nil WikiPage.find_by_id(1) + # make sure that page content and its history are deleted + assert WikiContent.find_all_by_page_id(1).empty? + assert WikiContent.versioned_class.find_all_by_page_id(1).empty? + end + + def test_destroy_should_not_nullify_children + page = WikiPage.find(2) + child_ids = page.child_ids + assert child_ids.any? + page.destroy + assert_nil WikiPage.find_by_id(2) + + children = WikiPage.find_all_by_id(child_ids) + assert_equal child_ids.size, children.size + children.each do |child| + assert_nil child.parent_id + end + end + + def test_updated_on_eager_load + page = WikiPage.with_updated_on.first(:order => 'id') + assert page.is_a?(WikiPage) + assert_not_nil page.read_attribute(:updated_on) + assert_equal Time.gm(2007, 3, 6, 23, 10, 51), page.content.updated_on + assert_equal page.content.updated_on, page.updated_on + assert_not_nil page.read_attribute(:version) + end + + def test_descendants + page = WikiPage.create!(:wiki => @wiki, :title => 'Parent') + child1 = WikiPage.create!(:wiki => @wiki, :title => 'Child1', :parent => page) + child11 = WikiPage.create!(:wiki => @wiki, :title => 'Child11', :parent => child1) + child111 = WikiPage.create!(:wiki => @wiki, :title => 'Child111', :parent => child11) + child2 = WikiPage.create!(:wiki => @wiki, :title => 'Child2', :parent => page) + + assert_equal %w(Child1 Child11 Child111 Child2), page.descendants.map(&:title).sort + assert_equal %w(Child1 Child11 Child111 Child2), page.descendants(nil).map(&:title).sort + assert_equal %w(Child1 Child11 Child2), page.descendants(2).map(&:title).sort + assert_equal %w(Child1 Child2), page.descendants(1).map(&:title).sort + + assert_equal %w(Child1 Child11 Child111 Child2 Parent), page.self_and_descendants.map(&:title).sort + assert_equal %w(Child1 Child11 Child111 Child2 Parent), page.self_and_descendants(nil).map(&:title).sort + assert_equal %w(Child1 Child11 Child2 Parent), page.self_and_descendants(2).map(&:title).sort + assert_equal %w(Child1 Child2 Parent), page.self_and_descendants(1).map(&:title).sort + end + + def test_diff_for_page_with_deleted_version_should_pick_the_previous_available_version + WikiContent::Version.find_by_page_id_and_version(1, 2).destroy + + page = WikiPage.find(1) + diff = page.diff(3) + assert_not_nil diff + assert_equal 3, diff.content_to.version + assert_equal 1, diff.content_from.version + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/2d/2dbd0564d495b47bea2303b32f1609a8bf5c4b1a.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2d/2dbd0564d495b47bea2303b32f1609a8bf5c4b1a.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,41 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class RoutingJournalsTest < ActionController::IntegrationTest + def test_journals + assert_routing( + { :method => 'post', :path => "/issues/1/quoted" }, + { :controller => 'journals', :action => 'new', :id => '1' } + ) + assert_routing( + { :method => 'get', :path => "/issues/changes" }, + { :controller => 'journals', :action => 'index' } + ) + assert_routing( + { :method => 'get', :path => "/journals/diff/1" }, + { :controller => 'journals', :action => 'diff', :id => '1' } + ) + ["get", "post"].each do |method| + assert_routing( + { :method => method, :path => "/journals/edit/1" }, + { :controller => 'journals', :action => 'edit', :id => '1' } + ) + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/2d/2dbde491b7a799c190d49fd409652b2fbab77ca3.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2d/2dbde491b7a799c190d49fd409652b2fbab77ca3.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,66 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class News < ActiveRecord::Base + include Redmine::SafeAttributes + belongs_to :project + belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' + has_many :comments, :as => :commented, :dependent => :delete_all, :order => "created_on" + + validates_presence_of :title, :description + validates_length_of :title, :maximum => 60 + validates_length_of :summary, :maximum => 255 + + acts_as_attachable :delete_permission => :manage_news + acts_as_searchable :columns => ['title', 'summary', "#{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]}, + :author_key => :author_id + acts_as_watchable + + after_create :add_author_as_watcher + + scope :visible, lambda {|*args| + includes(:project).where(Project.allowed_to_condition(args.shift || User.current, :view_news, *args)) + } + + safe_attributes 'title', 'summary', 'description' + + def visible?(user=User.current) + !user.nil? && user.allowed_to?(:view_news, project) + end + + # Returns true if the news can be commented by user + def commentable?(user=User.current) + user.allowed_to?(:comment_news, project) + end + + def recipients + project.users.select {|user| user.notify_about?(self)}.map(&:mail) + end + + # returns latest news for projects visible by user + def self.latest(user = User.current, count = 5) + visible(user).includes([:author, :project]).order("#{News.table_name}.created_on DESC").limit(count).all + end + + private + + def add_author_as_watcher + Watcher.create(:watchable => self, :user => author) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/2d/2ddfd454c5c1fbd92838296a3b268aeb1e87e384.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2d/2ddfd454c5c1fbd92838296a3b268aeb1e87e384.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,212 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class Redmine::ApiTest::GroupsTest < Redmine::ApiTest::Base + fixtures :users, :groups_users + + def setup + Setting.rest_api_enabled = '1' + end + + context "GET /groups" do + context ".xml" do + should "require authentication" do + get '/groups.xml' + assert_response 401 + end + + should "return groups" do + get '/groups.xml', {}, credentials('admin') + assert_response :success + assert_equal 'application/xml', response.content_type + + assert_select 'groups' do + assert_select 'group' do + assert_select 'name', :text => 'A Team' + assert_select 'id', :text => '10' + end + end + end + end + + context ".json" do + should "require authentication" do + get '/groups.json' + assert_response 401 + end + + should "return groups" do + get '/groups.json', {}, credentials('admin') + assert_response :success + assert_equal 'application/json', response.content_type + + json = MultiJson.load(response.body) + groups = json['groups'] + assert_kind_of Array, groups + group = groups.detect {|g| g['name'] == 'A Team'} + assert_not_nil group + assert_equal({'id' => 10, 'name' => 'A Team'}, group) + end + end + end + + context "GET /groups/:id" do + context ".xml" do + should "return the group with its users" do + get '/groups/10.xml', {}, credentials('admin') + assert_response :success + assert_equal 'application/xml', response.content_type + + assert_select 'group' do + assert_select 'name', :text => 'A Team' + assert_select 'id', :text => '10' + end + end + + should "include users if requested" do + get '/groups/10.xml?include=users', {}, credentials('admin') + assert_response :success + assert_equal 'application/xml', response.content_type + + assert_select 'group' do + assert_select 'users' do + assert_select 'user', Group.find(10).users.count + assert_select 'user[id=8]' + end + end + end + + should "include memberships if requested" do + get '/groups/10.xml?include=memberships', {}, credentials('admin') + assert_response :success + assert_equal 'application/xml', response.content_type + + assert_select 'group' do + assert_select 'memberships' + end + end + end + end + + context "POST /groups" do + context "with valid parameters" do + context ".xml" do + should "create groups" do + assert_difference('Group.count') do + post '/groups.xml', {:group => {:name => 'Test', :user_ids => [2, 3]}}, credentials('admin') + assert_response :created + assert_equal 'application/xml', response.content_type + end + + group = Group.order('id DESC').first + assert_equal 'Test', group.name + assert_equal [2, 3], group.users.map(&:id).sort + + assert_select 'group' do + assert_select 'name', :text => 'Test' + end + end + end + end + + context "with invalid parameters" do + context ".xml" do + should "return errors" do + assert_no_difference('Group.count') do + post '/groups.xml', {:group => {:name => ''}}, credentials('admin') + end + assert_response :unprocessable_entity + assert_equal 'application/xml', response.content_type + + assert_select 'errors' do + assert_select 'error', :text => /Name can't be blank/ + end + end + end + end + end + + context "PUT /groups/:id" do + context "with valid parameters" do + context ".xml" do + should "update the group" do + put '/groups/10.xml', {:group => {:name => 'New name', :user_ids => [2, 3]}}, credentials('admin') + assert_response :ok + assert_equal '', @response.body + + group = Group.find(10) + assert_equal 'New name', group.name + assert_equal [2, 3], group.users.map(&:id).sort + end + end + end + + context "with invalid parameters" do + context ".xml" do + should "return errors" do + put '/groups/10.xml', {:group => {:name => ''}}, credentials('admin') + assert_response :unprocessable_entity + assert_equal 'application/xml', response.content_type + + assert_select 'errors' do + assert_select 'error', :text => /Name can't be blank/ + end + end + end + end + end + + context "DELETE /groups/:id" do + context ".xml" do + should "delete the group" do + assert_difference 'Group.count', -1 do + delete '/groups/10.xml', {}, credentials('admin') + assert_response :ok + assert_equal '', @response.body + end + end + end + end + + context "POST /groups/:id/users" do + context ".xml" do + should "add user to the group" do + assert_difference 'Group.find(10).users.count' do + post '/groups/10/users.xml', {:user_id => 5}, credentials('admin') + assert_response :ok + assert_equal '', @response.body + end + assert_include User.find(5), Group.find(10).users + end + end + end + + context "DELETE /groups/:id/users/:user_id" do + context ".xml" do + should "remove user from the group" do + assert_difference 'Group.find(10).users.count', -1 do + delete '/groups/10/users/8.xml', {}, credentials('admin') + assert_response :ok + assert_equal '', @response.body + end + assert_not_include User.find(8), Group.find(10).users + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/2d/2de3a87e3fe026c2ba56948a00c54c7c54909e85.svn-base --- a/.svn/pristine/2d/2de3a87e3fe026c2ba56948a00c54c7c54909e85.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,417 +0,0 @@ -/* redMine - project management software - Copyright (C) 2006-2008 Jean-Philippe Lang */ - -function checkAll (id, checked) { - var els = Element.descendants(id); - for (var i = 0; i < els.length; i++) { - if (els[i].disabled==false) { - els[i].checked = checked; - } - } -} - -function toggleCheckboxesBySelector(selector) { - boxes = $$(selector); - var all_checked = true; - for (i = 0; i < boxes.length; i++) { if (boxes[i].checked == false) { all_checked = false; } } - for (i = 0; i < boxes.length; i++) { boxes[i].checked = !all_checked; } -} - -function setCheckboxesBySelector(checked, selector) { - var boxes = $$(selector); - boxes.each(function(ele) { - ele.checked = checked; - }); -} - -function showAndScrollTo(id, focus) { - Element.show(id); - if (focus!=null) { Form.Element.focus(focus); } - Element.scrollTo(id); -} - -function toggleRowGroup(el) { - var tr = Element.up(el, 'tr'); - var n = Element.next(tr); - tr.toggleClassName('open'); - while (n != undefined && !n.hasClassName('group')) { - Element.toggle(n); - n = Element.next(n); - } -} - -function collapseAllRowGroups(el) { - var tbody = Element.up(el, 'tbody'); - tbody.childElements('tr').each(function(tr) { - if (tr.hasClassName('group')) { - tr.removeClassName('open'); - } else { - tr.hide(); - } - }) -} - -function expandAllRowGroups(el) { - var tbody = Element.up(el, 'tbody'); - tbody.childElements('tr').each(function(tr) { - if (tr.hasClassName('group')) { - tr.addClassName('open'); - } else { - tr.show(); - } - }) -} - -function toggleAllRowGroups(el) { - var tr = Element.up(el, 'tr'); - if (tr.hasClassName('open')) { - collapseAllRowGroups(el); - } else { - expandAllRowGroups(el); - } -} - -function toggleFieldset(el) { - var fieldset = Element.up(el, 'fieldset'); - fieldset.toggleClassName('collapsed'); - Effect.toggle(fieldset.down('div'), 'slide', {duration:0.2}); -} - -function hideFieldset(el) { - var fieldset = Element.up(el, 'fieldset'); - fieldset.toggleClassName('collapsed'); - fieldset.down('div').hide(); -} - -var fileFieldCount = 1; - -function addFileField() { - var fields = $('attachments_fields'); - if (fields.childElements().length >= 10) return false; - fileFieldCount++; - var s = new Element('span'); - s.update(fields.down('span').innerHTML); - s.down('input.file').name = "attachments[" + fileFieldCount + "][file]"; - s.down('input.description').name = "attachments[" + fileFieldCount + "][description]"; - fields.appendChild(s); -} - -function removeFileField(el) { - var fields = $('attachments_fields'); - var s = Element.up(el, 'span'); - if (fields.childElements().length > 1) { - s.remove(); - } else { - s.update(s.innerHTML); - } -} - -function checkFileSize(el, maxSize, message) { - var files = el.files; - if (files) { - for (var i=0; i maxSize) { - alert(message); - el.value = ""; - } - } - } -} - -function showTab(name) { - var f = $$('div#content .tab-content'); - for(var i=0; i0) { - lis[i-1].show(); - } -} - -function displayTabsButtons() { - var lis; - var tabsWidth = 0; - var i; - $$('div.tabs').each(function(el) { - lis = el.down('ul').childElements(); - for (i=0; i 0) { - Element.show('ajax-indicator'); - } - }, - onComplete: function(){ - if ($('ajax-indicator') && Ajax.activeRequestCount == 0) { - Element.hide('ajax-indicator'); - } - } -}); - -function hideOnLoad() { - $$('.hol').each(function(el) { - el.hide(); - }); -} - -Event.observe(window, 'load', hideOnLoad); diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/2e/2e1157d3f81f3880c0395bb75f0b975ce97e7bd4.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2e/2e1157d3f81f3880c0395bb75f0b975ce97e7bd4.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,8 @@ +<%= error_messages_for @group %> + +
+

<%= f.text_field :name, :required => true, :size => 60 %>

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

<%= custom_field_tag_with_label :group, value %>

+ <% end %> +
diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/2e/2e26af3877bdd2fe792449a4fd743a6ba7463f7e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2e/2e26af3877bdd2fe792449a4fd743a6ba7463f7e.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,53 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 PreviewsController < ApplicationController + before_filter :find_project, :find_attachments + + def issue + @issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank? + if @issue + @description = params[:issue] && params[:issue][:description] + if @description && @description.gsub(/(\r?\n|\n\r?)/, "\n") == @issue.description.to_s.gsub(/(\r?\n|\n\r?)/, "\n") + @description = nil + end + # params[:notes] is useful for preview of notes in issue history + @notes = params[:notes] || (params[:issue] ? params[:issue][:notes] : nil) + else + @description = (params[:issue] ? params[:issue][:description] : nil) + end + render :layout => false + end + + def news + if params[:id].present? && news = News.visible.find_by_id(params[:id]) + @previewed = news + end + @text = (params[:news] ? params[:news][:description] : nil) + render :partial => 'common/preview' + end + + private + + def find_project + project_id = (params[:issue] && params[:issue][:project_id]) || params[:project_id] + @project = Project.find(project_id) + rescue ActiveRecord::RecordNotFound + render_404 + end + +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/2e/2e2d614b9e64145af9606e9c3e56e58eb315056d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2e/2e2d614b9e64145af9606e9c3e56e58eb315056d.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,34 @@ +
+
"> + <%= l(:label_filter_plural) %> +
"> + <%= render :partial => 'queries/filters', :locals => {:query => @query} %> +
+
+ +
+ +

+ <%= link_to_function l(:button_apply), 'submit_query_form("query_form")', :class => 'icon icon-checked' %> + <%= link_to l(:button_clear), {:project_id => @project, :issue_id => @issue}, :class => 'icon icon-reload' %> +

+ +
+<% query_params = params.slice(:f, :op, :v, :sort) %> +
    +
  • <%= link_to(l(:label_details), query_params.merge({:controller => 'timelog', :action => 'index', :project_id => @project, :issue_id => @issue }), + :class => (action_name == 'index' ? 'selected' : nil)) %>
  • +
  • <%= link_to(l(:label_report), query_params.merge({:controller => 'timelog', :action => 'report', :project_id => @project, :issue_id => @issue}), + :class => (action_name == 'report' ? 'selected' : nil)) %>
  • +
+
diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/2e/2e4d7c7eff8f75e07fa65599f46a1983efb8afeb.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2e/2e4d7c7eff8f75e07fa65599f46a1983efb8afeb.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,150 @@ +

<%= @copy ? l(:button_copy) : l(:label_bulk_edit_selected_issues) %>

+ +
    +<% @issues.each do |issue| %> + <%= content_tag 'li', link_to_issue(issue) %> +<% end %> +
+ +<%= form_tag(bulk_update_issues_path, :id => 'bulk_edit_form') do %> +<%= @issues.collect {|i| hidden_field_tag('ids[]', i.id)}.join("\n").html_safe %> +
+
+<%= l(:label_change_properties) %> + +
+<% if @allowed_projects.present? %> +

+ + <%= select_tag('issue[project_id]', content_tag('option', l(:label_no_change_option), :value => '') + project_tree_options_for_select(@allowed_projects, :selected => @target_project), + :onchange => "updateBulkEditFrom('#{escape_javascript url_for(:action => 'bulk_edit', :format => 'js')}')") %> +

+<% end %> +

+ + <%= select_tag('issue[tracker_id]', content_tag('option', l(:label_no_change_option), :value => '') + options_from_collection_for_select(@trackers, :id, :name)) %> +

+<% if @available_statuses.any? %> +

+ + <%= select_tag('issue[status_id]',content_tag('option', l(:label_no_change_option), :value => '') + options_from_collection_for_select(@available_statuses, :id, :name)) %> +

+<% end %> + +<% if @safe_attributes.include?('priority_id') -%> +

+ + <%= select_tag('issue[priority_id]', content_tag('option', l(:label_no_change_option), :value => '') + options_from_collection_for_select(IssuePriority.active, :id, :name)) %> +

+<% end %> + +<% if @safe_attributes.include?('assigned_to_id') -%> +

+ + <%= select_tag('issue[assigned_to_id]', content_tag('option', l(:label_no_change_option), :value => '') + + content_tag('option', l(:label_nobody), :value => 'none') + + principals_options_for_select(@assignables)) %> +

+<% end %> + +<% if @safe_attributes.include?('category_id') -%> +

+ + <%= select_tag('issue[category_id]', content_tag('option', l(:label_no_change_option), :value => '') + + content_tag('option', l(:label_none), :value => 'none') + + options_from_collection_for_select(@categories, :id, :name)) %> +

+<% end %> + +<% if @safe_attributes.include?('fixed_version_id') -%> +

+ + <%= select_tag('issue[fixed_version_id]', content_tag('option', l(:label_no_change_option), :value => '') + + content_tag('option', l(:label_none), :value => 'none') + + version_options_for_select(@versions.sort)) %> +

+<% end %> + +<% @custom_fields.each do |custom_field| %> +

<%= custom_field_tag_for_bulk_edit('issue', custom_field, @projects) %>

+<% end %> + +<% if @copy && @attachments_present %> +

+ + <%= check_box_tag 'copy_attachments', '1', true %> +

+<% end %> + +<% if @copy && @subtasks_present %> +

+ + <%= check_box_tag 'copy_subtasks', '1', true %> +

+<% end %> + +<%= call_hook(:view_issues_bulk_edit_details_bottom, { :issues => @issues }) %> +
+ +
+<% if @safe_attributes.include?('is_private') %> +

+ + <%= select_tag('issue[is_private]', content_tag('option', l(:label_no_change_option), :value => '') + + content_tag('option', l(:general_text_Yes), :value => '1') + + content_tag('option', l(:general_text_No), :value => '0')) %> +

+<% end %> + +<% if @safe_attributes.include?('parent_issue_id') && @project %> +

+ + <%= text_field_tag 'issue[parent_issue_id]', '', :size => 10 %> +

+<%= javascript_tag "observeAutocompleteField('issue_parent_issue_id', '#{escape_javascript auto_complete_issues_path(:project_id => @project)}')" %> +<% end %> + +<% if @safe_attributes.include?('start_date') %> +

+ + <%= text_field_tag 'issue[start_date]', '', :size => 10 %><%= calendar_for('issue_start_date') %> +

+<% end %> + +<% if @safe_attributes.include?('due_date') %> +

+ + <%= text_field_tag 'issue[due_date]', '', :size => 10 %><%= calendar_for('issue_due_date') %> +

+<% end %> + +<% if @safe_attributes.include?('done_ratio') && Issue.use_field_for_done_ratio? %> +

+ + <%= select_tag 'issue[done_ratio]', options_for_select([[l(:label_no_change_option), '']] + (0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %> +

+<% end %> +
+ +
+ +
<%= l(:field_notes) %> +<%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10, :class => 'wiki-edit' %> +<%= wikitoolbar_for 'notes' %> +
+
+ +

+ <% if @copy %> + <%= hidden_field_tag 'copy', '1' %> + <%= submit_tag l(:button_copy) %> + <%= submit_tag l(:button_copy_and_follow), :name => 'follow' %> + <% elsif @target_project %> + <%= submit_tag l(:button_move) %> + <%= submit_tag l(:button_move_and_follow), :name => 'follow' %> + <% else %> + <%= submit_tag l(:button_submit) %> + <% end %> +

+ +<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/2e/2e9778d3950cea9aad42938a07deac546bf3beda.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2e/2e9778d3950cea9aad42938a07deac546bf3beda.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,74 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../../test_helper', __FILE__) + +class ThemesTest < ActionController::IntegrationTest + + def setup + @theme = Redmine::Themes.themes.last + Setting.ui_theme = @theme.id + end + + def teardown + Setting.ui_theme = '' + end + + def test_application_css + get '/' + + assert_response :success + assert_tag :tag => 'link', + :attributes => {:href => %r{^/themes/#{@theme.dir}/stylesheets/application.css}} + end + + def test_without_theme_js + get '/' + + assert_response :success + assert_no_tag :tag => 'script', + :attributes => {:src => %r{^/themes/#{@theme.dir}/javascripts/theme.js}} + end + + def test_with_theme_js + # Simulates a theme.js + @theme.javascripts << 'theme' + get '/' + + assert_response :success + assert_tag :tag => 'script', + :attributes => {:src => %r{^/themes/#{@theme.dir}/javascripts/theme.js}} + + ensure + @theme.javascripts.delete 'theme' + end + + def test_with_sub_uri + Redmine::Utils.relative_url_root = '/foo' + @theme.javascripts << 'theme' + get '/' + + assert_response :success + assert_tag :tag => 'link', + :attributes => {:href => %r{^/foo/themes/#{@theme.dir}/stylesheets/application.css}} + assert_tag :tag => 'script', + :attributes => {:src => %r{^/foo/themes/#{@theme.dir}/javascripts/theme.js}} + + ensure + Redmine::Utils.relative_url_root = '' + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/2e/2ee21f20a298f9183bcf6da4115281a958daccfb.svn-base --- a/.svn/pristine/2e/2ee21f20a298f9183bcf6da4115281a958daccfb.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,6 +0,0 @@ -

<%= link_to l(:label_issue_status_plural), issue_statuses_path %> » <%=h @issue_status %>

- -<% form_for @issue_status, :builder => TabularFormBuilder do |f| %> - <%= render :partial => 'form', :locals => {:f => f} %> - <%= submit_tag l(:button_save) %> -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/2f/2f133168a880fffbd35ca248f3d226a5ea263cb4.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2f/2f133168a880fffbd35ca248f3d226a5ea263cb4.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,90 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class NewsTest < ActiveSupport::TestCase + fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules, :news + + def valid_news + { :title => 'Test news', :description => 'Lorem ipsum etc', :author => User.first } + end + + def setup + end + + def test_create_should_send_email_notification + ActionMailer::Base.deliveries.clear + news = Project.find(1).news.new(valid_news) + + with_settings :notified_events => %w(news_added) do + assert news.save + end + assert_equal 1, ActionMailer::Base.deliveries.size + 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 + EnabledModule.delete_all(["project_id = ? AND name = ?", 2, 'news']) + project = Project.find(2) + + # Add a piece of news to the project + news = project.news.create(valid_news) + + # 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 + assert News.latest(User.anonymous).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(valid_news) } + 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(valid_news) } + assert_equal 5, News.latest(users(:users_004)).size + end + + def test_attachments_should_be_visible + assert News.find(1).attachments_visible?(User.anonymous) + end + + def test_attachments_should_be_deletable_with_manage_news_permission + manager = User.find(2) + assert News.find(1).attachments_deletable?(manager) + end + + def test_attachments_should_not_be_deletable_without_manage_news_permission + manager = User.find(2) + Role.find_by_name('Manager').remove_permission!(:manage_news) + assert !News.find(1).attachments_deletable?(manager) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/2f/2f1f6f8262e47819e2d086a5c6cc0817e0e7d2b5.svn-base --- a/.svn/pristine/2f/2f1f6f8262e47819e2d086a5c6cc0817e0e7d2b5.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,526 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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 'uri' -require 'cgi' - -class Unauthorized < Exception; end - -class ApplicationController < ActionController::Base - include Redmine::I18n - - layout 'base' - exempt_from_layout 'builder', 'rsb' - - protect_from_forgery - def handle_unverified_request - super - cookies.delete(:autologin) - end - # Remove broken cookie after upgrade from 0.8.x (#4292) - # See https://rails.lighthouseapp.com/projects/8994/tickets/3360 - # TODO: remove it when Rails is fixed - before_filter :delete_broken_cookies - def delete_broken_cookies - if cookies['_redmine_session'] && cookies['_redmine_session'] !~ /--/ - cookies.delete '_redmine_session' - redirect_to home_path - return false - end - end - - before_filter :user_setup, :check_if_login_required, :set_localization - filter_parameter_logging :password - - rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token - rescue_from ::Unauthorized, :with => :deny_access - - include Redmine::Search::Controller - include Redmine::MenuManager::MenuController - helper Redmine::MenuManager::MenuHelper - - Redmine::Scm::Base.all.each do |scm| - require_dependency "repository/#{scm.underscore}" - end - - def user_setup - # Check the settings cache for each request - Setting.check_cache - # Find the current user - User.current = find_current_user - end - - # Returns the current user or nil if no user is logged in - # and starts a session if needed - def find_current_user - if session[:user_id] - # existing session - (User.active.find(session[:user_id]) rescue nil) - elsif cookies[:autologin] && Setting.autologin? - # auto-login feature starts a new session - user = User.try_to_autologin(cookies[:autologin]) - session[:user_id] = user.id if user - user - elsif params[:format] == 'atom' && params[:key] && request.get? && accept_rss_auth? - # RSS key authentication does not start a session - User.find_by_rss_key(params[:key]) - elsif Setting.rest_api_enabled? && accept_api_auth? - if (key = api_key_from_request) - # Use API key - User.find_by_api_key(key) - else - # HTTP Basic, either username/password or API key/random - authenticate_with_http_basic do |username, password| - User.try_to_login(username, password) || User.find_by_api_key(username) - end - end - end - end - - # Sets the logged in user - def logged_user=(user) - reset_session - if user && user.is_a?(User) - User.current = user - session[:user_id] = user.id - else - User.current = User.anonymous - end - end - - # check if login is globally required to access the application - def check_if_login_required - # no check needed if user is already logged in - return true if User.current.logged? - require_login if Setting.login_required? - end - - def set_localization - lang = nil - if User.current.logged? - lang = find_language(User.current.language) - end - if lang.nil? && request.env['HTTP_ACCEPT_LANGUAGE'] - accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first - if !accept_lang.blank? - accept_lang = accept_lang.downcase - lang = find_language(accept_lang) || find_language(accept_lang.split('-').first) - end - end - lang ||= Setting.default_language - set_language_if_valid(lang) - end - - def require_login - if !User.current.logged? - # Extract only the basic url parameters on non-GET requests - if request.get? - url = url_for(params) - else - url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id]) - end - respond_to do |format| - format.html { redirect_to :controller => "account", :action => "login", :back_url => url } - format.atom { redirect_to :controller => "account", :action => "login", :back_url => url } - format.xml { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' } - format.js { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' } - format.json { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' } - end - return false - end - true - end - - def require_admin - return unless require_login - if !User.current.admin? - render_403 - return false - end - true - end - - def deny_access - User.current.logged? ? render_403 : require_login - end - - # Authorize the user for the requested action - def authorize(ctrl = params[:controller], action = params[:action], global = false) - allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project || @projects, :global => global) - if allowed - true - else - if @project && @project.archived? - render_403 :message => :notice_not_authorized_archived_project - else - deny_access - end - end - end - - # Authorize the user for the requested action outside a project - def authorize_global(ctrl = params[:controller], action = params[:action], global = true) - authorize(ctrl, action, global) - end - - # Find project of id params[:id] - def find_project - @project = Project.find(params[:id]) - rescue ActiveRecord::RecordNotFound - render_404 - end - - # Find project of id params[:project_id] - def find_project_by_project_id - @project = Project.find(params[:project_id]) - rescue ActiveRecord::RecordNotFound - render_404 - end - - # Find a project based on params[:project_id] - # TODO: some subclasses override this, see about merging their logic - def find_optional_project - @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 - - # Finds and sets @project based on @object.project - def find_project_from_association - render_404 unless @object.present? - - @project = @object.project - end - - def find_model_object - model = self.class.read_inheritable_attribute('model_object') - if model - @object = model.find(params[:id]) - self.instance_variable_set('@' + controller_name.singularize, @object) if @object - end - rescue ActiveRecord::RecordNotFound - render_404 - end - - def self.model_object(model) - write_inheritable_attribute('model_object', model) - end - - # Filter for bulk issue operations - def find_issues - @issues = Issue.find_all_by_id(params[:id] || params[:ids]) - raise ActiveRecord::RecordNotFound if @issues.empty? - if @issues.detect {|issue| !issue.visible?} - deny_access - return - end - @projects = @issues.collect(&:project).compact.uniq - @project = @projects.first if @projects.size == 1 - rescue ActiveRecord::RecordNotFound - render_404 - end - - # Check if project is unique before bulk operations - def check_project_uniqueness - unless @project - # TODO: let users bulk edit/move/destroy issues from different projects - render_error 'Can not bulk edit/move/destroy issues from different projects' - return false - end - end - - # make sure that the user is a member of the project (or admin) if project is private - # used as a before_filter for actions that do not require any particular permission on the project - def check_project_privacy - if @project && @project.active? - if @project.is_public? || User.current.member_of?(@project) || User.current.admin? - true - else - deny_access - end - else - @project = nil - render_404 - false - end - end - - def back_url - params[:back_url] || request.env['HTTP_REFERER'] - end - - def redirect_back_or_default(default) - back_url = CGI.unescape(params[:back_url].to_s) - if !back_url.blank? - 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) - return - end - rescue URI::InvalidURIError - # redirect to default - end - end - redirect_to default - false - end - - def render_403(options={}) - @project = nil - render_error({:message => :notice_not_authorized, :status => 403}.merge(options)) - return false - end - - def render_404(options={}) - render_error({:message => :notice_file_not_found, :status => 404}.merge(options)) - return false - end - - # Renders an error response - def render_error(arg) - arg = {:message => arg} unless arg.is_a?(Hash) - - @message = arg[:message] - @message = l(@message) if @message.is_a?(Symbol) - @status = arg[:status] || 500 - - respond_to do |format| - format.html { - render :template => 'common/error', :layout => use_layout, :status => @status - } - format.atom { head @status } - format.xml { head @status } - format.js { head @status } - format.json { head @status } - end - end - - # Filter for actions that provide an API response - # but have no HTML representation for non admin users - def require_admin_or_api_request - return true if api_request? - if User.current.admin? - true - elsif User.current.logged? - render_error(:status => 406) - else - deny_access - end - end - - # Picks which layout to use based on the request - # - # @return [boolean, string] name of the layout to use or false for no layout - def use_layout - request.xhr? ? false : 'base' - end - - def invalid_authenticity_token - if api_request? - logger.error "Form authenticity token is missing or is invalid. API calls must include a proper Content-type header (text/xml or text/json)." - end - render_error "Invalid form authenticity token." - end - - def render_feed(items, options={}) - @items = items || [] - @items.sort! {|x,y| y.event_datetime <=> x.event_datetime } - @items = @items.slice(0, Setting.feeds_limit.to_i) - @title = options[:title] || Setting.app_title - render :template => "common/feed.atom", :layout => false, - :content_type => 'application/atom+xml' - end - - # TODO: remove in Redmine 1.4 - def self.accept_key_auth(*actions) - ActiveSupport::Deprecation.warn "ApplicationController.accept_key_auth is deprecated and will be removed in Redmine 1.4. Use accept_rss_auth (or accept_api_auth) instead." - accept_rss_auth(*actions) - end - - # TODO: remove in Redmine 1.4 - def accept_key_auth_actions - ActiveSupport::Deprecation.warn "ApplicationController.accept_key_auth_actions is deprecated and will be removed in Redmine 1.4. Use accept_rss_auth (or accept_api_auth) instead." - self.class.accept_rss_auth - end - - def self.accept_rss_auth(*actions) - if actions.any? - write_inheritable_attribute('accept_rss_auth_actions', actions) - else - read_inheritable_attribute('accept_rss_auth_actions') || [] - end - end - - def accept_rss_auth?(action=action_name) - self.class.accept_rss_auth.include?(action.to_sym) - end - - def self.accept_api_auth(*actions) - if actions.any? - write_inheritable_attribute('accept_api_auth_actions', actions) - else - read_inheritable_attribute('accept_api_auth_actions') || [] - end - end - - def accept_api_auth?(action=action_name) - self.class.accept_api_auth.include?(action.to_sym) - end - - # Returns the number of objects that should be displayed - # on the paginated list - def per_page_option - per_page = nil - if params[:per_page] && Setting.per_page_options_array.include?(params[:per_page].to_s.to_i) - per_page = params[:per_page].to_s.to_i - session[:per_page] = per_page - elsif session[:per_page] - per_page = session[:per_page] - else - per_page = Setting.per_page_options_array.first || 25 - end - per_page - end - - # Returns offset and limit used to retrieve objects - # for an API response based on offset, limit and page parameters - def api_offset_and_limit(options=params) - if options[:offset].present? - offset = options[:offset].to_i - if offset < 0 - offset = 0 - end - end - limit = options[:limit].to_i - if limit < 1 - limit = 25 - elsif limit > 100 - limit = 100 - end - if offset.nil? && options[:page].present? - offset = (options[:page].to_i - 1) * limit - offset = 0 if offset < 0 - end - offset ||= 0 - - [offset, limit] - end - - # qvalues http header parser - # code taken from webrick - def parse_qvalues(value) - tmp = [] - if value - parts = value.split(/,\s*/) - parts.each {|part| - if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part) - val = m[1] - q = (m[2] or 1).to_f - tmp.push([val, q]) - end - } - tmp = tmp.sort_by{|val, q| -q} - tmp.collect!{|val, q| val} - end - return tmp - rescue - nil - end - - # Returns a string that can be used as filename value in Content-Disposition header - def filename_for_content_disposition(name) - request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name - end - - def api_request? - %w(xml json).include? params[:format] - end - - # Returns the API key present in the request - def api_key_from_request - if params[:key].present? - params[:key] - elsif request.headers["X-Redmine-API-Key"].present? - request.headers["X-Redmine-API-Key"] - end - end - - # Renders a warning flash if obj has unsaved attachments - def render_attachment_warning_if_needed(obj) - flash[:warning] = l(:warning_attachments_not_saved, obj.unsaved_attachments.size) if obj.unsaved_attachments.present? - end - - # Sets the `flash` notice or error based the number of issues that did not save - # - # @param [Array, Issue] issues all of the saved and unsaved Issues - # @param [Array, Integer] unsaved_issue_ids the issue ids that were not saved - def set_flash_from_bulk_issue_save(issues, unsaved_issue_ids) - if unsaved_issue_ids.empty? - flash[:notice] = l(:notice_successful_update) unless issues.empty? - else - flash[:error] = l(:notice_failed_to_save_issues, - :count => unsaved_issue_ids.size, - :total => issues.size, - :ids => '#' + unsaved_issue_ids.join(', #')) - end - end - - # Rescues an invalid query statement. Just in case... - def query_statement_invalid(exception) - logger.error "Query::StatementInvalid: #{exception.message}" if logger - session.delete(:query) - sort_clear if respond_to?(:sort_clear) - render_error "An error occurred while executing the query and has been logged. Please report this error to your Redmine administrator." - end - - # Renders API response on validation failure - def render_validation_errors(object) - options = { :status => :unprocessable_entity, :layout => false } - options.merge!(case params[:format] - when 'xml'; { :xml => object.errors } - when 'json'; { :json => {'errors' => object.errors} } # ActiveResource client compliance - else - raise "Unknown format #{params[:format]} in #render_validation_errors" - end - ) - render options - end - - # Overrides #default_template so that the api template - # is used automatically if it exists - def default_template(action_name = self.action_name) - if api_request? - begin - return self.view_paths.find_template(default_template_name(action_name), 'api') - rescue ::ActionView::MissingTemplate - # the api template was not found - # fallback to the default behaviour - end - end - super - end - - # Overrides #pick_layout so that #render with no arguments - # doesn't use the layout for api requests - def pick_layout(*args) - api_request? ? nil : super - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/2f/2f2c80ebd6e33d1fd4e81ae48e2384cbacf46e18.svn-base --- a/.svn/pristine/2f/2f2c80ebd6e33d1fd4e81ae48e2384cbacf46e18.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class MessageObserver < ActiveRecord::Observer - def after_create(message) - Mailer.deliver_message_posted(message) if Setting.notified_events.include?('message_posted') - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/2f/2f3ef4ed7d4fcc37d2d5c06ce61c1093cb75fa73.svn-base --- a/.svn/pristine/2f/2f3ef4ed7d4fcc37d2d5c06ce61c1093cb75fa73.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,8 +0,0 @@ -class AlphaPluginController < ApplicationController - def an_action - render_class_and_action - end - def action_with_layout - render_class_and_action(nil, :layout => "plugin_layout") - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/2f/2ff7c4a24ae3be5225189f1a6fd5fd0c91104ce3.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/2f/2ff7c4a24ae3be5225189f1a6fd5fd0c91104ce3.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,34 @@ +
+<%= link_to l(:label_document_new), new_project_document_path(@project), :class => 'icon icon-add', + :onclick => 'showAndScrollTo("add-document", "document_title"); return false;' if User.current.allowed_to?(:add_documents, @project) %> +
+ + + +

<%=l(:label_document_plural)%>

+ +<% if @grouped.empty? %>

<%= l(:label_no_data) %>

<% end %> + +<% @grouped.keys.sort.each do |group| %> +

<%= group %>

+ <%= render :partial => 'documents/document', :collection => @grouped[group] %> +<% end %> + +<% content_for :sidebar do %> +

<%= l(:label_sort_by, '') %>

+ <%= link_to l(:field_category), {:sort_by => 'category'}, :class => (@sort_by == 'category' ? 'selected' :nil) %>
+ <%= link_to l(:label_date), {:sort_by => 'date'}, :class => (@sort_by == 'date' ? 'selected' :nil) %>
+ <%= link_to l(:field_title), {:sort_by => 'title'}, :class => (@sort_by == 'title' ? 'selected' :nil) %>
+ <%= link_to l(:field_author), {:sort_by => 'author'}, :class => (@sort_by == 'author' ? 'selected' :nil) %> +<% end %> + +<% html_title(l(:label_document_plural)) -%> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/30/301185c13f555c3833de6896f8f45895314d605b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/30/301185c13f555c3833de6896f8f45895314d605b.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,32 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class RoutingCommentsTest < ActionController::IntegrationTest + def test_comments + assert_routing( + { :method => 'post', :path => "/news/567/comments" }, + { :controller => 'comments', :action => 'create', :id => '567' } + ) + assert_routing( + { :method => 'delete', :path => "/news/567/comments/15" }, + { :controller => 'comments', :action => 'destroy', :id => '567', + :comment_id => '15' } + ) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/30/304b68e7e7a6cf907c51306f2e95b29f2a8946e9.svn-base --- a/.svn/pristine/30/304b68e7e7a6cf907c51306f2e95b29f2a8946e9.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,256 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) - -class RepositoryTest < ActiveSupport::TestCase - fixtures :projects, - :trackers, - :projects_trackers, - :enabled_modules, - :repositories, - :issues, - :issue_statuses, - :issue_categories, - :changesets, - :changes, - :users, - :members, - :member_roles, - :roles, - :enumerations - - def setup - @repository = Project.find(1).repository - end - - def test_create - repository = Repository::Subversion.new(:project => Project.find(3)) - assert !repository.save - - repository.url = "svn://localhost" - assert repository.save - repository.reload - - project = Project.find(3) - assert_equal repository, project.repository - end - - def test_destroy - changesets = Changeset.count(:all, :conditions => "repository_id = 10") - changes = Change.count(:all, :conditions => "repository_id = 10", - :include => :changeset) - assert_difference 'Changeset.count', -changesets do - assert_difference 'Change.count', -changes do - Repository.find(10).destroy - end - end - end - - def test_should_not_create_with_disabled_scm - # disable Subversion - with_settings :enabled_scm => ['Darcs', 'Git'] do - repository = Repository::Subversion.new( - :project => Project.find(3), :url => "svn://localhost") - assert !repository.save - assert_equal I18n.translate('activerecord.errors.messages.invalid'), - repository.errors.on(:type) - end - end - - def test_scan_changesets_for_issue_ids - Setting.default_language = 'en' - Setting.notified_events = ['issue_added','issue_updated'] - - # 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" - Setting.commit_ref_keywords = 'refs , references, IssueID' - Setting.commit_fix_keywords = 'fixes , closes' - Setting.default_language = 'en' - ActionMailer::Base.deliveries.clear - - # make sure issue 1 is not already closed - fixed_issue = Issue.find(1) - assert !fixed_issue.status.is_closed? - old_status = fixed_issue.status - - Repository.scan_changesets_for_issue_ids - assert_equal [101, 102], Issue.find(3).changeset_ids - - # fixed issues - fixed_issue.reload - assert fixed_issue.status.is_closed? - assert_equal 90, fixed_issue.done_ratio - assert_equal [101], fixed_issue.changeset_ids - - # issue change - journal = fixed_issue.journals.find(:first, :order => 'created_on desc') - assert_equal User.find_by_login('dlopper'), journal.user - assert_equal 'Applied in changeset r2.', journal.notes - - # 2 email notifications - assert_equal 2, ActionMailer::Base.deliveries.size - mail = ActionMailer::Base.deliveries.first - assert_kind_of TMail::Mail, mail - assert mail.subject.starts_with?( - "[#{fixed_issue.project.name} - #{fixed_issue.tracker.name} ##{fixed_issue.id}]") - assert mail.body.include?( - "Status changed from #{old_status} to #{fixed_issue.status}") - - # ignoring commits referencing an issue of another project - assert_equal [], Issue.find(4).changesets - end - - def test_for_changeset_comments_strip - repository = Repository::Mercurial.create( - :project => Project.find( 4 ), - :url => '/foo/bar/baz' ) - comment = <<-COMMENT - This is a loooooooooooooooooooooooooooong comment - - - COMMENT - changeset = Changeset.new( - :comments => comment, :commit_date => Time.now, - :revision => 0, :scmid => 'f39b7922fb3c', - :committer => 'foo ', - :committed_on => Time.now, :repository => repository ) - assert( changeset.save ) - assert_not_equal( comment, changeset.comments ) - assert_equal( 'This is a loooooooooooooooooooooooooooong comment', - changeset.comments ) - end - - def test_for_urls_strip_cvs - repository = Repository::Cvs.create( - :project => Project.find(4), - :url => ' :pserver:login:password@host:/path/to/the/repository', - :root_url => 'foo ', - :log_encoding => 'UTF-8') - assert repository.save - repository.reload - assert_equal ':pserver:login:password@host:/path/to/the/repository', - repository.url - assert_equal 'foo', repository.root_url - end - - def test_for_urls_strip_subversion - repository = Repository::Subversion.create( - :project => Project.find(4), - :url => ' file:///dummy ') - assert repository.save - repository.reload - assert_equal 'file:///dummy', repository.url - end - - def test_for_urls_strip_git - repository = Repository::Git.create( - :project => Project.find(4), - :url => ' c:\dummy ') - assert repository.save - repository.reload - assert_equal 'c:\dummy', repository.url - end - - 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 ', - :committed_on => Time.now, - :revision => 100, - :comments => 'Committed by john.' - ) - assert_equal User.find(2), c.user - end - - def test_filesystem_avaialbe - klass = Repository::Filesystem - assert klass.scm_adapter_class - assert_equal true, klass.scm_available - end - - def test_merge_extra_info - repo = Repository::Subversion.new(:project => Project.find(3)) - assert !repo.save - repo.url = "svn://localhost" - assert repo.save - repo.reload - project = Project.find(3) - assert_equal repo, project.repository - assert_nil repo.extra_info - h1 = {"test_1" => {"test_11" => "test_value_11"}} - repo.merge_extra_info(h1) - assert_equal h1, repo.extra_info - h2 = {"test_2" => { - "test_21" => "test_value_21", - "test_22" => "test_value_22", - }} - repo.merge_extra_info(h2) - assert_equal (h = {"test_11" => "test_value_11"}), - repo.extra_info["test_1"] - assert_equal "test_value_21", - repo.extra_info["test_2"]["test_21"] - h3 = {"test_2" => { - "test_23" => "test_value_23", - "test_24" => "test_value_24", - }} - repo.merge_extra_info(h3) - assert_equal (h = {"test_11" => "test_value_11"}), - repo.extra_info["test_1"] - assert_nil repo.extra_info["test_2"]["test_21"] - assert_equal "test_value_23", - repo.extra_info["test_2"]["test_23"] - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/30/30787e3d5bfe04d8411e5332a384d8a1a749f767.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/30/30787e3d5bfe04d8411e5332a384d8a1a749f767.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,38 @@ +

<%= l(:label_board_plural) %>

+ + + + + + + + + +<% Board.board_tree(@boards) do |board, level| %> + + + + + + +<% end %> + +
<%= l(:label_board) %><%= l(:label_topic_plural) %><%= l(:label_message_plural) %><%= l(:label_message_last) %>
+ <%= link_to h(board.name), project_board_path(board.project, board), :class => "board" %>
+ <%=h board.description %> +
<%= board.topics_count %><%= board.messages_count %> + <% if board.last_message %> + <%= authoring board.last_message.created_on, board.last_message.author %>
+ <%= link_to_message board.last_message %> + <% end %> +
+ +<% other_formats_links do |f| %> + <%= f.link_to 'Atom', :url => {:controller => 'activities', :action => 'index', :id => @project, :show_messages => 1, :key => User.current.rss_key} %> +<% end %> + +<% content_for :header_tags do %> + <%= auto_discovery_link_tag(:atom, {:controller => 'activities', :action => 'index', :id => @project, :format => 'atom', :show_messages => 1, :key => User.current.rss_key}) %> +<% end %> + +<% html_title l(:label_board_plural) %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/30/30881f171a667b3a44449e71bb74b80be13859b2.svn-base --- a/.svn/pristine/30/30881f171a667b3a44449e71bb74b80be13859b2.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,95 +0,0 @@ -module CodeRay -module Scanners - - # Scanner for JSON (JavaScript Object Notation). - class JSON < Scanner - - register_for :json - file_extension 'json' - - KINDS_NOT_LOC = [ - :float, :char, :content, :delimiter, - :error, :integer, :operator, :value, - ] # :nodoc: - - ESCAPE = / [bfnrt\\"\/] /x # :nodoc: - UNICODE_ESCAPE = / u[a-fA-F0-9]{4} /x # :nodoc: - - protected - - # See http://json.org/ for a definition of the JSON lexic/grammar. - def scan_tokens encoder, options - - state = :initial - stack = [] - key_expected = false - - until eos? - - case state - - when :initial - if match = scan(/ \s+ /x) - encoder.text_token match, :space - elsif match = scan(/"/) - state = key_expected ? :key : :string - encoder.begin_group state - encoder.text_token match, :delimiter - elsif match = scan(/ [:,\[{\]}] /x) - encoder.text_token match, :operator - case match - when ':' then key_expected = false - when ',' then key_expected = true if stack.last == :object - when '{' then stack << :object; key_expected = true - when '[' then stack << :array - when '}', ']' then stack.pop # no error recovery, but works for valid JSON - end - elsif match = scan(/ true | false | null /x) - encoder.text_token match, :value - elsif match = scan(/ -? (?: 0 | [1-9]\d* ) /x) - if scan(/ \.\d+ (?:[eE][-+]?\d+)? | [eE][-+]? \d+ /x) - match << matched - encoder.text_token match, :float - else - encoder.text_token match, :integer - end - else - encoder.text_token getch, :error - end - - when :string, :key - if match = scan(/[^\\"]+/) - encoder.text_token match, :content - elsif match = scan(/"/) - encoder.text_token match, :delimiter - encoder.end_group state - state = :initial - elsif match = scan(/ \\ (?: #{ESCAPE} | #{UNICODE_ESCAPE} ) /mox) - encoder.text_token match, :char - elsif match = scan(/\\./m) - encoder.text_token match, :content - elsif match = scan(/ \\ | $ /x) - encoder.end_group state - encoder.text_token match, :error - state = :initial - else - raise_inspect "else case \" reached; %p not handled." % peek(1), encoder - end - - else - raise_inspect 'Unknown state: %p' % [state], encoder - - end - end - - if [:string, :key].include? state - encoder.end_group state - end - - encoder - end - - end - -end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/30/308a50990fe7138ee80b066400a54a3b1b066faf.svn-base --- a/.svn/pristine/30/308a50990fe7138ee80b066400a54a3b1b066faf.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -<%= TestHelper.view_path_for __FILE__ %> \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/30/308f688f7ea9b63ad1f36c2cfc3b151507bfbaea.svn-base --- a/.svn/pristine/30/308f688f7ea9b63ad1f36c2cfc3b151507bfbaea.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,16 +0,0 @@ -api.array :time_entries, api_meta(:total_count => @entry_count, :offset => @offset, :limit => @limit) do - @entries.each do |time_entry| - api.time_entry do - api.id time_entry.id - api.project(:id => time_entry.project_id, :name => time_entry.project.name) unless time_entry.project.nil? - api.issue(:id => time_entry.issue_id) unless time_entry.issue.nil? - api.user(:id => time_entry.user_id, :name => time_entry.user.name) unless time_entry.user.nil? - api.activity(:id => time_entry.activity_id, :name => time_entry.activity.name) unless time_entry.activity.nil? - api.hours time_entry.hours - api.comments time_entry.comments - api.spent_on time_entry.spent_on - api.created_on time_entry.created_on - api.updated_on time_entry.updated_on - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/31/3111f66f2205d5a3793f8baff324b532853a4a60.svn-base --- a/.svn/pristine/31/3111f66f2205d5a3793f8baff324b532853a4a60.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1023 +0,0 @@ -# French translations for Ruby on Rails -# by Christian Lescuyer (christian@flyingcoders.com) -# contributor: Sebastien Grosjean - ZenCocoon.com -# contributor: Thibaut Cuvelier - Developpez.com - -fr: - direction: ltr - date: - formats: - default: "%d/%m/%Y" - short: "%e %b" - long: "%e %B %Y" - long_ordinal: "%e %B %Y" - only_day: "%e" - - day_names: [dimanche, lundi, mardi, mercredi, jeudi, vendredi, samedi] - abbr_day_names: [dim, lun, mar, mer, jeu, ven, sam] - month_names: [~, janvier, février, mars, avril, mai, juin, juillet, août, septembre, octobre, novembre, décembre] - abbr_month_names: [~, jan., fév., mar., avr., mai, juin, juil., août, sept., oct., nov., déc.] - order: - - :day - - :month - - :year - - time: - formats: - default: "%d/%m/%Y %H:%M" - time: "%H:%M" - short: "%d %b %H:%M" - long: "%A %d %B %Y %H:%M:%S %Z" - long_ordinal: "%A %d %B %Y %H:%M:%S %Z" - only_second: "%S" - am: 'am' - pm: 'pm' - - datetime: - distance_in_words: - half_a_minute: "30 secondes" - less_than_x_seconds: - zero: "moins d'une seconde" - one: "moins d'une seconde" - other: "moins de %{count} secondes" - x_seconds: - one: "1 seconde" - other: "%{count} secondes" - less_than_x_minutes: - zero: "moins d'une minute" - one: "moins d'une minute" - other: "moins de %{count} minutes" - x_minutes: - one: "1 minute" - other: "%{count} minutes" - about_x_hours: - one: "environ une heure" - other: "environ %{count} heures" - x_days: - one: "un jour" - other: "%{count} jours" - about_x_months: - one: "environ un mois" - other: "environ %{count} mois" - x_months: - one: "un mois" - other: "%{count} mois" - about_x_years: - one: "environ un an" - other: "environ %{count} ans" - over_x_years: - one: "plus d'un an" - other: "plus de %{count} ans" - almost_x_years: - one: "presqu'un an" - other: "presque %{count} ans" - prompts: - year: "Année" - month: "Mois" - day: "Jour" - hour: "Heure" - minute: "Minute" - second: "Seconde" - - number: - format: - precision: 3 - separator: ',' - delimiter: ' ' - currency: - format: - unit: '€' - precision: 2 - format: '%n %u' - human: - format: - precision: 2 - storage_units: - format: "%n %u" - units: - byte: - one: "octet" - other: "octet" - kb: "ko" - mb: "Mo" - gb: "Go" - tb: "To" - - support: - array: - sentence_connector: 'et' - skip_last_comma: true - word_connector: ", " - two_words_connector: " et " - last_word_connector: " et " - - activerecord: - errors: - template: - header: - one: "Impossible d'enregistrer %{model} : une erreur" - other: "Impossible d'enregistrer %{model} : %{count} erreurs." - body: "Veuillez vérifier les champs suivants :" - messages: - inclusion: "n'est pas inclus(e) dans la liste" - exclusion: "n'est pas disponible" - invalid: "n'est pas valide" - confirmation: "ne concorde pas avec la confirmation" - accepted: "doit être accepté(e)" - empty: "doit être renseigné(e)" - blank: "doit être renseigné(e)" - too_long: "est trop long (pas plus de %{count} caractères)" - too_short: "est trop court (au moins %{count} caractères)" - wrong_length: "ne fait pas la bonne longueur (doit comporter %{count} caractères)" - taken: "est déjà utilisé" - not_a_number: "n'est pas un nombre" - not_a_date: "n'est pas une date valide" - greater_than: "doit être supérieur à %{count}" - greater_than_or_equal_to: "doit être supérieur ou égal à %{count}" - equal_to: "doit être égal à %{count}" - less_than: "doit être inférieur à %{count}" - less_than_or_equal_to: "doit être inférieur ou égal à %{count}" - odd: "doit être impair" - even: "doit être pair" - greater_than_start_date: "doit être postérieure à la date de début" - not_same_project: "n'appartient pas au même projet" - circular_dependency: "Cette relation créerait une dépendance circulaire" - cant_link_an_issue_with_a_descendant: "Une demande ne peut pas être liée à l'une de ses sous-tâches" - - actionview_instancetag_blank_option: Choisir - - general_text_No: 'Non' - general_text_Yes: 'Oui' - general_text_no: 'non' - general_text_yes: 'oui' - general_lang_name: 'Français' - general_csv_separator: ';' - general_csv_decimal_separator: ',' - general_csv_encoding: ISO-8859-1 - general_pdf_encoding: UTF-8 - general_first_day_of_week: '1' - - notice_account_updated: Le compte a été mis à jour avec succès. - notice_account_invalid_creditentials: Identifiant ou mot de passe invalide. - notice_account_password_updated: Mot de passe mis à jour avec succès. - notice_account_wrong_password: Mot de passe incorrect - notice_account_register_done: Un message contenant les instructions pour activer votre compte vous a été envoyé. - notice_account_unknown_email: Aucun compte ne correspond à cette adresse. - notice_can_t_change_password: Ce compte utilise une authentification externe. Impossible de changer le mot de passe. - notice_account_lost_email_sent: Un message contenant les instructions pour choisir un nouveau mot de passe vous a été envoyé. - notice_account_activated: Votre compte a été activé. Vous pouvez à présent vous connecter. - notice_successful_create: Création effectuée avec succès. - notice_successful_update: Mise à jour effectuée avec succès. - notice_successful_delete: Suppression effectuée avec succès. - notice_successful_connection: Connexion réussie. - notice_file_not_found: "La page à laquelle vous souhaitez accéder n'existe pas ou a été supprimée." - notice_locking_conflict: Les données ont été mises à jour par un autre utilisateur. Mise à jour impossible. - notice_not_authorized: "Vous n'êtes pas autorisé à accéder à cette page." - notice_not_authorized_archived_project: Le projet auquel vous tentez d'accéder a été archivé. - notice_email_sent: "Un email a été envoyé à %{value}" - notice_email_error: "Erreur lors de l'envoi de l'email (%{value})" - notice_feeds_access_key_reseted: "Votre clé d'accès aux flux RSS a été réinitialisée." - notice_failed_to_save_issues: "%{count} demande(s) sur les %{total} sélectionnées n'ont pas pu être mise(s) à jour : %{ids}." - 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. - notice_issue_done_ratios_updated: L'avancement des demandes a été mis à jour. - notice_api_access_key_reseted: Votre clé d'accès API a été réinitialisée. - notice_gantt_chart_truncated: "Le diagramme a été tronqué car il excède le nombre maximal d'éléments pouvant être affichés (%{max})" - notice_issue_successful_create: "La demande %{id} a été créée." - - error_can_t_load_default_data: "Une erreur s'est produite lors du chargement du paramétrage : %{value}" - error_scm_not_found: "L'entrée et/ou la révision demandée n'existe pas dans le dépôt." - error_scm_command_failed: "Une erreur s'est produite lors de l'accès au dépôt : %{value}" - 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" - error_can_not_reopen_issue_on_closed_version: 'Une demande assignée à une version fermée ne peut pas être réouverte' - error_can_not_archive_project: "Ce projet ne peut pas être archivé" - error_workflow_copy_source: 'Veuillez sélectionner un tracker et/ou un rôle source' - error_workflow_copy_target: 'Veuillez sélectionner les trackers et rôles cibles' - error_issue_done_ratios_not_updated: L'avancement des demandes n'a pas pu être mis à jour. - error_attachment_too_big: Ce fichier ne peut pas être attaché car il excède la taille maximale autorisée (%{max_size}) - - warning_attachments_not_saved: "%{count} fichier(s) n'ont pas pu être sauvegardés." - - mail_subject_lost_password: "Votre mot de passe %{value}" - mail_body_lost_password: 'Pour changer votre mot de passe, cliquez sur le lien suivant :' - mail_subject_register: "Activation de votre compte %{value}" - mail_body_register: 'Pour activer votre compte, cliquez sur le lien suivant :' - mail_body_account_information_external: "Vous pouvez utiliser votre compte %{value} pour vous connecter." - mail_body_account_information: Paramètres de connexion de votre compte - mail_subject_account_activation_request: "Demande d'activation d'un compte %{value}" - mail_body_account_activation_request: "Un nouvel utilisateur (%{value}) s'est inscrit. Son compte nécessite votre approbation :" - mail_subject_reminder: "%{count} demande(s) arrivent à échéance (%{days})" - mail_body_reminder: "%{count} demande(s) qui vous sont assignées arrivent à échéance dans les %{days} prochains jours :" - mail_subject_wiki_content_added: "Page wiki '%{id}' ajoutée" - mail_body_wiki_content_added: "La page wiki '%{id}' a été ajoutée par %{author}." - mail_subject_wiki_content_updated: "Page wiki '%{id}' mise à jour" - mail_body_wiki_content_updated: "La page wiki '%{id}' a été mise à jour par %{author}." - - gui_validation_error: 1 erreur - gui_validation_error_plural: "%{count} erreurs" - - field_name: Nom - field_description: Description - field_summary: Résumé - field_is_required: Obligatoire - field_firstname: Prénom - field_lastname: Nom - field_mail: "Email " - field_filename: Fichier - field_filesize: Taille - field_downloads: Téléchargements - field_author: Auteur - field_created_on: "Créé " - field_updated_on: "Mis-à-jour " - field_field_format: Format - field_is_for_all: Pour tous les projets - field_possible_values: Valeurs possibles - field_regexp: Expression régulière - field_min_length: Longueur minimum - field_max_length: Longueur maximum - field_value: Valeur - field_category: Catégorie - field_title: Titre - field_project: Projet - field_issue: Demande - field_status: Statut - field_notes: Notes - field_is_closed: Demande fermée - field_is_default: Valeur par défaut - field_tracker: Tracker - field_subject: Sujet - field_due_date: Echéance - field_assigned_to: Assigné à - field_priority: Priorité - field_fixed_version: Version cible - field_user: Utilisateur - field_role: Rôle - field_homepage: "Site web " - field_is_public: Public - field_parent: Sous-projet de - field_is_in_roadmap: Demandes affichées dans la roadmap - field_login: "Identifiant " - field_mail_notification: Notifications par mail - field_admin: Administrateur - field_last_login_on: "Dernière connexion " - field_language: Langue - field_effective_date: Date - field_password: Mot de passe - field_new_password: Nouveau mot de passe - field_password_confirmation: Confirmation - field_version: Version - field_type: Type - field_host: Hôte - field_port: Port - field_account: Compte - field_base_dn: Base DN - field_attr_login: Attribut Identifiant - field_attr_firstname: Attribut Prénom - field_attr_lastname: Attribut Nom - field_attr_mail: Attribut Email - field_onthefly: Création des utilisateurs à la volée - field_start_date: Début - field_done_ratio: "% réalisé" - field_auth_source: Mode d'authentification - field_hide_mail: Cacher mon adresse mail - field_comments: Commentaire - field_url: URL - field_start_page: Page de démarrage - field_subproject: Sous-projet - field_hours: Heures - field_activity: Activité - field_spent_on: Date - field_identifier: Identifiant - field_is_filter: Utilisé comme filtre - field_issue_to: Demande liée - field_delay: Retard - field_assignable: Demandes assignables à ce rôle - field_redirect_existing_links: Rediriger les liens existants - field_estimated_hours: Temps estimé - field_column_names: Colonnes - field_time_zone: Fuseau horaire - field_searchable: Utilisé pour les recherches - field_default_value: Valeur par défaut - field_comments_sorting: Afficher les commentaires - field_parent_title: Page parent - field_editable: Modifiable - field_watcher: Observateur - field_identity_url: URL OpenID - field_content: Contenu - field_group_by: Grouper par - field_sharing: Partage - field_active: Actif - field_parent_issue: Tâche parente - field_visible: Visible - field_warn_on_leaving_unsaved: "M'avertir lorsque je quitte une page contenant du texte non sauvegardé" - field_issues_visibility: Visibilité des demandes - field_is_private: Privée - field_commit_logs_encoding: Encodage des messages de commit - - setting_app_title: Titre de l'application - setting_app_subtitle: Sous-titre de l'application - setting_welcome_text: Texte d'accueil - setting_default_language: Langue par défaut - setting_login_required: Authentification obligatoire - setting_self_registration: Inscription des nouveaux utilisateurs - setting_attachment_max_size: Taille maximale des fichiers - setting_issues_export_limit: Limite d'exportation des demandes - setting_mail_from: Adresse d'émission - setting_bcc_recipients: Destinataires en copie cachée (cci) - setting_plain_text_mail: Mail en texte brut (non HTML) - setting_host_name: Nom d'hôte et chemin - setting_text_formatting: Formatage du texte - setting_wiki_compression: Compression de l'historique des pages wiki - setting_feeds_limit: Nombre maximal d'éléments dans les flux Atom - setting_default_projects_public: Définir les nouveaux projets comme publics par défaut - setting_autofetch_changesets: Récupération automatique des commits - setting_sys_api_enabled: Activer les WS pour la gestion des dépôts - setting_commit_ref_keywords: Mots-clés de référencement - setting_commit_fix_keywords: Mots-clés de résolution - setting_autologin: Durée maximale de connexion automatique - setting_date_format: Format de date - setting_time_format: Format d'heure - setting_cross_project_issue_relations: Autoriser les relations entre demandes de différents projets - setting_issue_list_default_columns: Colonnes affichées par défaut sur la liste des demandes - setting_emails_footer: Pied-de-page des emails - setting_protocol: Protocole - setting_per_page_options: Options d'objets affichés par page - setting_user_format: Format d'affichage des utilisateurs - setting_activity_days_default: Nombre de jours affichés sur l'activité des projets - setting_display_subprojects_issues: Afficher par défaut les demandes des sous-projets sur les projets principaux - setting_enabled_scm: SCM activés - setting_mail_handler_body_delimiters: "Tronquer les emails après l'une de ces lignes" - 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_gravatar_enabled: Afficher les Gravatar des utilisateurs - setting_diff_max_lines_displayed: Nombre maximum de lignes de diff affichées - setting_file_max_size_displayed: Taille maximum des fichiers texte affichés en ligne - setting_repository_log_display_limit: "Nombre maximum de révisions affichées sur l'historique d'un fichier" - setting_openid: "Autoriser l'authentification et l'enregistrement OpenID" - setting_password_min_length: Longueur minimum des mots de passe - setting_new_project_user_role_id: Rôle donné à un utilisateur non-administrateur qui crée un projet - setting_default_projects_modules: Modules activés par défaut pour les nouveaux projets - setting_issue_done_ratio: Calcul de l'avancement des demandes - setting_issue_done_ratio_issue_status: Utiliser le statut - setting_issue_done_ratio_issue_field: 'Utiliser le champ % effectué' - setting_rest_api_enabled: Activer l'API REST - setting_gravatar_default: Image Gravatar par défaut - setting_start_of_week: Jour de début des calendriers - setting_cache_formatted_text: Mettre en cache le texte formaté - setting_commit_logtime_enabled: Permettre la saisie de temps - setting_commit_logtime_activity_id: Activité pour le temps saisi - setting_gantt_items_limit: Nombre maximum d'éléments affichés sur le gantt - setting_issue_group_assignment: Permettre l'assignement des demandes aux groupes - setting_default_issue_start_date_to_creation_date: Donner à la date de début d'une nouvelle demande la valeur de la date du jour - - permission_add_project: Créer un projet - permission_add_subprojects: Créer des sous-projets - permission_edit_project: Modifier le projet - permission_select_project_modules: Choisir les modules - permission_manage_members: Gérer les membres - permission_manage_versions: Gérer les versions - permission_manage_categories: Gérer les catégories de demandes - permission_view_issues: Voir les demandes - permission_add_issues: Créer des demandes - permission_edit_issues: Modifier les demandes - permission_manage_issue_relations: Gérer les relations - permission_set_issues_private: Rendre les demandes publiques ou privées - permission_set_own_issues_private: Rendre ses propres demandes publiques ou privées - 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_delete_issue_watchers: Supprimer 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 - permission_export_wiki_pages: Exporter les pages - permission_manage_project_activities: Gérer les activités - permission_manage_subtasks: Gérer les sous-tâches - - project_module_issue_tracking: Suivi des demandes - project_module_time_tracking: Suivi du temps passé - project_module_news: Publication d'annonces - project_module_documents: Publication de documents - project_module_files: Publication de fichiers - project_module_wiki: Wiki - project_module_repository: Dépôt de sources - project_module_boards: Forums de discussion - - label_user: Utilisateur - label_user_plural: Utilisateurs - label_user_new: Nouvel utilisateur - label_user_anonymous: Anonyme - label_project: Projet - label_project_new: Nouveau projet - label_project_plural: Projets - label_x_projects: - zero: aucun projet - one: un projet - other: "%{count} projets" - label_project_all: Tous les projets - label_project_latest: Derniers projets - label_issue: Demande - label_issue_new: Nouvelle demande - label_issue_plural: Demandes - label_issue_view_all: Voir toutes les demandes - label_issue_added: Demande ajoutée - label_issue_updated: Demande mise à jour - label_issue_note_added: Note ajoutée - label_issue_status_updated: Statut changé - label_issue_priority_updated: Priorité changée - label_issues_by: "Demandes par %{value}" - label_document: Document - label_document_new: Nouveau document - label_document_plural: Documents - label_document_added: Document ajouté - label_role: Rôle - label_role_plural: Rôles - label_role_new: Nouveau rôle - label_role_and_permissions: Rôles et permissions - label_role_anonymous: Anonyme - label_role_non_member: Non membre - label_member: Membre - label_member_new: Nouveau membre - label_member_plural: Membres - label_tracker: Tracker - label_tracker_plural: Trackers - label_tracker_new: Nouveau tracker - label_workflow: Workflow - label_issue_status: Statut de demandes - label_issue_status_plural: Statuts de demandes - label_issue_status_new: Nouveau statut - label_issue_category: Catégorie de demandes - label_issue_category_plural: Catégories de demandes - label_issue_category_new: Nouvelle catégorie - label_custom_field: Champ personnalisé - label_custom_field_plural: Champs personnalisés - label_custom_field_new: Nouveau champ personnalisé - label_enumerations: Listes de valeurs - label_enumeration_new: Nouvelle valeur - label_information: Information - label_information_plural: Informations - label_please_login: Identification - label_register: S'enregistrer - label_login_with_open_id_option: S'authentifier avec OpenID - label_password_lost: Mot de passe perdu - label_home: Accueil - label_my_page: Ma page - label_my_account: Mon compte - label_my_projects: Mes projets - label_my_page_block: Blocs disponibles - label_administration: Administration - label_login: Connexion - label_logout: Déconnexion - label_help: Aide - label_reported_issues: "Demandes soumises " - label_assigned_to_me_issues: Demandes qui me sont assignées - label_last_login: "Dernière connexion " - label_registered_on: "Inscrit le " - label_activity: Activité - label_overall_activity: Activité globale - label_user_activity: "Activité de %{value}" - label_new: Nouveau - label_logged_as: Connecté en tant que - label_environment: Environnement - label_authentication: Authentification - label_auth_source: Mode d'authentification - label_auth_source_new: Nouveau mode d'authentification - label_auth_source_plural: Modes d'authentification - label_subproject_plural: Sous-projets - label_subproject_new: Nouveau sous-projet - label_and_its_subprojects: "%{value} et ses sous-projets" - label_min_max_length: Longueurs mini - maxi - label_list: Liste - label_date: Date - label_integer: Entier - label_float: Nombre décimal - label_boolean: Booléen - label_string: Texte - label_text: Texte long - label_attribute: Attribut - label_attribute_plural: Attributs - label_download: "%{count} téléchargement" - label_download_plural: "%{count} téléchargements" - label_no_data: Aucune donnée à afficher - label_change_status: Changer le statut - label_history: Historique - label_attachment: Fichier - label_attachment_new: Nouveau fichier - label_attachment_delete: Supprimer le fichier - label_attachment_plural: Fichiers - label_file_added: Fichier ajouté - label_report: Rapport - label_report_plural: Rapports - label_news: Annonce - label_news_new: Nouvelle annonce - label_news_plural: Annonces - label_news_latest: Dernières annonces - label_news_view_all: Voir toutes les annonces - label_news_added: Annonce ajoutée - label_news_comment_added: Commentaire ajouté à une annonce - label_settings: Configuration - label_overview: Aperçu - label_version: Version - label_version_new: Nouvelle version - label_version_plural: Versions - label_confirmation: Confirmation - label_export_to: 'Formats disponibles :' - label_read: Lire... - label_public_projects: Projets publics - label_open_issues: ouvert - label_open_issues_plural: ouverts - label_closed_issues: fermé - label_closed_issues_plural: fermés - label_x_open_issues_abbr_on_total: - zero: 0 ouvert sur %{total} - one: 1 ouvert sur %{total} - other: "%{count} ouverts sur %{total}" - label_x_open_issues_abbr: - zero: 0 ouvert - one: 1 ouvert - other: "%{count} ouverts" - label_x_closed_issues_abbr: - zero: 0 fermé - one: 1 fermé - other: "%{count} fermés" - label_total: Total - label_permissions: Permissions - label_current_status: Statut actuel - label_new_statuses_allowed: Nouveaux statuts autorisés - label_all: tous - label_none: aucun - label_nobody: personne - label_next: Suivant - label_previous: Précédent - label_used_by: Utilisé par - label_details: Détails - label_add_note: Ajouter une note - label_per_page: Par page - label_calendar: Calendrier - label_months_from: mois depuis - label_gantt: Gantt - label_internal: Interne - label_last_changes: "%{count} derniers changements" - label_change_view_all: Voir tous les changements - label_personalize_page: Personnaliser cette page - label_comment: Commentaire - label_comment_plural: Commentaires - label_x_comments: - zero: aucun commentaire - one: un commentaire - other: "%{count} commentaires" - label_comment_add: Ajouter un commentaire - label_comment_added: Commentaire ajouté - label_comment_delete: Supprimer les commentaires - label_query: Rapport personnalisé - label_query_plural: Rapports personnalisés - label_query_new: Nouveau rapport - label_my_queries: Mes rapports personnalisés - label_filter_add: "Ajouter le filtre " - label_filter_plural: Filtres - label_equals: égal - label_not_equals: différent - label_in_less_than: dans moins de - label_in_more_than: dans plus de - label_in: dans - label_today: aujourd'hui - label_all_time: toute la période - label_yesterday: hier - label_this_week: cette semaine - label_last_week: la semaine dernière - label_last_n_days: "les %{count} derniers jours" - label_this_month: ce mois-ci - label_last_month: le mois dernier - label_this_year: cette année - label_date_range: Période - label_less_than_ago: il y a moins de - label_more_than_ago: il y a plus de - label_ago: il y a - label_contains: contient - label_not_contains: ne contient pas - label_day_plural: jours - label_repository: Dépôt - label_repository_plural: Dépôts - label_browse: Parcourir - label_modification: "%{count} modification" - label_modification_plural: "%{count} modifications" - label_revision: "Révision " - label_revision_plural: Révisions - label_associated_revisions: Révisions associées - label_added: ajouté - label_modified: modifié - label_copied: copié - label_renamed: renommé - label_deleted: supprimé - label_latest_revision: Dernière révision - label_latest_revision_plural: Dernières révisions - label_view_revisions: Voir les révisions - label_max_size: Taille maximale - label_sort_highest: Remonter en premier - label_sort_higher: Remonter - label_sort_lower: Descendre - label_sort_lowest: Descendre en dernier - label_roadmap: Roadmap - label_roadmap_due_in: "Échéance dans %{value}" - label_roadmap_overdue: "En retard de %{value}" - label_roadmap_no_issues: Aucune demande pour cette version - label_search: "Recherche " - label_result_plural: Résultats - label_all_words: Tous les mots - label_wiki: Wiki - label_wiki_edit: Révision wiki - label_wiki_edit_plural: Révisions wiki - label_wiki_page: Page wiki - label_wiki_page_plural: Pages wiki - label_index_by_title: Index par titre - label_index_by_date: Index par date - label_current_version: Version actuelle - label_preview: Prévisualisation - label_feed_plural: Flux RSS - label_changes_details: Détails de tous les changements - label_issue_tracking: Suivi des demandes - label_spent_time: Temps passé - label_f_hour: "%{value} heure" - label_f_hour_plural: "%{value} heures" - label_time_tracking: Suivi du temps - label_change_plural: Changements - label_statistics: Statistiques - label_commits_per_month: Commits par mois - label_commits_per_author: Commits par auteur - label_view_diff: Voir les différences - label_diff_inline: en ligne - label_diff_side_by_side: côte à côte - label_options: Options - label_copy_workflow_from: Copier le workflow de - label_permissions_report: Synthèse des permissions - label_watched_issues: Demandes surveillées - label_related_issues: Demandes liées - label_applied_status: Statut appliqué - label_loading: Chargement... - label_relation_new: Nouvelle relation - label_relation_delete: Supprimer la relation - label_relates_to: lié à - label_duplicates: duplique - label_duplicated_by: dupliqué par - label_blocks: bloque - label_blocked_by: bloqué par - label_precedes: précède - label_follows: suit - label_end_to_start: fin à début - label_end_to_end: fin à fin - label_start_to_start: début à début - label_start_to_end: début à fin - label_stay_logged_in: Rester connecté - label_disabled: désactivé - label_show_completed_versions: Voir les versions passées - label_me: moi - label_board: Forum - label_board_new: Nouveau forum - label_board_plural: Forums - label_topic_plural: Discussions - label_message_plural: Messages - label_message_last: Dernier message - label_message_new: Nouveau message - label_message_posted: Message ajouté - label_reply_plural: Réponses - label_send_information: Envoyer les informations à l'utilisateur - label_year: Année - label_month: Mois - label_week: Semaine - label_date_from: Du - label_date_to: Au - label_language_based: Basé sur la langue de l'utilisateur - label_sort_by: "Trier par %{value}" - 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 %{value}" - label_module_plural: Modules - label_added_time_by: "Ajouté par %{author} il y a %{age}" - label_updated_time_by: "Mis à jour par %{author} il y a %{age}" - label_updated_time: "Mis à jour il y a %{value}" - label_jump_to_a_project: Aller à un projet... - label_file_plural: Fichiers - label_changeset_plural: Révisions - label_default_columns: Colonnes par défaut - label_no_change_option: (Pas de changement) - label_bulk_edit_selected_issues: Modifier les demandes sélectionnées - label_theme: Thème - label_default: Défaut - label_search_titles_only: Uniquement dans les titres - label_user_mail_option_all: "Pour tous les événements de tous mes projets" - label_user_mail_option_selected: "Pour tous les événements des projets sélectionnés..." - label_user_mail_no_self_notified: "Je ne veux pas être notifié des changements que j'effectue" - label_registration_activation_by_email: activation du compte par email - label_registration_manual_activation: activation manuelle du compte - label_registration_automatic_activation: activation automatique du compte - label_display_per_page: "Par page : %{value}" - label_age: Âge - label_change_properties: Changer les propriétés - label_general: Général - label_more: Plus - label_scm: SCM - label_plugins: Plugins - label_ldap_authentication: Authentification LDAP - label_downloads_abbr: D/L - label_optional_description: Description facultative - label_add_another_file: Ajouter un autre fichier - label_preferences: Préférences - label_chronological_order: Dans l'ordre chronologique - label_reverse_chronological_order: Dans l'ordre chronologique inverse - label_planning: Planning - label_incoming_emails: Emails entrants - label_generate_key: Générer une clé - label_issue_watchers: Observateurs - label_example: Exemple - label_display: Affichage - label_sort: Tri - label_ascending: Croissant - label_descending: Décroissant - label_date_from_to: Du %{start} au %{end} - label_wiki_content_added: Page wiki ajoutée - label_wiki_content_updated: Page wiki mise à jour - label_group_plural: Groupes - label_group: Groupe - label_group_new: Nouveau groupe - label_time_entry_plural: Temps passé - label_version_sharing_none: Non partagé - label_version_sharing_descendants: Avec les sous-projets - label_version_sharing_hierarchy: Avec toute la hiérarchie - label_version_sharing_tree: Avec tout l'arbre - label_version_sharing_system: Avec tous les projets - label_copy_source: Source - label_copy_target: Cible - label_copy_same_as_target: Comme la cible - label_update_issue_done_ratios: Mettre à jour l'avancement des demandes - label_display_used_statuses_only: N'afficher que les statuts utilisés dans ce tracker - label_api_access_key: Clé d'accès API - label_api_access_key_created_on: Clé d'accès API créée il y a %{value} - label_feeds_access_key: Clé d'accès RSS - label_missing_api_access_key: Clé d'accès API manquante - label_missing_feeds_access_key: Clé d'accès RSS manquante - label_close_versions: Fermer les versions terminées - label_revision_id: Revision %{value} - label_profile: Profil - label_subtask_plural: Sous-tâches - label_project_copy_notifications: Envoyer les notifications durant la copie du projet - label_principal_search: "Rechercher un utilisateur ou un groupe :" - label_user_search: "Rechercher un utilisateur :" - label_additional_workflow_transitions_for_author: Autorisations supplémentaires lorsque l'utilisateur a créé la demande - label_additional_workflow_transitions_for_assignee: Autorisations supplémentaires lorsque la demande est assignée à l'utilisateur - label_issues_visibility_all: Toutes les demandes - label_issues_visibility_public: Toutes les demandes non privées - label_issues_visibility_own: Demandes créées par ou assignées à l'utilisateur - label_export_options: Options d'exportation %{export_format} - - button_login: Connexion - button_submit: Soumettre - button_save: Sauvegarder - button_check_all: Tout cocher - button_uncheck_all: Tout décocher - button_collapse_all: Plier tout - button_expand_all: Déplier tout - button_delete: Supprimer - button_create: Créer - button_create_and_continue: Créer et continuer - button_test: Tester - button_edit: Modifier - button_add: Ajouter - button_change: Changer - button_apply: Appliquer - button_clear: Effacer - button_lock: Verrouiller - button_unlock: Déverrouiller - button_download: Télécharger - button_list: Lister - button_view: Voir - button_move: Déplacer - button_move_and_follow: Déplacer et suivre - button_back: Retour - button_cancel: Annuler - button_activate: Activer - button_sort: Trier - button_log_time: Saisir temps - button_rollback: Revenir à cette version - button_watch: Surveiller - button_unwatch: Ne plus surveiller - button_reply: Répondre - button_archive: Archiver - button_unarchive: Désarchiver - button_reset: Réinitialiser - button_rename: Renommer - button_change_password: Changer de mot de passe - button_copy: Copier - button_copy_and_follow: Copier et suivre - button_annotate: Annoter - button_update: Mettre à jour - button_configure: Configurer - button_quote: Citer - button_duplicate: Dupliquer - button_show: Afficher - button_edit_section: Modifier cette section - button_export: Exporter - - status_active: actif - status_registered: enregistré - status_locked: verrouillé - - version_status_open: ouvert - version_status_locked: verrouillé - version_status_closed: fermé - - text_select_mail_notifications: Actions pour lesquelles une notification par e-mail est envoyée - text_regexp_info: ex. ^[A-Z0-9]+$ - text_min_max_length_info: 0 pour aucune restriction - text_project_destroy_confirmation: Êtes-vous sûr de vouloir supprimer ce projet et toutes ses données ? - text_subprojects_destroy_warning: "Ses sous-projets : %{value} seront également supprimés." - text_workflow_edit: Sélectionner un tracker et un rôle pour éditer le workflow - text_are_you_sure: Êtes-vous sûr ? - text_tip_issue_begin_day: tâche commençant ce jour - text_tip_issue_end_day: tâche finissant ce jour - text_tip_issue_begin_end_day: tâche commençant et finissant ce jour - text_project_identifier_info: 'Seuls les lettres minuscules (a-z), chiffres et tirets sont autorisés.
Un fois sauvegardé, l''identifiant ne pourra plus être modifié.' - text_caracters_maximum: "%{count} caractères maximum." - text_caracters_minimum: "%{count} caractères minimum." - text_length_between: "Longueur comprise entre %{min} et %{max} caractères." - text_tracker_no_workflow: Aucun worflow n'est défini pour ce tracker - text_unallowed_characters: Caractères non autorisés - text_comma_separated: Plusieurs valeurs possibles (séparées par des virgules). - text_line_separated: Plusieurs valeurs possibles (une valeur par ligne). - text_issues_ref_in_commit_messages: Référencement et résolution des demandes dans les commentaires de commits - text_issue_added: "La demande %{id} a été soumise par %{author}." - text_issue_updated: "La demande %{id} a été mise à jour par %{author}." - text_wiki_destroy_confirmation: Etes-vous sûr de vouloir supprimer ce wiki et tout son contenu ? - text_issue_category_destroy_question: "%{count} demandes sont affectées à cette catégorie. Que voulez-vous faire ?" - text_issue_category_destroy_assignments: N'affecter les demandes à aucune autre catégorie - text_issue_category_reassign_to: Réaffecter les demandes à cette catégorie - text_user_mail_option: "Pour les projets non sélectionnés, vous recevrez seulement des notifications pour ce que vous surveillez ou à quoi vous participez (exemple: demandes dont vous êtes l'auteur ou la personne assignée)." - text_no_configuration_data: "Les rôles, trackers, statuts et le workflow ne sont pas encore paramétrés.\nIl est vivement recommandé de charger le paramétrage par defaut. Vous pourrez le modifier une fois chargé." - text_load_default_configuration: Charger le paramétrage par défaut - text_status_changed_by_changeset: "Appliqué par commit %{value}." - text_time_logged_by_changeset: "Appliqué par commit %{value}" - text_issues_destroy_confirmation: 'Êtes-vous sûr de vouloir supprimer la ou les demandes(s) selectionnée(s) ?' - text_issues_destroy_descendants_confirmation: "Cela entrainera également la suppression de %{count} sous-tâche(s)." - text_select_project_modules: 'Sélectionner les modules à activer pour ce projet :' - 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: "%{hours} heures ont été enregistrées sur les demandes à supprimer. Que voulez-vous faire ?" - text_destroy_time_entries: Supprimer les heures - text_assign_time_entries_to_project: Reporter les heures sur le projet - text_reassign_time_entries: 'Reporter les heures sur cette demande:' - text_user_wrote: "%{value} a écrit :" - text_enumeration_destroy_question: "Cette valeur est affectée à %{count} 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/configuration.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' - text_wiki_page_destroy_question: "Cette page possède %{descendants} sous-page(s) et descendante(s). Que voulez-vous faire ?" - text_wiki_page_nullify_children: "Conserver les sous-pages en tant que pages racines" - text_wiki_page_destroy_children: "Supprimer les sous-pages et toutes leurs descedantes" - text_wiki_page_reassign_children: "Réaffecter les sous-pages à cette page" - text_own_membership_delete_confirmation: "Vous allez supprimer tout ou partie de vos permissions sur ce projet et ne serez peut-être plus autorisé à modifier ce projet.\nEtes-vous sûr de vouloir continuer ?" - text_warn_on_leaving_unsaved: "Cette page contient du texte non sauvegardé qui sera perdu si vous quittez la page." - - default_role_manager: "Manager " - default_role_developer: "Développeur " - default_role_reporter: "Rapporteur " - default_tracker_bug: Anomalie - default_tracker_feature: Evolution - default_tracker_support: Assistance - default_issue_status_new: Nouveau - default_issue_status_in_progress: En cours - default_issue_status_resolved: Résolu - default_issue_status_feedback: Commentaire - default_issue_status_closed: Fermé - default_issue_status_rejected: Rejeté - default_doc_category_user: Documentation utilisateur - default_doc_category_tech: Documentation technique - default_priority_low: Bas - default_priority_normal: Normal - default_priority_high: Haut - default_priority_urgent: Urgent - default_priority_immediate: Immédiat - default_activity_design: Conception - 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) - label_greater_or_equal: ">=" - label_less_or_equal: "<=" - label_between: entre - label_view_all_revisions: Voir toutes les révisions - label_tag: Tag - label_branch: Branche - error_no_tracker_in_project: "Aucun tracker n'est associé à ce projet. Vérifier la configuration du projet." - error_no_default_issue_status: "Aucun statut de demande n'est défini par défaut. Vérifier votre configuration (Administration -> Statuts de demandes)." - text_journal_changed: "%{label} changé de %{old} à %{new}" - text_journal_changed_no_detail: "%{label} mis à jour" - text_journal_set_to: "%{label} mis à %{value}" - text_journal_deleted: "%{label} %{old} supprimé" - text_journal_added: "%{label} %{value} ajouté" - enumeration_system_activity: Activité système - label_board_sticky: Sticky - label_board_locked: Verrouillé - error_unable_delete_issue_status: Impossible de supprimer le statut de demande - error_can_not_delete_custom_field: Impossible de supprimer le champ personnalisé - error_unable_to_connect: Connexion impossible (%{value}) - error_can_not_remove_role: Ce rôle est utilisé et ne peut pas être supprimé. - error_can_not_delete_tracker: Ce tracker contient des demandes et ne peut pas être supprimé. - field_principal: Principal - notice_failed_to_save_members: "Erreur lors de la sauvegarde des membres: %{errors}." - text_zoom_out: Zoom arrière - text_zoom_in: Zoom avant - notice_unable_delete_time_entry: Impossible de supprimer le temps passé. - label_overall_spent_time: Temps passé global - field_time_entries: Temps passé - project_module_gantt: Gantt - project_module_calendar: Calendrier - button_edit_associated_wikipage: "Modifier la page wiki associée: %{page_title}" - text_are_you_sure_with_children: Supprimer la demande et toutes ses sous-demandes ? - field_text: Champ texte - label_user_mail_option_only_owner: Seulement pour ce que j'ai créé - setting_default_notification_option: Option de notification par défaut - label_user_mail_option_only_my_events: Seulement pour ce que je surveille - label_user_mail_option_only_assigned: Seulement pour ce qui m'est assigné - label_user_mail_option_none: Aucune notification - field_member_of_group: Groupe de l'assigné - field_assigned_to_role: Rôle de l'assigné - setting_emails_header: En-tête des emails - label_bulk_edit_selected_time_entries: Modifier les temps passés sélectionnées - text_time_entries_destroy_confirmation: "Etes-vous sûr de vouloir supprimer les temps passés sélectionnés ?" - field_scm_path_encoding: Encodage des chemins - text_scm_path_encoding_note: "Défaut : UTF-8" - field_path_to_repository: Chemin du dépôt - field_root_directory: Répertoire racine - field_cvs_module: Module - field_cvsroot: CVSROOT - text_mercurial_repository_note: "Dépôt local (exemples : /hgrepo, c:\\hgrepo)" - text_scm_command: Commande - text_scm_command_version: Version - label_git_report_last_commit: Afficher le dernier commit des fichiers et répertoires - text_scm_config: Vous pouvez configurer les commandes des SCM dans config/configuration.yml. Redémarrer l'application après modification. - text_scm_command_not_available: Ce SCM n'est pas disponible. Vérifier les paramètres dans la section administration. - label_diff: diff - text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) - description_query_sort_criteria_direction: Ordre de tri - description_project_scope: Périmètre de recherche - description_filter: Filtre - description_user_mail_notification: Option de notification - description_date_from: Date de début - description_message_content: Contenu du message - description_available_columns: Colonnes disponibles - description_all_columns: Toutes les colonnes - description_date_range_interval: Choisir une période - description_issue_category_reassign: Choisir une catégorie - description_search: Champ de recherche - description_notes: Notes - description_date_range_list: Choisir une période prédéfinie - description_choose_project: Projets - description_date_to: Date de fin - description_query_sort_criteria_attribute: Critère de tri - description_wiki_subpages_reassign: Choisir une nouvelle page parent - description_selected_columns: Colonnes sélectionnées - label_parent_revision: Parent - label_child_revision: Enfant - error_scm_annotate_big_text_file: Cette entrée ne peut pas être annotée car elle excède la taille maximale. - setting_repositories_encodings: Encodages des fichiers et des dépôts diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/31/31192143eb1c52ad53b2edf56c4ab09d186df056.svn-base --- a/.svn/pristine/31/31192143eb1c52ad53b2edf56c4ab09d186df056.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) - -class IssueCategoryTest < ActiveSupport::TestCase - fixtures :issue_categories, :issues - - def setup - @category = IssueCategory.find(1) - end - - def test_create - assert IssueCategory.new(:project_id => 2, :name => 'New category').save - category = IssueCategory.first(:order => 'id DESC') - assert_equal 'New category', category.name - end - - def test_create_with_group_assignment - assert IssueCategory.new(:project_id => 2, :name => 'Group assignment', :assigned_to_id => 11).save - category = IssueCategory.first(:order => 'id DESC') - assert_kind_of Group, category.assigned_to - assert_equal Group.find(11), category.assigned_to - end - - def test_destroy - issue = @category.issues.first - @category.destroy - # Make sure the category was nullified on the issue - assert_nil issue.reload.category - end - - def test_destroy_with_reassign - issue = @category.issues.first - reassign_to = IssueCategory.find(2) - @category.destroy(reassign_to) - # Make sure the issue was reassigned - assert_equal reassign_to, issue.reload.category - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/31/3185cbd184bff678cc310485aae2abf79881c171.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/31/3185cbd184bff678cc310485aae2abf79881c171.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,22 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 NewsObserver < ActiveRecord::Observer + def after_create(news) + Mailer.news_added(news).deliver if Setting.notified_events.include?('news_added') + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/31/31fa227633ea6c4259f0d7dd6d5668da6f1d4647.svn-base --- a/.svn/pristine/31/31fa227633ea6c4259f0d7dd6d5668da6f1d4647.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,237 +0,0 @@ -/* The main calendar widget. DIV containing a table. */ - -img.calendar-trigger { - cursor: pointer; - vertical-align: middle; - margin-left: 4px; -} - -div.calendar { position: relative; z-index: 30;} - -div.calendar, div.calendar table { - border: 1px solid #556; - font-size: 11px; - color: #000; - cursor: default; - background: #fafbfc; - font-family: tahoma,verdana,sans-serif; -} - -/* Header part -- contains navigation buttons and day names. */ - -div.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */ - text-align: center; /* They are the navigation buttons */ - padding: 2px; /* Make the buttons seem like they're pressing */ -} - -div.calendar .nav { - background: #467aa7; -} - -div.calendar thead .title { /* This holds the current "month, year" */ - font-weight: bold; /* Pressing it will take you to the current date */ - text-align: center; - background: #fff; - color: #000; - padding: 2px; -} - -div.calendar thead .headrow { /* Row containing navigation buttons */ - background: #467aa7; - color: #fff; -} - -div.calendar thead .daynames { /* Row containing the day names */ - background: #bdf; -} - -div.calendar thead .name { /* Cells containing the day names */ - border-bottom: 1px solid #556; - padding: 2px; - text-align: center; - color: #000; -} - -div.calendar thead .weekend { /* How a weekend day name shows in header */ - color: #a66; -} - -div.calendar thead .hilite { /* How do the buttons in header appear when hover */ - background-color: #80b0da; - color: #000; - padding: 1px; -} - -div.calendar thead .active { /* Active (pressed) buttons in header */ - background-color: #77c; - padding: 2px 0px 0px 2px; -} - -/* The body part -- contains all the days in month. */ - -div.calendar tbody .day { /* Cells containing month days dates */ - width: 2em; - color: #456; - text-align: right; - padding: 2px 4px 2px 2px; -} -div.calendar tbody .day.othermonth { - font-size: 80%; - color: #bbb; -} -div.calendar tbody .day.othermonth.oweekend { - color: #fbb; -} - -div.calendar table .wn { - padding: 2px 3px 2px 2px; - border-right: 1px solid #000; - background: #bdf; -} - -div.calendar tbody .rowhilite td { - background: #def; -} - -div.calendar tbody .rowhilite td.wn { - background: #80b0da; -} - -div.calendar tbody td.hilite { /* Hovered cells */ - background: #80b0da; - padding: 1px 3px 1px 1px; - border: 1px solid #bbb; -} - -div.calendar tbody td.active { /* Active (pressed) cells */ - background: #cde; - padding: 2px 2px 0px 2px; -} - -div.calendar tbody td.selected { /* Cell showing today date */ - font-weight: bold; - border: 1px solid #000; - padding: 1px 3px 1px 1px; - background: #fff; - color: #000; -} - -div.calendar tbody td.weekend { /* Cells showing weekend days */ - color: #a66; -} - -div.calendar tbody td.today { /* Cell showing selected date */ - font-weight: bold; - color: #f00; -} - -div.calendar tbody .disabled { color: #999; } - -div.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */ - visibility: hidden; -} - -div.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */ - display: none; -} - -/* The footer part -- status bar and "Close" button */ - -div.calendar tfoot .footrow { /* The in footer (only one right now) */ - text-align: center; - background: #556; - color: #fff; -} - -div.calendar tfoot .ttip { /* Tooltip (status bar) cell */ - background: #fff; - color: #445; - border-top: 1px solid #556; - padding: 1px; -} - -div.calendar tfoot .hilite { /* Hover style for buttons in footer */ - background: #aaf; - border: 1px solid #04f; - color: #000; - padding: 1px; -} - -div.calendar tfoot .active { /* Active (pressed) style for buttons in footer */ - background: #77c; - padding: 2px 0px 0px 2px; -} - -/* Combo boxes (menus that display months/years for direct selection) */ - -div.calendar .combo { - position: absolute; - display: none; - top: 0px; - left: 0px; - width: 4em; - cursor: default; - border: 1px solid #655; - background: #def; - color: #000; - font-size: 90%; - z-index: 100; -} - -div.calendar .combo .label, -div.calendar .combo .label-IEfix { - text-align: center; - padding: 1px; -} - -div.calendar .combo .label-IEfix { - width: 4em; -} - -div.calendar .combo .hilite { - background: #acf; -} - -div.calendar .combo .active { - border-top: 1px solid #46a; - border-bottom: 1px solid #46a; - background: #eef; - font-weight: bold; -} - -div.calendar td.time { - border-top: 1px solid #000; - padding: 1px 0px; - text-align: center; - background-color: #f4f0e8; -} - -div.calendar td.time .hour, -div.calendar td.time .minute, -div.calendar td.time .ampm { - padding: 0px 3px 0px 4px; - border: 1px solid #889; - font-weight: bold; - background-color: #fff; -} - -div.calendar td.time .ampm { - text-align: center; -} - -div.calendar td.time .colon { - padding: 0px 2px 0px 3px; - font-weight: bold; -} - -div.calendar td.time span.hilite { - border-color: #000; - background-color: #667; - color: #fff; -} - -div.calendar td.time span.active { - border-color: #f00; - background-color: #000; - color: #0f0; -} diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/32/326a62adc02ab48e12d9e9d717fea2e84b8c44cf.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/32/326a62adc02ab48e12d9e9d717fea2e84b8c44cf.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,601 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class RepositoryGitTest < ActiveSupport::TestCase + fixtures :projects, :repositories, :enabled_modules, :users, :roles + + include Redmine::I18n + + REPOSITORY_PATH = Rails.root.join('tmp/test/git_repository').to_s + REPOSITORY_PATH.gsub!(/\//, "\\") if Redmine::Platform.mswin? + + NUM_REV = 28 + NUM_HEAD = 6 + + FELIX_HEX = "Felix Sch\xC3\xA4fer" + CHAR_1_HEX = "\xc3\x9c" + + ## Git, Mercurial and CVS path encodings are binary. + ## Subversion supports URL encoding for path. + ## Redmine Mercurial adapter and extension use URL encoding. + ## Git accepts only binary path in command line parameter. + ## So, there is no way to use binary command line parameter in JRuby. + JRUBY_SKIP = (RUBY_PLATFORM == 'java') + JRUBY_SKIP_STR = "TODO: This test fails in JRuby" + + def setup + @project = Project.find(3) + @repository = Repository::Git.create( + :project => @project, + :url => REPOSITORY_PATH, + :path_encoding => 'ISO-8859-1' + ) + assert @repository + @char_1 = CHAR_1_HEX.dup + if @char_1.respond_to?(:force_encoding) + @char_1.force_encoding('UTF-8') + end + end + + def test_blank_path_to_repository_error_message + set_language_if_valid 'en' + repo = Repository::Git.new( + :project => @project, + :identifier => 'test' + ) + assert !repo.save + assert_include "Path to repository can't be blank", + repo.errors.full_messages + end + + def test_blank_path_to_repository_error_message_fr + set_language_if_valid 'fr' + str = "Chemin du d\xc3\xa9p\xc3\xb4t doit \xc3\xaatre renseign\xc3\xa9(e)" + str.force_encoding('UTF-8') if str.respond_to?(:force_encoding) + repo = Repository::Git.new( + :project => @project, + :url => "", + :identifier => 'test', + :path_encoding => '' + ) + assert !repo.save + assert_include str, repo.errors.full_messages + end + + if File.directory?(REPOSITORY_PATH) + ## Ruby uses ANSI api to fork a process on Windows. + ## Japanese Shift_JIS and Traditional Chinese Big5 have 0x5c(backslash) problem + ## and these are incompatible with ASCII. + ## Git for Windows (msysGit) changed internal API from ANSI to Unicode in 1.7.10 + ## http://code.google.com/p/msysgit/issues/detail?id=80 + ## So, Latin-1 path tests fail on Japanese Windows + WINDOWS_PASS = (Redmine::Platform.mswin? && + Redmine::Scm::Adapters::GitAdapter.client_version_above?([1, 7, 10])) + WINDOWS_SKIP_STR = "TODO: This test fails in Git for Windows above 1.7.10" + + def test_scm_available + klass = Repository::Git + assert_equal "Git", klass.scm_name + assert klass.scm_adapter_class + assert_not_equal "", klass.scm_command + assert_equal true, klass.scm_available + end + + def test_entries + entries = @repository.entries + assert_kind_of Redmine::Scm::Adapters::Entries, entries + end + + def test_fetch_changesets_from_scratch + assert_nil @repository.extra_info + + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + + assert_equal NUM_REV, @repository.changesets.count + assert_equal 39, @repository.filechanges.count + + commit = @repository.changesets.find_by_revision("7234cb2750b63f47bff735edc50a1c0a433c2518") + assert_equal "7234cb2750b63f47bff735edc50a1c0a433c2518", commit.scmid + assert_equal "Initial import.\nThe repository contains 3 files.", commit.comments + assert_equal "jsmith ", 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 3, commit.filechanges.count + change = commit.filechanges.sort_by(&:path).first + assert_equal "README", change.path + assert_equal nil, change.from_path + assert_equal "A", change.action + + assert_equal NUM_HEAD, @repository.extra_info["heads"].size + end + + def test_fetch_changesets_incremental + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + extra_info_heads = @repository.extra_info["heads"].dup + assert_equal NUM_HEAD, extra_info_heads.size + extra_info_heads.delete_if { |x| x == "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c" } + assert_equal 4, extra_info_heads.size + + del_revs = [ + "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c", + "ed5bb786bbda2dee66a2d50faf51429dbc043a7b", + "4f26664364207fa8b1af9f8722647ab2d4ac5d43", + "deff712f05a90d96edbd70facc47d944be5897e3", + "32ae898b720c2f7eec2723d5bdd558b4cb2d3ddf", + "7e61ac704deecde634b51e59daa8110435dcb3da", + ] + @repository.changesets.each do |rev| + rev.destroy if del_revs.detect {|r| r == rev.scmid.to_s } + end + @project.reload + cs1 = @repository.changesets + assert_equal NUM_REV - 6, cs1.count + extra_info_heads << "4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8" + h = {} + h["heads"] = extra_info_heads + @repository.merge_extra_info(h) + @repository.save + @project.reload + assert @repository.extra_info["heads"].index("4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8") + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + assert_equal NUM_HEAD, @repository.extra_info["heads"].size + assert @repository.extra_info["heads"].index("83ca5fd546063a3c7dc2e568ba3355661a9e2b2c") + end + + def test_fetch_changesets_history_editing + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + extra_info_heads = @repository.extra_info["heads"].dup + assert_equal NUM_HEAD, extra_info_heads.size + extra_info_heads.delete_if { |x| x == "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c" } + assert_equal 4, extra_info_heads.size + + del_revs = [ + "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c", + "ed5bb786bbda2dee66a2d50faf51429dbc043a7b", + "4f26664364207fa8b1af9f8722647ab2d4ac5d43", + "deff712f05a90d96edbd70facc47d944be5897e3", + "32ae898b720c2f7eec2723d5bdd558b4cb2d3ddf", + "7e61ac704deecde634b51e59daa8110435dcb3da", + ] + @repository.changesets.each do |rev| + rev.destroy if del_revs.detect {|r| r == rev.scmid.to_s } + end + @project.reload + assert_equal NUM_REV - 6, @repository.changesets.count + + c = Changeset.new(:repository => @repository, + :committed_on => Time.now, + :revision => "abcd1234efgh", + :scmid => "abcd1234efgh", + :comments => 'test') + assert c.save + @project.reload + assert_equal NUM_REV - 5, @repository.changesets.count + + extra_info_heads << "1234abcd5678" + h = {} + h["heads"] = extra_info_heads + @repository.merge_extra_info(h) + @repository.save + @project.reload + h1 = @repository.extra_info["heads"].dup + assert h1.index("1234abcd5678") + assert_equal 5, h1.size + + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV - 5, @repository.changesets.count + h2 = @repository.extra_info["heads"].dup + assert_equal h1, h2 + end + + def test_keep_extra_report_last_commit_in_clear_changesets + assert_nil @repository.extra_info + h = {} + h["extra_report_last_commit"] = "1" + @repository.merge_extra_info(h) + @repository.save + @project.reload + + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + + assert_equal NUM_REV, @repository.changesets.count + @repository.send(:clear_changesets) + assert_equal 1, @repository.extra_info.size + assert_equal "1", @repository.extra_info["extra_report_last_commit"] + end + + def test_refetch_after_clear_changesets + assert_nil @repository.extra_info + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + + @repository.send(:clear_changesets) + @project.reload + assert_equal 0, @repository.changesets.count + + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + end + + def test_parents + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + r1 = @repository.find_changeset_by_name("7234cb2750b63") + assert_equal [], r1.parents + r2 = @repository.find_changeset_by_name("899a15dba03a3") + assert_equal 1, r2.parents.length + assert_equal "7234cb2750b63f47bff735edc50a1c0a433c2518", + r2.parents[0].identifier + r3 = @repository.find_changeset_by_name("32ae898b720c2") + assert_equal 2, r3.parents.length + r4 = [r3.parents[0].identifier, r3.parents[1].identifier].sort + assert_equal "4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8", r4[0] + assert_equal "7e61ac704deecde634b51e59daa8110435dcb3da", r4[1] + end + + def test_db_consistent_ordering_init + assert_nil @repository.extra_info + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal 1, @repository.extra_info["db_consistent"]["ordering"] + end + + def test_db_consistent_ordering_before_1_2 + assert_nil @repository.extra_info + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + assert_not_nil @repository.extra_info + h = {} + h["heads"] = [] + h["branches"] = {} + h["db_consistent"] = {} + @repository.merge_extra_info(h) + @repository.save + assert_equal NUM_REV, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal 0, @repository.extra_info["db_consistent"]["ordering"] + + extra_info_heads = @repository.extra_info["heads"].dup + extra_info_heads.delete_if { |x| x == "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c" } + del_revs = [ + "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c", + "ed5bb786bbda2dee66a2d50faf51429dbc043a7b", + "4f26664364207fa8b1af9f8722647ab2d4ac5d43", + "deff712f05a90d96edbd70facc47d944be5897e3", + "32ae898b720c2f7eec2723d5bdd558b4cb2d3ddf", + "7e61ac704deecde634b51e59daa8110435dcb3da", + ] + @repository.changesets.each do |rev| + rev.destroy if del_revs.detect {|r| r == rev.scmid.to_s } + end + @project.reload + cs1 = @repository.changesets + assert_equal NUM_REV - 6, cs1.count + assert_equal 0, @repository.extra_info["db_consistent"]["ordering"] + + extra_info_heads << "4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8" + h = {} + h["heads"] = extra_info_heads + @repository.merge_extra_info(h) + @repository.save + @project.reload + assert @repository.extra_info["heads"].index("4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8") + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + assert_equal NUM_HEAD, @repository.extra_info["heads"].size + + assert_equal 0, @repository.extra_info["db_consistent"]["ordering"] + end + + def test_heads_from_branches_hash + assert_nil @repository.extra_info + assert_equal 0, @repository.changesets.count + assert_equal [], @repository.heads_from_branches_hash + h = {} + h["branches"] = {} + h["branches"]["test1"] = {} + h["branches"]["test1"]["last_scmid"] = "1234abcd" + h["branches"]["test2"] = {} + h["branches"]["test2"]["last_scmid"] = "abcd1234" + @repository.merge_extra_info(h) + @repository.save + @project.reload + assert_equal ["1234abcd", "abcd1234"], @repository.heads_from_branches_hash.sort + end + + def test_latest_changesets + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + # with limit + changesets = @repository.latest_changesets('', 'master', 2) + assert_equal 2, changesets.size + + # with path + changesets = @repository.latest_changesets('images', 'master') + assert_equal [ + 'deff712f05a90d96edbd70facc47d944be5897e3', + '899a15dba03a3b350b89c3f537e4bbe02a03cdc9', + '7234cb2750b63f47bff735edc50a1c0a433c2518', + ], changesets.collect(&:revision) + + changesets = @repository.latest_changesets('README', nil) + assert_equal [ + '32ae898b720c2f7eec2723d5bdd558b4cb2d3ddf', + '4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8', + '713f4944648826f558cf548222f813dabe7cbb04', + '61b685fbe55ab05b5ac68402d5720c1a6ac973d1', + '899a15dba03a3b350b89c3f537e4bbe02a03cdc9', + '7234cb2750b63f47bff735edc50a1c0a433c2518', + ], changesets.collect(&:revision) + + # with path, revision and limit + changesets = @repository.latest_changesets('images', '899a15dba') + assert_equal [ + '899a15dba03a3b350b89c3f537e4bbe02a03cdc9', + '7234cb2750b63f47bff735edc50a1c0a433c2518', + ], changesets.collect(&:revision) + + changesets = @repository.latest_changesets('images', '899a15dba', 1) + assert_equal [ + '899a15dba03a3b350b89c3f537e4bbe02a03cdc9', + ], changesets.collect(&:revision) + + changesets = @repository.latest_changesets('README', '899a15dba') + assert_equal [ + '899a15dba03a3b350b89c3f537e4bbe02a03cdc9', + '7234cb2750b63f47bff735edc50a1c0a433c2518', + ], changesets.collect(&:revision) + + changesets = @repository.latest_changesets('README', '899a15dba', 1) + assert_equal [ + '899a15dba03a3b350b89c3f537e4bbe02a03cdc9', + ], changesets.collect(&:revision) + + # with path, tag and limit + changesets = @repository.latest_changesets('images', 'tag01.annotated') + assert_equal [ + '899a15dba03a3b350b89c3f537e4bbe02a03cdc9', + '7234cb2750b63f47bff735edc50a1c0a433c2518', + ], changesets.collect(&:revision) + + changesets = @repository.latest_changesets('images', 'tag01.annotated', 1) + assert_equal [ + '899a15dba03a3b350b89c3f537e4bbe02a03cdc9', + ], changesets.collect(&:revision) + + changesets = @repository.latest_changesets('README', 'tag01.annotated') + assert_equal [ + '899a15dba03a3b350b89c3f537e4bbe02a03cdc9', + '7234cb2750b63f47bff735edc50a1c0a433c2518', + ], changesets.collect(&:revision) + + changesets = @repository.latest_changesets('README', 'tag01.annotated', 1) + assert_equal [ + '899a15dba03a3b350b89c3f537e4bbe02a03cdc9', + ], changesets.collect(&:revision) + + # with path, branch and limit + changesets = @repository.latest_changesets('images', 'test_branch') + assert_equal [ + '899a15dba03a3b350b89c3f537e4bbe02a03cdc9', + '7234cb2750b63f47bff735edc50a1c0a433c2518', + ], changesets.collect(&:revision) + + changesets = @repository.latest_changesets('images', 'test_branch', 1) + assert_equal [ + '899a15dba03a3b350b89c3f537e4bbe02a03cdc9', + ], changesets.collect(&:revision) + + changesets = @repository.latest_changesets('README', 'test_branch') + assert_equal [ + '713f4944648826f558cf548222f813dabe7cbb04', + '61b685fbe55ab05b5ac68402d5720c1a6ac973d1', + '899a15dba03a3b350b89c3f537e4bbe02a03cdc9', + '7234cb2750b63f47bff735edc50a1c0a433c2518', + ], changesets.collect(&:revision) + + changesets = @repository.latest_changesets('README', 'test_branch', 2) + assert_equal [ + '713f4944648826f558cf548222f813dabe7cbb04', + '61b685fbe55ab05b5ac68402d5720c1a6ac973d1', + ], changesets.collect(&:revision) + + if WINDOWS_PASS + puts WINDOWS_SKIP_STR + elsif JRUBY_SKIP + puts JRUBY_SKIP_STR + else + # latin-1 encoding path + changesets = @repository.latest_changesets( + "latin-1-dir/test-#{@char_1}-2.txt", '64f1f3e89') + assert_equal [ + '64f1f3e89ad1cb57976ff0ad99a107012ba3481d', + '4fc55c43bf3d3dc2efb66145365ddc17639ce81e', + ], changesets.collect(&:revision) + + changesets = @repository.latest_changesets( + "latin-1-dir/test-#{@char_1}-2.txt", '64f1f3e89', 1) + assert_equal [ + '64f1f3e89ad1cb57976ff0ad99a107012ba3481d', + ], changesets.collect(&:revision) + end + end + + def test_latest_changesets_latin_1_dir + if WINDOWS_PASS + puts WINDOWS_SKIP_STR + elsif JRUBY_SKIP + puts JRUBY_SKIP_STR + else + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + changesets = @repository.latest_changesets( + "latin-1-dir/test-#{@char_1}-subdir", '1ca7f5ed') + assert_equal [ + '1ca7f5ed374f3cb31a93ae5215c2e25cc6ec5127', + ], changesets.collect(&:revision) + end + end + + def test_find_changeset_by_name + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + ['7234cb2750b63f47bff735edc50a1c0a433c2518', '7234cb2750b'].each do |r| + assert_equal '7234cb2750b63f47bff735edc50a1c0a433c2518', + @repository.find_changeset_by_name(r).revision + end + end + + def test_find_changeset_by_empty_name + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + ['', ' ', nil].each do |r| + assert_nil @repository.find_changeset_by_name(r) + end + end + + def test_identifier + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + c = @repository.changesets.find_by_revision( + '7234cb2750b63f47bff735edc50a1c0a433c2518') + assert_equal c.scmid, c.identifier + end + + def test_format_identifier + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + c = @repository.changesets.find_by_revision( + '7234cb2750b63f47bff735edc50a1c0a433c2518') + assert_equal '7234cb27', c.format_identifier + end + + def test_activities + c = Changeset.new(:repository => @repository, + :committed_on => Time.now, + :revision => 'abc7234cb2750b63f47bff735edc50a1c0a433c2', + :scmid => 'abc7234cb2750b63f47bff735edc50a1c0a433c2', + :comments => 'test') + assert c.event_title.include?('abc7234c:') + assert_equal 'abc7234cb2750b63f47bff735edc50a1c0a433c2', c.event_url[:rev] + end + + def test_log_utf8 + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + str_felix_hex = FELIX_HEX.dup + if str_felix_hex.respond_to?(:force_encoding) + str_felix_hex.force_encoding('UTF-8') + end + c = @repository.changesets.find_by_revision( + 'ed5bb786bbda2dee66a2d50faf51429dbc043a7b') + assert_equal "#{str_felix_hex} ", c.committer + end + + def test_previous + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + %w|1ca7f5ed374f3cb31a93ae5215c2e25cc6ec5127 1ca7f5ed|.each do |r1| + changeset = @repository.find_changeset_by_name(r1) + %w|64f1f3e89ad1cb57976ff0ad99a107012ba3481d 64f1f3e89ad1|.each do |r2| + assert_equal @repository.find_changeset_by_name(r2), changeset.previous + end + end + end + + def test_previous_nil + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + %w|7234cb2750b63f47bff735edc50a1c0a433c2518 7234cb275|.each do |r1| + changeset = @repository.find_changeset_by_name(r1) + assert_nil changeset.previous + end + end + + def test_next + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + %w|64f1f3e89ad1cb57976ff0ad99a107012ba3481d 64f1f3e89ad1|.each do |r2| + changeset = @repository.find_changeset_by_name(r2) + %w|1ca7f5ed374f3cb31a93ae5215c2e25cc6ec5127 1ca7f5ed|.each do |r1| + assert_equal @repository.find_changeset_by_name(r1), changeset.next + end + end + end + + def test_next_nil + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + %w|2a682156a3b6e77a8bf9cd4590e8db757f3c6c78 2a682156a3b6e77a|.each do |r1| + changeset = @repository.find_changeset_by_name(r1) + assert_nil changeset.next + end + end + else + puts "Git test repository NOT FOUND. Skipping unit tests !!!" + def test_fake; assert true end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/32/326ec63d3dde535244f38884da87eca7ff6e8ba3.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/32/326ec63d3dde535244f38884da87eca7ff6e8ba3.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,1101 @@ +# German translations for Ruby on Rails +# by Clemens Kofler (clemens@railway.at) +# additions for Redmine 1.2 by Jens Martsch (jmartsch@gmail.com) + +de: + direction: ltr + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%d.%m.%Y" + short: "%e. %b" + long: "%e. %B %Y" + + day_names: [Sonntag, Montag, Dienstag, Mittwoch, Donnerstag, Freitag, Samstag] + abbr_day_names: [So, Mo, Di, Mi, Do, Fr, Sa] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, Januar, Februar, März, April, Mai, Juni, Juli, August, September, Oktober, November, Dezember] + abbr_month_names: [~, Jan, Feb, Mär, Apr, Mai, Jun, Jul, Aug, Sep, Okt, Nov, Dez] + # Used in date_select and datime_select. + order: + - :day + - :month + - :year + + time: + formats: + default: "%d.%m.%Y %H:%M" + time: "%H:%M" + short: "%e. %b %H:%M" + long: "%A, %e. %B %Y, %H:%M Uhr" + am: "vormittags" + pm: "nachmittags" + + datetime: + distance_in_words: + half_a_minute: 'eine halbe Minute' + less_than_x_seconds: + one: 'weniger als 1 Sekunde' + other: 'weniger als %{count} Sekunden' + x_seconds: + one: '1 Sekunde' + other: '%{count} Sekunden' + less_than_x_minutes: + one: 'weniger als 1 Minute' + other: 'weniger als %{count} Minuten' + x_minutes: + one: '1 Minute' + other: '%{count} Minuten' + about_x_hours: + one: 'etwa 1 Stunde' + other: 'etwa %{count} Stunden' + x_hours: + one: "1 Stunde" + other: "%{count} Stunden" + x_days: + one: '1 Tag' + other: '%{count} Tagen' + about_x_months: + one: 'etwa 1 Monat' + other: 'etwa %{count} Monaten' + x_months: + one: '1 Monat' + other: '%{count} Monaten' + about_x_years: + one: 'etwa 1 Jahr' + other: 'etwa %{count} Jahren' + over_x_years: + one: 'mehr als 1 Jahr' + other: 'mehr als %{count} Jahren' + almost_x_years: + one: "fast 1 Jahr" + other: "fast %{count} Jahren" + + number: + # Default format for numbers + format: + separator: ',' + delimiter: '.' + precision: 2 + currency: + format: + unit: '€' + format: '%n %u' + delimiter: '' + percentage: + format: + delimiter: "" + precision: + format: + delimiter: "" + human: + format: + delimiter: "" + precision: 3 + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + +# Used in array.to_sentence. + support: + array: + sentence_connector: "und" + skip_last_comma: true + + activerecord: + errors: + template: + header: + one: "Dieses %{model}-Objekt konnte nicht gespeichert werden: %{count} Fehler." + other: "Dieses %{model}-Objekt konnte nicht gespeichert werden: %{count} Fehler." + body: "Bitte überprüfen Sie die folgenden Felder:" + + messages: + inclusion: "ist kein gültiger Wert" + exclusion: "ist nicht verfügbar" + invalid: "ist nicht gültig" + confirmation: "stimmt nicht mit der Bestätigung überein" + accepted: "muss akzeptiert werden" + empty: "muss ausgefüllt werden" + blank: "muss ausgefüllt werden" + too_long: "ist zu lang (nicht mehr als %{count} Zeichen)" + too_short: "ist zu kurz (nicht weniger als %{count} Zeichen)" + wrong_length: "hat die falsche Länge (muss genau %{count} Zeichen haben)" + taken: "ist bereits vergeben" + not_a_number: "ist keine Zahl" + not_a_date: "is kein gültiges Datum" + greater_than: "muss größer als %{count} sein" + greater_than_or_equal_to: "muss größer oder gleich %{count} sein" + equal_to: "muss genau %{count} sein" + less_than: "muss kleiner als %{count} sein" + less_than_or_equal_to: "muss kleiner oder gleich %{count} sein" + odd: "muss ungerade sein" + even: "muss gerade sein" + greater_than_start_date: "muss größer als Anfangsdatum sein" + not_same_project: "gehört nicht zum selben Projekt" + circular_dependency: "Diese Beziehung würde eine zyklische Abhängigkeit erzeugen" + cant_link_an_issue_with_a_descendant: "Ein Ticket kann nicht mit einer Ihrer Unteraufgaben verlinkt werden" + + actionview_instancetag_blank_option: Bitte auswählen + + button_activate: Aktivieren + button_add: Hinzufügen + button_annotate: Annotieren + button_apply: Anwenden + button_archive: Archivieren + button_back: Zurück + button_cancel: Abbrechen + button_change: Wechseln + button_change_password: Kennwort ändern + button_check_all: Alles auswählen + button_clear: Zurücksetzen + button_close: Schließen + button_collapse_all: Alle einklappen + button_configure: Konfigurieren + button_copy: Kopieren + button_copy_and_follow: Kopieren und Ticket anzeigen + button_create: Anlegen + button_create_and_continue: Anlegen und weiter + button_delete: Löschen + button_delete_my_account: Mein Benutzerkonto löschen + button_download: Download + button_duplicate: Duplizieren + button_edit: Bearbeiten + button_edit_associated_wikipage: "Zugehörige Wikiseite bearbeiten: %{page_title}" + button_edit_section: Diesen Bereich bearbeiten + button_expand_all: Alle ausklappen + button_export: Exportieren + button_hide: Verstecken + button_list: Liste + button_lock: Sperren + button_log_time: Aufwand buchen + button_login: Anmelden + button_move: Verschieben + button_move_and_follow: Verschieben und Ticket anzeigen + button_quote: Zitieren + button_rename: Umbenennen + button_reopen: Öffnen + button_reply: Antworten + button_reset: Zurücksetzen + button_rollback: Auf diese Version zurücksetzen + button_save: Speichern + button_show: Anzeigen + button_sort: Sortieren + button_submit: OK + button_test: Testen + button_unarchive: Entarchivieren + button_uncheck_all: Alles abwählen + button_unlock: Entsperren + button_unwatch: Nicht beobachten + button_update: Bearbeiten + button_view: Anzeigen + button_watch: Beobachten + + default_activity_design: Design + default_activity_development: Entwicklung + default_doc_category_tech: Technische Dokumentation + default_doc_category_user: Benutzerdokumentation + default_issue_status_closed: Erledigt + default_issue_status_feedback: Feedback + default_issue_status_in_progress: In Bearbeitung + default_issue_status_new: Neu + default_issue_status_rejected: Abgewiesen + default_issue_status_resolved: Gelöst + default_priority_high: Hoch + default_priority_immediate: Sofort + default_priority_low: Niedrig + default_priority_normal: Normal + default_priority_urgent: Dringend + default_role_developer: Entwickler + default_role_manager: Manager + default_role_reporter: Reporter + default_tracker_bug: Fehler + default_tracker_feature: Feature + default_tracker_support: Unterstützung + + description_all_columns: Alle Spalten + description_available_columns: Verfügbare Spalten + description_choose_project: Projekte + description_date_from: Startdatum eintragen + description_date_range_interval: Zeitraum durch Start- und Enddatum festlegen + description_date_range_list: Zeitraum aus einer Liste wählen + description_date_to: Enddatum eintragen + description_filter: Filter + description_issue_category_reassign: Neue Kategorie wählen + description_message_content: Nachrichteninhalt + description_notes: Kommentare + description_project_scope: Suchbereich + description_query_sort_criteria_attribute: Sortierattribut + description_query_sort_criteria_direction: Sortierrichtung + description_search: Suchfeld + description_selected_columns: Ausgewählte Spalten + description_user_mail_notification: Mailbenachrichtigungseinstellung + description_wiki_subpages_reassign: Neue Elternseite wählen + + enumeration_activities: Aktivitäten (Zeiterfassung) + enumeration_doc_categories: Dokumentenkategorien + enumeration_issue_priorities: Ticket-Prioritäten + enumeration_system_activity: System-Aktivität + + error_attachment_too_big: Diese Datei kann nicht hochgeladen werden, da sie die maximale Dateigröße von (%{max_size}) überschreitet. + error_can_not_archive_project: Dieses Projekt kann nicht archiviert werden. + error_can_not_delete_custom_field: Kann das benutzerdefinierte Feld nicht löschen. + error_can_not_delete_tracker: Dieser Tracker enthält Tickets und kann nicht gelöscht werden. + error_can_not_remove_role: Diese Rolle wird verwendet und kann nicht gelöscht werden. + error_can_not_reopen_issue_on_closed_version: Das Ticket ist einer abgeschlossenen Version zugeordnet und kann daher nicht wieder geöffnet werden. + error_can_t_load_default_data: "Die Standard-Konfiguration konnte nicht geladen werden: %{value}" + error_issue_done_ratios_not_updated: Der Ticket-Fortschritt wurde nicht aktualisiert. + error_issue_not_found_in_project: 'Das Ticket wurde nicht gefunden oder gehört nicht zu diesem Projekt.' + error_no_default_issue_status: Es ist kein Status als Standard definiert. Bitte überprüfen Sie Ihre Konfiguration (unter "Administration -> Ticket-Status"). + error_no_tracker_in_project: Diesem Projekt ist kein Tracker zugeordnet. Bitte überprüfen Sie die Projekteinstellungen. + error_scm_annotate: "Der Eintrag existiert nicht oder kann nicht annotiert werden." + error_scm_annotate_big_text_file: Der Eintrag kann nicht umgesetzt werden, da er die maximale Textlänge überschreitet. + error_scm_command_failed: "Beim Zugriff auf das Projektarchiv ist ein Fehler aufgetreten: %{value}" + error_scm_not_found: Eintrag und/oder Revision existiert nicht im Projektarchiv. + error_session_expired: Ihre Sitzung ist abgelaufen. Bitte melden Sie sich erneut an. + error_unable_delete_issue_status: "Der Ticket-Status konnte nicht gelöscht werden." + error_unable_to_connect: Fehler beim Verbinden (%{value}) + error_workflow_copy_source: Bitte wählen Sie einen Quell-Tracker und eine Quell-Rolle. + error_workflow_copy_target: Bitte wählen Sie die Ziel-Tracker und -Rollen. + + field_account: Konto + field_active: Aktiv + field_activity: Aktivität + field_admin: Administrator + field_assignable: Tickets können dieser Rolle zugewiesen werden + field_assigned_to: Zugewiesen an + field_assigned_to_role: Zuständigkeitsrolle + field_attr_firstname: Vorname-Attribut + field_attr_lastname: Name-Attribut + field_attr_login: Mitgliedsname-Attribut + field_attr_mail: E-Mail-Attribut + field_auth_source: Authentifizierungs-Modus + field_auth_source_ldap_filter: LDAP-Filter + field_author: Autor + field_base_dn: Base DN + field_board_parent: Übergeordnetes Forum + field_category: Kategorie + field_column_names: Spalten + field_closed_on: Geschlossen am + field_comments: Kommentar + field_comments_sorting: Kommentare anzeigen + field_commit_logs_encoding: Kodierung der Commit-Log-Meldungen + field_content: Inhalt + field_core_fields: Standardwerte + field_created_on: Angelegt + field_cvs_module: Modul + field_cvsroot: CVSROOT + field_default_value: Standardwert + field_delay: Pufferzeit + field_description: Beschreibung + field_done_ratio: "% erledigt" + field_downloads: Downloads + field_due_date: Abgabedatum + field_editable: Bearbeitbar + field_effective_date: Datum + field_estimated_hours: Geschätzter Aufwand + field_field_format: Format + field_filename: Datei + field_filesize: Größe + field_firstname: Vorname + field_fixed_version: Zielversion + field_group_by: Gruppiere Ergebnisse nach + field_hide_mail: E-Mail-Adresse nicht anzeigen + field_homepage: Projekt-Homepage + field_host: Host + field_hours: Stunden + field_identifier: Kennung + field_identity_url: OpenID-URL + field_inherit_members: Benutzer vererben + field_is_closed: Ticket geschlossen + field_is_default: Standardeinstellung + field_is_filter: Als Filter benutzen + field_is_for_all: Für alle Projekte + field_is_in_roadmap: In der Roadmap anzeigen + field_is_private: Privat + field_is_public: Öffentlich + field_is_required: Erforderlich + field_issue: Ticket + field_issue_to: Zugehöriges Ticket + field_issues_visibility: Ticket Sichtbarkeit + field_language: Sprache + field_last_login_on: Letzte Anmeldung + field_lastname: Nachname + field_login: Mitgliedsname + field_mail: E-Mail + field_mail_notification: Mailbenachrichtigung + field_max_length: Maximale Länge + field_member_of_group: Zuständigkeitsgruppe + field_min_length: Minimale Länge + field_multiple: Mehrere Werte + field_name: Name + field_new_password: Neues Kennwort + field_notes: Kommentare + field_onthefly: On-the-fly-Benutzererstellung + field_parent: Unterprojekt von + field_parent_issue: Übergeordnete Aufgabe + field_parent_title: Übergeordnete Seite + field_password: Kennwort + field_password_confirmation: Bestätigung + field_path_to_repository: Pfad zum Repository + field_port: Port + field_possible_values: Mögliche Werte + field_principal: Auftraggeber + field_priority: Priorität + field_private_notes: Privater Kommentar + field_project: Projekt + field_redirect_existing_links: Existierende Links umleiten + field_regexp: Regulärer Ausdruck + field_repository_is_default: Haupt-Repository + field_role: Rolle + field_root_directory: Wurzelverzeichnis + field_scm_path_encoding: Pfad-Kodierung + field_searchable: Durchsuchbar + field_sharing: Gemeinsame Verwendung + field_spent_on: Datum + field_start_date: Beginn + field_start_page: Hauptseite + field_status: Status + field_subject: Thema + field_subproject: Unterprojekt von + field_summary: Zusammenfassung + field_text: Textfeld + field_time_entries: Logzeit + field_time_zone: Zeitzone + field_timeout: Auszeit (in Sekunden) + field_title: Titel + field_tracker: Tracker + field_type: Typ + field_updated_on: Aktualisiert + field_url: URL + field_user: Benutzer + field_value: Wert + field_version: Version + field_visible: Sichtbar + field_warn_on_leaving_unsaved: Vor dem Verlassen einer Seite mit ungesichertem Text im Editor warnen + field_watcher: Beobachter + + general_csv_decimal_separator: ',' + general_csv_encoding: ISO-8859-1 + general_csv_separator: ';' + general_first_day_of_week: '1' + general_lang_name: 'Deutsch' + general_pdf_encoding: UTF-8 + general_text_No: 'Nein' + general_text_Yes: 'Ja' + general_text_no: 'nein' + general_text_yes: 'ja' + + label_activity: Aktivität + label_add_another_file: Eine weitere Datei hinzufügen + label_add_note: Kommentar hinzufügen + label_added: hinzugefügt + label_added_time_by: "Von %{author} vor %{age} hinzugefügt" + label_additional_workflow_transitions_for_assignee: Zusätzliche Berechtigungen wenn der Benutzer der Zugewiesene ist + label_additional_workflow_transitions_for_author: Zusätzliche Berechtigungen wenn der Benutzer der Autor ist + label_administration: Administration + label_age: Geändert vor + label_ago: vor + label_all: alle + label_all_time: gesamter Zeitraum + label_all_words: Alle Wörter + label_and_its_subprojects: "%{value} und dessen Unterprojekte" + label_any: alle + label_any_issues_in_project: irgendein Ticket im Projekt + label_any_issues_not_in_project: irgendein Ticket nicht im Projekt + label_api_access_key: API-Zugriffsschlüssel + label_api_access_key_created_on: Der API-Zugriffsschlüssel wurde vor %{value} erstellt + label_applied_status: Zugewiesener Status + label_ascending: Aufsteigend + label_assigned_to_me_issues: Mir zugewiesene Tickets + label_associated_revisions: Zugehörige Revisionen + label_attachment: Datei + label_attachment_delete: Anhang löschen + label_attachment_new: Neue Datei + label_attachment_plural: Dateien + label_attribute: Attribut + label_attribute_of_assigned_to: "%{name} des Bearbeiters" + label_attribute_of_author: "%{name} des Autors" + label_attribute_of_fixed_version: "%{name} der Zielversion" + label_attribute_of_issue: "%{name} des Tickets" + label_attribute_of_project: "%{name} des Projekts" + label_attribute_of_user: "%{name} des Benutzers" + label_attribute_plural: Attribute + label_auth_source: Authentifizierungs-Modus + label_auth_source_new: Neuer Authentifizierungs-Modus + label_auth_source_plural: Authentifizierungs-Arten + label_authentication: Authentifizierung + label_between: zwischen + label_blocked_by: Blockiert durch + label_blocks: Blockiert + label_board: Forum + label_board_locked: Gesperrt + label_board_new: Neues Forum + label_board_plural: Foren + label_board_sticky: Wichtig (immer oben) + label_boolean: Boolean + label_branch: Zweig + label_browse: Codebrowser + label_bulk_edit_selected_issues: Alle ausgewählten Tickets bearbeiten + label_bulk_edit_selected_time_entries: Ausgewählte Zeitaufwände bearbeiten + label_calendar: Kalender + label_change_plural: Änderungen + label_change_properties: Eigenschaften ändern + label_change_status: Statuswechsel + label_change_view_all: Alle Änderungen anzeigen + label_changes_details: Details aller Änderungen + label_changeset_plural: Changesets + label_child_revision: Nachfolger + label_chronological_order: in zeitlicher Reihenfolge + label_close_versions: Vollständige Versionen schließen + label_closed_issues: geschlossen + label_closed_issues_plural: geschlossen + label_comment: Kommentar + label_comment_add: Kommentar hinzufügen + label_comment_added: Kommentar hinzugefügt + label_comment_delete: Kommentar löschen + label_comment_plural: Kommentare + label_commits_per_author: Übertragungen pro Autor + label_commits_per_month: Übertragungen pro Monat + label_completed_versions: Abgeschlossene Versionen + label_confirmation: Bestätigung + label_contains: enthält + label_copied: kopiert + label_copied_from: Kopiert von + label_copied_to: Kopiert nach + label_copy_attachments: Anhänge kopieren + label_copy_same_as_target: So wie das Ziel + label_copy_source: Quelle + label_copy_subtasks: Unteraufgaben kopieren + label_copy_target: Ziel + label_copy_workflow_from: Workflow kopieren von + label_cross_project_descendants: Mit Unterprojekten + label_cross_project_hierarchy: Mit Projekthierarchie + label_cross_project_system: Mit allen Projekten + label_cross_project_tree: Mit Projektbaum + label_current_status: Gegenwärtiger Status + label_current_version: Gegenwärtige Version + label_custom_field: Benutzerdefiniertes Feld + label_custom_field_new: Neues Feld + label_custom_field_plural: Benutzerdefinierte Felder + label_date: Datum + label_date_from: Von + label_date_from_to: von %{start} bis %{end} + label_date_range: Zeitraum + label_date_to: Bis + label_day_plural: Tage + label_default: Standard + label_default_columns: Standard-Spalten + label_deleted: gelöscht + label_descending: Absteigend + label_details: Details + label_diff: diff + label_diff_inline: einspaltig + label_diff_side_by_side: nebeneinander + label_disabled: gesperrt + label_display: Anzeige + label_display_per_page: "Pro Seite: %{value}" + label_display_used_statuses_only: Zeige nur Status an, die von diesem Tracker verwendet werden + label_document: Dokument + label_document_added: Dokument hinzugefügt + label_document_new: Neues Dokument + label_document_plural: Dokumente + label_downloads_abbr: D/L + label_duplicated_by: Dupliziert durch + label_duplicates: Duplikat von + label_end_to_end: Ende - Ende + label_end_to_start: Ende - Anfang + label_enumeration_new: Neuer Wert + label_enumerations: Aufzählungen + label_environment: Umgebung + label_equals: ist + label_example: Beispiel + label_export_options: "%{export_format} Export-Eigenschaften" + label_export_to: "Auch abrufbar als:" + label_f_hour: "%{value} Stunde" + label_f_hour_plural: "%{value} Stunden" + label_feed_plural: Feeds + label_feeds_access_key: Atom-Zugriffsschlüssel + label_feeds_access_key_created_on: "Atom-Zugriffsschlüssel vor %{value} erstellt" + label_fields_permissions: Feldberechtigungen + label_file_added: Datei hinzugefügt + label_file_plural: Dateien + label_filter_add: Filter hinzufügen + label_filter_plural: Filter + label_float: Fließkommazahl + label_follows: Nachfolger von + label_gantt: Gantt-Diagramm + label_gantt_progress_line: Fortschrittslinie + label_general: Allgemein + label_generate_key: Generieren + label_git_report_last_commit: Bericht des letzten Commits für Dateien und Verzeichnisse + label_greater_or_equal: ">=" + label_group: Gruppe + label_group_new: Neue Gruppe + label_group_plural: Gruppen + label_help: Hilfe + label_history: Historie + label_home: Hauptseite + label_in: in + label_in_less_than: in weniger als + label_in_more_than: in mehr als + label_in_the_next_days: in den nächsten + label_in_the_past_days: in den letzten + label_incoming_emails: Eingehende E-Mails + label_index_by_date: Seiten nach Datum sortiert + label_index_by_title: Seiten nach Titel sortiert + label_information: Information + label_information_plural: Informationen + label_integer: Zahl + label_internal: Intern + label_issue: Ticket + label_issue_added: Ticket hinzugefügt + label_issue_category: Ticket-Kategorie + label_issue_category_new: Neue Kategorie + label_issue_category_plural: Ticket-Kategorien + label_issue_new: Neues Ticket + label_issue_note_added: Notiz hinzugefügt + label_issue_plural: Tickets + label_issue_priority_updated: Priorität aktualisiert + label_issue_status: Ticket-Status + label_issue_status_new: Neuer Status + label_issue_status_plural: Ticket-Status + label_issue_status_updated: Status aktualisiert + label_issue_tracking: Tickets + label_issue_updated: Ticket aktualisiert + label_issue_view_all: Alle Tickets anzeigen + label_issue_watchers: Beobachter + label_issues_by: "Tickets pro %{value}" + label_issues_visibility_all: Alle Tickets + label_issues_visibility_own: Tickets die folgender Benutzer erstellt hat oder die ihm zugewiesen sind + label_issues_visibility_public: Alle öffentlichen Tickets + label_item_position: "%{position}/%{count}" + label_jump_to_a_project: Zu einem Projekt springen... + label_language_based: Sprachabhängig + label_last_changes: "%{count} letzte Änderungen" + label_last_login: Letzte Anmeldung + label_last_month: voriger Monat + label_last_n_days: "die letzten %{count} Tage" + label_last_n_weeks: letzte %{count} Wochen + label_last_week: vorige Woche + label_latest_revision: Aktuellste Revision + label_latest_revision_plural: Aktuellste Revisionen + label_ldap_authentication: LDAP-Authentifizierung + label_less_or_equal: "<=" + label_less_than_ago: vor weniger als + label_list: Liste + label_loading: Lade... + label_logged_as: Angemeldet als + label_login: Anmelden + label_login_with_open_id_option: oder mit OpenID anmelden + label_logout: Abmelden + label_max_size: Maximale Größe + label_me: ich + label_member: Mitglied + label_member_new: Neues Mitglied + label_member_plural: Mitglieder + label_message_last: Letzter Forenbeitrag + label_message_new: Neues Thema + label_message_plural: Forenbeiträge + label_message_posted: Forenbeitrag hinzugefügt + label_min_max_length: Länge (Min. - Max.) + label_missing_api_access_key: Der API-Zugriffsschlüssel fehlt. + label_missing_feeds_access_key: Der Atom-Zugriffsschlüssel fehlt. + label_modified: geändert + label_module_plural: Module + label_month: Monat + label_months_from: Monate ab + label_more: Mehr + label_more_than_ago: vor mehr als + label_my_account: Mein Konto + label_my_page: Meine Seite + label_my_page_block: Verfügbare Widgets + label_my_projects: Meine Projekte + label_my_queries: Meine eigenen Abfragen + label_new: Neu + label_new_statuses_allowed: Neue Berechtigungen + label_news: News + label_news_added: News hinzugefügt + label_news_comment_added: Kommentar zu einer News hinzugefügt + label_news_latest: Letzte News + label_news_new: News hinzufügen + label_news_plural: News + label_news_view_all: Alle News anzeigen + label_next: Weiter + label_no_change_option: (Keine Änderung) + label_no_data: Nichts anzuzeigen + label_no_issues_in_project: keine Tickets im Projekt + label_nobody: Niemand + label_none: kein + label_not_contains: enthält nicht + label_not_equals: ist nicht + label_open_issues: offen + label_open_issues_plural: offen + label_optional_description: Beschreibung (optional) + label_options: Optionen + label_overall_activity: Aktivitäten aller Projekte anzeigen + label_overall_spent_time: Aufgewendete Zeit aller Projekte anzeigen + label_overview: Übersicht + label_parent_revision: Vorgänger + label_password_lost: Kennwort vergessen + label_per_page: Pro Seite + label_permissions: Berechtigungen + label_permissions_report: Berechtigungsübersicht + label_personalize_page: Diese Seite anpassen + label_planning: Terminplanung + label_please_login: Anmelden + label_plugins: Plugins + label_precedes: Vorgänger von + label_preferences: Präferenzen + label_preview: Vorschau + label_previous: Zurück + label_principal_search: "Nach Benutzer oder Gruppe suchen:" + label_profile: Profil + label_project: Projekt + label_project_all: Alle Projekte + label_project_copy_notifications: Sende Mailbenachrichtigungen beim Kopieren des Projekts. + label_project_latest: Neueste Projekte + label_project_new: Neues Projekt + label_project_plural: Projekte + label_public_projects: Öffentliche Projekte + label_query: Benutzerdefinierte Abfrage + label_query_new: Neue Abfrage + label_query_plural: Benutzerdefinierte Abfragen + label_read: Lesen... + label_readonly: Nur-Lese-Zugriff + label_register: Registrieren + label_registered_on: Angemeldet am + label_registration_activation_by_email: Kontoaktivierung durch E-Mail + label_registration_automatic_activation: Automatische Kontoaktivierung + label_registration_manual_activation: Manuelle Kontoaktivierung + label_related_issues: Zugehörige Tickets + label_relates_to: Beziehung mit + label_relation_delete: Beziehung löschen + label_relation_new: Neue Beziehung + label_renamed: umbenannt + label_reply_plural: Antworten + label_report: Bericht + label_report_plural: Berichte + label_reported_issues: Erstellte Tickets + label_repository: Projektarchiv + label_repository_new: Neues Repository + label_repository_plural: Projektarchive + label_required: Erforderlich + label_result_plural: Resultate + label_reverse_chronological_order: in umgekehrter zeitlicher Reihenfolge + label_revision: Revision + label_revision_id: Revision %{value} + label_revision_plural: Revisionen + label_roadmap: Roadmap + label_roadmap_due_in: "Fällig in %{value}" + label_roadmap_no_issues: Keine Tickets für diese Version + label_roadmap_overdue: "%{value} verspätet" + label_role: Rolle + label_role_and_permissions: Rollen und Rechte + label_role_anonymous: Anonymous + label_role_new: Neue Rolle + label_role_non_member: Nichtmitglied + label_role_plural: Rollen + label_scm: Versionskontrollsystem + label_search: Suche + label_search_for_watchers: Nach hinzufügbaren Beobachtern suchen + label_search_titles_only: Nur Titel durchsuchen + label_send_information: Sende Kontoinformationen an Benutzer + label_send_test_email: Test-E-Mail senden + label_session_expiration: Ende einer Sitzung + label_settings: Konfiguration + label_show_closed_projects: Geschlossene Projekte anzeigen + label_show_completed_versions: Abgeschlossene Versionen anzeigen + label_sort: Sortierung + label_sort_by: "Sortiert nach %{value}" + label_sort_higher: Eins höher + label_sort_highest: An den Anfang + label_sort_lower: Eins tiefer + label_sort_lowest: Ans Ende + label_spent_time: Aufgewendete Zeit + label_start_to_end: Anfang - Ende + label_start_to_start: Anfang - Anfang + label_statistics: Statistiken + label_status_transitions: Statusänderungen + label_stay_logged_in: Angemeldet bleiben + label_string: Text + label_subproject_new: Neues Unterprojekt + label_subproject_plural: Unterprojekte + label_subtask_plural: Unteraufgaben + label_tag: Markierung + label_text: Langer Text + label_theme: Stil + label_this_month: aktueller Monat + label_this_week: aktuelle Woche + label_this_year: aktuelles Jahr + label_time_entry_plural: Benötigte Zeit + label_time_tracking: Zeiterfassung + label_today: heute + label_topic_plural: Themen + label_total: Gesamtzahl + label_tracker: Tracker + label_tracker_new: Neuer Tracker + label_tracker_plural: Tracker + label_update_issue_done_ratios: Ticket-Fortschritt aktualisieren + label_updated_time: "Vor %{value} aktualisiert" + label_updated_time_by: "Von %{author} vor %{age} aktualisiert" + label_used_by: Benutzt von + label_user: Benutzer + label_user_activity: "Aktivität von %{value}" + label_user_anonymous: Anonym + label_user_mail_no_self_notified: "Ich möchte nicht über Änderungen benachrichtigt werden, die ich selbst durchführe." + label_user_mail_option_all: "Für alle Ereignisse in all meinen Projekten" + label_user_mail_option_none: Keine Ereignisse + label_user_mail_option_only_assigned: Nur für Aufgaben für die ich zuständig bin + label_user_mail_option_only_my_events: Nur für Aufgaben die ich beobachte oder an welchen ich mitarbeite + label_user_mail_option_only_owner: Nur für Aufgaben die ich angelegt habe + label_user_mail_option_selected: "Für alle Ereignisse in den ausgewählten Projekten..." + label_user_new: Neuer Benutzer + label_user_plural: Benutzer + label_user_search: "Nach Benutzer suchen:" + label_version: Version + label_version_new: Neue Version + label_version_plural: Versionen + label_version_sharing_descendants: Mit Unterprojekten + label_version_sharing_hierarchy: Mit Projekthierarchie + label_version_sharing_none: Nicht gemeinsam verwenden + label_version_sharing_system: Mit allen Projekten + label_version_sharing_tree: Mit Projektbaum + label_view_all_revisions: Alle Revisionen anzeigen + label_view_diff: Unterschiede anzeigen + label_view_revisions: Revisionen anzeigen + label_watched_issues: Beobachtete Tickets + label_week: Woche + label_wiki: Wiki + label_wiki_content_added: Wiki-Seite hinzugefügt + label_wiki_content_updated: Wiki-Seite aktualisiert + label_wiki_edit: Wiki-Bearbeitung + label_wiki_edit_plural: Wiki-Bearbeitungen + label_wiki_page: Wiki-Seite + label_wiki_page_plural: Wiki-Seiten + label_workflow: Workflow + label_x_closed_issues_abbr: + zero: 0 geschlossen + one: 1 geschlossen + other: "%{count} geschlossen" + label_x_comments: + zero: keine Kommentare + one: 1 Kommentar + other: "%{count} Kommentare" + label_x_issues: + zero: 0 Tickets + one: 1 Ticket + other: "%{count} Tickets" + label_x_open_issues_abbr: + zero: 0 offen + one: 1 offen + other: "%{count} offen" + label_x_open_issues_abbr_on_total: + zero: 0 offen / %{total} + one: 1 offen / %{total} + other: "%{count} offen / %{total}" + label_x_projects: + zero: keine Projekte + one: 1 Projekt + other: "%{count} Projekte" + label_year: Jahr + label_yesterday: gestern + + mail_body_account_activation_request: "Ein neuer Benutzer (%{value}) hat sich registriert. Sein Konto wartet auf Ihre Genehmigung:" + mail_body_account_information: Ihre Konto-Informationen + mail_body_account_information_external: "Sie können sich mit Ihrem Konto %{value} anmelden." + mail_body_lost_password: 'Benutzen Sie den folgenden Link, um Ihr Kennwort zu ändern:' + mail_body_register: 'Um Ihr Konto zu aktivieren, benutzen Sie folgenden Link:' + mail_body_reminder: "%{count} Tickets, die Ihnen zugewiesen sind, müssen in den nächsten %{days} Tagen abgegeben werden:" + mail_body_wiki_content_added: "Die Wiki-Seite '%{id}' wurde von %{author} hinzugefügt." + mail_body_wiki_content_updated: "Die Wiki-Seite '%{id}' wurde von %{author} aktualisiert." + mail_subject_account_activation_request: "Antrag auf %{value} Kontoaktivierung" + mail_subject_lost_password: "Ihr %{value} Kennwort" + mail_subject_register: "%{value} Kontoaktivierung" + mail_subject_reminder: "%{count} Tickets müssen in den nächsten %{days} Tagen abgegeben werden" + mail_subject_wiki_content_added: "Wiki-Seite '%{id}' hinzugefügt" + mail_subject_wiki_content_updated: "Wiki-Seite '%{id}' erfolgreich aktualisiert" + + notice_account_activated: Ihr Konto ist aktiviert. Sie können sich jetzt anmelden. + notice_account_deleted: Ihr Benutzerkonto wurde unwiderruflich gelöscht. + notice_account_invalid_creditentials: Benutzer oder Kennwort ist ungültig. + notice_account_lost_email_sent: Eine E-Mail mit Anweisungen, ein neues Kennwort zu wählen, wurde Ihnen geschickt. + notice_account_password_updated: Kennwort wurde erfolgreich aktualisiert. + notice_account_pending: "Ihr Konto wurde erstellt und wartet jetzt auf die Genehmigung des Administrators." + notice_account_register_done: Konto wurde erfolgreich angelegt. + notice_account_unknown_email: Unbekannter Benutzer. + notice_account_updated: Konto wurde erfolgreich aktualisiert. + notice_account_wrong_password: Falsches Kennwort. + notice_api_access_key_reseted: Ihr API-Zugriffsschlüssel wurde zurückgesetzt. + notice_can_t_change_password: Dieses Konto verwendet eine externe Authentifizierungs-Quelle. Unmöglich, das Kennwort zu ändern. + notice_default_data_loaded: Die Standard-Konfiguration wurde erfolgreich geladen. + notice_email_error: "Beim Senden einer E-Mail ist ein Fehler aufgetreten (%{value})." + notice_email_sent: "Eine E-Mail wurde an %{value} gesendet." + notice_failed_to_save_issues: "%{count} von %{total} ausgewählten Tickets konnte(n) nicht gespeichert werden: %{ids}." + notice_failed_to_save_members: "Benutzer konnte nicht gespeichert werden: %{errors}." + notice_failed_to_save_time_entries: "Gescheitert %{count} Zeiteinträge für %{total} von ausgewählten: %{ids} zu speichern." + notice_feeds_access_key_reseted: Ihr Atom-Zugriffsschlüssel wurde zurückgesetzt. + notice_file_not_found: Anhang existiert nicht oder ist gelöscht worden. + notice_gantt_chart_truncated: Die Grafik ist unvollständig, da das Maximum der anzeigbaren Aufgaben überschritten wurde (%{max}) + notice_issue_done_ratios_updated: Der Ticket-Fortschritt wurde aktualisiert. + notice_issue_successful_create: Ticket %{id} erstellt. + notice_issue_update_conflict: Das Ticket wurde von einem anderen Nutzer überarbeitet während Ihrer Bearbeitung. + notice_locking_conflict: Datum wurde von einem anderen Benutzer geändert. + notice_no_issue_selected: "Kein Ticket ausgewählt! Bitte wählen Sie die Tickets, die Sie bearbeiten möchten." + notice_not_authorized: Sie sind nicht berechtigt, auf diese Seite zuzugreifen. + notice_not_authorized_archived_project: Das Projekt wurde archiviert und ist daher nicht nicht verfügbar. + notice_successful_connection: Verbindung erfolgreich. + notice_successful_create: Erfolgreich angelegt + notice_successful_delete: Erfolgreich gelöscht. + notice_successful_update: Erfolgreich aktualisiert. + notice_unable_delete_time_entry: Der Zeiterfassungseintrag konnte nicht gelöscht werden. + notice_unable_delete_version: Die Version konnte nicht gelöscht werden. + notice_user_successful_create: Benutzer %{id} angelegt. + + permission_add_issue_notes: Kommentare hinzufügen + permission_add_issue_watchers: Beobachter hinzufügen + permission_add_issues: Tickets hinzufügen + permission_add_messages: Forenbeiträge hinzufügen + permission_add_project: Projekt erstellen + permission_add_subprojects: Unterprojekte erstellen + permission_add_documents: Dokumente hinzufügen + permission_browse_repository: Projektarchiv ansehen + permission_close_project: Schließen / erneutes Öffnen eines Projekts + permission_comment_news: News kommentieren + permission_commit_access: Commit-Zugriff + permission_delete_issue_watchers: Beobachter löschen + permission_delete_issues: Tickets löschen + permission_delete_messages: Forenbeiträge löschen + permission_delete_own_messages: Eigene Forenbeiträge löschen + permission_delete_wiki_pages: Wiki-Seiten löschen + permission_delete_wiki_pages_attachments: Anhänge löschen + permission_delete_documents: Dokumente löschen + permission_edit_issue_notes: Kommentare bearbeiten + permission_edit_issues: Tickets bearbeiten + permission_edit_messages: Forenbeiträge bearbeiten + permission_edit_own_issue_notes: Eigene Kommentare bearbeiten + permission_edit_own_messages: Eigene Forenbeiträge bearbeiten + permission_edit_own_time_entries: Selbst gebuchte Aufwände bearbeiten + permission_edit_project: Projekt bearbeiten + permission_edit_time_entries: Gebuchte Aufwände bearbeiten + permission_edit_wiki_pages: Wiki-Seiten bearbeiten + permission_edit_documents: Dokumente bearbeiten + permission_export_wiki_pages: Wiki-Seiten exportieren + permission_log_time: Aufwände buchen + permission_manage_boards: Foren verwalten + permission_manage_categories: Ticket-Kategorien verwalten + permission_manage_files: Dateien verwalten + permission_manage_issue_relations: Ticket-Beziehungen verwalten + permission_manage_members: Mitglieder verwalten + permission_manage_news: News verwalten + permission_manage_project_activities: Aktivitäten (Zeiterfassung) verwalten + permission_manage_public_queries: Öffentliche Filter verwalten + permission_manage_related_issues: Zugehörige Tickets verwalten + permission_manage_repository: Projektarchiv verwalten + permission_manage_subtasks: Unteraufgaben verwalten + permission_manage_versions: Versionen verwalten + permission_manage_wiki: Wiki verwalten + permission_move_issues: Tickets verschieben + permission_protect_wiki_pages: Wiki-Seiten schützen + permission_rename_wiki_pages: Wiki-Seiten umbenennen + permission_save_queries: Filter speichern + permission_select_project_modules: Projektmodule auswählen + permission_set_issues_private: Tickets privat oder öffentlich markieren + permission_set_notes_private: Kommentar als privat markieren + permission_set_own_issues_private: Eigene Tickets privat oder öffentlich markieren + permission_view_calendar: Kalender ansehen + permission_view_changesets: Changesets ansehen + permission_view_documents: Dokumente ansehen + permission_view_files: Dateien ansehen + permission_view_gantt: Gantt-Diagramm ansehen + permission_view_issue_watchers: Liste der Beobachter ansehen + permission_view_issues: Tickets anzeigen + permission_view_messages: Forenbeiträge ansehen + permission_view_private_notes: Private Kommentare sehen + permission_view_time_entries: Gebuchte Aufwände ansehen + permission_view_wiki_edits: Wiki-Versionsgeschichte ansehen + permission_view_wiki_pages: Wiki ansehen + + project_module_boards: Foren + project_module_calendar: Kalender + project_module_documents: Dokumente + project_module_files: Dateien + project_module_gantt: Gantt + project_module_issue_tracking: Ticket-Verfolgung + project_module_news: News + project_module_repository: Projektarchiv + project_module_time_tracking: Zeiterfassung + project_module_wiki: Wiki + project_status_active: aktiv + project_status_archived: archiviert + project_status_closed: geschlossen + + setting_activity_days_default: Anzahl Tage pro Seite der Projekt-Aktivität + setting_app_subtitle: Applikations-Untertitel + setting_app_title: Applikations-Titel + setting_attachment_max_size: Max. Dateigröße + setting_autofetch_changesets: Changesets automatisch abrufen + setting_autologin: Automatische Anmeldung + setting_bcc_recipients: E-Mails als Blindkopie (BCC) senden + setting_cache_formatted_text: Formatierten Text im Cache speichern + setting_commit_cross_project_ref: Erlauben auf Tickets aller anderen Projekte zu referenzieren + setting_commit_fix_keywords: Schlüsselwörter (Status) + setting_commit_logtime_activity_id: Aktivität für die Zeiterfassung + setting_commit_logtime_enabled: Aktiviere Zeitlogging + setting_commit_ref_keywords: Schlüsselwörter (Beziehungen) + setting_cross_project_issue_relations: Ticket-Beziehungen zwischen Projekten erlauben + setting_cross_project_subtasks: Projektübergreifende Unteraufgaben erlauben + setting_date_format: Datumsformat + setting_default_issue_start_date_to_creation_date: Aktuelles Datum als Beginn für neue Tickets verwenden + setting_default_language: Standardsprache + setting_default_notification_option: Standard Benachrichtigungsoptionen + setting_default_projects_modules: Standardmäßig aktivierte Module für neue Projekte + setting_default_projects_public: Neue Projekte sind standardmäßig öffentlich + setting_diff_max_lines_displayed: Maximale Anzahl anzuzeigender Diff-Zeilen + setting_display_subprojects_issues: Tickets von Unterprojekten im Hauptprojekt anzeigen + setting_emails_footer: E-Mail-Fußzeile + setting_emails_header: E-Mail-Kopfzeile + setting_enabled_scm: Aktivierte Versionskontrollsysteme + setting_feeds_limit: Max. Anzahl Einträge pro Atom-Feed + setting_file_max_size_displayed: Maximale Größe inline angezeigter Textdateien + setting_gantt_items_limit: Maximale Anzahl von Aufgaben die im Gantt-Chart angezeigt werden + setting_gravatar_default: Standard-Gravatar-Bild + setting_gravatar_enabled: Gravatar-Benutzerbilder benutzen + setting_host_name: Hostname + setting_issue_done_ratio: Berechne den Ticket-Fortschritt mittels + setting_issue_done_ratio_issue_field: Ticket-Feld %-erledigt + setting_issue_done_ratio_issue_status: Ticket-Status + setting_issue_group_assignment: Ticketzuweisung an Gruppen erlauben + setting_issue_list_default_columns: Standard-Spalten in der Ticket-Auflistung + setting_issues_export_limit: Max. Anzahl Tickets bei CSV/PDF-Export + setting_jsonp_enabled: JSONP Unterstützung aktivieren + setting_login_required: Authentifizierung erforderlich + setting_mail_from: E-Mail-Absender + setting_mail_handler_api_enabled: Abruf eingehender E-Mails aktivieren + setting_mail_handler_api_key: API-Schlüssel + setting_mail_handler_body_delimiters: "Schneide E-Mails nach einer dieser Zeilen ab" + setting_new_project_user_role_id: Rolle, die einem Nicht-Administrator zugeordnet wird, der ein Projekt erstellt + setting_non_working_week_days: Arbeitsfreie Tage + setting_openid: Erlaube OpenID-Anmeldung und -Registrierung + setting_password_min_length: Mindestlänge des Kennworts + setting_per_page_options: Objekte pro Seite + setting_plain_text_mail: Nur reinen Text (kein HTML) senden + setting_protocol: Protokoll + setting_repositories_encodings: Enkodierung von Anhängen und Repositories + setting_repository_log_display_limit: Maximale Anzahl anzuzeigender Revisionen in der Historie einer Datei + setting_rest_api_enabled: REST-Schnittstelle aktivieren + setting_self_registration: Anmeldung ermöglicht + setting_sequential_project_identifiers: Fortlaufende Projektkennungen generieren + setting_session_lifetime: Längste Dauer einer Sitzung + setting_session_timeout: Zeitüberschreitung bei Inaktivität + setting_start_of_week: Wochenanfang + setting_sys_api_enabled: Webservice zur Verwaltung der Projektarchive benutzen + setting_text_formatting: Textformatierung + setting_thumbnails_enabled: Vorschaubilder von Dateianhängen anzeigen + setting_thumbnails_size: Größe der Vorschaubilder (in Pixel) + setting_time_format: Zeitformat + setting_unsubscribe: Erlaubt Benutzern das eigene Benutzerkonto zu löschen + setting_user_format: Benutzer-Anzeigeformat + setting_welcome_text: Willkommenstext + setting_wiki_compression: Wiki-Historie komprimieren + setting_default_projects_tracker_ids: Standardmäßig aktivierte Tracker für neue Projekte + + status_active: aktiv + status_locked: gesperrt + status_registered: nicht aktivierte + + text_account_destroy_confirmation: Möchten Sie wirklich fortfahren?\nIhr Benutzerkonto wird für immer gelöscht und kann nicht wiederhergestellt werden. + text_are_you_sure: Sind Sie sicher? + text_assign_time_entries_to_project: Gebuchte Aufwände dem Projekt zuweisen + text_caracters_maximum: "Max. %{count} Zeichen." + text_caracters_minimum: "Muss mindestens %{count} Zeichen lang sein." + text_comma_separated: Mehrere Werte erlaubt (durch Komma getrennt). + text_custom_field_possible_values_info: 'Eine Zeile pro Wert' + text_default_administrator_account_changed: Administrator-Kennwort geändert + text_destroy_time_entries: Gebuchte Aufwände löschen + text_destroy_time_entries_question: Es wurden bereits %{hours} Stunden auf dieses Ticket gebucht. Was soll mit den Aufwänden geschehen? + text_diff_truncated: '... Dieser Diff wurde abgeschnitten, weil er die maximale Anzahl anzuzeigender Zeilen überschreitet.' + 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/configuration.yml vor und starten Sie die Applikation neu." + text_enumeration_category_reassign_to: 'Die Objekte stattdessen diesem Wert zuordnen:' + text_enumeration_destroy_question: "%{count} Objekt(e) sind diesem Wert zugeordnet." + text_file_repository_writable: Verzeichnis für Dateien beschreibbar + text_git_repository_note: Repository steht für sich alleine (bare) und liegt lokal (z.B. /gitrepo, c:\gitrepo) + text_issue_added: "Ticket %{id} wurde erstellt von %{author}." + text_issue_category_destroy_assignments: Kategorie-Zuordnung entfernen + text_issue_category_destroy_question: "Einige Tickets (%{count}) sind dieser Kategorie zugeodnet. Was möchten Sie tun?" + text_issue_category_reassign_to: Tickets dieser Kategorie zuordnen + text_issue_conflict_resolution_add_notes: Meine Änderungen übernehmen und alle anderen Änderungen verwerfen + text_issue_conflict_resolution_cancel: Meine Änderungen verwerfen und %{link} neu anzeigen + text_issue_conflict_resolution_overwrite: Meine Änderungen trotzdem übernehmen (vorherige Notizen bleiben erhalten aber manche können überschrieben werden) + text_issue_updated: "Ticket %{id} wurde aktualisiert von %{author}." + text_issues_destroy_confirmation: 'Sind Sie sicher, dass Sie die ausgewählten Tickets löschen möchten?' + text_issues_destroy_descendants_confirmation: Dies wird auch %{count} Unteraufgabe/n löschen. + text_issues_ref_in_commit_messages: Ticket-Beziehungen und -Status in Commit-Log-Meldungen + text_journal_added: "%{label} %{value} wurde hinzugefügt" + text_journal_changed: "%{label} wurde von %{old} zu %{new} geändert" + text_journal_changed_no_detail: "%{label} aktualisiert" + text_journal_deleted: "%{label} %{old} wurde gelöscht" + text_journal_set_to: "%{label} wurde auf %{value} gesetzt" + text_length_between: "Länge zwischen %{min} und %{max} Zeichen." + text_line_separated: Mehrere Werte sind erlaubt (eine Zeile pro Wert). + text_load_default_configuration: Standard-Konfiguration laden + text_mercurial_repository_note: Lokales repository (e.g. /hgrepo, c:\hgrepo) + text_min_max_length_info: 0 heißt keine Beschränkung + text_no_configuration_data: "Rollen, Tracker, Ticket-Status und Workflows wurden noch nicht konfiguriert.\nEs ist sehr zu empfehlen, die Standard-Konfiguration zu laden. Sobald sie geladen ist, können Sie sie abändern." + text_own_membership_delete_confirmation: "Sie sind dabei, einige oder alle Ihre Berechtigungen zu entfernen. Es ist möglich, dass Sie danach das Projekt nicht mehr ansehen oder bearbeiten dürfen.\nSind Sie sicher, dass Sie dies tun möchten?" + text_plugin_assets_writable: Verzeichnis für Plugin-Assets beschreibbar + text_project_closed: Dieses Projekt ist geschlossen und kann nicht bearbeitet werden. + text_project_destroy_confirmation: Sind Sie sicher, dass Sie das Projekt löschen wollen? + text_project_identifier_info: 'Kleinbuchstaben (a-z), Ziffern, Binde- und Unterstriche erlaubt, muss mit einem Kleinbuchstaben beginnen.
Einmal gespeichert, kann die Kennung nicht mehr geändert werden.' + text_reassign_time_entries: 'Gebuchte Aufwände diesem Ticket zuweisen:' + text_regexp_info: z. B. ^[A-Z0-9]+$ + text_repository_identifier_info: 'Kleinbuchstaben (a-z), Ziffern, Binde- und Unterstriche erlaubt.
Einmal gespeichert, kann die Kennung nicht mehr geändert werden.' + 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_rmagick_available: RMagick verfügbar (optional) + text_scm_command: Kommando + text_scm_command_not_available: SCM-Kommando ist nicht verfügbar. Bitte prüfen Sie die Einstellungen im Administrationspanel. + text_scm_command_version: Version + text_scm_config: Die SCM-Kommandos können in der in config/configuration.yml konfiguriert werden. Redmine muss anschließend neu gestartet werden. + text_scm_path_encoding_note: "Standard: UTF-8" + text_select_mail_notifications: Bitte wählen Sie die Aktionen aus, für die eine Mailbenachrichtigung gesendet werden soll. + text_select_project_modules: 'Bitte wählen Sie die Module aus, die in diesem Projekt aktiviert sein sollen:' + text_session_expiration_settings: "Achtung: Änderungen können aktuelle Sitzungen beenden, Ihre eingeschlossen!" + text_status_changed_by_changeset: "Status geändert durch Changeset %{value}." + text_subprojects_destroy_warning: "Dessen Unterprojekte (%{value}) werden ebenfalls gelöscht." + text_time_entries_destroy_confirmation: Sind Sie sicher, dass Sie die ausgewählten Zeitaufwände löschen möchten? + text_time_logged_by_changeset: Angewendet in Changeset %{value}. + text_tip_issue_begin_day: Aufgabe, die an diesem Tag beginnt + text_tip_issue_begin_end_day: Aufgabe, die an diesem Tag beginnt und endet + text_tip_issue_end_day: Aufgabe, die an diesem Tag endet + text_tracker_no_workflow: Kein Workflow für diesen Tracker definiert. + text_turning_multiple_off: Wenn Sie die Mehrfachauswahl deaktivieren, werden Felder mit Mehrfachauswahl bereinigt. + Dadurch wird sichergestellt, dass lediglich ein Wert pro Feld ausgewählt ist. + text_unallowed_characters: Nicht erlaubte Zeichen + text_user_mail_option: "Für nicht ausgewählte Projekte werden Sie nur Benachrichtigungen für Dinge erhalten, die Sie beobachten oder an denen Sie beteiligt sind (z. B. Tickets, deren Autor Sie sind oder die Ihnen zugewiesen sind)." + text_user_wrote: "%{value} schrieb:" + text_warn_on_leaving_unsaved: Die aktuellen Änderungen gehen verloren, wenn Sie diese Seite verlassen. + text_wiki_destroy_confirmation: Sind Sie sicher, dass Sie dieses Wiki mit sämtlichem Inhalt löschen möchten? + text_wiki_page_destroy_children: Lösche alle Unterseiten + text_wiki_page_destroy_question: "Diese Seite hat %{descendants} Unterseite(n). Was möchten Sie tun?" + text_wiki_page_nullify_children: Verschiebe die Unterseiten auf die oberste Ebene + text_wiki_page_reassign_children: Ordne die Unterseiten dieser Seite zu + text_workflow_edit: Workflow zum Bearbeiten auswählen + text_zoom_in: Ansicht vergrößern + text_zoom_out: Ansicht verkleinern + + version_status_closed: abgeschlossen + version_status_locked: gesperrt + version_status_open: offen + + warning_attachments_not_saved: "%{count} Datei(en) konnten nicht gespeichert werden." + label_total_time: Gesamtzeit diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/32/3298e1c1ddf5d23eff798c7cd5979f9e2dc8ea9f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/32/3298e1c1ddf5d23eff798c7cd5979f9e2dc8ea9f.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,212 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +begin + require 'mocha' +rescue + # Won't run some tests +end + +class AccountTest < ActionController::IntegrationTest + fixtures :users, :roles + + # Replace this with your real tests. + def test_login + get "my/page" + assert_redirected_to "/login?back_url=http%3A%2F%2Fwww.example.com%2Fmy%2Fpage" + log_user('jsmith', 'jsmith') + + get "my/account" + assert_response :success + assert_template "my/account" + end + + def test_autologin + user = User.find(1) + Setting.autologin = "7" + Token.delete_all + + # User logs in with 'autologin' checked + post '/login', :username => user.login, :password => 'admin', :autologin => 1 + assert_redirected_to '/my/page' + token = Token.first + assert_not_nil token + assert_equal user, token.user + assert_equal 'autologin', token.action + assert_equal user.id, session[:user_id] + assert_equal token.value, cookies['autologin'] + + # Session is cleared + reset! + User.current = nil + # Clears user's last login timestamp + user.update_attribute :last_login_on, nil + assert_nil user.reload.last_login_on + + # User comes back with his autologin cookie + cookies[:autologin] = token.value + get '/my/page' + assert_response :success + assert_template 'my/page' + assert_equal user.id, session[:user_id] + assert_not_nil user.reload.last_login_on + end + + def test_autologin_should_use_autologin_cookie_name + Token.delete_all + Redmine::Configuration.stubs(:[]).with('autologin_cookie_name').returns('custom_autologin') + Redmine::Configuration.stubs(:[]).with('autologin_cookie_path').returns('/') + Redmine::Configuration.stubs(:[]).with('autologin_cookie_secure').returns(false) + + with_settings :autologin => '7' do + assert_difference 'Token.count' do + post '/login', :username => 'admin', :password => 'admin', :autologin => 1 + end + assert_response 302 + assert cookies['custom_autologin'].present? + token = cookies['custom_autologin'] + + # Session is cleared + reset! + cookies['custom_autologin'] = token + get '/my/page' + assert_response :success + + assert_difference 'Token.count', -1 do + post '/logout' + end + assert cookies['custom_autologin'].blank? + end + end + + def test_lost_password + Token.delete_all + + get "account/lost_password" + assert_response :success + assert_template "account/lost_password" + assert_select 'input[name=mail]' + + post "account/lost_password", :mail => 'jSmith@somenet.foo' + assert_redirected_to "/login" + + token = Token.first + assert_equal 'recovery', token.action + assert_equal 'jsmith@somenet.foo', token.user.mail + assert !token.expired? + + get "account/lost_password", :token => token.value + assert_response :success + assert_template "account/password_recovery" + assert_select 'input[type=hidden][name=token][value=?]', token.value + assert_select 'input[name=new_password]' + assert_select 'input[name=new_password_confirmation]' + + post "account/lost_password", :token => token.value, :new_password => 'newpass123', :new_password_confirmation => 'newpass123' + assert_redirected_to "/login" + assert_equal 'Password was successfully updated.', flash[:notice] + + log_user('jsmith', 'newpass123') + assert_equal 0, Token.count + end + + def test_register_with_automatic_activation + Setting.self_registration = '3' + + get 'account/register' + assert_response :success + assert_template 'account/register' + + post 'account/register', :user => {:login => "newuser", :language => "en", :firstname => "New", :lastname => "User", :mail => "newuser@foo.bar", + :password => "newpass123", :password_confirmation => "newpass123"} + assert_redirected_to '/my/account' + follow_redirect! + assert_response :success + assert_template 'my/account' + + user = User.find_by_login('newuser') + assert_not_nil user + assert user.active? + assert_not_nil user.last_login_on + end + + def test_register_with_manual_activation + Setting.self_registration = '2' + + post 'account/register', :user => {:login => "newuser", :language => "en", :firstname => "New", :lastname => "User", :mail => "newuser@foo.bar", + :password => "newpass123", :password_confirmation => "newpass123"} + assert_redirected_to '/login' + assert !User.find_by_login('newuser').active? + end + + def test_register_with_email_activation + Setting.self_registration = '1' + Token.delete_all + + post 'account/register', :user => {:login => "newuser", :language => "en", :firstname => "New", :lastname => "User", :mail => "newuser@foo.bar", + :password => "newpass123", :password_confirmation => "newpass123"} + assert_redirected_to '/login' + assert !User.find_by_login('newuser').active? + + token = Token.first + assert_equal 'register', token.action + assert_equal 'newuser@foo.bar', token.user.mail + assert !token.expired? + + get 'account/activate', :token => token.value + assert_redirected_to '/login' + log_user('newuser', 'newpass123') + end + + def test_onthefly_registration + # disable registration + Setting.self_registration = '0' + AuthSource.expects(:authenticate).returns({:login => 'foo', :firstname => 'Foo', :lastname => 'Smith', :mail => 'foo@bar.com', :auth_source_id => 66}) + + post '/login', :username => 'foo', :password => 'bar' + assert_redirected_to '/my/page' + + user = User.find_by_login('foo') + assert user.is_a?(User) + assert_equal 66, user.auth_source_id + assert user.hashed_password.blank? + end + + def test_onthefly_registration_with_invalid_attributes + # disable registration + Setting.self_registration = '0' + AuthSource.expects(:authenticate).returns({:login => 'foo', :lastname => 'Smith', :auth_source_id => 66}) + + post '/login', :username => 'foo', :password => 'bar' + assert_response :success + assert_template 'account/register' + assert_tag :input, :attributes => { :name => 'user[firstname]', :value => '' } + assert_tag :input, :attributes => { :name => 'user[lastname]', :value => 'Smith' } + assert_no_tag :input, :attributes => { :name => 'user[login]' } + assert_no_tag :input, :attributes => { :name => 'user[password]' } + + post 'account/register', :user => {:firstname => 'Foo', :lastname => 'Smith', :mail => 'foo@bar.com'} + assert_redirected_to '/my/account' + + user = User.find_by_login('foo') + assert user.is_a?(User) + assert_equal 66, user.auth_source_id + assert user.hashed_password.blank? + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/32/32f548872e7dda9a87d4e449afcdf5184ca9059d.svn-base --- a/.svn/pristine/32/32f548872e7dda9a87d4e449afcdf5184ca9059d.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,87 +0,0 @@ -// ** I18N - -// Calendar EN language -// Author: Mihai Bazon, -// 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 ("日曜日", "月曜日", "ç«æ›œæ—¥", "水曜日", "木曜日", "金曜日", "土曜日"); - -// 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 = 0; - -// full month names -Calendar._MN = new Array ("1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"); - -// short month names -Calendar._SMN = new Array ("1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"); - -// 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" + -"日付ã®é¸æŠžæ–¹æ³•:\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."; - -Calendar._TT["PREV_YEAR"] = "å‰å¹´ (長押ã—ã§ãƒ¡ãƒ‹ãƒ¥ãƒ¼è¡¨ç¤º)"; -Calendar._TT["PREV_MONTH"] = "剿œˆ (長押ã—ã§ãƒ¡ãƒ‹ãƒ¥ãƒ¼è¡¨ç¤º)"; -Calendar._TT["GO_TODAY"] = "ä»Šæ—¥ã®æ—¥ä»˜ã‚’é¸æŠž"; -Calendar._TT["NEXT_MONTH"] = "翌月 (長押ã—ã§ãƒ¡ãƒ‹ãƒ¥ãƒ¼è¡¨ç¤º)"; -Calendar._TT["NEXT_YEAR"] = "翌年 (長押ã—ã§ãƒ¡ãƒ‹ãƒ¥ãƒ¼è¡¨ç¤º)"; -Calendar._TT["SEL_DATE"] = "æ—¥ä»˜ã‚’é¸æŠžã—ã¦ãã ã•ã„"; -Calendar._TT["DRAG_TO_MOVE"] = "ドラッグã§ç§»å‹•"; -Calendar._TT["PART_TODAY"] = " (今日)"; - -// the following is to inform that "%s" is to be the first day of week -// %s will be replaced with the day name. -Calendar._TT["DAY_FIRST"] = "%så§‹ã¾ã‚Šã§è¡¨ç¤º"; - -// This may be locale-dependent. It specifies the week-end days, as an array -// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 -// means Monday, etc. -Calendar._TT["WEEKEND"] = "0,6"; - -Calendar._TT["CLOSE"] = "é–‰ã˜ã‚‹"; -Calendar._TT["TODAY"] = "今日"; -Calendar._TT["TIME_PART"] = "(Shift-)Click or drag to change value"; - -// date formats -Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; -Calendar._TT["TT_DATE_FORMAT"] = "%b%eæ—¥(%a)"; - -Calendar._TT["WK"] = "週"; -Calendar._TT["TIME"] = "Time:"; diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/33/3310ce8725bdb75d262ab89bf521469eb429b588.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/33/3310ce8725bdb75d262ab89bf521469eb429b588.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,771 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 'active_record' +require 'iconv' if RUBY_VERSION < '1.9' +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) + feedback_status = IssueStatus.find_by_position(4) + closed_status = IssueStatus.where(:is_closed => true).first + STATUS_MAPPING = {'new' => DEFAULT_STATUS, + 'reopened' => feedback_status, + 'assigned' => assigned_status, + 'closed' => closed_status + } + + priorities = IssuePriority.all + DEFAULT_PRIORITY = priorities[0] + PRIORITY_MAPPING = {'lowest' => priorities[0], + 'low' => priorities[0], + 'normal' => priorities[1], + 'high' => priorities[2], + 'highest' => priorities[3], + # --- + 'trivial' => priorities[0], + 'minor' => priorities[1], + 'major' => priorities[2], + 'critical' => priorities[3], + 'blocker' => priorities[4] + } + + TRACKER_BUG = Tracker.find_by_position(1) + TRACKER_FEATURE = Tracker.find_by_position(2) + DEFAULT_TRACKER = TRACKER_BUG + TRACKER_MAPPING = {'defect' => TRACKER_BUG, + 'enhancement' => TRACKER_FEATURE, + 'task' => TRACKER_FEATURE, + 'patch' =>TRACKER_FEATURE + } + + roles = Role.where(:builtin => 0).order('position ASC').all + manager_role = roles[0] + developer_role = roles[1] + DEFAULT_ROLE = roles.last + ROLE_MAPPING = {'admin' => manager_role, + 'developer' => developer_role + } + + class ::Time + class << self + alias :real_now :now + def now + real_now - @fake_diff.to_i + end + def fake(time) + @fake_diff = real_now - time + res = yield + @fake_diff = 0 + res + end + end + end + + class TracComponent < ActiveRecord::Base + self.table_name = :component + end + + class TracMilestone < ActiveRecord::Base + self.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 + else + 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 + self.table_name = :ticket_custom + end + + class TracAttachment < ActiveRecord::Base + self.table_name = :attachment + set_inheritance_column :none + + def time; Time.at(read_attribute(:time)) end + + def original_filename + filename + end + + def content_type + '' + end + + def exist? + File.file? trac_fullpath + end + + def open + File.open("#{trac_fullpath}", 'rb') {|f| + @file = f + yield self + } + end + + def read(*args) + @file.read(*args) + end + + def description + read_attribute(:description).to_s.slice(0,255) + end + + private + def trac_fullpath + attachment_type = read_attribute(:type) + trac_file = filename.gsub( /[^a-zA-Z0-9\-_\.!~*']/n ) {|x| sprintf('%%%02x', x[0]) } + "#{TracMigrate.trac_attachments_directory}/#{attachment_type}/#{id}/#{trac_file}" + end + end + + class TracTicket < ActiveRecord::Base + self.table_name = :ticket + set_inheritance_column :none + + # ticket changes: only migrate status changes and comments + has_many :ticket_changes, :class_name => "TracTicketChange", :foreign_key => :ticket + has_many :customs, :class_name => "TracTicketCustom", :foreign_key => :ticket + + def attachments + TracMigrate::TracAttachment.all(:conditions => ["type = 'ticket' AND id = ?", self.id.to_s]) + end + + 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 + self.table_name = :ticket_change + + def self.columns + # Hides Trac field 'field' to prevent clash with AR field_changed? method (Rails 3.0) + super.select {|column| column.name.to_s != 'field'} + end + + 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 \ + TracReports TracRevisionLog TracRoadmap TracRss TracSearch TracStandalone TracSupport TracSyntaxColoring TracTickets \ + TracTicketsCustomFields TracTimeline TracUnicode TracUpgrade TracWiki WikiDeletePage WikiFormatting \ + WikiHtml WikiMacros WikiNewPage WikiPageNames WikiProcessors WikiRestructuredText WikiRestructuredTextLinks \ + CamelCase TitleIndex) + + class TracWikiPage < ActiveRecord::Base + self.table_name = :wiki + set_primary_key :name + + 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 attachments + TracMigrate::TracAttachment.all(:conditions => ["type = 'wiki' AND id = ?", self.id.to_s]) + end + + def time; Time.at(read_attribute(:time)) end + end + + class TracPermission < ActiveRecord::Base + self.table_name = :permission + end + + class TracSessionAttribute < ActiveRecord::Base + self.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 + mail = username[0, User::MAIL_LENGTH_LIMIT] + if mail_attr = TracSessionAttribute.find_by_sid_and_name(username, 'email') + 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 + end + name =~ (/(.*)(\s+\w+)?/) + fn = $1.strip + ln = ($2 || '-').strip + + u = User.new :mail => mail.gsub(/[^-@a-z0-9\.]/i, '-'), + :firstname => fn[0, limit_for(User, 'firstname')], + :lastname => ln[0, limit_for(User, 'lastname')] + + u.login = username[0, User::LOGIN_LENGTH_LIMIT].gsub(/[^a-z0-9_\-@\.]/i, '-') + u.password = 'trac' + u.admin = true if TracPermission.find_by_username_and_action(username, 'admin') + # finally, a default user is used if the new user is not valid + u = User.first unless u.save + end + # Make sure he is a member of the project + if project_member && !u.member_of?(@target_project) + role = DEFAULT_ROLE + if u.admin + role = ROLE_MAPPING['admin'] + elsif TracPermission.find_by_username_and_action(username, 'developer') + role = ROLE_MAPPING['developer'] + end + Member.create(:user => u, :project => @target_project, :roles => [role]) + u.reload + 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(',./?;|:')}]]"} + text = text.gsub(/\[wiki:\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"} + text = text.gsub(/\[wiki:\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"} + text = text.gsub(/\[wiki:([^\s\]]+)\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"} + text = text.gsub(/\[wiki:([^\s\]]+)\s(.*)\]/) {|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') + # Revisions links + text = text.gsub(/\[(\d+)\]/, 'r\1') + # Ticket number re-writing + text = text.gsub(/#(\d+)/) do |s| + if $1.length < 10 +# TICKET_MAP[$1.to_i] ||= $1 + "\##{TICKET_MAP[$1.to_i] || $1}" + else + s + end + end + # 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 = '
'
+          else
+            m_sl = shebang_re.match(line)
+            if m_sl
+              shebang_line = true
+              line = ''
+            end
+            m_pre_end = pre_end_re.match(line)
+            if m_pre_end
+              line = '
' + if shebang_line + line = '' + line + end + end + end + line + end + + # Highlighting + text = text.gsub(/'''''([^\s])/, '_*\1') + text = text.gsub(/([^\s])'''''/, '\1*_') + text = text.gsub(/'''/, '*') + 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_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.all.each do |component| + 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 + end + puts + + # Milestones + print "Migrating milestones" + version_map = {} + TracMilestone.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 => 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" + custom_field_map = {} + TracTicketCustom.find_by_sql("SELECT DISTINCT name FROM #{TracTicketCustom.table_name}").each do |field| + print '.' + STDOUT.flush + # Redmine custom field name + field_name = encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize + # Find if the custom already exists in Redmine + f = IssueCustomField.find_by_name(field_name) + # 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.all + f.projects << @target_project + custom_field_map[field.name] = f + end + puts + + # Trac 'resolution' field as a Redmine custom field + r = IssueCustomField.where(:name => "Resolution").first + r = IssueCustomField.new(:name => 'Resolution', + :field_format => 'list', + :is_filter => true) if r.nil? + r.trackers = Tracker.all + r.projects << @target_project + 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_each(:batch_size => 200) do |ticket| + 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.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.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 && + STATUS_MAPPING[status_change.oldvalue] && + STATUS_MAPPING[status_change.newvalue] && + (STATUS_MAPPING[status_change.oldvalue] != STATUS_MAPPING[status_change.newvalue]) + n.details << JournalDetail.new(:property => 'attr', + :prop_key => 'status_id', + :old_value => STATUS_MAPPING[status_change.oldvalue].id, + :value => STATUS_MAPPING[status_change.newvalue].id) + end + if resolution_change + n.details << JournalDetail.new(:property => 'cf', + :prop_key => custom_field_map['resolution'].id, + :old_value => resolution_change.oldvalue, + :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? + attachment.open { + 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 + 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 + 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 + print "Migrating wiki" + if wiki.save + TracWikiPage.order('name, version').all.each do |page| + # Do not migrate Trac manual wiki pages + next if TRAC_WIKI_PAGES.include?(page.name) + wiki_edit_count += 1 + print '.' + STDOUT.flush + p = wiki.find_or_new_page(page.name) + p.content = WikiContent.new(:page => p) if p.new_record? + p.content.text = page.text + 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 + + # Attachments + page.attachments.each do |attachment| + next unless attachment.exist? + next if p.attachments.find_by_filename(attachment.filename.gsub(/^.*(\\|\/)/, '').gsub(/[^\w\.\-]/,'_')) #add only once per page + attachment.open { + a = Attachment.new :created_on => attachment.time + a.file = attachment + a.author = find_or_create_user(attachment.author) + a.description = attachment.description + a.container = p + migrated_wiki_attachments += 1 if a.save + } + end + end + + wiki.reload + wiki.pages.each do |page| + page.content.text = convert_wiki_text(page.content.text) + Time.fake(page.content.updated_on) { page.content.save } + end + end + puts + + puts + puts "Components: #{migrated_components}/#{TracComponent.count}" + puts "Milestones: #{migrated_milestones}/#{TracMilestone.count}" + puts "Tickets: #{migrated_tickets}/#{TracTicket.count}" + puts "Ticket files: #{migrated_ticket_attachments}/" + TracAttachment.count(:conditions => {:type => 'ticket'}).to_s + puts "Custom values: #{migrated_custom_values}/#{TracTicketCustom.count}" + 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) + @charset = charset + end + + def self.set_trac_directory(path) + @@trac_directory = path + raise "This directory doesn't exist!" unless File.directory?(path) + raise "#{trac_attachments_directory} doesn't exist!" unless File.directory?(trac_attachments_directory) + @@trac_directory + rescue Exception => e + puts e + return false + end + + def self.trac_directory + @@trac_directory + end + + def self.set_trac_adapter(adapter) + return false if adapter.blank? + raise "Unknown adapter: #{adapter}!" unless %w(sqlite3 mysql postgresql).include?(adapter) + # If adapter is sqlite or sqlite3, make sure that trac.db exists + raise "#{trac_db_path} doesn't exist!" if %w(sqlite3).include?(adapter) && !File.exist?(trac_db_path) + @@trac_adapter = adapter + rescue Exception => e + puts e + return false + end + + def self.set_trac_db_host(host) + return nil if host.blank? + @@trac_db_host = host + end + + def self.set_trac_db_port(port) + 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 + end + + 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) + if !project + # create the target project + project = Project.new :name => identifier.humanize, + :description => '' + project.identifier = identifier + puts "Unable to create a project with identifier '#{identifier}'!" unless project.save + # enable issues and wiki for the created project + project.enabled_module_names = ['issue_tracking', 'wiki'] + else + puts + puts "This project already exists in your Redmine database." + print "Are you sure you want to append data to this project ? [Y/n] " + STDOUT.flush + 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 + @target_project.reload + end + + def self.connection_params + if trac_adapter == 'sqlite3' + {:adapter => 'sqlite3', + :database => trac_db_path} + else + {:adapter => trac_adapter, + :database => trac_db_name, + :host => trac_db_host, + :port => trac_db_port, + :username => trac_db_username, + :password => trac_db_password, + :schema_search_path => trac_db_schema + } + end + end + + def self.establish_connection + constants.each do |const| + klass = const_get(const) + next unless klass.respond_to? 'establish_connection' + klass.establish_connection connection_params + end + end + + def self.encode(text) + if RUBY_VERSION < '1.9' + @ic ||= Iconv.new('UTF-8', @charset) + @ic.iconv text + else + text.to_s.force_encoding(@charset).encode('UTF-8') + end + end + end + + puts + if Redmine::DefaultData::Loader.no_data? + puts "Redmine configuration need to be loaded before importing data." + puts "Please, run this first:" + puts + 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] " + STDOUT.flush + break unless STDIN.gets.match(/^y$/i) + puts + + def prompt(text, options = {}, &block) + default = options[:default] || '' + while true + print "#{text} [#{default}]: " + STDOUT.flush + value = STDIN.gets.chomp! + value = default if value.blank? + 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 (sqlite3, mysql2, postgresql)', :default => 'sqlite3') {|adapter| TracMigrate.set_trac_adapter adapter} + unless %w(sqlite3).include?(TracMigrate.trac_adapter) + prompt('Trac database host', :default => 'localhost') {|host| TracMigrate.set_trac_db_host host} + prompt('Trac database port', :default => DEFAULT_PORTS[TracMigrate.trac_adapter]) {|port| TracMigrate.set_trac_db_port port} + prompt('Trac database name') {|name| TracMigrate.set_trac_db_name name} + prompt('Trac database schema', :default => 'public') {|schema| TracMigrate.set_trac_db_schema schema} + prompt('Trac database username') {|username| TracMigrate.set_trac_db_username username} + prompt('Trac database password') {|password| TracMigrate.set_trac_db_password password} + end + prompt('Trac database encoding', :default => 'UTF-8') {|encoding| TracMigrate.encoding encoding} + prompt('Target project identifier') {|identifier| TracMigrate.target_project_identifier identifier} + puts + + # Turn off email notifications + Setting.notified_events = [] + + TracMigrate.migrate + end +end + diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/33/332beac4e5a854120b53b926beb38dfe9376b34a.svn-base --- a/.svn/pristine/33/332beac4e5a854120b53b926beb38dfe9376b34a.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,72 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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 Search - - mattr_accessor :available_search_types - - @@available_search_types = [] - - class << self - def map(&block) - yield self - end - - # Registers a search provider - def register(search_type, options={}) - search_type = search_type.to_s - @@available_search_types << search_type unless @@available_search_types.include?(search_type) - end - end - - module Controller - def self.included(base) - base.extend(ClassMethods) - end - - module ClassMethods - @@default_search_scopes = Hash.new {|hash, key| hash[key] = {:default => nil, :actions => {}}} - mattr_accessor :default_search_scopes - - # Set the default search scope for a controller or specific actions - # Examples: - # * search_scope :issues # => sets the search scope to :issues for the whole controller - # * search_scope :issues, :only => :index - # * search_scope :issues, :only => [:index, :show] - def default_search_scope(id, options = {}) - if actions = options[:only] - actions = [] << actions unless actions.is_a?(Array) - actions.each {|a| default_search_scopes[controller_name.to_sym][:actions][a.to_sym] = id.to_s} - else - default_search_scopes[controller_name.to_sym][:default] = id.to_s - end - end - end - - def default_search_scopes - self.class.default_search_scopes - end - - # Returns the default search scope according to the current action - def default_search_scope - @default_search_scope ||= default_search_scopes[controller_name.to_sym][:actions][action_name.to_sym] || - default_search_scopes[controller_name.to_sym][:default] - end - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/33/3349206fec5e88c2e11146378e031801f405150d.svn-base --- a/.svn/pristine/33/3349206fec5e88c2e11146378e031801f405150d.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,28 +0,0 @@ -<%= link_to 'root', :action => 'show', :id => @project, :path => '', :rev => @rev %> -<% -dirs = path.split('/') -if 'file' == kind - filename = dirs.pop -end -link_path = '' -dirs.each do |dir| - next if dir.blank? - link_path << '/' unless link_path.empty? - link_path << "#{dir}" - %> - / <%= link_to h(dir), :action => 'show', :id => @project, - :path => to_path_param(link_path), :rev => @rev %> -<% end %> -<% if filename %> - / <%= link_to h(filename), - :action => 'changes', :id => @project, - :path => to_path_param("#{link_path}/#{filename}"), :rev => @rev %> -<% end %> -<% - # @rev is revsion or Git and Mercurial branch or tag. - # For Mercurial *tip*, @rev and @changeset are nil. - rev_text = @changeset.nil? ? @rev : format_revision(@changeset) -%> -<%= "@ #{h rev_text}" unless rev_text.blank? %> - -<% html_title(with_leading_slash(path)) -%> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/33/3357225f79b9b7908f02d51c4dcc557f0e92041e.svn-base --- a/.svn/pristine/33/3357225f79b9b7908f02d51c4dcc557f0e92041e.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,388 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -require 'redmine/scm/adapters/abstract_adapter' - -module Redmine - module Scm - module Adapters - class GitAdapter < AbstractAdapter - - # Git executable name - GIT_BIN = Redmine::Configuration['scm_git_command'] || "git" - - class << self - def client_command - @@bin ||= GIT_BIN - end - - def sq_bin - @@sq_bin ||= shell_quote_command - end - - def client_version - @@client_version ||= (scm_command_version || []) - end - - def client_available - !client_version.empty? - end - - def scm_command_version - scm_version = scm_version_from_command_line.dup - if scm_version.respond_to?(:force_encoding) - scm_version.force_encoding('ASCII-8BIT') - end - if m = scm_version.match(%r{\A(.*?)((\d+\.)+\d+)}) - m[2].scan(%r{\d+}).collect(&:to_i) - end - end - - def scm_version_from_command_line - shellout("#{sq_bin} --version --no-color") { |io| io.read }.to_s - end - end - - def initialize(url, root_url=nil, login=nil, password=nil, path_encoding=nil) - super - @path_encoding = path_encoding.blank? ? 'UTF-8' : path_encoding - end - - def path_encoding - @path_encoding - end - - def info - begin - Info.new(:root_url => url, :lastrev => lastrev('',nil)) - rescue - nil - end - end - - def branches - return @branches if @branches - @branches = [] - cmd_args = %w|branch --no-color --verbose --no-abbrev| - scm_cmd(*cmd_args) do |io| - io.each_line do |line| - branch_rev = line.match('\s*\*?\s*(.*?)\s*([0-9a-f]{40}).*$') - bran = Branch.new(branch_rev[1]) - bran.revision = branch_rev[2] - bran.scmid = branch_rev[2] - @branches << bran - end - end - @branches.sort! - rescue ScmCommandAborted - nil - end - - def tags - return @tags if @tags - cmd_args = %w|tag| - scm_cmd(*cmd_args) do |io| - @tags = io.readlines.sort!.map{|t| t.strip} - end - rescue ScmCommandAborted - nil - end - - def default_branch - bras = self.branches - return nil if bras.nil? - bras.include?('master') ? 'master' : bras.first - end - - def entry(path=nil, identifier=nil) - parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?} - search_path = parts[0..-2].join('/') - search_name = parts[-1] - if search_path.blank? && search_name.blank? - # Root entry - Entry.new(:path => '', :kind => 'dir') - else - # Search for the entry in the parent directory - es = entries(search_path, identifier, - options = {:report_last_commit => false}) - es ? es.detect {|e| e.name == search_name} : nil - end - end - - def entries(path=nil, identifier=nil, options={}) - path ||= '' - p = scm_iconv(@path_encoding, 'UTF-8', path) - entries = Entries.new - cmd_args = %w|ls-tree -l| - cmd_args << "HEAD:#{p}" if identifier.nil? - cmd_args << "#{identifier}:#{p}" if identifier - scm_cmd(*cmd_args) do |io| - io.each_line do |line| - e = line.chomp.to_s - if e =~ /^\d+\s+(\w+)\s+([0-9a-f]{40})\s+([0-9-]+)\t(.+)$/ - type = $1 - sha = $2 - size = $3 - name = $4 - if name.respond_to?(:force_encoding) - name.force_encoding(@path_encoding) - end - full_path = p.empty? ? name : "#{p}/#{name}" - n = scm_iconv('UTF-8', @path_encoding, name) - full_p = scm_iconv('UTF-8', @path_encoding, full_path) - entries << Entry.new({:name => n, - :path => full_p, - :kind => (type == "tree") ? 'dir' : 'file', - :size => (type == "tree") ? nil : size, - :lastrev => options[:report_last_commit] ? - lastrev(full_path, identifier) : Revision.new - }) unless entries.detect{|entry| entry.name == name} - end - end - end - entries.sort_by_name - rescue ScmCommandAborted - nil - end - - def lastrev(path, rev) - return nil if path.nil? - cmd_args = %w|log --no-color --encoding=UTF-8 --date=iso --pretty=fuller --no-merges -n 1| - cmd_args << rev if rev - cmd_args << "--" << path unless path.empty? - lines = [] - scm_cmd(*cmd_args) { |io| lines = io.readlines } - begin - id = lines[0].split[1] - author = lines[1].match('Author:\s+(.*)$')[1] - time = Time.parse(lines[4].match('CommitDate:\s+(.*)$')[1]) - - Revision.new({ - :identifier => id, - :scmid => id, - :author => author, - :time => time, - :message => nil, - :paths => nil - }) - rescue NoMethodError => e - logger.error("The revision '#{path}' has a wrong format") - return nil - end - rescue ScmCommandAborted - nil - end - - def revisions(path, identifier_from, identifier_to, options={}) - revs = Revisions.new - cmd_args = %w|log --no-color --encoding=UTF-8 --raw --date=iso --pretty=fuller --parents| - cmd_args << "--reverse" if options[:reverse] - cmd_args << "--all" if options[:all] - cmd_args << "-n" << "#{options[:limit].to_i}" if options[:limit] - from_to = "" - from_to << "#{identifier_from}.." if identifier_from - from_to << "#{identifier_to}" if identifier_to - cmd_args << from_to if !from_to.empty? - cmd_args << "--since=#{options[:since].strftime("%Y-%m-%d %H:%M:%S")}" if options[:since] - cmd_args << "--" << scm_iconv(@path_encoding, 'UTF-8', path) if path && !path.empty? - - scm_cmd *cmd_args do |io| - files=[] - changeset = {} - parsing_descr = 0 #0: not parsing desc or files, 1: parsing desc, 2: parsing files - - io.each_line do |line| - if line =~ /^commit ([0-9a-f]{40})(( [0-9a-f]{40})*)$/ - key = "commit" - value = $1 - parents_str = $2 - if (parsing_descr == 1 || parsing_descr == 2) - parsing_descr = 0 - revision = Revision.new({ - :identifier => changeset[:commit], - :scmid => changeset[:commit], - :author => changeset[:author], - :time => Time.parse(changeset[:date]), - :message => changeset[:description], - :paths => files, - :parents => changeset[:parents] - }) - if block_given? - yield revision - else - revs << revision - end - changeset = {} - files = [] - end - changeset[:commit] = $1 - unless parents_str.nil? or parents_str == "" - changeset[:parents] = parents_str.strip.split(' ') - end - elsif (parsing_descr == 0) && line =~ /^(\w+):\s*(.*)$/ - key = $1 - value = $2 - if key == "Author" - changeset[:author] = value - elsif key == "CommitDate" - changeset[:date] = value - end - elsif (parsing_descr == 0) && line.chomp.to_s == "" - parsing_descr = 1 - changeset[:description] = "" - elsif (parsing_descr == 1 || parsing_descr == 2) \ - && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\t(.+)$/ - parsing_descr = 2 - fileaction = $1 - filepath = $2 - p = scm_iconv('UTF-8', @path_encoding, filepath) - files << {:action => fileaction, :path => p} - elsif (parsing_descr == 1 || parsing_descr == 2) \ - && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\d+\s+(\S+)\t(.+)$/ - parsing_descr = 2 - fileaction = $1 - filepath = $3 - p = scm_iconv('UTF-8', @path_encoding, filepath) - files << {:action => fileaction, :path => p} - elsif (parsing_descr == 1) && line.chomp.to_s == "" - parsing_descr = 2 - elsif (parsing_descr == 1) - changeset[:description] << line[4..-1] - end - end - - if changeset[:commit] - revision = Revision.new({ - :identifier => changeset[:commit], - :scmid => changeset[:commit], - :author => changeset[:author], - :time => Time.parse(changeset[:date]), - :message => changeset[:description], - :paths => files, - :parents => changeset[:parents] - }) - if block_given? - yield revision - else - revs << revision - end - end - end - revs - rescue ScmCommandAborted => e - logger.error("git log #{from_to.to_s} error: #{e.message}") - revs - end - - def diff(path, identifier_from, identifier_to=nil) - path ||= '' - cmd_args = [] - if identifier_to - cmd_args << "diff" << "--no-color" << identifier_to << identifier_from - else - cmd_args << "show" << "--no-color" << identifier_from - end - cmd_args << "--" << scm_iconv(@path_encoding, 'UTF-8', path) unless path.empty? - diff = [] - scm_cmd *cmd_args do |io| - io.each_line do |line| - diff << line - end - end - diff - rescue ScmCommandAborted - nil - end - - def annotate(path, identifier=nil) - identifier = 'HEAD' if identifier.blank? - cmd_args = %w|blame| - cmd_args << "-p" << identifier << "--" << scm_iconv(@path_encoding, 'UTF-8', path) - blame = Annotate.new - content = nil - scm_cmd(*cmd_args) { |io| io.binmode; content = io.read } - # git annotates binary files - return nil if content.is_binary_data? - identifier = '' - # git shows commit author on the first occurrence only - authors_by_commit = {} - content.split("\n").each do |line| - if line =~ /^([0-9a-f]{39,40})\s.*/ - identifier = $1 - elsif line =~ /^author (.+)/ - authors_by_commit[identifier] = $1.strip - elsif line =~ /^\t(.*)/ - blame.add_line($1, Revision.new( - :identifier => identifier, - :revision => identifier, - :scmid => identifier, - :author => authors_by_commit[identifier] - )) - identifier = '' - author = '' - end - end - blame - rescue ScmCommandAborted - nil - end - - def cat(path, identifier=nil) - if identifier.nil? - identifier = 'HEAD' - end - cmd_args = %w|show --no-color| - cmd_args << "#{identifier}:#{scm_iconv(@path_encoding, 'UTF-8', path)}" - cat = nil - scm_cmd(*cmd_args) do |io| - io.binmode - cat = io.read - end - cat - rescue ScmCommandAborted - nil - end - - class Revision < Redmine::Scm::Adapters::Revision - # Returns the readable identifier - def format_identifier - identifier[0,8] - end - end - - def scm_cmd(*args, &block) - repo_path = root_url || url - full_args = ['--git-dir', repo_path] - if self.class.client_version_above?([1, 7, 2]) - full_args << '-c' << 'core.quotepath=false' - full_args << '-c' << 'log.decorate=no' - end - full_args += args - ret = shellout( - self.class.sq_bin + ' ' + full_args.map { |e| shell_quote e.to_s }.join(' '), - &block - ) - if $? && $?.exitstatus != 0 - raise ScmCommandAborted, "git exited with non-zero status: #{$?.exitstatus}" - end - ret - end - private :scm_cmd - end - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/33/3396ee85e7860d58bce2840b1f00fae7a535409f.svn-base --- a/.svn/pristine/33/3396ee85e7860d58bce2840b1f00fae7a535409f.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +0,0 @@ -module TestHelper - def self.report_location(path) - [RAILS_ROOT + '/', 'vendor/plugins/'].each { |part| path.sub! part, ''} - path = path.split('/') - location, subject = path.first, path.last - if subject.sub! '.rb', '' - subject = subject.classify - else - subject.sub! '.html.erb', '' - end - "#{subject} (from #{location})" - end - - def self.view_path_for path - [RAILS_ROOT + '/', 'vendor/plugins/', '.html.erb'].each { |part| path.sub! part, ''} - parts = path.split('/') - parts[(parts.index('views')+1)..-1].join('/') - end -end - -class Test::Unit::TestCase - # Add more helper methods to be used by all tests here... - def get_action_on_controller(*args) - action = args.shift - with_controller *args - get action - end - - def with_controller(controller, namespace = nil) - classname = controller.to_s.classify + 'Controller' - classname = namespace.to_s.classify + '::' + classname unless namespace.nil? - @controller = classname.constantize.new - end - - def assert_response_body(expected) - assert_equal expected, @response.body - end -end - -# Because we're testing this behaviour, we actually want these features on! -Engines.disable_application_view_loading = false -Engines.disable_application_code_loading = false diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/33/339df18b16b4ab05fedfba76e31080e6dda3b82a.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/33/339df18b16b4ab05fedfba76e31080e6dda3b82a.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,27 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class RoutingMailHandlerTest < ActionController::IntegrationTest + def test_mail_handler + assert_routing( + { :method => "post", :path => "/mail_handler" }, + { :controller => 'mail_handler', :action => 'index' } + ) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/33/33a69333d665b6d06e88fdb324d960ab74dff338.svn-base --- a/.svn/pristine/33/33a69333d665b6d06e88fdb324d960ab74dff338.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,55 +0,0 @@ -module CodeRay - - # The result of a scan operation is a TokensProxy, but should act like Tokens. - # - # This proxy makes it possible to use the classic CodeRay.scan.encode API - # while still providing the benefits of direct streaming. - class TokensProxy - - attr_accessor :input, :lang, :options, :block - - # Create a new TokensProxy with the arguments of CodeRay.scan. - def initialize input, lang, options = {}, block = nil - @input = input - @lang = lang - @options = options - @block = block - end - - # Call CodeRay.encode if +encoder+ is a Symbol; - # otherwise, convert the receiver to tokens and call encoder.encode_tokens. - def encode encoder, options = {} - if encoder.respond_to? :to_sym - CodeRay.encode(input, lang, encoder, options) - else - encoder.encode_tokens tokens, options - end - end - - # Tries to call encode; - # delegates to tokens otherwise. - def method_missing method, *args, &blk - encode method.to_sym, *args - rescue PluginHost::PluginNotFound - tokens.send(method, *args, &blk) - end - - # The (cached) result of the tokenized input; a Tokens instance. - def tokens - @tokens ||= scanner.tokenize(input) - end - - # A (cached) scanner instance to use for the scan task. - def scanner - @scanner ||= CodeRay.scanner(lang, options, &block) - end - - # Overwrite Struct#each. - def each *args, &blk - tokens.each(*args, &blk) - self - end - - end - -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/34/3426569be9b65b6dce3f0afa56586bb79fc07741.svn-base --- a/.svn/pristine/34/3426569be9b65b6dce3f0afa56586bb79fc07741.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,112 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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 Customizable - def self.included(base) - base.extend ClassMethods - end - - module ClassMethods - def acts_as_customizable(options = {}) - return if self.included_modules.include?(Redmine::Acts::Customizable::InstanceMethods) - cattr_accessor :customizable_options - self.customizable_options = options - has_many :custom_values, :as => :customized, - :include => :custom_field, - :order => "#{CustomField.table_name}.position", - :dependent => :delete_all - before_validation { |customized| customized.custom_field_values if customized.new_record? } - # Trigger validation only if custom values were changed - validates_associated :custom_values, :on => :update, :if => Proc.new { |customized| customized.custom_field_values_changed? } - send :include, Redmine::Acts::Customizable::InstanceMethods - # Save custom values when saving the customized object - after_save :save_custom_field_values - end - end - - module InstanceMethods - def self.included(base) - base.extend ClassMethods - end - - def available_custom_fields - CustomField.find(:all, :conditions => "type = '#{self.class.name}CustomField'", - :order => 'position') - end - - # Sets the values of the object's custom fields - # values is an array like [{'id' => 1, 'value' => 'foo'}, {'id' => 2, 'value' => 'bar'}] - def custom_fields=(values) - values_to_hash = values.inject({}) do |hash, v| - v = v.stringify_keys - if v['id'] && v.has_key?('value') - hash[v['id']] = v['value'] - end - hash - end - self.custom_field_values = values_to_hash - end - - # Sets the values of the object's custom fields - # values is a hash like {'1' => 'foo', 2 => 'bar'} - def custom_field_values=(values) - @custom_field_values_changed = true - values = values.stringify_keys - custom_field_values.each do |custom_value| - custom_value.value = values[custom_value.custom_field_id.to_s] if values.has_key?(custom_value.custom_field_id.to_s) - end if values.is_a?(Hash) - end - - def custom_field_values - @custom_field_values ||= available_custom_fields.collect { |x| custom_values.detect { |v| v.custom_field == x } || custom_values.build(:customized => self, :custom_field => x, :value => nil) } - end - - def visible_custom_field_values - custom_field_values.select(&:visible?) - end - - def custom_field_values_changed? - @custom_field_values_changed == true - end - - def custom_value_for(c) - field_id = (c.is_a?(CustomField) ? c.id : c.to_i) - custom_values.detect {|v| v.custom_field_id == field_id } - end - - def save_custom_field_values - self.custom_values = custom_field_values - custom_field_values.each(&:save) - @custom_field_values_changed = false - @custom_field_values = nil - end - - def reset_custom_values! - @custom_field_values = nil - @custom_field_values_changed = true - values = custom_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h} - custom_values.each {|cv| cv.destroy unless custom_field_values.include?(cv)} - end - - module ClassMethods - end - end - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/34/342bfb95b19c2d2bc64485e9d4d12de5afbbc42e.svn-base --- a/.svn/pristine/34/342bfb95b19c2d2bc64485e9d4d12de5afbbc42e.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +0,0 @@ -Return-Path: -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" -To: -Subject: New ticket on a given project with a very long subject line which exceeds 255 chars and should not be ignored but chopped off. And if the subject line is still not long enough, we just add more text. And more text. Wow, this is really annoying. Especially, if you have nothing to say... -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. - ---- This line starts with a delimiter and should not be stripped - -This paragraph is before delimiters. - -BREAK - -This paragraph is between delimiters. - ---- - -This paragraph is after the delimiter so it shouldn't appear. - -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. - -Project: onlinestore -Status: Resolved -due date: 2010-12-31 -Start Date:2010-01-01 -Assigned to: John Smith - diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/34/345b699f46121e103fdce4587bf3ec87db1cc445.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/34/345b699f46121e103fdce4587bf3ec87db1cc445.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,107 @@ +module ActiveRecord + module Acts + module Tree + def self.included(base) + base.extend(ClassMethods) + end + + # Specify this +acts_as+ extension if you want to model a tree structure by providing a parent association and a children + # association. This requires that you have a foreign key column, which by default is called +parent_id+. + # + # class Category < ActiveRecord::Base + # acts_as_tree :order => "name" + # end + # + # Example: + # root + # \_ child1 + # \_ subchild1 + # \_ subchild2 + # + # root = Category.create("name" => "root") + # child1 = root.children.create("name" => "child1") + # subchild1 = child1.children.create("name" => "subchild1") + # + # root.parent # => nil + # child1.parent # => root + # root.children # => [child1] + # root.children.first.children.first # => subchild1 + # + # In addition to the parent and children associations, the following instance methods are added to the class + # after calling acts_as_tree: + # * siblings - Returns all the children of the parent, excluding the current node ([subchild2] when called on subchild1) + # * self_and_siblings - Returns all the children of the parent, including the current node ([subchild1, subchild2] when called on subchild1) + # * ancestors - Returns all the ancestors of the current node ([child1, root] when called on subchild2) + # * root - Returns the root of the current node (root when called on subchild2) + module ClassMethods + # Configuration options are: + # + # * foreign_key - specifies the column name to use for tracking of the tree (default: +parent_id+) + # * order - makes it possible to sort the children according to this SQL snippet. + # * counter_cache - keeps a count in a +children_count+ column if set to +true+ (default: +false+). + def acts_as_tree(options = {}) + configuration = { :foreign_key => "parent_id", :dependent => :destroy, :order => nil, :counter_cache => nil } + configuration.update(options) if options.is_a?(Hash) + + belongs_to :parent, :class_name => name, :foreign_key => configuration[:foreign_key], :counter_cache => configuration[:counter_cache] + has_many :children, :class_name => name, :foreign_key => configuration[:foreign_key], :order => configuration[:order], :dependent => configuration[:dependent] + + scope :roots, where("#{configuration[:foreign_key]} IS NULL").order(configuration[:order]) + + send :include, ActiveRecord::Acts::Tree::InstanceMethods + end + end + + module InstanceMethods + # Returns list of ancestors, starting from parent until root. + # + # subchild1.ancestors # => [child1, root] + def ancestors + node, nodes = self, [] + nodes << node = node.parent while node.parent + nodes + end + + # Returns list of descendants. + # + # root.descendants # => [child1, subchild1, subchild2] + def descendants(depth=nil) + depth ||= 0 + result = children.dup + unless depth == 1 + result += children.collect {|child| child.descendants(depth-1)}.flatten + end + result + end + + # Returns list of descendants and a reference to the current node. + # + # root.self_and_descendants # => [root, child1, subchild1, subchild2] + def self_and_descendants(depth=nil) + [self] + descendants(depth) + end + + # Returns the root node of the tree. + def root + node = self + node = node.parent while node.parent + node + end + + # Returns all siblings of the current node. + # + # subchild1.siblings # => [subchild2] + def siblings + self_and_siblings - [self] + end + + # Returns all siblings and a reference to the current node. + # + # subchild1.self_and_siblings # => [subchild1, subchild2] + def self_and_siblings + parent ? parent.children : self.class.roots + end + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/34/34e888bcdeab7ab62509c340e12806f908468391.svn-base --- a/.svn/pristine/34/34e888bcdeab7ab62509c340e12806f908468391.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -author: James Adam -email: james.adam@gmail.com -homepage: http://www.rails-engines.org -summary: Enhances the plugin mechanism to perform more flexible sharing -description: The Rails Engines plugin allows the sharing of almost any type of code or asset that you could use in a Rails application, including controllers, models, stylesheets, and views. -license: MIT -version: 2.3.2 \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/35/3512f0a7e8a0b7dd8c33b6309137ca6b0f893a68.svn-base --- a/.svn/pristine/35/3512f0a7e8a0b7dd8c33b6309137ca6b0f893a68.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -<%= TestHelper.view_path_for __FILE__ %> (from a_view) \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/35/35159d7789b8fbcfc57c58eacb8fb9632b6c69c9.svn-base --- a/.svn/pristine/35/35159d7789b8fbcfc57c58eacb8fb9632b6c69c9.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,67 +0,0 @@ -api.issue do - api.id @issue.id - api.project(:id => @issue.project_id, :name => @issue.project.name) unless @issue.project.nil? - api.tracker(:id => @issue.tracker_id, :name => @issue.tracker.name) unless @issue.tracker.nil? - api.status(:id => @issue.status_id, :name => @issue.status.name) unless @issue.status.nil? - api.priority(:id => @issue.priority_id, :name => @issue.priority.name) unless @issue.priority.nil? - api.author(:id => @issue.author_id, :name => @issue.author.name) unless @issue.author.nil? - api.assigned_to(:id => @issue.assigned_to_id, :name => @issue.assigned_to.name) unless @issue.assigned_to.nil? - api.category(:id => @issue.category_id, :name => @issue.category.name) unless @issue.category.nil? - api.fixed_version(:id => @issue.fixed_version_id, :name => @issue.fixed_version.name) unless @issue.fixed_version.nil? - api.parent(:id => @issue.parent_id) unless @issue.parent.nil? - - api.subject @issue.subject - api.description @issue.description - api.start_date @issue.start_date - api.due_date @issue.due_date - api.done_ratio @issue.done_ratio - api.estimated_hours @issue.estimated_hours - api.spent_hours(@issue.spent_hours) if User.current.allowed_to?(:view_time_entries, @project) - - render_api_custom_values @issue.custom_field_values, api - - api.created_on @issue.created_on - api.updated_on @issue.updated_on - - render_api_issue_children(@issue, api) if include_in_api_response?('children') - - api.array :attachments do - @issue.attachments.each do |attachment| - render_api_attachment(attachment, api) - end - end if include_in_api_response?('attachments') - - api.array :relations do - @relations.each do |relation| - api.relation(:id => relation.id, :issue_id => relation.issue_from_id, :issue_to_id => relation.issue_to_id, :relation_type => relation.relation_type, :delay => relation.delay) - end - end if include_in_api_response?('relations') && @relations.present? - - api.array :changesets do - @issue.changesets.each do |changeset| - api.changeset :revision => changeset.revision do - api.user(:id => changeset.user_id, :name => changeset.user.name) unless changeset.user.nil? - api.comments changeset.comments - api.committed_on changeset.committed_on - end - end - end if include_in_api_response?('changesets') && User.current.allowed_to?(:view_changesets, @project) - - api.array :journals do - @issue.journals.each do |journal| - api.journal :id => journal.id do - api.user(:id => journal.user_id, :name => journal.user.name) unless journal.user.nil? - api.notes journal.notes - api.created_on journal.created_on - api.array :details do - journal.details.each do |detail| - api.detail :property => detail.property, :name => detail.prop_key do - api.old_value detail.old_value - api.new_value detail.value - end - end - end - end - end - end if include_in_api_response?('journals') -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/35/35a0a9f6553f729051c01dc102ed98a7a0151c3f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/35/35a0a9f6553f729051c01dc102ed98a7a0151c3f.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,243 @@ +# encoding: utf-8 +# +# Helpers to sort tables using clickable column headers. +# +# Author: Stuart Rackham , March 2005. +# Jean-Philippe Lang, 2009 +# License: This source code is released under the MIT license. +# +# - Consecutive clicks toggle the column's sort order. +# - Sort state is maintained by a session hash entry. +# - CSS classes identify sort column and state. +# - Typically used in conjunction with the Pagination module. +# +# Example code snippets: +# +# Controller: +# +# helper :sort +# include SortHelper +# +# def list +# sort_init 'last_name' +# sort_update %w(first_name last_name) +# @items = Contact.find_all nil, sort_clause +# end +# +# Controller (using Pagination module): +# +# helper :sort +# include SortHelper +# +# def list +# sort_init 'last_name' +# sort_update %w(first_name last_name) +# @contact_pages, @items = paginate :contacts, +# :order_by => sort_clause, +# :per_page => 10 +# end +# +# View (table header in list.rhtml): +# +# +# +# <%= sort_header_tag('id', :title => 'Sort by contact ID') %> +# <%= sort_header_tag('last_name', :caption => 'Name') %> +# <%= sort_header_tag('phone') %> +# <%= sort_header_tag('address', :width => 200) %> +# +# +# +# - Introduces instance variables: @sort_default, @sort_criteria +# - Introduces param :sort +# + +module SortHelper + class SortCriteria + + def initialize + @criteria = [] + end + + def available_criteria=(criteria) + unless criteria.is_a?(Hash) + criteria = criteria.inject({}) {|h,k| h[k] = k; h} + end + @available_criteria = criteria + end + + def from_param(param) + @criteria = param.to_s.split(',').collect {|s| s.split(':')[0..1]} + normalize! + end + + def criteria=(arg) + @criteria = arg + normalize! + end + + def to_param + @criteria.collect {|k,o| k + (o ? '' : ':desc')}.join(',') + end + + # Returns an array of SQL fragments used to sort the list + def to_sql + sql = @criteria.collect do |k,o| + if s = @available_criteria[k] + (o ? s.to_a : s.to_a.collect {|c| append_desc(c)}) + end + end.flatten.compact + sql.blank? ? nil : sql + end + + def to_a + @criteria.dup + end + + def add!(key, asc) + @criteria.delete_if {|k,o| k == key} + @criteria = [[key, asc]] + @criteria + normalize! + end + + def add(*args) + r = self.class.new.from_param(to_param) + r.add!(*args) + r + end + + def first_key + @criteria.first && @criteria.first.first + end + + def first_asc? + @criteria.first && @criteria.first.last + end + + def empty? + @criteria.empty? + end + + private + + def normalize! + @criteria ||= [] + @criteria = @criteria.collect {|s| s = s.to_a; [s.first, (s.last == false || s.last == 'desc') ? false : true]} + @criteria = @criteria.select {|k,o| @available_criteria.has_key?(k)} if @available_criteria + @criteria.slice!(3) + self + end + + # Appends DESC to the sort criterion unless it has a fixed order + def append_desc(criterion) + if criterion =~ / (asc|desc)$/i + criterion + else + "#{criterion} DESC" + end + end + end + + def sort_name + controller_name + '_' + action_name + '_sort' + end + + # Initializes the default sort. + # Examples: + # + # sort_init 'name' + # sort_init 'id', 'desc' + # sort_init ['name', ['id', 'desc']] + # sort_init [['name', 'desc'], ['id', 'desc']] + # + def sort_init(*args) + case args.size + when 1 + @sort_default = args.first.is_a?(Array) ? args.first : [[args.first]] + when 2 + @sort_default = [[args.first, args.last]] + else + raise ArgumentError + end + end + + # Updates the sort state. Call this in the controller prior to calling + # sort_clause. + # - criteria can be either an array or a hash of allowed keys + # + def sort_update(criteria, sort_name=nil) + sort_name ||= self.sort_name + @sort_criteria = SortCriteria.new + @sort_criteria.available_criteria = criteria + @sort_criteria.from_param(params[:sort] || session[sort_name]) + @sort_criteria.criteria = @sort_default if @sort_criteria.empty? + session[sort_name] = @sort_criteria.to_param + end + + # Clears the sort criteria session data + # + def sort_clear + session[sort_name] = nil + 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() + @sort_criteria.to_sql + end + + def sort_criteria + @sort_criteria + end + + # Returns a link which sorts by the named column. + # + # - column is the name of an attribute in the sorted record collection. + # - the optional caption explicitly specifies the displayed link text. + # - 2 CSS classes reflect the state of the link: sort and asc or desc + # + def sort_link(column, caption, default_order) + css, order = nil, default_order + + if column.to_s == @sort_criteria.first_key + if @sort_criteria.first_asc? + css = 'sort asc' + order = 'desc' + else + css = 'sort desc' + order = 'asc' + end + end + caption = column.to_s.humanize unless caption + + sort_options = { :sort => @sort_criteria.add(column.to_s, order).to_param } + url_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_content_update(h(caption), url_options, :class => css) + end + + # Returns a table header tag with a sort link for the named column + # attribute. + # + # Options: + # :caption The displayed link name (defaults to titleized column name). + # :title The tag's 'title' attribute (defaults to 'Sort by :caption'). + # + # Other options hash entries generate additional table header tag attributes. + # + # Example: + # + # <%= sort_header_tag('id', :title => 'Sort by contact ID', :width => 40) %> + # + def sort_header_tag(column, options = {}) + caption = options.delete(:caption) || column.to_s.humanize + default_order = options.delete(:default_order) || 'asc' + options[:title] = l(:label_sort_by, "\"#{caption}\"") unless options[:title] + content_tag('th', sort_link(column, caption, default_order), options) + end +end + diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/35/35c1cd09fb09a0095fc148484431ad643d188dd6.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/35/35c1cd09fb09a0095fc148484431ad643d188dd6.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,24 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 CommentObserver < ActiveRecord::Observer + def after_create(comment) + if comment.commented.is_a?(News) && Setting.notified_events.include?('news_comment_added') + Mailer.news_comment_added(comment).deliver + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/35/35d2a8dc4a5a8983e7bc3e63a00a706b7e3bfb4c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/35/35d2a8dc4a5a8983e7bc3e63a00a706b7e3bfb4c.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,223 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +module QueriesHelper + def filters_options_for_select(query) + options_for_select(filters_options(query)) + end + + def filters_options(query) + options = [[]] + options += query.available_filters.map do |field, field_options| + [field_options[:name], field] + end + end + + def query_filters_hidden_tags(query) + tags = ''.html_safe + query.filters.each do |field, options| + tags << hidden_field_tag("f[]", field, :id => nil) + tags << hidden_field_tag("op[#{field}]", options[:operator], :id => nil) + options[:values].each do |value| + tags << hidden_field_tag("v[#{field}][]", value, :id => nil) + end + end + tags + end + + def query_columns_hidden_tags(query) + tags = ''.html_safe + query.columns.each do |column| + tags << hidden_field_tag("c[]", column.name, :id => nil) + end + tags + end + + def query_hidden_tags(query) + query_filters_hidden_tags(query) + query_columns_hidden_tags(query) + end + + def available_block_columns_tags(query) + tags = ''.html_safe + query.available_block_columns.each do |column| + tags << content_tag('label', check_box_tag('c[]', column.name.to_s, query.has_column?(column)) + " #{column.caption}", :class => 'inline') + end + tags + end + + def query_available_inline_columns_options(query) + (query.available_inline_columns - query.columns).reject(&:frozen?).collect {|column| [column.caption, column.name]} + end + + def query_selected_inline_columns_options(query) + (query.inline_columns & query.available_inline_columns).reject(&:frozen?).collect {|column| [column.caption, column.name]} + end + + def render_query_columns_selection(query, options={}) + tag_name = (options[:name] || 'c') + '[]' + render :partial => 'queries/columns', :locals => {:query => query, :tag_name => tag_name} + end + + def column_header(column) + column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption, + :default_order => column.default_order) : + content_tag('th', h(column.caption)) + end + + def column_content(column, issue) + value = column.value(issue) + if value.is_a?(Array) + value.collect {|v| column_value(column, issue, v)}.compact.join(', ').html_safe + else + column_value(column, issue, value) + end + end + + def column_value(column, issue, value) + case value.class.name + when 'String' + if column.name == :subject + link_to(h(value), :controller => 'issues', :action => 'show', :id => issue) + elsif column.name == :description + issue.description? ? content_tag('div', textilizable(issue, :description), :class => "wiki") : '' + else + h(value) + end + when 'Time' + format_time(value) + when 'Date' + format_date(value) + when 'Fixnum' + if column.name == :id + link_to value, issue_path(issue) + elsif column.name == :done_ratio + progress_bar(value, :width => '80px') + else + value.to_s + end + when 'Float' + sprintf "%.2f", value + when 'User' + link_to_user value + when 'Project' + link_to_project value + when 'Version' + link_to(h(value), :controller => 'versions', :action => 'show', :id => value) + when 'TrueClass' + l(:general_text_Yes) + when 'FalseClass' + l(:general_text_No) + when 'Issue' + value.visible? ? link_to_issue(value) : "##{value.id}" + when 'IssueRelation' + other = value.other_issue(issue) + content_tag('span', + (l(value.label_for(issue)) + " " + link_to_issue(other, :subject => false, :tracker => false)).html_safe, + :class => value.css_classes_for(issue)) + else + h(value) + end + end + + def csv_content(column, issue) + value = column.value(issue) + if value.is_a?(Array) + value.collect {|v| csv_value(column, issue, v)}.compact.join(', ') + else + csv_value(column, issue, value) + end + end + + def csv_value(column, issue, value) + case value.class.name + when 'Time' + format_time(value) + when 'Date' + format_date(value) + when 'Float' + sprintf("%.2f", value).gsub('.', l(:general_csv_decimal_separator)) + when 'IssueRelation' + other = value.other_issue(issue) + l(value.label_for(issue)) + " ##{other.id}" + else + value.to_s + end + end + + def query_to_csv(items, query, options={}) + encoding = l(:general_csv_encoding) + columns = (options[:columns] == 'all' ? query.available_inline_columns : query.inline_columns) + query.available_block_columns.each do |column| + if options[column.name].present? + columns << column + end + end + + export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv| + # csv header fields + csv << columns.collect {|c| Redmine::CodesetUtil.from_utf8(c.caption.to_s, encoding) } + # csv lines + items.each do |item| + csv << columns.collect {|c| Redmine::CodesetUtil.from_utf8(csv_content(c, item), encoding) } + end + end + export + end + + # Retrieve query from session or build a new query + def retrieve_query + if !params[:query_id].blank? + cond = "project_id IS NULL" + cond << " OR project_id = #{@project.id}" if @project + @query = IssueQuery.find(params[:query_id], :conditions => cond) + raise ::Unauthorized unless @query.visible? + @query.project = @project + session[:query] = {:id => @query.id, :project_id => @query.project_id} + sort_clear + elsif api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil) + # Give it a name, required to be valid + @query = IssueQuery.new(:name => "_") + @query.project = @project + @query.build_from_params(params) + session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names} + else + # retrieve from session + @query = IssueQuery.find_by_id(session[:query][:id]) if session[:query][:id] + @query ||= IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names]) + @query.project = @project + end + end + + def retrieve_query_from_session + if session[:query] + if session[:query][:id] + @query = IssueQuery.find_by_id(session[:query][:id]) + return unless @query + else + @query = IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names]) + end + if session[:query].has_key?(:project_id) + @query.project_id = session[:query][:project_id] + else + @query.project = @project + end + @query + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/35/35f7ce114c4047a04259024e5c563af98880d0e2.svn-base --- a/.svn/pristine/35/35f7ce114c4047a04259024e5c563af98880d0e2.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -# Add new mime types for use in respond_to blocks: - -Mime::SET << Mime::CSV unless Mime::SET.include?(Mime::CSV) -Mime::Type.register 'application/pdf', :pdf -Mime::Type.register 'image/png', :png diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/36/36167ee499dcc4b045fdbe86a18299795ead7b8c.svn-base --- a/.svn/pristine/36/36167ee499dcc4b045fdbe86a18299795ead7b8c.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +0,0 @@ -

<%=l(:label_my_account)%>

- -

<%=l(:field_login)%>: <%= link_to_user(@user, :format => :username) %>
-<%=l(:field_created_on)%>: <%= format_time(@user.created_on) %>

- - -

<%= l(:label_feeds_access_key) %>

- -

-<% if @user.rss_token %> -<%= l(:label_feeds_access_key_created_on, distance_of_time_in_words(Time.now, @user.rss_token.created_on)) %> -<% else %> -<%= l(:label_missing_feeds_access_key) %> -<% end %> -(<%= link_to l(:button_reset), {:action => 'reset_rss_key'}, :method => :post %>) -

- -<% if Setting.rest_api_enabled? %> -

<%= l(:label_api_access_key) %>

-
- <%= link_to_function(l(:button_show), "$('api-access-key').toggle();")%> -
<%= h(@user.api_key) %>
-
-<%= javascript_tag("$('api-access-key').hide();") %> -

-<% if @user.api_token %> -<%= l(:label_api_access_key_created_on, distance_of_time_in_words(Time.now, @user.api_token.created_on)) %> -<% else %> -<%= l(:label_missing_api_access_key) %> -<% end %> -(<%= link_to l(:button_reset), {:action => 'reset_api_key'}, :method => :post %>) -

-<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/36/362876bd65a226af3b36d0e31a3e589d5fcd9d1f.svn-base --- a/.svn/pristine/36/362876bd65a226af3b36d0e31a3e589d5fcd9d1f.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,75 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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 SafeAttributes - def self.included(base) - base.extend(ClassMethods) - end - - module ClassMethods - # Declares safe attributes - # An optional Proc can be given for conditional inclusion - # - # Example: - # safe_attributes 'title', 'pages' - # safe_attributes 'isbn', :if => {|book, user| book.author == user} - def safe_attributes(*args) - @safe_attributes ||= [] - if args.empty? - @safe_attributes - else - options = args.last.is_a?(Hash) ? args.pop : {} - @safe_attributes << [args, options] - end - end - end - - # Returns an array that can be safely set by user or current user - # - # Example: - # book.safe_attributes # => ['title', 'pages'] - # book.safe_attributes(book.author) # => ['title', 'pages', 'isbn'] - def safe_attribute_names(user=User.current) - names = [] - self.class.safe_attributes.collect do |attrs, options| - if options[:if].nil? || options[:if].call(self, user) - names += attrs.collect(&:to_s) - end - end - names.uniq - end - - # Returns a hash with unsafe attributes removed - # from the given attrs hash - # - # Example: - # book.delete_unsafe_attributes({'title' => 'My book', 'foo' => 'bar'}) - # # => {'title' => 'My book'} - def delete_unsafe_attributes(attrs, user=User.current) - safe = safe_attribute_names(user) - attrs.dup.delete_if {|k,v| !safe.include?(k)} - end - - # Sets attributes from attrs that are safe - # attrs is a Hash with string keys - def safe_attributes=(attrs, user=User.current) - return unless attrs.is_a?(Hash) - self.attributes = delete_unsafe_attributes(attrs, user) - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/36/3661c33de673c9465c49c9836851558b3db7deb2.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/36/3661c33de673c9465c49c9836851558b3db7deb2.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,69 @@ +Installing gems for testing +=========================== + +Remove your .bundle/config if you've already installed Redmine without +the test dependencies. Then, run `bundle install`. + +Running Tests +============= + +Run `rake --tasks test` to see available tests. +Run `rake test` to run the entire test suite (except the tests for the +Apache perl module Redmine.pm and Capybara tests, see below). + +You can run `ruby test/unit/issue_test.rb` for running a single test case. + +Before running tests, you need to configure both development +and test databases. + +Creating test repositories +========================== + +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. + +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. The repositories are +unpacked into {redmine_root}/tmp/test. + +If the test repositories are not present, the tests that need them will be +skipped. + +Creating a test ldap database +============================= + +Redmine supports using LDAP for user authentications. To test LDAP +with Redmine, load the LDAP export from test/fixtures/ldap/test-ldap.ldif +into a testing LDAP server. Make sure that the LDAP server can be accessed +at 127.0.0.1 on port 389. + +Setting up the test LDAP server is beyond the scope of this documentation. +The OpenLDAP project provides a simple LDAP implementation that should work +good as a test server. + +If the LDAP is not available, the tests that need it will be skipped. + +Running Redmine.pm tests +======================== + +(work in progress) + +Running the tests for the Redmine.pm perl module needs a bit more setup. +You need an Apache server with mod_perl, mod_dav_svn and Redmine.pm configured. +See: http://www.redmine.org/projects/redmine/wiki/Repositories_access_control_with_apache_mod_dav_svn_and_mod_perl + +You need an empty repository accessible at http://127.0.0.1/svn/ecookbook +Then, you can run the tests with: +`ruby test\extra\redmine_pm\repository_subversion_test.rb` + +If you svn server is not running on localhost, you can use the REDMINE_TEST_DAV_SERVER +environment variable to specify another host. + +Running Capybara tests +====================== + +You need to have PhantomJS WebDriver listening on port 4444: +`phantomjs --webdriver 4444` + +Capybara tests can be run with: +`rake test:ui` diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/36/36e4e7ac1d9860e3b7d32ae92975617939624a06.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/36/36e4e7ac1d9860e3b7d32ae92975617939624a06.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,1084 @@ +en: + # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl) + direction: ltr + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%m/%d/%Y" + short: "%b %d" + long: "%B %d, %Y" + + day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday] + abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December] + abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec] + # Used in date_select and datime_select. + order: + - :year + - :month + - :day + + time: + formats: + default: "%m/%d/%Y %I:%M %p" + time: "%I:%M %p" + short: "%d %b %H:%M" + long: "%B %d, %Y %H:%M" + am: "am" + pm: "pm" + + datetime: + distance_in_words: + half_a_minute: "half a minute" + less_than_x_seconds: + one: "less than 1 second" + other: "less than %{count} seconds" + x_seconds: + one: "1 second" + other: "%{count} seconds" + less_than_x_minutes: + one: "less than a minute" + other: "less than %{count} minutes" + x_minutes: + one: "1 minute" + other: "%{count} minutes" + about_x_hours: + one: "about 1 hour" + other: "about %{count} hours" + x_hours: + one: "1 hour" + other: "%{count} hours" + x_days: + one: "1 day" + other: "%{count} days" + about_x_months: + one: "about 1 month" + other: "about %{count} months" + x_months: + one: "1 month" + other: "%{count} months" + about_x_years: + one: "about 1 year" + other: "about %{count} years" + over_x_years: + one: "over 1 year" + other: "over %{count} years" + almost_x_years: + one: "almost 1 year" + other: "almost %{count} years" + + number: + format: + separator: "." + delimiter: "" + precision: 3 + + human: + format: + delimiter: "" + precision: 3 + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + +# Used in array.to_sentence. + support: + array: + sentence_connector: "and" + skip_last_comma: false + + activerecord: + errors: + template: + header: + one: "1 error prohibited this %{model} from being saved" + other: "%{count} errors prohibited this %{model} from being saved" + messages: + inclusion: "is not included in the list" + exclusion: "is reserved" + invalid: "is invalid" + confirmation: "doesn't match confirmation" + accepted: "must be accepted" + empty: "can't be empty" + blank: "can't be blank" + too_long: "is too long (maximum is %{count} characters)" + too_short: "is too short (minimum is %{count} characters)" + wrong_length: "is the wrong length (should be %{count} characters)" + taken: "has already been taken" + not_a_number: "is not a number" + not_a_date: "is not a valid date" + greater_than: "must be greater than %{count}" + greater_than_or_equal_to: "must be greater than or equal to %{count}" + equal_to: "must be equal to %{count}" + less_than: "must be less than %{count}" + less_than_or_equal_to: "must be less than or equal to %{count}" + odd: "must be odd" + even: "must be even" + greater_than_start_date: "must be greater than start date" + not_same_project: "doesn't belong to the same project" + circular_dependency: "This relation would create a circular dependency" + cant_link_an_issue_with_a_descendant: "An issue cannot be linked to one of its subtasks" + + actionview_instancetag_blank_option: Please select + + general_text_No: 'No' + general_text_Yes: 'Yes' + general_text_no: 'no' + general_text_yes: 'yes' + general_lang_name: 'English' + general_csv_separator: ',' + general_csv_decimal_separator: '.' + general_csv_encoding: ISO-8859-1 + general_pdf_encoding: UTF-8 + general_first_day_of_week: '7' + + notice_account_updated: Account was successfully updated. + notice_account_invalid_creditentials: Invalid user or password + notice_account_password_updated: Password was successfully updated. + notice_account_wrong_password: Wrong password + notice_account_register_done: Account was successfully created. To activate your account, click on the link that was emailed to you. + notice_account_unknown_email: Unknown user. + notice_can_t_change_password: This account uses an external authentication source. Impossible to change the password. + notice_account_lost_email_sent: An email with instructions to choose a new password has been sent to you. + notice_account_activated: Your account has been activated. You can now log in. + notice_successful_create: Successful creation. + notice_successful_update: Successful update. + notice_successful_delete: Successful deletion. + notice_successful_connection: Successful connection. + notice_file_not_found: The page you were trying to access doesn't exist or has been removed. + notice_locking_conflict: Data has been updated by another user. + notice_not_authorized: You are not authorized to access this page. + notice_not_authorized_archived_project: The project you're trying to access has been archived. + notice_email_sent: "An email was sent to %{value}" + notice_email_error: "An error occurred while sending mail (%{value})" + notice_feeds_access_key_reseted: Your RSS access key was reset. + notice_api_access_key_reseted: Your API access key was reset. + notice_failed_to_save_issues: "Failed to save %{count} issue(s) on %{total} selected: %{ids}." + notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." + notice_failed_to_save_members: "Failed to save member(s): %{errors}." + notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit." + notice_account_pending: "Your account was created and is now pending administrator approval." + notice_default_data_loaded: Default configuration successfully loaded. + notice_unable_delete_version: Unable to delete version. + notice_unable_delete_time_entry: Unable to delete time log entry. + notice_issue_done_ratios_updated: Issue done ratios updated. + notice_gantt_chart_truncated: "The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max})" + notice_issue_successful_create: "Issue %{id} created." + notice_issue_update_conflict: "The issue has been updated by an other user while you were editing it." + notice_account_deleted: "Your account has been permanently deleted." + notice_user_successful_create: "User %{id} created." + + error_can_t_load_default_data: "Default configuration could not be loaded: %{value}" + 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: %{value}" + error_scm_annotate: "The entry does not exist or cannot be annotated." + error_scm_annotate_big_text_file: "The entry cannot be annotated, as it exceeds the maximum text file size." + error_issue_not_found_in_project: 'The issue was not found or does not belong to this project' + error_no_tracker_in_project: 'No tracker is associated to this project. Please check the Project settings.' + error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").' + error_can_not_delete_custom_field: Unable to delete custom field + error_can_not_delete_tracker: "This tracker contains issues and cannot be deleted." + error_can_not_remove_role: "This role is in use and cannot be deleted." + error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version cannot be reopened' + error_can_not_archive_project: This project cannot be archived + error_issue_done_ratios_not_updated: "Issue done ratios not updated." + error_workflow_copy_source: 'Please select a source tracker or role' + error_workflow_copy_target: 'Please select target tracker(s) and role(s)' + error_unable_delete_issue_status: 'Unable to delete issue status' + error_unable_to_connect: "Unable to connect (%{value})" + error_attachment_too_big: "This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size})" + error_session_expired: "Your session has expired. Please login again." + warning_attachments_not_saved: "%{count} file(s) could not be saved." + + mail_subject_lost_password: "Your %{value} password" + mail_body_lost_password: 'To change your password, click on the following link:' + mail_subject_register: "Your %{value} account activation" + mail_body_register: 'To activate your account, click on the following link:' + mail_body_account_information_external: "You can use your %{value} account to log in." + mail_body_account_information: Your account information + mail_subject_account_activation_request: "%{value} account activation request" + mail_body_account_activation_request: "A new user (%{value}) has registered. The account is pending your approval:" + mail_subject_reminder: "%{count} issue(s) due in the next %{days} days" + mail_body_reminder: "%{count} issue(s) that are assigned to you are due in the next %{days} days:" + mail_subject_wiki_content_added: "'%{id}' wiki page has been added" + mail_body_wiki_content_added: "The '%{id}' wiki page has been added by %{author}." + mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated" + mail_body_wiki_content_updated: "The '%{id}' wiki page has been updated by %{author}." + + field_name: Name + field_description: Description + field_summary: Summary + field_is_required: Required + field_firstname: First name + field_lastname: Last name + field_mail: Email + field_filename: File + field_filesize: Size + field_downloads: Downloads + field_author: Author + field_created_on: Created + field_updated_on: Updated + field_closed_on: Closed + field_field_format: Format + field_is_for_all: For all projects + field_possible_values: Possible values + field_regexp: Regular expression + field_min_length: Minimum length + field_max_length: Maximum length + field_value: Value + field_category: Category + field_title: Title + field_project: Project + field_issue: Issue + field_status: Status + field_notes: Notes + field_is_closed: Issue closed + field_is_default: Default value + field_tracker: Tracker + field_subject: Subject + field_due_date: Due date + field_assigned_to: Assignee + field_priority: Priority + field_fixed_version: Target version + field_user: User + field_principal: Principal + field_role: Role + field_homepage: Homepage + field_is_public: Public + field_parent: Subproject of + field_is_in_roadmap: Issues displayed in roadmap + field_login: Login + field_mail_notification: Email notifications + field_admin: Administrator + field_last_login_on: Last connection + field_language: Language + field_effective_date: Date + field_password: Password + field_new_password: New password + field_password_confirmation: Confirmation + field_version: Version + field_type: Type + field_host: Host + field_port: 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: Start date + field_done_ratio: "% Done" + field_auth_source: Authentication mode + field_hide_mail: Hide my email address + field_comments: Comment + field_url: URL + field_start_page: Start page + field_subproject: Subproject + field_hours: Hours + field_activity: Activity + field_spent_on: Date + field_identifier: Identifier + field_is_filter: Used as a filter + field_issue_to: 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_column_names: Columns + field_time_entries: Log time + field_time_zone: Time zone + field_searchable: Searchable + field_default_value: Default value + field_comments_sorting: Display comments + field_parent_title: Parent page + field_editable: Editable + field_watcher: Watcher + field_identity_url: OpenID URL + field_content: Content + field_group_by: Group results by + field_sharing: Sharing + field_parent_issue: Parent task + field_member_of_group: "Assignee's group" + field_assigned_to_role: "Assignee's role" + field_text: Text field + field_visible: Visible + field_warn_on_leaving_unsaved: "Warn me when leaving a page with unsaved text" + field_issues_visibility: Issues visibility + field_is_private: Private + field_commit_logs_encoding: Commit messages encoding + field_scm_path_encoding: Path encoding + field_path_to_repository: Path to repository + field_root_directory: Root directory + field_cvsroot: CVSROOT + field_cvs_module: Module + field_repository_is_default: Main repository + field_multiple: Multiple values + field_auth_source_ldap_filter: LDAP filter + field_core_fields: Standard fields + field_timeout: "Timeout (in seconds)" + field_board_parent: Parent forum + field_private_notes: Private notes + field_inherit_members: Inherit members + + setting_app_title: Application title + setting_app_subtitle: Application subtitle + setting_welcome_text: Welcome text + setting_default_language: Default language + setting_login_required: Authentication required + setting_self_registration: Self-registration + setting_attachment_max_size: Maximum attachment size + setting_issues_export_limit: Issues export limit + setting_mail_from: Emission email address + setting_bcc_recipients: Blind carbon copy recipients (bcc) + 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: Maximum number of items in Atom feeds + setting_default_projects_public: New projects are public by default + setting_autofetch_changesets: Fetch commits automatically + setting_sys_api_enabled: Enable WS for repository management + setting_commit_ref_keywords: Referencing keywords + setting_commit_fix_keywords: Fixing keywords + setting_autologin: Autologin + setting_date_format: Date format + setting_time_format: Time format + setting_cross_project_issue_relations: Allow cross-project issue relations + setting_cross_project_subtasks: Allow cross-project subtasks + setting_issue_list_default_columns: Default columns displayed on the issue list + setting_repositories_encodings: Attachments and repositories encodings + setting_emails_header: Email header + setting_emails_footer: Email footer + setting_protocol: Protocol + setting_per_page_options: Objects per page options + setting_user_format: Users display format + setting_activity_days_default: Days displayed on project activity + setting_display_subprojects_issues: Display subprojects issues on main projects by default + setting_enabled_scm: Enabled SCM + setting_mail_handler_body_delimiters: "Truncate emails after one of these lines" + 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_gravatar_enabled: Use Gravatar user icons + setting_gravatar_default: Default Gravatar image + setting_diff_max_lines_displayed: Maximum number of diff lines displayed + setting_file_max_size_displayed: Maximum size of text files displayed inline + setting_repository_log_display_limit: Maximum number of revisions displayed on file log + setting_openid: Allow OpenID login and registration + setting_password_min_length: Minimum password length + setting_new_project_user_role_id: Role given to a non-admin user who creates a project + setting_default_projects_modules: Default enabled modules for new projects + setting_issue_done_ratio: Calculate the issue done ratio with + setting_issue_done_ratio_issue_field: Use the issue field + setting_issue_done_ratio_issue_status: Use the issue status + setting_start_of_week: Start calendars on + setting_rest_api_enabled: Enable REST web service + setting_cache_formatted_text: Cache formatted text + setting_default_notification_option: Default notification option + setting_commit_logtime_enabled: Enable time logging + setting_commit_logtime_activity_id: Activity for logged time + setting_gantt_items_limit: Maximum number of items displayed on the gantt chart + setting_issue_group_assignment: Allow issue assignment to groups + setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues + setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed + setting_unsubscribe: Allow users to delete their own account + setting_session_lifetime: Session maximum lifetime + setting_session_timeout: Session inactivity timeout + setting_thumbnails_enabled: Display attachment thumbnails + setting_thumbnails_size: Thumbnails size (in pixels) + setting_non_working_week_days: Non-working days + setting_jsonp_enabled: Enable JSONP support + setting_default_projects_tracker_ids: Default trackers for new projects + + permission_add_project: Create project + permission_add_subprojects: Create subprojects + permission_edit_project: Edit project + permission_close_project: Close / reopen the project + permission_select_project_modules: Select project modules + permission_manage_members: Manage members + permission_manage_project_activities: Manage project activities + permission_manage_versions: Manage versions + permission_manage_categories: Manage issue categories + permission_view_issues: View Issues + permission_add_issues: Add issues + permission_edit_issues: Edit issues + permission_manage_issue_relations: Manage issue relations + permission_set_issues_private: Set issues public or private + permission_set_own_issues_private: Set own issues public or private + permission_add_issue_notes: Add notes + permission_edit_issue_notes: Edit notes + permission_edit_own_issue_notes: Edit own notes + permission_view_private_notes: View private notes + permission_set_notes_private: Set notes as private + 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 calendar + permission_view_issue_watchers: View watchers list + permission_add_issue_watchers: Add watchers + permission_delete_issue_watchers: Delete 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_view_documents: View documents + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete 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 forums + 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 + permission_export_wiki_pages: Export wiki pages + permission_manage_subtasks: Manage subtasks + permission_manage_related_issues: Manage related issues + + project_module_issue_tracking: Issue tracking + project_module_time_tracking: Time tracking + project_module_news: News + project_module_documents: Documents + project_module_files: Files + project_module_wiki: Wiki + project_module_repository: Repository + project_module_boards: Forums + project_module_calendar: Calendar + project_module_gantt: Gantt + + label_user: User + label_user_plural: Users + label_user_new: New user + label_user_anonymous: Anonymous + label_project: Project + label_project_new: New project + label_project_plural: Projects + label_x_projects: + zero: no projects + one: 1 project + other: "%{count} projects" + label_project_all: All Projects + label_project_latest: Latest projects + label_issue: Issue + label_issue_new: New issue + label_issue_plural: Issues + label_issue_view_all: View all issues + label_issues_by: "Issues by %{value}" + label_issue_added: Issue added + label_issue_updated: Issue updated + label_issue_note_added: Note added + label_issue_status_updated: Status updated + label_issue_priority_updated: Priority updated + label_document: Document + label_document_new: New document + label_document_plural: Documents + label_document_added: Document added + label_role: Role + label_role_plural: Roles + label_role_new: New role + label_role_and_permissions: Roles and permissions + label_role_anonymous: Anonymous + label_role_non_member: Non member + label_member: Member + label_member_new: New member + label_member_plural: Members + label_tracker: Tracker + label_tracker_plural: Trackers + label_tracker_new: New tracker + label_workflow: Workflow + label_issue_status: Issue status + label_issue_status_plural: Issue statuses + label_issue_status_new: New status + label_issue_category: Issue category + label_issue_category_plural: Issue categories + label_issue_category_new: New category + 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: 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 + label_my_account: My account + label_my_projects: My projects + label_my_page_block: My page block + label_administration: Administration + label_login: Sign in + label_logout: Sign out + label_help: Help + label_reported_issues: Reported issues + label_assigned_to_me_issues: Issues assigned to me + label_last_login: Last connection + label_registered_on: Registered on + label_activity: Activity + label_overall_activity: Overall activity + label_user_activity: "%{value}'s activity" + label_new: New + label_logged_as: Logged in as + 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: Subprojects + label_subproject_new: New subproject + label_and_its_subprojects: "%{value} and its subprojects" + label_min_max_length: Min - Max length + label_list: List + label_date: Date + label_integer: Integer + label_float: Float + label_boolean: Boolean + label_string: Text + label_text: Long text + label_attribute: Attribute + label_attribute_plural: Attributes + label_no_data: No data to display + label_change_status: Change status + label_history: History + label_attachment: File + label_attachment_new: New file + label_attachment_delete: Delete file + label_attachment_plural: Files + label_file_added: File added + label_report: Report + label_report_plural: Reports + label_news: News + label_news_new: Add news + label_news_plural: News + label_news_latest: Latest news + label_news_view_all: View all news + label_news_added: News added + label_news_comment_added: Comment added to a news + label_settings: Settings + label_overview: Overview + label_version: Version + label_version_new: New version + label_version_plural: Versions + label_close_versions: Close completed versions + label_confirmation: Confirmation + label_export_to: 'Also available in:' + label_read: Read... + label_public_projects: Public projects + label_open_issues: open + label_open_issues_plural: open + label_closed_issues: closed + label_closed_issues_plural: closed + label_x_open_issues_abbr_on_total: + zero: 0 open / %{total} + one: 1 open / %{total} + other: "%{count} open / %{total}" + label_x_open_issues_abbr: + zero: 0 open + one: 1 open + other: "%{count} open" + label_x_closed_issues_abbr: + zero: 0 closed + one: 1 closed + other: "%{count} closed" + label_x_issues: + zero: 0 issues + one: 1 issue + other: "%{count} issues" + label_total: Total + label_total_time: Total time + label_permissions: Permissions + label_current_status: Current status + label_new_statuses_allowed: New statuses allowed + label_all: all + label_any: any + label_none: none + label_nobody: nobody + label_next: Next + label_previous: Previous + label_used_by: Used by + label_details: Details + label_add_note: Add a note + label_per_page: Per page + label_calendar: Calendar + label_months_from: months from + label_gantt: Gantt + label_internal: Internal + label_last_changes: "last %{count} changes" + label_change_view_all: View all changes + label_personalize_page: Personalize this page + label_comment: Comment + label_comment_plural: Comments + label_x_comments: + zero: no comments + one: 1 comment + other: "%{count} comments" + label_comment_add: Add a comment + label_comment_added: Comment added + label_comment_delete: Delete comments + label_query: Custom query + label_query_plural: Custom queries + label_query_new: New query + label_my_queries: My custom queries + label_filter_add: Add filter + label_filter_plural: Filters + label_equals: is + label_not_equals: is not + label_in_less_than: in less than + label_in_more_than: in more than + label_in_the_next_days: in the next + label_in_the_past_days: in the past + label_greater_or_equal: '>=' + label_less_or_equal: '<=' + label_between: between + label_in: in + label_today: today + label_all_time: all time + label_yesterday: yesterday + label_this_week: this week + label_last_week: last week + label_last_n_weeks: "last %{count} weeks" + label_last_n_days: "last %{count} days" + label_this_month: this month + label_last_month: last month + label_this_year: this year + label_date_range: Date range + label_less_than_ago: less than days ago + label_more_than_ago: more than days ago + label_ago: days ago + label_contains: contains + label_not_contains: doesn't contain + label_any_issues_in_project: any issues in project + label_any_issues_not_in_project: any issues not in project + label_no_issues_in_project: no issues in project + label_day_plural: days + label_repository: Repository + label_repository_new: New repository + label_repository_plural: Repositories + label_browse: Browse + label_branch: Branch + label_tag: Tag + label_revision: Revision + label_revision_plural: Revisions + label_revision_id: "Revision %{value}" + label_associated_revisions: Associated revisions + label_added: added + label_modified: modified + label_copied: copied + label_renamed: renamed + label_deleted: deleted + label_latest_revision: Latest revision + label_latest_revision_plural: Latest revisions + label_view_revisions: View revisions + label_view_all_revisions: View all revisions + label_max_size: Maximum size + label_sort_highest: Move to top + label_sort_higher: Move up + label_sort_lower: Move down + label_sort_lowest: Move to bottom + label_roadmap: Roadmap + label_roadmap_due_in: "Due in %{value}" + label_roadmap_overdue: "%{value} late" + label_roadmap_no_issues: No issues for this version + label_search: Search + label_result_plural: Results + label_all_words: All words + 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: Current version + label_preview: Preview + label_feed_plural: Feeds + label_changes_details: Details of all changes + label_issue_tracking: Issue tracking + label_spent_time: Spent time + label_overall_spent_time: Overall spent time + label_f_hour: "%{value} hour" + label_f_hour_plural: "%{value} hours" + label_time_tracking: Time tracking + label_change_plural: Changes + label_statistics: Statistics + label_commits_per_month: Commits per month + label_commits_per_author: Commits per author + label_diff: diff + label_view_diff: View differences + label_diff_inline: inline + label_diff_side_by_side: side by side + label_options: Options + label_copy_workflow_from: Copy workflow from + label_permissions_report: Permissions report + 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_duplicated_by: Duplicated by + label_blocks: Blocks + label_blocked_by: Blocked by + label_precedes: Precedes + label_follows: Follows + label_copied_to: Copied to + label_copied_from: Copied from + label_end_to_start: end to start + label_end_to_end: end to end + label_start_to_start: start to start + label_start_to_end: start to end + label_stay_logged_in: Stay logged in + label_disabled: disabled + label_show_completed_versions: Show completed versions + label_me: me + label_board: Forum + label_board_new: New forum + label_board_plural: Forums + label_board_locked: Locked + label_board_sticky: Sticky + label_topic_plural: Topics + label_message_plural: Messages + label_message_last: Last message + label_message_new: New message + label_message_posted: Message added + 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: Based on user's language + label_sort_by: "Sort by %{value}" + label_send_test_email: Send a test email + label_feeds_access_key: RSS access key + label_missing_feeds_access_key: Missing a RSS access key + label_feeds_access_key_created_on: "RSS access key created %{value} ago" + label_module_plural: Modules + label_added_time_by: "Added by %{author} %{age} ago" + label_updated_time_by: "Updated by %{author} %{age} ago" + label_updated_time: "Updated %{value} ago" + label_jump_to_a_project: Jump to a project... + label_file_plural: Files + label_changeset_plural: Changesets + label_default_columns: Default columns + label_no_change_option: (No change) + label_bulk_edit_selected_issues: Bulk edit selected issues + label_bulk_edit_selected_time_entries: Bulk edit selected time entries + label_theme: Theme + label_default: Default + label_search_titles_only: Search titles only + label_user_mail_option_all: "For any event on all my projects" + label_user_mail_option_selected: "For any event on the selected projects only..." + label_user_mail_option_none: "No events" + label_user_mail_option_only_my_events: "Only for things I watch or I'm involved in" + label_user_mail_option_only_assigned: "Only for things I am assigned to" + label_user_mail_option_only_owner: "Only for things I am the owner of" + label_user_mail_no_self_notified: "I don't want to be notified of changes that I make myself" + 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: "Per page: %{value}" + label_age: Age + label_change_properties: Change properties + label_general: General + label_more: More + label_scm: SCM + label_plugins: Plugins + label_ldap_authentication: LDAP authentication + label_downloads_abbr: D/L + label_optional_description: Optional description + label_add_another_file: Add another file + label_preferences: Preferences + label_chronological_order: In chronological order + label_reverse_chronological_order: In reverse chronological order + label_planning: Planning + label_incoming_emails: Incoming emails + label_generate_key: Generate a key + label_issue_watchers: Watchers + label_example: Example + label_display: Display + label_sort: Sort + label_ascending: Ascending + label_descending: Descending + label_date_from_to: From %{start} to %{end} + label_wiki_content_added: Wiki page added + label_wiki_content_updated: Wiki page updated + label_group: Group + label_group_plural: Groups + label_group_new: New group + label_time_entry_plural: Spent time + label_version_sharing_none: Not shared + label_version_sharing_descendants: With subprojects + label_version_sharing_hierarchy: With project hierarchy + label_version_sharing_tree: With project tree + label_version_sharing_system: With all projects + label_update_issue_done_ratios: Update issue done ratios + label_copy_source: Source + label_copy_target: Target + label_copy_same_as_target: Same as target + label_display_used_statuses_only: Only display statuses that are used by this tracker + label_api_access_key: API access key + label_missing_api_access_key: Missing an API access key + label_api_access_key_created_on: "API access key created %{value} ago" + label_profile: Profile + label_subtask_plural: Subtasks + label_project_copy_notifications: Send email notifications during the project copy + label_principal_search: "Search for user or group:" + label_user_search: "Search for user:" + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + label_issues_visibility_all: All issues + label_issues_visibility_public: All non private issues + label_issues_visibility_own: Issues created by or assigned to the user + label_git_report_last_commit: Report last commit for files and directories + label_parent_revision: Parent + label_child_revision: Child + label_export_options: "%{export_format} export options" + label_copy_attachments: Copy attachments + label_copy_subtasks: Copy subtasks + label_item_position: "%{position} of %{count}" + label_completed_versions: Completed versions + label_search_for_watchers: Search for watchers to add + label_session_expiration: Session expiration + label_show_closed_projects: View closed projects + label_status_transitions: Status transitions + label_fields_permissions: Fields permissions + label_readonly: Read-only + label_required: Required + label_attribute_of_project: "Project's %{name}" + label_attribute_of_issue: "Issue's %{name}" + label_attribute_of_author: "Author's %{name}" + label_attribute_of_assigned_to: "Assignee's %{name}" + label_attribute_of_user: "User's %{name}" + label_attribute_of_fixed_version: "Target version's %{name}" + label_cross_project_descendants: With subprojects + label_cross_project_tree: With project tree + label_cross_project_hierarchy: With project hierarchy + label_cross_project_system: With all projects + label_gantt_progress_line: Progress line + + button_login: Login + button_submit: Submit + button_save: Save + button_check_all: Check all + button_uncheck_all: Uncheck all + button_collapse_all: Collapse all + button_expand_all: Expand all + button_delete: Delete + button_create: Create + button_create_and_continue: Create and continue + button_test: Test + button_edit: Edit + button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" + button_add: Add + button_change: Change + button_apply: Apply + button_clear: Clear + button_lock: Lock + button_unlock: Unlock + button_download: Download + button_list: List + button_view: View + button_move: Move + button_move_and_follow: Move and follow + button_back: Back + button_cancel: Cancel + button_activate: Activate + button_sort: Sort + button_log_time: Log time + button_rollback: Rollback to this version + button_watch: Watch + button_unwatch: Unwatch + button_reply: Reply + button_archive: Archive + button_unarchive: Unarchive + button_reset: Reset + button_rename: Rename + button_change_password: Change password + button_copy: Copy + button_copy_and_follow: Copy and follow + button_annotate: Annotate + button_update: Update + button_configure: Configure + button_quote: Quote + button_duplicate: Duplicate + button_show: Show + button_hide: Hide + button_edit_section: Edit this section + button_export: Export + button_delete_my_account: Delete my account + button_close: Close + button_reopen: Reopen + + status_active: active + status_registered: registered + status_locked: locked + + project_status_active: active + project_status_closed: closed + project_status_archived: archived + + version_status_open: open + version_status_locked: locked + version_status_closed: closed + + field_active: Active + + 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 means no restriction + text_project_destroy_confirmation: Are you sure you want to delete this project and related data? + text_subprojects_destroy_warning: "Its subproject(s): %{value} will be also deleted." + text_workflow_edit: Select a role and a tracker to edit the workflow + text_are_you_sure: Are you sure? + text_journal_changed: "%{label} changed from %{old} to %{new}" + text_journal_changed_no_detail: "%{label} updated" + text_journal_set_to: "%{label} set to %{value}" + text_journal_deleted: "%{label} deleted (%{old})" + text_journal_added: "%{label} %{value} added" + text_tip_issue_begin_day: issue beginning this day + text_tip_issue_end_day: issue ending this day + text_tip_issue_begin_end_day: issue beginning and ending this day + text_project_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed, must start with a lower case letter.
Once saved, the identifier cannot be changed.' + text_caracters_maximum: "%{count} characters maximum." + text_caracters_minimum: "Must be at least %{count} characters long." + text_length_between: "Length between %{min} and %{max} characters." + text_tracker_no_workflow: No workflow defined for this tracker + text_unallowed_characters: Unallowed characters + text_comma_separated: Multiple values allowed (comma separated). + text_line_separated: Multiple values allowed (one line for each value). + text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages + text_issue_added: "Issue %{id} has been reported by %{author}." + text_issue_updated: "Issue %{id} has been updated by %{author}." + text_wiki_destroy_confirmation: Are you sure you want to delete this wiki and all its content? + text_issue_category_destroy_question: "Some issues (%{count}) 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: "For unselected projects, you will only receive notifications about things you watch or you're involved in (eg. issues you're the author or assignee)." + text_no_configuration_data: "Roles, trackers, issue statuses and workflow have not been configured yet.\nIt is highly recommended to load the default configuration. You will be able to modify it once loaded." + text_load_default_configuration: Load the default configuration + text_status_changed_by_changeset: "Applied in changeset %{value}." + text_time_logged_by_changeset: "Applied in changeset %{value}." + text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s)?' + text_issues_destroy_descendants_confirmation: "This will also delete %{count} subtask(s)." + text_time_entries_destroy_confirmation: 'Are you sure you want to delete the selected time entr(y/ies)?' + text_select_project_modules: 'Select modules to enable for this project:' + text_default_administrator_account_changed: Default administrator account changed + 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: "%{hours} 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: "%{value} wrote:" + text_enumeration_destroy_question: "%{count} 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/configuration.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' + text_wiki_page_destroy_question: "This page has %{descendants} child page(s) and descendant(s). What do you want to do?" + text_wiki_page_nullify_children: "Keep child pages as root pages" + text_wiki_page_destroy_children: "Delete child pages and all their descendants" + text_wiki_page_reassign_children: "Reassign child pages to this parent page" + text_own_membership_delete_confirmation: "You are about to remove some or all of your permissions and may no longer be able to edit this project after that.\nAre you sure you want to continue?" + text_zoom_in: Zoom in + text_zoom_out: Zoom out + text_warn_on_leaving_unsaved: "The current page contains unsaved text that will be lost if you leave this page." + text_scm_path_encoding_note: "Default: UTF-8" + text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) + text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) + text_scm_command: Command + text_scm_command_version: Version + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + text_issue_conflict_resolution_overwrite: "Apply my changes anyway (previous notes will be kept but some changes may be overwritten)" + text_issue_conflict_resolution_add_notes: "Add my notes and discard my other changes" + text_issue_conflict_resolution_cancel: "Discard all my changes and redisplay %{link}" + text_account_destroy_confirmation: "Are you sure you want to proceed?\nYour account will be permanently deleted, with no way to reactivate it." + text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." + text_project_closed: This project is closed and read-only. + text_turning_multiple_off: "If you disable multiple values, multiple values will be removed in order to preserve only one value per item." + + default_role_manager: Manager + default_role_developer: Developer + default_role_reporter: Reporter + default_tracker_bug: Bug + default_tracker_feature: Feature + default_tracker_support: Support + default_issue_status_new: New + default_issue_status_in_progress: In Progress + default_issue_status_resolved: Resolved + default_issue_status_feedback: Feedback + default_issue_status_closed: Closed + default_issue_status_rejected: Rejected + default_doc_category_user: User documentation + default_doc_category_tech: Technical documentation + default_priority_low: Low + default_priority_normal: Normal + default_priority_high: High + default_priority_urgent: Urgent + default_priority_immediate: Immediate + default_activity_design: Design + default_activity_development: Development + + enumeration_issue_priorities: Issue priorities + enumeration_doc_categories: Document categories + enumeration_activities: Activities (time tracking) + enumeration_system_activity: System Activity + description_filter: Filter + description_search: Searchfield + description_choose_project: Projects + description_project_scope: Search scope + description_notes: Notes + description_message_content: Message content + description_query_sort_criteria_attribute: Sort attribute + description_query_sort_criteria_direction: Sort direction + description_user_mail_notification: Mail notification settings + description_available_columns: Available Columns + description_selected_columns: Selected Columns + description_all_columns: All Columns + description_issue_category_reassign: Choose issue category + description_wiki_subpages_reassign: Choose new parent page + description_date_range_list: Choose range from list + description_date_range_interval: Choose range by selecting start and end date + description_date_from: Enter start date + description_date_to: Enter end date + text_repository_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed.
Once saved, the identifier cannot be changed.' diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/37/3708cf8f04cafb1d769bb5ceca8ecd5dd9ef264d.svn-base --- a/.svn/pristine/37/3708cf8f04cafb1d769bb5ceca8ecd5dd9ef264d.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,21 +0,0 @@ -<% reply_links = authorize_for('issues', 'edit') -%> -<% for journal in journals %> -
-

- <%= avatar(journal.user, :size => "24") %> - <%= content_tag('a', '', :name => "note-#{journal.indice}")%> - <%= authoring journal.created_on, journal.user, :label => :label_updated_time_by %>

- - <% if journal.details.any? %> -
    - <% for detail in journal.details %> -
  • <%= show_detail(detail) %>
  • - <% end %> -
- <% end %> - <%= render_notes(issue, journal, :reply_links => reply_links) unless journal.notes.blank? %> -
- <%= call_hook(:view_issues_history_journal_bottom, { :journal => journal }) %> -<% end %> - -<% heads_for_wiki_formatter if User.current.allowed_to?(:edit_issue_notes, issue.project) || User.current.allowed_to?(:edit_own_issue_notes, issue.project) %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/37/3761b0e47a324667f29e56ceb9695ed94f68fe4d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/37/3761b0e47a324667f29e56ceb9695ed94f68fe4d.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,44 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 AutoCompletesController < ApplicationController + before_filter :find_project + + def issues + @issues = [] + q = (params[:q] || params[:term]).to_s.strip + if q.present? + scope = (params[:scope] == "all" || @project.nil? ? Issue : @project.issues).visible + if q.match(/\A#?(\d+)\z/) + @issues << scope.find_by_id($1.to_i) + end + @issues += scope.where("LOWER(#{Issue.table_name}.subject) LIKE LOWER(?)", "%#{q}%").order("#{Issue.table_name}.id DESC").limit(10).all + @issues.compact! + end + render :layout => false + end + + private + + def find_project + if params[:project_id].present? + @project = Project.find(params[:project_id]) + end + rescue ActiveRecord::RecordNotFound + render_404 + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/37/376d8c99e588cd6ce8e1e869de342e76d55623d6.svn-base --- a/.svn/pristine/37/376d8c99e588cd6ce8e1e869de342e76d55623d6.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +0,0 @@ -class AppAndPluginModel < ActiveRecord::Base - def self.report_location; TestHelper::report_location(__FILE__); end -end \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/37/379e1dce88d814d8a7bcf79ecfe42aa08a6ba5f8.svn-base --- a/.svn/pristine/37/379e1dce88d814d8a7bcf79ecfe42aa08a6ba5f8.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,19 +0,0 @@ -<%= error_messages_for 'enumeration' %> -
- -<%= hidden_field 'enumeration', 'type' %> - -

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

- -

-<%= check_box 'enumeration', 'active' %>

- -

-<%= check_box 'enumeration', 'is_default' %>

- - -<% @enumeration.custom_field_values.each do |value| %> -

<%= custom_field_tag_with_label :enumeration, value %>

-<% end %> -
diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/37/37ab6adb38ec399dce62202cc442ace6462d7703.svn-base --- a/.svn/pristine/37/37ab6adb38ec399dce62202cc442ace6462d7703.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,45 +0,0 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -module AttachmentsHelper - # Displays 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 - - def render_api_attachment(attachment, api) - api.attachment do - api.id attachment.id - api.filename attachment.filename - api.filesize attachment.filesize - api.content_type attachment.content_type - api.description attachment.description - api.content_url url_for(:controller => 'attachments', :action => 'download', :id => attachment, :filename => attachment.filename, :only_path => false) - api.author(:id => attachment.author.id, :name => attachment.author.name) if attachment.author - api.created_on attachment.created_on - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/37/37b31d80a7477985b5820b70128b01d1053a8cec.svn-base --- a/.svn/pristine/37/37b31d80a7477985b5820b70128b01d1053a8cec.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,89 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) - -class AuthSourceLdapTest < ActiveSupport::TestCase - fixtures :auth_sources - - def setup - end - - def test_create - a = AuthSourceLdap.new(:name => 'My LDAP', :host => 'ldap.example.net', :port => 389, :base_dn => 'dc=example,dc=net', :attr_login => 'sAMAccountName') - assert a.save - end - - def test_should_strip_ldap_attributes - a = AuthSourceLdap.new(:name => 'My LDAP', :host => 'ldap.example.net', :port => 389, :base_dn => 'dc=example,dc=net', :attr_login => 'sAMAccountName', - :attr_firstname => 'givenName ') - assert a.save - assert_equal 'givenName', a.reload.attr_firstname - end - - def test_replace_port_zero_to_389 - a = AuthSourceLdap.new( - :name => 'My LDAP', :host => 'ldap.example.net', :port => 0, - :base_dn => 'dc=example,dc=net', :attr_login => 'sAMAccountName', - :attr_firstname => 'givenName ') - assert a.save - assert_equal 389, a.port - end - - if ldap_configured? - context '#authenticate' do - setup do - @auth = AuthSourceLdap.find(1) - end - - context 'with a valid LDAP user' do - should 'return the user attributes' do - attributes = @auth.authenticate('example1','123456') - assert attributes.is_a?(Hash), "An hash was not returned" - assert_equal 'Example', attributes[:firstname] - assert_equal 'One', attributes[:lastname] - assert_equal 'example1@redmine.org', attributes[:mail] - assert_equal @auth.id, attributes[:auth_source_id] - attributes.keys.each do |attribute| - assert User.new.respond_to?("#{attribute}="), "Unexpected :#{attribute} attribute returned" - end - end - end - - context 'with an invalid LDAP user' do - should 'return nil' do - assert_equal nil, @auth.authenticate('nouser','123456') - end - end - - context 'without a login' do - should 'return nil' do - assert_equal nil, @auth.authenticate('','123456') - end - end - - context 'without a password' do - should 'return nil' do - assert_equal nil, @auth.authenticate('edavis','') - end - end - - end - else - puts '(Test LDAP server not configured)' - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/37/37c265b21c0c5ae4d4c2ec8cfe9dabe532a90dd1.svn-base --- a/.svn/pristine/37/37c265b21c0c5ae4d4c2ec8cfe9dabe532a90dd1.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,32 +0,0 @@ -
-<%= link_to_if_authorized l(:button_edit), {:controller => 'documents', :action => 'edit', :id => @document}, :class => 'icon icon-edit', :accesskey => accesskey(:edit) %> -<%= link_to_if_authorized l(:button_delete), {:controller => 'documents', :action => 'destroy', :id => @document}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %> -
- -

<%=h @document.title %>

- -

<%=h @document.category.name %>
-<%= format_date @document.created_on %>

-
-<%= textilizable @document.description, :attachments => @document.attachments %> -
- -

<%= l(:label_attachment_plural) %>

-<%= link_to_attachments @document %> - -<% if authorize_for('documents', 'add_attachment') %> -

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

- <% form_tag({ :controller => 'documents', :action => 'add_attachment', :id => @document }, :multipart => true, :id => "add_attachment_form", :style => "display:none;") do %> -
-

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

-
- <%= submit_tag l(:button_add) %> - <% end %> -<% end %> - -<% html_title @document.title -%> - -<% content_for :header_tags do %> - <%= stylesheet_link_tag 'scm' %> -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/37/37f5b9c7485d4f52f7798b9d54c2aab8e937964a.svn-base --- a/.svn/pristine/37/37f5b9c7485d4f52f7798b9d54c2aab8e937964a.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) - -class TokenTest < ActiveSupport::TestCase - fixtures :tokens - - def test_create - token = Token.new - token.save - assert_equal 40, token.value.length - assert !token.expired? - end - - def test_create_should_remove_existing_tokens - user = User.find(1) - t1 = Token.create(:user => user, :action => 'autologin') - t2 = Token.create(:user => user, :action => 'autologin') - assert_not_equal t1.value, t2.value - assert !Token.exists?(t1.id) - assert Token.exists?(t2.id) - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/38/3813f5e1b5a3dcfe71214c111cf9817ac22039cc.svn-base --- a/.svn/pristine/38/3813f5e1b5a3dcfe71214c111cf9817ac22039cc.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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 GroupCustomField < CustomField - def type_name - :label_group_plural - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/38/383b53ea01b1aa0055dd414210668c6694327465.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/38/383b53ea01b1aa0055dd414210668c6694327465.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,77 @@ +

<%= link_to l(:label_tracker_plural), trackers_path %> » <%= l(:field_summary) %>

+ +<% if @trackers.any? %> + <%= form_tag fields_trackers_path do %> +
+ + + + + <% @trackers.each do |tracker| %> + + <% end %> + + + + + + + <% Tracker::CORE_FIELDS.each do |field| %> + "> + + <% @trackers.each do |tracker| %> + + <% end %> + + <% end %> + <% if @custom_fields.any? %> + + + + <% @custom_fields.each do |field| %> + "> + + <% @trackers.each do |tracker| %> + + <% end %> + + <% end %> + <% end %> + +
+ <%= tracker.name %> + <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('input.tracker-#{tracker.id}')", + :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %> +
+   + <%= l(:field_core_fields) %> +
+ <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('input.core-field-#{field}')", + :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %> + <%= l("field_#{field}".sub(/_id$/, '')) %> + + <%= check_box_tag "trackers[#{tracker.id}][core_fields][]", field, tracker.core_fields.include?(field), + :class => "tracker-#{tracker.id} core-field-#{field}" %> +
+   + <%= l(:label_custom_field_plural) %> +
+ <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('input.custom-field-#{field.id}')", + :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %> + <%= field.name %> + + <%= check_box_tag "trackers[#{tracker.id}][custom_field_ids][]", field.id, tracker.custom_fields.include?(field), + :class => "tracker-#{tracker.id} custom-field-#{field.id}" %> +
+
+

<%= submit_tag l(:button_save) %>

+ <% @trackers.each do |tracker| %> + <%= hidden_field_tag "trackers[#{tracker.id}][core_fields][]", '' %> + <%= hidden_field_tag "trackers[#{tracker.id}][custom_field_ids][]", '' %> + <% end %> + <% end %> +<% else %> +

<%= l(:label_no_data) %>

+<% end %> + +<% html_title l(:field_summary) %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/38/387d439159b4d0ddd79c12022e45def5fc069c4f.svn-base --- a/.svn/pristine/38/387d439159b4d0ddd79c12022e45def5fc069c4f.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class EnabledModule < ActiveRecord::Base - belongs_to :project - - validates_presence_of :name - validates_uniqueness_of :name, :scope => :project_id - - after_create :module_enabled - - private - - # after_create callback used to do things when a module is enabled - def module_enabled - case name - when 'wiki' - # Create a wiki with a default start page - if project && project.wiki.nil? - Wiki.create(:project => project, :start_page => 'Wiki') - end - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/38/389e9dcc20c4e06169902989d099cd6ec772aa7a.svn-base --- a/.svn/pristine/38/389e9dcc20c4e06169902989d099cd6ec772aa7a.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,274 +0,0 @@ -= EDGE - -* Samuel Williams (http://www.oriontransfer.co.nz/): - Thanks to Tekin for his patches. - Updated migrations system to tie in more closely with the current rails mechanism. - Rake task for updating database schema info - rake db:migrate:upgrade_plugin_migrations - Please see http://engines.lighthouseapp.com/projects/10178-engines-plugin/tickets/17 for more information. - -* Refactored the view loading to work with changes in Edge Rails - -* Fixed integration of plugin migrations with the new, default timestamped migrations in Edge Rails - -* Refactored tests into the plugin itself - the plugin can now generate its own test_app harness and run tests within it. - - -= 2.0.0 - (ANOTHER) MASSIVE INTERNAL REFACTORING - -* Engines now conforms to the new plugin loading mechanism, delegating plugin load order and lots of other things to Rails itself. - - - -= 1.2.2 - -* Added the ability to code mix different types of files, cleaning up the existing code-mixing implementation slightly (Ticket #271) - - -= 1.2.1 - -* Added documentation to clarify some of the issues with Rails unloading classes that aren't required using "require_dependency" (Ticket #266) - -* Fixed a bug where test_help was being loaded when it wasn't needed, and was actually causing problems (Ticket #265) - - -= 1.2.0 - MASSIVE INTERNAL REFACTORING - -* !!!Support for Rails < 1.2 has been dropped!!!; if you are using Rails =< 1.1.6, please use Engines 1.1.6, available from http://svn.rails-engines.org/engines/tags/rel_1.1.6 - -* Engines are dead! Long live plugins! There is now no meaningful notion of an engine - all plugins can take advantage of the more powerful features that the engines plugin provides by including app directories, etc. - -* Init_engine.rb is no longer used; please use the plugin-standard init.rb instead. - -* Engines.start is no longer required; please use the config.plugins array provided by Rails instead - -* To get the most benefit from Engines, set config.plugins to ["engines", "*"] to load the engines plugin first, and then all other plugins in their normal order after. - -* Access all loaded plugins via the new Rails.plugins array, and by name using Rails.plugins[:plugin_name]. - -* Access plugin metadata loaded automatically from about.yml: Rails.plugins[:name].about. Plugin#version is provided directly, for easy access. - -* Module.config is has been removed - use mattr_accessor instead, and initialize your default values via the init.rb mechanism. - -* Public asset helpers have been rewritten; instead of engine_stylesheet, now use stylesheet_link_tag :name, :plugin => "plugin_name" - -* Plugin migrations have been reworked to integrate into the main migration stream. Please run script/generate plugin_migration to create plugin migrations in your main application. - -* The fixture method for loading fixtures against any class has been removed; instead, engines will now provide a mechanism for loading fixtures from all plugins, by mirroring fixtures into a common location. - -* All references to engines have been removed; For example, any rake tasks which applied to engines now apply to all plugins. The default Rails rake tasks for plugins are overridden where necessary. - -* Layouts can now be shared via plugins - inspiration gratefully taken from PluginAWeek's plugin_routing :) - -* Actual routing from plugins is now possible, by including routes.rb in your plugin directory and using the from_plugin method in config/routes.rb (Ticket #182) - -* Controllers are no longer loaded twice if they're not present in the normal app/ directory (Ticket #177) - -* The preferred location for javascripts/stylesheets/etc is now 'assets' rather than 'public' - -* Ensure that plugins started before routing have their controllers appropriately added to config.controller_paths (Ticket #258) - -* Removed Engines.version - it's not longer relevant, now we're loading version information from about.yml files. - -* Added a huge amount of documentation to all new modules. - -* Added new warning message if installation of engines 1.2.x is attempted in a Rails 1.1.x application - -* Added details of the removal of the config method to UPGRADING - -* Removed the plugins:info rake task in favour of adding information to script/about via the Rails::Info module (Ticket #261) - -* Improved handling of testing and documentation tasks for plugins - - - -= 1.1.4 - -* Fixed creation of multipart emails (Ticket #190) - -* Added a temporary fix to the code-mixing issue. In your engine's test/test_helper.rb, please add the following lines: - - # Ensure that the code mixing and view loading from the application is disabled - Engines.disable_app_views_loading = true - Engines.disable_app_code_mixing = true - - which will prevent code mixing for controllers and helpers, and loading views from the application. One thing to remember is to load any controllers/helpers using 'require_or_load' in your tests, to ensure that the engine behaviour is respected (Ticket #135) - -* Added tasks to easily test engines individually (Ticket #120) - -* Fixture extensions will now fail with an exception if the corresponding class cannot be loaded (Ticket #138) - -* Patch for new routing/controller loading in Rails 1.1.6. The routing code is now replaced with the contents of config.controller_paths, along with controller paths from any started engines (Ticket #196) - -* Rails' Configuration instance is now stored, and available from all engines and plugins. - - - -= 1.1.3 - -* Fixed README to show 'models' rather than 'model' class (Ticket #167) -* Fixed dependency loading to work with Rails 1.1.4 (Ticket #180) - - - -= 1.1.2 - -* Added better fix to version checking (Ticket #130, jdell@gbdev.com). - -* Fixed generated init_engine.rb so that VERSION module doesn't cause probems (Ticket #131, japgolly@gmail.com) - -* Fixed error with Rails 1.0 when trying to ignore the engine_schema_info table (Ticket #132, snowblink@gmail.com) - -* Re-added old style rake tasks (Ticket #133) - -* No longer adding all subdirectories of /app or /lib, as this can cause issues when files are grouped in modules (Ticket #149, kasatani@gmail.com) - -* Fixed engine precidence ordering for Rails 1.1 (Ticket #146) - -* Added new Engines.each method to assist in processing the engines in the desired order (Ticket #146) - -* Fixed annoying error message at appears when starting the console in development mode (Ticket #134) - -* Engines is now super-careful about loading the correct version of Rails from vendor (Ticket #154) - - - -= 1.1.1 - -* Fixed migration rake task failing when given a specific version (Ticket #115) - -* Added new rake task "test:engines" which will test engines (and other plugins) but ensure that the test database is cloned from development beforehand (Ticket #125) - -* Fixed issue where 'engine_schema_info' table was included in schema dumps (Ticket #87) - -* Fixed multi-part emails (Ticket #121) - -* Added an 'install.rb' file to new engines created by the bundled generator, which installs the engines plugin automatically if it doesn't already exist (Ticket #122) - -* Added a default VERSION module to generated engines (Ticket #123) - -* Refactored copying of engine's public files to a method of an Engine instance. You can now call Engines.get(:engine_name).copy_public_files (Ticket #108) - -* Changed engine generator templates from .rb files to .erb files (Ticket #106) - -* Fixed the test_helper.erb file to use the correct testing extensions and not load any schema - the schema will be cloned automatically via rake test:engines - -* Fixed problem when running with Rails 1.1.1 where version wasn't determined correctly (Ticket #129) - -* Fixed bug preventing engines from loading when both Rails 1.1.0 and 1.1.1 gems are installed and in use. - -* Updated version (d'oh!) - - - -= 1.1.0 - -* Improved regexp matching for Rails 1.0 engines with peculiar paths - -* Engine instance objects can be accessed via Engines[:name], an alias for Engines.get(:name) (Ticket #99) - -* init_engine.rb is now processed as the final step in the Engine.start process, so it can access files within the lib directory, which is now in the $LOAD_PATH at that point. (Ticket #99) - -* Clarified MIT license (Ticket #98) - -* Updated Rake tasks to integrate smoothly with Rails 1.1 namespaces - -* Changed the version to "1.1.0 (svn)" - -* Added more information about using the plugin with Edge Rails to the README - -* moved extensions into lib/engines/ directory to enable use of Engines module in extension code. - -* Added conditional require_or_load method which attempts to detect the current Rails version. To use the Edge Rails version of the loading mechanism, add the line: - -* Engines.config :edge, true - -* to your environment.rb file. - -* Merged changes from /branches/edge and /branches/rb_1.0 into /trunk - -* engine_schema_info now respects the prefix/suffixes set for ActiveRecord::Base (Ticket #67) - -* added ActiveRecord::Base.wrapped_table_name(name) method to assist in determining the correct table name - - - -= 1.0.6 - -* Added ability to determine version information for engines: rake engine_info - -* Added a custom logger for the Engines module, to stop pollution of the Rails logs. - -* Added some more tests (in particular, see rails_engines/applications/engines_test). - -* Another attempt at solving Ticket #53 - controllers and helpers should now be loadable from modules, and if a full path (including RAILS_ROOT/ENGINES_ROOT) is given, it should be safely stripped from the require filename such that corresponding files can be located in any active engines. In other words, controller/helper overloading should now completely work, even if the controllers/helpers are in modules. - -* Added (finally) patch from Ticket #22 - ActionMailer helpers should now load - -* Removed support for Engines.start :engine, :engine_name => 'whatever'. It was pointless. - -* Fixed engine name referencing; engine_stylesheet/engine_javascript can now happily use shorthand engine names (i.e. :test == :test_engine) (Ticket #45) - -* Fixed minor documentation error ('Engine.start' ==> 'Engines.start') (Ticket #57) - -* Fixed double inclusion of RAILS_ROOT in engine_migrate rake task (Ticket #61) - -* Added ability to force config values even if given as a hash (Ticket #62) - - - -= 1.0.5 - -* Fixed bug stopping fixtures from loading with PostgreSQL - - - -= 1.0.4 - -* Another attempt at loading controllers within modules (Ticket #56) - - - -= 1.0.3 - -* Fixed serious dependency bug stopping controllers being loaded (Ticket #56) - - - -= 1.0.2 - -* Fixed bug with overloading controllers in modules from /app directory - -* Fixed exception thrown when public files couldn't be created; exception is now logged (Ticket #52) - -* Fixed problem with generated test_helper.rb file via File.expand_path (Ticket #50) - - - -= 1.0.1 - -* Added engine generator for creation of new engines - -* Fixed 'Engine' typo in README - -* Fixed bug in fixtures extensions - -* Fixed /lib path management bug - -* Added method to determine public directory location from Engine object - -* Fixed bug in the error message in get_engine_dir() - -* Added proper component loading - -* Added preliminary tests for the config() methods module - - - -= pre-v170 - -* Fixed copyright notices to point to DHH, rather than me. - -* Moved extension require statements into lib/engines.rb, so the will be loaded if another module/file calls require 'engines - -* Added a CHANGELOG file (this file) diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/38/38ca6dda2ba6a1320c43393fa70400cd1996e3e9.svn-base --- a/.svn/pristine/38/38ca6dda2ba6a1320c43393fa70400cd1996e3e9.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,130 +0,0 @@ -require 'test/unit' - -$:.unshift File.expand_path('../../../lib', __FILE__) -require 'coderay' - -class ExamplesTest < Test::Unit::TestCase - - def test_examples - # output as HTML div (using inline CSS styles) - div = CodeRay.scan('puts "Hello, world!"', :ruby).div - assert_equal <<-DIV, div -
-
puts "Hello, world!"
-
- DIV - - # ...with line numbers - div = CodeRay.scan(<<-CODE.chomp, :ruby).div(:line_numbers => :table) -5.times do - puts 'Hello, world!' -end - CODE - assert_equal <<-DIV, div - - - -
1
-2
-3
-
5.times do
-  puts 'Hello, world!'
-end
- DIV - - # output as standalone HTML page (using CSS classes) - page = CodeRay.scan('puts "Hello, world!"', :ruby).page - assert page[<<-PAGE] - - - - - -
-
puts "Hello, world!"
- - - PAGE - - # keep scanned tokens for later use - tokens = CodeRay.scan('{ "just": "an", "example": 42 }', :json) - assert_kind_of CodeRay::TokensProxy, tokens - - assert_equal ["{", :operator, " ", :space, :begin_group, :key, - "\"", :delimiter, "just", :content, "\"", :delimiter, - :end_group, :key, ":", :operator, " ", :space, - :begin_group, :string, "\"", :delimiter, "an", :content, - "\"", :delimiter, :end_group, :string, ",", :operator, - " ", :space, :begin_group, :key, "\"", :delimiter, - "example", :content, "\"", :delimiter, :end_group, :key, - ":", :operator, " ", :space, "42", :integer, - " ", :space, "}", :operator], tokens.tokens - - # produce a token statistic - assert_equal <<-STATISTIC, tokens.statistic - -Code Statistics - -Tokens 26 - Non-Whitespace 15 -Bytes Total 31 - -Token Types (7): - type count ratio size (average) -------------------------------------------------------------- - TOTAL 26 100.00 % 1.2 - delimiter 6 23.08 % 1.0 - operator 5 19.23 % 1.0 - space 5 19.23 % 1.0 - key 4 15.38 % 0.0 - :begin_group 3 11.54 % 0.0 - :end_group 3 11.54 % 0.0 - content 3 11.54 % 4.3 - string 2 7.69 % 0.0 - integer 1 3.85 % 2.0 - - STATISTIC - - # count the tokens - assert_equal 26, tokens.count - - # produce a HTML div, but with CSS classes - div = tokens.div(:css => :class) - assert_equal <<-DIV, div -
-
{ "just": "an", "example": 42 }
-
- DIV - - # highlight a file (HTML div); guess the file type base on the extension - require 'coderay/helpers/file_type' - assert_equal :ruby, CodeRay::FileType[__FILE__] - - # get a new scanner for Python - python_scanner = CodeRay.scanner :python - assert_kind_of CodeRay::Scanners::Python, python_scanner - - # get a new encoder for terminal - terminal_encoder = CodeRay.encoder :term - assert_kind_of CodeRay::Encoders::Terminal, terminal_encoder - - # scanning into tokens - tokens = python_scanner.tokenize 'import this; # The Zen of Python' - assert_equal ["import", :keyword, " ", :space, "this", :include, - ";", :operator, " ", :space, "# The Zen of Python", :comment], tokens - - # format the tokens - term = terminal_encoder.encode_tokens(tokens) - assert_equal "\e[1;31mimport\e[0m \e[33mthis\e[0m; \e[37m# The Zen of Python\e[0m", term - - # re-using scanner and encoder - ruby_highlighter = CodeRay::Duo[:ruby, :div] - div = ruby_highlighter.encode('puts "Hello, world!"') - assert_equal <<-DIV, div -
-
puts "Hello, world!"
-
- DIV - end - -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/39/390e71b002ea8073c16b9d8e33ea8c3450b2266a.svn-base --- a/.svn/pristine/39/390e71b002ea8073c16b9d8e33ea8c3450b2266a.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,117 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../../../test_helper', __FILE__) - -class Redmine::PluginTest < ActiveSupport::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 - - def test_requires_redmine_plugin - test = self - other_version = '0.5.0' - - @klass.register :other do - name 'Other' - version other_version - end - - @klass.register :foo do - test.assert requires_redmine_plugin(:other, :version_or_higher => '0.1.0') - test.assert requires_redmine_plugin(:other, :version_or_higher => other_version) - test.assert requires_redmine_plugin(:other, other_version) - test.assert_raise Redmine::PluginRequirementError do - requires_redmine_plugin(:other, :version_or_higher => '99.0.0') - end - - test.assert requires_redmine_plugin(:other, :version => other_version) - test.assert requires_redmine_plugin(:other, :version => [other_version, '99.0.0']) - test.assert_raise Redmine::PluginRequirementError do - requires_redmine_plugin(:other, :version => '99.0.0') - end - test.assert_raise Redmine::PluginRequirementError do - requires_redmine_plugin(:other, :version => ['98.0.0', '99.0.0']) - end - # Missing plugin - test.assert_raise Redmine::PluginNotFound do - requires_redmine_plugin(:missing, :version_or_higher => '0.1.0') - end - test.assert_raise Redmine::PluginNotFound do - requires_redmine_plugin(:missing, '0.1.0') - end - test.assert_raise Redmine::PluginNotFound do - requires_redmine_plugin(:missing, :version => '0.1.0') - end - - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/39/391b1c790687d518fea17dcd03fc5883b7da327b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/39/391b1c790687d518fea17dcd03fc5883b7da327b.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,23 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 IssuePriorityCustomField < CustomField + def type_name + :enumeration_issue_priorities + end +end + diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/39/3989df463d71da05bed8c7a24a4bb085b8755d7f.svn-base --- a/.svn/pristine/39/3989df463d71da05bed8c7a24a4bb085b8755d7f.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,17 +0,0 @@ -Return-Path: -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> -To: -Subject: Ticket by unknown user -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 - -This is a ticket submitted by an unknown user. - diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/39/399f2a5d4375067a1c2f5abc78f9f28143ca9321.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/39/399f2a5d4375067a1c2f5abc78f9f28143ca9321.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,95 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class ReportsController < ApplicationController + menu_item :issues + before_filter :find_project, :authorize, :find_issue_statuses + + def issue_report + @trackers = @project.trackers + @versions = @project.shared_versions.sort + @priorities = IssuePriority.all.reverse + @categories = @project.issue_categories + @assignees = (Setting.issue_group_assignment? ? @project.principals : @project.users).sort + @authors = @project.users.sort + @subprojects = @project.descendants.visible + + @issues_by_tracker = Issue.by_tracker(@project) + @issues_by_version = Issue.by_version(@project) + @issues_by_priority = Issue.by_priority(@project) + @issues_by_category = Issue.by_category(@project) + @issues_by_assigned_to = Issue.by_assigned_to(@project) + @issues_by_author = Issue.by_author(@project) + @issues_by_subproject = Issue.by_subproject(@project) || [] + + render :template => "reports/issue_report" + end + + def issue_report_details + case params[:detail] + when "tracker" + @field = "tracker_id" + @rows = @project.trackers + @data = Issue.by_tracker(@project) + @report_title = l(:field_tracker) + when "version" + @field = "fixed_version_id" + @rows = @project.shared_versions.sort + @data = Issue.by_version(@project) + @report_title = l(:field_version) + when "priority" + @field = "priority_id" + @rows = IssuePriority.all.reverse + @data = Issue.by_priority(@project) + @report_title = l(:field_priority) + when "category" + @field = "category_id" + @rows = @project.issue_categories + @data = Issue.by_category(@project) + @report_title = l(:field_category) + when "assigned_to" + @field = "assigned_to_id" + @rows = (Setting.issue_group_assignment? ? @project.principals : @project.users).sort + @data = Issue.by_assigned_to(@project) + @report_title = l(:field_assigned_to) + when "author" + @field = "author_id" + @rows = @project.users.sort + @data = Issue.by_author(@project) + @report_title = l(:field_author) + when "subproject" + @field = "project_id" + @rows = @project.descendants.visible + @data = Issue.by_subproject(@project) || [] + @report_title = l(:field_subproject) + end + + respond_to do |format| + if @field + format.html {} + else + format.html { redirect_to :action => 'issue_report', :id => @project } + end + end + end + + private + + def find_issue_statuses + @statuses = IssueStatus.sorted.all + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/39/39a608089a590c01af61f645951f37bfe827c689.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/39/39a608089a590c01af61f645951f37bfe827c689.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,61 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class AdminTest < ActionController::IntegrationTest + fixtures :projects, :trackers, :issue_statuses, :issues, + :enumerations, :users, :issue_categories, + :projects_trackers, + :roles, + :member_roles, + :members, + :enabled_modules + + def test_add_user + log_user("admin", "admin") + get "/users/new" + assert_response :success + assert_template "users/new" + post "/users", + :user => { :login => "psmith", :firstname => "Paul", + :lastname => "Smith", :mail => "psmith@somenet.foo", + :language => "en", :password => "psmith09", + :password_confirmation => "psmith09" } + + user = User.find_by_login("psmith") + assert_kind_of User, user + assert_redirected_to "/users/#{ user.id }/edit" + + logged_user = User.try_to_login("psmith", "psmith09") + assert_kind_of User, logged_user + assert_equal "Paul", logged_user.firstname + + put "users/#{user.id}", :id => user.id, :user => { :status => User::STATUS_LOCKED } + assert_redirected_to "/users/#{ user.id }/edit" + locked_user = User.try_to_login("psmith", "psmith09") + assert_equal nil, locked_user + end + + test "Add a user as an anonymous user should fail" do + post '/users', + :user => { :login => 'psmith', :firstname => 'Paul'}, + :password => "psmith09", :password_confirmation => "psmith09" + assert_response :redirect + assert_redirected_to "/login?back_url=http%3A%2F%2Fwww.example.com%2Fusers" + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/39/39e07ef2d5632f8e7ca245cda7735bd6e7674f6d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/39/39e07ef2d5632f8e7ca245cda7735bd6e7674f6d.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,341 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require 'redmine/scm/adapters/abstract_adapter' + +module Redmine + module Scm + module Adapters + class BazaarAdapter < AbstractAdapter + + # Bazaar executable name + BZR_BIN = Redmine::Configuration['scm_bazaar_command'] || "bzr" + + class << self + def client_command + @@bin ||= BZR_BIN + end + + def sq_bin + @@sq_bin ||= shell_quote_command + end + + def client_version + @@client_version ||= (scm_command_version || []) + end + + def client_available + !client_version.empty? + end + + def scm_command_version + scm_version = scm_version_from_command_line.dup + if scm_version.respond_to?(:force_encoding) + scm_version.force_encoding('ASCII-8BIT') + end + if m = scm_version.match(%r{\A(.*?)((\d+\.)+\d+)}) + m[2].scan(%r{\d+}).collect(&:to_i) + end + end + + def scm_version_from_command_line + shellout("#{sq_bin} --version") { |io| io.read }.to_s + end + end + + def initialize(url, root_url=nil, login=nil, password=nil, path_encoding=nil) + @url = url + @root_url = url + @path_encoding = 'UTF-8' + # do not call *super* for non ASCII repository path + end + + def bzr_path_encodig=(encoding) + @path_encoding = encoding + end + + # Get info about the repository + def info + cmd_args = %w|revno| + cmd_args << bzr_target('') + info = nil + scm_cmd(*cmd_args) do |io| + if io.read =~ %r{^(\d+)\r?$} + info = Info.new({:root_url => url, + :lastrev => Revision.new({ + :identifier => $1 + }) + }) + end + end + info + rescue ScmCommandAborted + return nil + end + + # Returns an Entries collection + # or nil if the given path doesn't exist in the repository + def entries(path=nil, identifier=nil, options={}) + path ||= '' + entries = Entries.new + identifier = -1 unless identifier && identifier.to_i > 0 + cmd_args = %w|ls -v --show-ids| + cmd_args << "-r#{identifier.to_i}" + cmd_args << bzr_target(path) + scm_cmd(*cmd_args) do |io| + prefix_utf8 = "#{url}/#{path}".gsub('\\', '/') + logger.debug "PREFIX: #{prefix_utf8}" + prefix = scm_iconv(@path_encoding, 'UTF-8', prefix_utf8) + prefix.force_encoding('ASCII-8BIT') if prefix.respond_to?(:force_encoding) + re = %r{^V\s+(#{Regexp.escape(prefix)})?(\/?)([^\/]+)(\/?)\s+(\S+)\r?$} + io.each_line do |line| + next unless line =~ re + name_locale, slash, revision = $3.strip, $4, $5.strip + name = scm_iconv('UTF-8', @path_encoding, name_locale) + entries << Entry.new({:name => name, + :path => ((path.empty? ? "" : "#{path}/") + name), + :kind => (slash.blank? ? 'file' : 'dir'), + :size => nil, + :lastrev => Revision.new(:revision => revision) + }) + end + end + if logger && logger.debug? + logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") + end + entries.sort_by_name + rescue ScmCommandAborted + return nil + end + + def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) + path ||= '' + identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : 'last:1' + identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : 1 + revisions = Revisions.new + cmd_args = %w|log -v --show-ids| + cmd_args << "-r#{identifier_to}..#{identifier_from}" + cmd_args << bzr_target(path) + scm_cmd(*cmd_args) do |io| + revision = nil + parsing = nil + io.each_line do |line| + if line =~ /^----/ + revisions << revision if revision + revision = Revision.new(:paths => [], :message => '') + parsing = nil + else + next unless revision + if line =~ /^revno: (\d+)($|\s\[merge\]$)/ + revision.identifier = $1.to_i + elsif line =~ /^committer: (.+)$/ + revision.author = $1.strip + elsif line =~ /^revision-id:(.+)$/ + revision.scmid = $1.strip + elsif line =~ /^timestamp: (.+)$/ + revision.time = Time.parse($1).localtime + elsif line =~ /^ -----/ + # partial revisions + parsing = nil unless parsing == 'message' + elsif line =~ /^(message|added|modified|removed|renamed):/ + parsing = $1 + elsif line =~ /^ (.*)$/ + if parsing == 'message' + revision.message << "#{$1}\n" + else + if $1 =~ /^(.*)\s+(\S+)$/ + path_locale = $1.strip + path = scm_iconv('UTF-8', @path_encoding, path_locale) + revid = $2 + case parsing + when 'added' + revision.paths << {:action => 'A', :path => "/#{path}", :revision => revid} + when 'modified' + revision.paths << {:action => 'M', :path => "/#{path}", :revision => revid} + when 'removed' + revision.paths << {:action => 'D', :path => "/#{path}", :revision => revid} + when 'renamed' + new_path = path.split('=>').last + if new_path + revision.paths << {:action => 'M', :path => "/#{new_path.strip}", + :revision => revid} + end + end + end + end + else + parsing = nil + end + end + end + revisions << revision if revision + end + revisions + rescue ScmCommandAborted + return nil + end + + def diff(path, identifier_from, identifier_to=nil) + path ||= '' + if identifier_to + identifier_to = identifier_to.to_i + else + identifier_to = identifier_from.to_i - 1 + end + if identifier_from + identifier_from = identifier_from.to_i + end + diff = [] + cmd_args = %w|diff| + cmd_args << "-r#{identifier_to}..#{identifier_from}" + cmd_args << bzr_target(path) + scm_cmd_no_raise(*cmd_args) do |io| + io.each_line do |line| + diff << line + end + end + diff + end + + def cat(path, identifier=nil) + cat = nil + cmd_args = %w|cat| + cmd_args << "-r#{identifier.to_i}" if identifier && identifier.to_i > 0 + cmd_args << bzr_target(path) + scm_cmd(*cmd_args) do |io| + io.binmode + cat = io.read + end + cat + rescue ScmCommandAborted + return nil + end + + def annotate(path, identifier=nil) + blame = Annotate.new + cmd_args = %w|annotate -q --all| + cmd_args << "-r#{identifier.to_i}" if identifier && identifier.to_i > 0 + cmd_args << bzr_target(path) + scm_cmd(*cmd_args) do |io| + author = nil + identifier = nil + io.each_line do |line| + next unless line =~ %r{^(\d+) ([^|]+)\| (.*)$} + rev = $1 + blame.add_line($3.rstrip, + Revision.new( + :identifier => rev, + :revision => rev, + :author => $2.strip + )) + end + end + blame + rescue ScmCommandAborted + return nil + end + + def self.branch_conf_path(path) + bcp = nil + m = path.match(%r{^(.*[/\\])\.bzr.*$}) + if m + bcp = m[1] + else + bcp = path + end + bcp.gsub!(%r{[\/\\]$}, "") + if bcp + bcp = File.join(bcp, ".bzr", "branch", "branch.conf") + end + bcp + end + + def append_revisions_only + return @aro if ! @aro.nil? + @aro = false + bcp = self.class.branch_conf_path(url) + if bcp && File.exist?(bcp) + begin + f = File::open(bcp, "r") + cnt = 0 + f.each_line do |line| + l = line.chomp.to_s + if l =~ /^\s*append_revisions_only\s*=\s*(\w+)\s*$/ + str_aro = $1 + if str_aro.upcase == "TRUE" + @aro = true + cnt += 1 + elsif str_aro.upcase == "FALSE" + @aro = false + cnt += 1 + end + if cnt > 1 + @aro = false + break + end + end + end + ensure + f.close + end + end + @aro + end + + def scm_cmd(*args, &block) + full_args = [] + full_args += args + full_args_locale = [] + full_args.map do |e| + full_args_locale << scm_iconv(@path_encoding, 'UTF-8', e) + end + ret = shellout( + self.class.sq_bin + ' ' + + full_args_locale.map { |e| shell_quote e.to_s }.join(' '), + &block + ) + if $? && $?.exitstatus != 0 + raise ScmCommandAborted, "bzr exited with non-zero status: #{$?.exitstatus}" + end + ret + end + private :scm_cmd + + def scm_cmd_no_raise(*args, &block) + full_args = [] + full_args += args + full_args_locale = [] + full_args.map do |e| + full_args_locale << scm_iconv(@path_encoding, 'UTF-8', e) + end + ret = shellout( + self.class.sq_bin + ' ' + + full_args_locale.map { |e| shell_quote e.to_s }.join(' '), + &block + ) + ret + end + private :scm_cmd_no_raise + + def bzr_target(path) + target(path, false) + end + private :bzr_target + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/39/39e52a0654ea7d5e49b76b29ba4c1190481acad6.svn-base --- a/.svn/pristine/39/39e52a0654ea7d5e49b76b29ba4c1190481acad6.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,118 +0,0 @@ -# RedMine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# FileSystem adapter -# File written by Paul Rivier, at Demotera. -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -require 'redmine/scm/adapters/abstract_adapter' -require 'find' - -module Redmine - module Scm - module Adapters - class FilesystemAdapter < AbstractAdapter - - class << self - def client_available - true - end - end - - def initialize(url, root_url=nil, login=nil, password=nil, - path_encoding=nil) - @url = with_trailling_slash(url) - @path_encoding = path_encoding.blank? ? 'UTF-8' : path_encoding - end - - def path_encoding - @path_encoding - end - - def format_path_ends(path, leading=true, trailling=true) - path = leading ? with_leading_slash(path) : - without_leading_slash(path) - trailling ? with_trailling_slash(path) : - without_trailling_slash(path) - end - - def info - info = Info.new({:root_url => target(), - :lastrev => nil - }) - info - rescue CommandFailed - return nil - end - - def entries(path="", identifier=nil, options={}) - entries = Entries.new - trgt_utf8 = target(path) - trgt = scm_iconv(@path_encoding, 'UTF-8', trgt_utf8) - Dir.new(trgt).each do |e1| - e_utf8 = scm_iconv('UTF-8', @path_encoding, e1) - next if e_utf8.blank? - relative_path_utf8 = format_path_ends( - (format_path_ends(path,false,true) + e_utf8),false,false) - t1_utf8 = target(relative_path_utf8) - t1 = scm_iconv(@path_encoding, 'UTF-8', t1_utf8) - relative_path = scm_iconv(@path_encoding, 'UTF-8', relative_path_utf8) - e1 = scm_iconv(@path_encoding, 'UTF-8', e_utf8) - if File.exist?(t1) and # paranoid test - %w{file directory}.include?(File.ftype(t1)) and # avoid special types - not File.basename(e1).match(/^\.+$/) # avoid . and .. - p1 = File.readable?(t1) ? relative_path : "" - utf_8_path = scm_iconv('UTF-8', @path_encoding, p1) - entries << - Entry.new({ :name => scm_iconv('UTF-8', @path_encoding, File.basename(e1)), - # below : list unreadable files, but dont link them. - :path => utf_8_path, - :kind => (File.directory?(t1) ? 'dir' : 'file'), - :size => (File.directory?(t1) ? nil : [File.size(t1)].pack('l').unpack('L').first), - :lastrev => - Revision.new({:time => (File.mtime(t1)) }) - }) - end - end - entries.sort_by_name - rescue => err - logger.error "scm: filesystem: error: #{err.message}" - raise CommandFailed.new(err.message) - end - - def cat(path, identifier=nil) - p = scm_iconv(@path_encoding, 'UTF-8', target(path)) - File.new(p, "rb").read - rescue => err - logger.error "scm: filesystem: error: #{err.message}" - raise CommandFailed.new(err.message) - end - - private - - # AbstractAdapter::target is implicitly made to quote paths. - # Here we do not shell-out, so we do not want quotes. - def target(path=nil) - # Prevent the use of .. - if path and !path.match(/(^|\/)\.\.(\/|$)/) - return "#{self.url}#{without_leading_slash(path)}" - end - return self.url - end - end - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/39/39faeb0151b407d013fa291b6fe7a94c6860c63f.svn-base --- a/.svn/pristine/39/39faeb0151b407d013fa291b6fe7a94c6860c63f.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -class AppAndPluginController < ApplicationController - def an_action - render_class_and_action 'from app' - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/3a/3a0e01441fbdb0f5b1aa103fddb2a58126ec7f0e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3a/3a0e01441fbdb0f5b1aa103fddb2a58126ec7f0e.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,33 @@ +

<%=l(:label_report_plural)%>

+ +
+

<%=l(:field_tracker)%>  <%= link_to image_tag('zoom_in.png'), project_issues_report_details_path(@project, :detail => 'tracker') %>

+<%= render :partial => 'simple', :locals => { :data => @issues_by_tracker, :field_name => "tracker_id", :rows => @trackers } %> +
+

<%=l(:field_priority)%>  <%= link_to image_tag('zoom_in.png'), project_issues_report_details_path(@project, :detail => 'priority') %>

+<%= render :partial => 'simple', :locals => { :data => @issues_by_priority, :field_name => "priority_id", :rows => @priorities } %> +
+

<%=l(:field_assigned_to)%>  <%= link_to image_tag('zoom_in.png'), project_issues_report_details_path(@project, :detail => 'assigned_to') %>

+<%= render :partial => 'simple', :locals => { :data => @issues_by_assigned_to, :field_name => "assigned_to_id", :rows => @assignees } %> +
+

<%=l(:field_author)%>  <%= link_to image_tag('zoom_in.png'), project_issues_report_details_path(@project, :detail => 'author') %>

+<%= render :partial => 'simple', :locals => { :data => @issues_by_author, :field_name => "author_id", :rows => @authors } %> +
+<%= call_hook(:view_reports_issue_report_split_content_left, :project => @project) %> +
+ +
+

<%=l(:field_version)%>  <%= link_to image_tag('zoom_in.png'), project_issues_report_details_path(@project, :detail => 'version') %>

+<%= render :partial => 'simple', :locals => { :data => @issues_by_version, :field_name => "fixed_version_id", :rows => @versions } %> +
+<% if @project.children.any? %> +

<%=l(:field_subproject)%>  <%= link_to image_tag('zoom_in.png'), project_issues_report_details_path(@project, :detail => 'subproject') %>

+<%= render :partial => 'simple', :locals => { :data => @issues_by_subproject, :field_name => "project_id", :rows => @subprojects } %> +
+<% end %> +

<%=l(:field_category)%>  <%= link_to image_tag('zoom_in.png'), project_issues_report_details_path(@project, :detail => 'category') %>

+<%= render :partial => 'simple', :locals => { :data => @issues_by_category, :field_name => "category_id", :rows => @categories } %> +
+<%= call_hook(:view_reports_issue_report_split_content_right, :project => @project) %> +
+ diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/3a/3abba0780c56be34f089a5b70681173c4f0cb4c0.svn-base --- a/.svn/pristine/3a/3abba0780c56be34f089a5b70681173c4f0cb4c0.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ -api.time_entry do - api.id @time_entry.id - api.project(:id => @time_entry.project_id, :name => @time_entry.project.name) unless @time_entry.project.nil? - api.issue(:id => @time_entry.issue_id) unless @time_entry.issue.nil? - api.user(:id => @time_entry.user_id, :name => @time_entry.user.name) unless @time_entry.user.nil? - api.activity(:id => @time_entry.activity_id, :name => @time_entry.activity.name) unless @time_entry.activity.nil? - api.hours @time_entry.hours - api.comments @time_entry.comments - api.spent_on @time_entry.spent_on - api.created_on @time_entry.created_on - api.updated_on @time_entry.updated_on -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/3b/3b0a04b81b3503971b4e570669380aa1831579cc.svn-base --- a/.svn/pristine/3b/3b0a04b81b3503971b4e570669380aa1831579cc.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,162 +0,0 @@ -require File.expand_path('../../test_helper', __FILE__) -require 'search_controller' - -# Re-raise errors caught by the controller. -class SearchController; def rescue_action(e) raise e end; end - -class SearchControllerTest < ActionController::TestCase - fixtures :projects, :enabled_modules, :roles, :users, :members, :member_roles, - :issues, :trackers, :issue_statuses, - :custom_fields, :custom_values, - :repositories, :changesets - - def setup - @controller = SearchController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - User.current = nil - end - - def test_search_for_projects - get :index - assert_response :success - assert_template 'index' - - get :index, :q => "cook" - assert_response :success - assert_template 'index' - assert assigns(:results).include?(Project.find(1)) - end - - def test_search_all_projects - get :index, :q => 'recipe subproject commit', :all_words => '' - assert_response :success - assert_template 'index' - - assert assigns(:results).include?(Issue.find(2)) - assert assigns(:results).include?(Issue.find(5)) - assert assigns(:results).include?(Changeset.find(101)) - assert_tag :dt, :attributes => { :class => /issue/ }, - :child => { :tag => 'a', :content => /Add ingredients categories/ }, - :sibling => { :tag => 'dd', :content => /should be classified by categories/ } - - assert assigns(:results_by_type).is_a?(Hash) - assert_equal 5, assigns(:results_by_type)['changesets'] - assert_tag :a, :content => 'Changesets (5)' - end - - def test_search_issues - get :index, :q => 'issue', :issues => 1 - assert_response :success - assert_template 'index' - - assert_equal true, assigns(:all_words) - assert_equal false, assigns(:titles_only) - 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', :all_words => '' - assert_response :success - assert_template 'index' - assert assigns(:results).include?(Issue.find(1)) - assert assigns(:results).include?(Issue.find(5)) - end - - def test_search_without_searchable_custom_fields - CustomField.update_all "searchable = #{ActiveRecord::Base.connection.quoted_false}" - - get :index, :id => 1 - assert_response :success - assert_template 'index' - assert_not_nil assigns(:project) - - get :index, :id => 1, :q => "can" - assert_response :success - assert_template 'index' - end - - def test_search_with_searchable_custom_fields - get :index, :id => 1, :q => "stringforcustomfield" - assert_response :success - results = assigns(:results) - assert_not_nil results - assert_equal 1, results.size - assert results.include?(Issue.find(7)) - end - - def test_search_all_words - # 'all words' is on by default - get :index, :id => 1, :q => 'recipe updating saving', :all_words => '1' - assert_equal true, assigns(:all_words) - results = assigns(:results) - assert_not_nil results - assert_equal 1, results.size - assert results.include?(Issue.find(3)) - end - - def test_search_one_of_the_words - get :index, :id => 1, :q => 'recipe updating saving', :all_words => '' - assert_equal false, assigns(:all_words) - results = assigns(:results) - assert_not_nil results - assert_equal 3, results.size - assert results.include?(Issue.find(3)) - end - - def test_search_titles_only_without_result - get :index, :id => 1, :q => 'recipe updating saving', :titles_only => '1' - results = assigns(:results) - assert_not_nil results - assert_equal 0, results.size - end - - def test_search_titles_only - get :index, :id => 1, :q => 'recipe', :titles_only => '1' - assert_equal true, assigns(:titles_only) - results = assigns(:results) - assert_not_nil results - assert_equal 2, results.size - end - - def test_search_content - Issue.update_all("description = 'This is a searchkeywordinthecontent'", "id=1") - - get :index, :id => 1, :q => 'searchkeywordinthecontent', :titles_only => '' - assert_equal false, assigns(:titles_only) - results = assigns(:results) - assert_not_nil results - assert_equal 1, results.size - end - - def test_search_with_invalid_project_id - get :index, :id => 195, :q => 'recipe' - assert_response 404 - assert_nil assigns(:results) - end - - def test_quick_jump_to_issue - # issue of a public project - get :index, :q => "3" - assert_redirected_to '/issues/3' - - # issue of a private project - get :index, :q => "4" - assert_response :success - assert_template 'index' - end - - def test_large_integer - get :index, :q => '4615713488' - assert_response :success - assert_template 'index' - end - - def test_tokens_with_quotes - get :index, :id => 1, :q => '"good bye" hello "bye bye"' - assert_equal ["good bye", "hello", "bye bye"], assigns(:tokens) - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/3b/3b1b10ce6290c2ed3a8ab7a0cbea04c53b0b08e9.svn-base --- a/.svn/pristine/3b/3b1b10ce6290c2ed3a8ab7a0cbea04c53b0b08e9.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,232 +0,0 @@ -/* redMine - project management software - Copyright (C) 2006-2008 Jean-Philippe Lang */ - -var observingContextMenuClick; - -ContextMenu = Class.create(); -ContextMenu.prototype = { - initialize: function (url) { - this.url = url; - this.createMenu(); - - if (!observingContextMenuClick) { - Event.observe(document, 'click', this.Click.bindAsEventListener(this)); - Event.observe(document, 'contextmenu', this.RightClick.bindAsEventListener(this)); - observingContextMenuClick = true; - } - - this.unselectAll(); - this.lastSelected = null; - }, - - RightClick: function(e) { - this.hideMenu(); - // do not show the context menu on links - if (Event.element(e).tagName == 'A') { return; } - var tr = Event.findElement(e, 'tr'); - if (tr == document || tr == undefined || !tr.hasClassName('hascontextmenu')) { return; } - Event.stop(e); - if (!this.isSelected(tr)) { - this.unselectAll(); - this.addSelection(tr); - this.lastSelected = tr; - } - this.showMenu(e); - }, - - Click: function(e) { - this.hideMenu(); - if (Event.element(e).tagName == 'A' || Event.element(e).tagName == 'IMG') { return; } - if (Event.isLeftClick(e) || (navigator.appVersion.match(/\bMSIE\b/))) { - var tr = Event.findElement(e, 'tr'); - 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) { - // a checkbox may be clicked - if (box.checked) { - tr.addClassName('context-menu-selection'); - } else { - tr.removeClassName('context-menu-selection'); - } - } else { - if (e.ctrlKey || e.metaKey) { - this.toggleSelection(tr); - } else if (e.shiftKey) { - if (this.lastSelected != null) { - var toggling = false; - var rows = $$('.hascontextmenu'); - for (i=0; i window_width) { - render_x -= menu_width; - $('context-menu').addClassName('reverse-x'); - } else { - $('context-menu').removeClassName('reverse-x'); - } - if (max_height > window_height) { - render_y -= menu_height; - $('context-menu').addClassName('reverse-y'); - } else { - $('context-menu').removeClassName('reverse-y'); - } - if (render_x <= 0) render_x = 1; - if (render_y <= 0) render_y = 1; - $('context-menu').style['left'] = (render_x + 'px'); - $('context-menu').style['top'] = (render_y + 'px'); - - Effect.Appear('context-menu', {duration: 0.20}); - if (window.parseStylesheets) { window.parseStylesheets(); } // IE - }}) - }, - - hideMenu: function() { - Element.hide('context-menu'); - }, - - addSelection: function(tr) { - tr.addClassName('context-menu-selection'); - this.checkSelectionBox(tr, true); - this.clearDocumentSelection(); - }, - - toggleSelection: function(tr) { - if (this.isSelected(tr)) { - this.removeSelection(tr); - } else { - this.addSelection(tr); - } - }, - - removeSelection: function(tr) { - tr.removeClassName('context-menu-selection'); - this.checkSelectionBox(tr, false); - }, - - unselectAll: function() { - var rows = $$('.hascontextmenu'); - for (i=0; i 0) { inputs[0].checked = checked; } - }, - - isSelected: function(tr) { - return Element.hasClassName(tr, 'context-menu-selection'); - }, - - clearDocumentSelection: function() { - if (document.selection) { - document.selection.clear(); // IE - } else { - window.getSelection().removeAllRanges(); - } - } -} - -function toggleIssuesSelection(el) { - var boxes = el.getElementsBySelector('input[type=checkbox]'); - var all_checked = true; - for (i = 0; i < boxes.length; i++) { if (boxes[i].checked == false) { all_checked = false; } } - for (i = 0; i < boxes.length; i++) { - if (all_checked) { - boxes[i].checked = false; - boxes[i].up('tr').removeClassName('context-menu-selection'); - } else if (boxes[i].checked == false) { - boxes[i].checked = true; - boxes[i].up('tr').addClassName('context-menu-selection'); - } - } -} - -function window_size() { - var w; - var h; - if (window.innerWidth) { - w = window.innerWidth; - h = window.innerHeight; - } else if (document.documentElement) { - w = document.documentElement.clientWidth; - h = document.documentElement.clientHeight; - } else { - w = document.body.clientWidth; - h = document.body.clientHeight; - } - return {width: w, height: h}; -} diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/3b/3b35aa3e53757a7316cb173ab97cf120044ce7fe.svn-base --- a/.svn/pristine/3b/3b35aa3e53757a7316cb173ab97cf120044ce7fe.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -<%= yield %> (with plugin layout) \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/3b/3b77726a8efda3938653233bf84d8e02401c4bc3.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3b/3b77726a8efda3938653233bf84d8e02401c4bc3.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,30 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class WelcomeController < ApplicationController + caches_action :robots + + def index + @news = News.latest User.current + @projects = Project.latest User.current + end + + def robots + @projects = Project.all_public.active + render :layout => false, :content_type => 'text/plain' + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/3b/3bdc9be57907c77b3244c04ca134e43fe57a10c8.svn-base --- a/.svn/pristine/3b/3bdc9be57907c77b3244c04ca134e43fe57a10c8.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,40 +0,0 @@ -<%= call_hook(:view_issues_form_details_top, { :issue => @issue, :form => f }) %> - -
> -<% if @issue.safe_attribute_names.include?('is_private') %> -

- -

-<% end %> -

<%= f.select :tracker_id, @project.trackers.collect {|t| [t.name, t.id]}, :required => true %>

-<%= observe_field :issue_tracker_id, :url => { :action => :new, :project_id => @project, :id => @issue }, - :update => :attributes, - :with => "Form.serialize('issue-form')" %> - -

<%= f.text_field :subject, :size => 80, :required => true %>

-

<%= 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' %>

-
- -
- <%= render :partial => 'issues/attributes' %> -
- -<% if @issue.new_record? %> -

<%= label_tag('attachments[1][file]', l(:label_attachment_plural))%><%= render :partial => 'attachments/form' %>

-<% end %> - -<% if @issue.new_record? && User.current.allowed_to?(:add_issue_watchers, @project) -%> -

-<% @issue.project.users.sort.each do |user| -%> - -<% end -%> -

-<% end %> - -<%= call_hook(:view_issues_form_details_bottom, { :issue => @issue, :form => f }) %> - -<%= wikitoolbar_for 'issue_description' %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/3c/3c16dbd6e7c58f730f5ff1711301f6b5ab6045e0.svn-base --- a/.svn/pristine/3c/3c16dbd6e7c58f730f5ff1711301f6b5ab6045e0.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,16 +0,0 @@ -# Settings specified here will take precedence over those in config/environment.rb - -# In the development environment your application's code is reloaded on -# every request. This slows down response time but is perfect for development -# since you don't have to restart the webserver when you make code changes. -config.cache_classes = false - -# Log error messages when you accidentally call methods on nil. -config.whiny_nils = true - -# Show full error reports and disable caching -config.action_controller.consider_all_requests_local = true -config.action_controller.perform_caching = false - -# Don't care if the mailer can't send -config.action_mailer.raise_delivery_errors = false diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/3c/3c376c5f92040d674ff0d9660198f1ddf43a3782.svn-base --- a/.svn/pristine/3c/3c376c5f92040d674ff0d9660198f1ddf43a3782.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,264 +0,0 @@ ---- -issues_001: - created_on: <%= 3.days.ago.to_date.to_s(:db) %> - project_id: 1 - updated_on: <%= 1.day.ago.to_date.to_s(:db) %> - priority_id: 4 - subject: Can't print recipes - id: 1 - fixed_version_id: - category_id: 1 - description: Unable to print recipes - tracker_id: 1 - assigned_to_id: - author_id: 2 - status_id: 1 - start_date: <%= 1.day.ago.to_date.to_s(:db) %> - due_date: <%= 10.day.from_now.to_date.to_s(:db) %> - root_id: 1 - lft: 1 - rgt: 2 -issues_002: - created_on: 2006-07-19 21:04:21 +02:00 - project_id: 1 - updated_on: 2006-07-19 21:09:50 +02:00 - priority_id: 5 - subject: Add ingredients categories - id: 2 - fixed_version_id: 2 - category_id: - description: Ingredients of the recipe should be classified by categories - tracker_id: 2 - assigned_to_id: 3 - author_id: 2 - status_id: 2 - start_date: <%= 2.day.ago.to_date.to_s(:db) %> - due_date: - root_id: 2 - lft: 1 - rgt: 2 - lock_version: 3 - done_ratio: 30 -issues_003: - created_on: 2006-07-19 21:07:27 +02:00 - project_id: 1 - updated_on: 2006-07-19 21:07:27 +02:00 - priority_id: 4 - subject: Error 281 when updating a recipe - id: 3 - fixed_version_id: - category_id: - description: Error 281 is encountered when saving a recipe - tracker_id: 1 - assigned_to_id: 3 - author_id: 2 - status_id: 1 - start_date: <%= 15.day.ago.to_date.to_s(:db) %> - due_date: <%= 5.day.ago.to_date.to_s(:db) %> - root_id: 3 - lft: 1 - rgt: 2 -issues_004: - created_on: <%= 5.days.ago.to_date.to_s(:db) %> - project_id: 2 - updated_on: <%= 2.days.ago.to_date.to_s(:db) %> - priority_id: 4 - subject: Issue on project 2 - id: 4 - fixed_version_id: - category_id: - description: Issue on project 2 - tracker_id: 1 - assigned_to_id: 2 - author_id: 2 - status_id: 1 - root_id: 4 - lft: 1 - rgt: 2 -issues_005: - created_on: <%= 5.days.ago.to_date.to_s(:db) %> - project_id: 3 - updated_on: <%= 2.days.ago.to_date.to_s(:db) %> - priority_id: 4 - subject: Subproject issue - id: 5 - fixed_version_id: - category_id: - description: This is an issue on a cookbook subproject - tracker_id: 1 - assigned_to_id: - author_id: 2 - status_id: 1 - root_id: 5 - lft: 1 - rgt: 2 -issues_006: - created_on: <%= 1.minute.ago.to_date.to_s(:db) %> - project_id: 5 - updated_on: <%= 1.minute.ago.to_date.to_s(:db) %> - priority_id: 4 - subject: Issue of a private subproject - id: 6 - fixed_version_id: - category_id: - description: This is an issue of a private subproject of cookbook - tracker_id: 1 - assigned_to_id: - author_id: 2 - status_id: 1 - start_date: <%= Date.today.to_s(:db) %> - due_date: <%= 1.days.from_now.to_date.to_s(:db) %> - root_id: 6 - lft: 1 - rgt: 2 -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: 5 - 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 - root_id: 7 - lft: 1 - rgt: 2 -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: 5 - 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 - root_id: 8 - lft: 1 - rgt: 2 -issues_009: - created_on: <%= 1.minute.ago.to_date.to_s(:db) %> - project_id: 5 - updated_on: <%= 1.minute.ago.to_date.to_s(:db) %> - priority_id: 5 - subject: Blocked Issue - id: 9 - fixed_version_id: - category_id: - description: This is an issue that is blocked by issue #10 - tracker_id: 1 - assigned_to_id: - author_id: 2 - status_id: 1 - start_date: <%= Date.today.to_s(:db) %> - due_date: <%= 1.days.from_now.to_date.to_s(:db) %> - root_id: 9 - lft: 1 - rgt: 2 -issues_010: - created_on: <%= 1.minute.ago.to_date.to_s(:db) %> - project_id: 5 - updated_on: <%= 1.minute.ago.to_date.to_s(:db) %> - priority_id: 5 - subject: Issue Doing the Blocking - id: 10 - fixed_version_id: - category_id: - description: This is an issue that blocks issue #9 - tracker_id: 1 - assigned_to_id: - author_id: 2 - status_id: 1 - start_date: <%= Date.today.to_s(:db) %> - due_date: <%= 1.days.from_now.to_date.to_s(:db) %> - root_id: 10 - lft: 1 - rgt: 2 -issues_011: - created_on: <%= 3.days.ago.to_date.to_s(:db) %> - project_id: 1 - updated_on: <%= 1.day.ago.to_date.to_s(:db) %> - priority_id: 5 - subject: Closed issue on a closed version - id: 11 - fixed_version_id: 1 - category_id: 1 - description: - tracker_id: 1 - assigned_to_id: - author_id: 2 - status_id: 5 - start_date: <%= 1.day.ago.to_date.to_s(:db) %> - due_date: - root_id: 11 - lft: 1 - rgt: 2 -issues_012: - created_on: <%= 3.days.ago.to_date.to_s(:db) %> - project_id: 1 - updated_on: <%= 1.day.ago.to_date.to_s(:db) %> - priority_id: 5 - subject: Closed issue on a locked version - id: 12 - fixed_version_id: 2 - category_id: 1 - description: - tracker_id: 1 - assigned_to_id: - author_id: 3 - status_id: 5 - start_date: <%= 1.day.ago.to_date.to_s(:db) %> - due_date: - root_id: 12 - lft: 1 - rgt: 2 -issues_013: - created_on: <%= 5.days.ago.to_date.to_s(:db) %> - project_id: 3 - updated_on: <%= 2.days.ago.to_date.to_s(:db) %> - priority_id: 4 - subject: Subproject issue two - id: 13 - fixed_version_id: - category_id: - description: This is a second issue on a cookbook subproject - tracker_id: 1 - assigned_to_id: - author_id: 2 - status_id: 1 - root_id: 13 - lft: 1 - rgt: 2 -issues_014: - id: 14 - created_on: <%= 15.days.ago.to_date.to_s(:db) %> - project_id: 3 - updated_on: <%= 15.days.ago.to_date.to_s(:db) %> - priority_id: 5 - subject: Private issue on public project - fixed_version_id: - category_id: - description: This is a private issue - tracker_id: 1 - assigned_to_id: - author_id: 2 - status_id: 1 - is_private: true - root_id: 14 - lft: 1 - rgt: 2 diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/3c/3c57aa09eea8e28ae469dce0482885ac576762ef.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3c/3c57aa09eea8e28ae469dce0482885ac576762ef.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,47 @@ +<%= form_tag({}) do -%> +<%= hidden_field_tag 'back_url', url_for(params), :id => nil %> +
+ + + + + <% query.inline_columns.each do |column| %> + <%= column_header(column) %> + <% end %> + + + <% previous_group = false %> + + <% issue_list(issues) do |issue, level| -%> + <% if @query.grouped? && (group = @query.group_by_column.value(issue)) != previous_group %> + <% reset_cycle %> + + + + <% previous_group = group %> + <% end %> + "> + + <%= raw query.inline_columns.map {|column| ""}.join %> + + <% @query.block_columns.each do |column| + if (text = column_content(column, issue)) && text.present? -%> + + + + <% end -%> + <% end -%> + <% end -%> + +
+ <%= link_to image_tag('toggle_check.png'), {}, + :onclick => 'toggleIssuesSelection(this); return false;', + :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %> +
+   + <%= group.blank? ? l(:label_none) : column_content(@query.group_by_column, issue) %> <%= @issue_count_by_group[group] %> + <%= link_to_function("#{l(:button_collapse_all)}/#{l(:button_expand_all)}", + "toggleAllRowGroups(this)", :class => 'toggle-all') %> +
<%= check_box_tag("ids[]", issue.id, false, :id => nil) %>#{column_content(column, issue)}
<%= text %>
+
+<% end -%> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/3c/3c7ff621c4b3a329adfd7d06c0fa2ae1d92c4a27.svn-base --- a/.svn/pristine/3c/3c7ff621c4b3a329adfd7d06c0fa2ae1d92c4a27.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,142 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class IssueRelation < ActiveRecord::Base - belongs_to :issue_from, :class_name => 'Issue', :foreign_key => 'issue_from_id' - belongs_to :issue_to, :class_name => 'Issue', :foreign_key => 'issue_to_id' - - TYPE_RELATES = "relates" - TYPE_DUPLICATES = "duplicates" - TYPE_DUPLICATED = "duplicated" - TYPE_BLOCKS = "blocks" - TYPE_BLOCKED = "blocked" - TYPE_PRECEDES = "precedes" - TYPE_FOLLOWS = "follows" - - TYPES = { TYPE_RELATES => { :name => :label_relates_to, :sym_name => :label_relates_to, :order => 1, :sym => TYPE_RELATES }, - TYPE_DUPLICATES => { :name => :label_duplicates, :sym_name => :label_duplicated_by, :order => 2, :sym => TYPE_DUPLICATED }, - TYPE_DUPLICATED => { :name => :label_duplicated_by, :sym_name => :label_duplicates, :order => 3, :sym => TYPE_DUPLICATES, :reverse => TYPE_DUPLICATES }, - TYPE_BLOCKS => { :name => :label_blocks, :sym_name => :label_blocked_by, :order => 4, :sym => TYPE_BLOCKED }, - TYPE_BLOCKED => { :name => :label_blocked_by, :sym_name => :label_blocks, :order => 5, :sym => TYPE_BLOCKS, :reverse => TYPE_BLOCKS }, - TYPE_PRECEDES => { :name => :label_precedes, :sym_name => :label_follows, :order => 6, :sym => TYPE_FOLLOWS }, - TYPE_FOLLOWS => { :name => :label_follows, :sym_name => :label_precedes, :order => 7, :sym => TYPE_PRECEDES, :reverse => TYPE_PRECEDES } - }.freeze - - validates_presence_of :issue_from, :issue_to, :relation_type - validates_inclusion_of :relation_type, :in => TYPES.keys - validates_numericality_of :delay, :allow_nil => true - validates_uniqueness_of :issue_to_id, :scope => :issue_from_id - - validate :validate_issue_relation - - attr_protected :issue_from_id, :issue_to_id - - before_save :handle_issue_order - - def visible?(user=User.current) - (issue_from.nil? || issue_from.visible?(user)) && (issue_to.nil? || issue_to.visible?(user)) - end - - def deletable?(user=User.current) - visible?(user) && - ((issue_from.nil? || user.allowed_to?(:manage_issue_relations, issue_from.project)) || - (issue_to.nil? || user.allowed_to?(:manage_issue_relations, issue_to.project))) - end - - def after_initialize - if new_record? - if relation_type.blank? - self.relation_type = IssueRelation::TYPE_RELATES - end - end - end - - def validate_issue_relation - if issue_from && issue_to - errors.add :issue_to_id, :invalid if issue_from_id == issue_to_id - errors.add :issue_to_id, :not_same_project unless issue_from.project_id == issue_to.project_id || Setting.cross_project_issue_relations? - #detect circular dependencies depending wether the relation should be reversed - if TYPES.has_key?(relation_type) && TYPES[relation_type][:reverse] - errors.add :base, :circular_dependency if issue_from.all_dependent_issues.include? issue_to - else - errors.add :base, :circular_dependency if issue_to.all_dependent_issues.include? issue_from - end - errors.add :base, :cant_link_an_issue_with_a_descendant if issue_from.is_descendant_of?(issue_to) || issue_from.is_ancestor_of?(issue_to) - end - end - - def other_issue(issue) - (self.issue_from_id == issue.id) ? issue_to : issue_from - end - - # Returns the relation type for +issue+ - def relation_type_for(issue) - if TYPES[relation_type] - if self.issue_from_id == issue.id - relation_type - else - TYPES[relation_type][:sym] - end - end - end - - def label_for(issue) - TYPES[relation_type] ? TYPES[relation_type][(self.issue_from_id == issue.id) ? :name : :sym_name] : :unknow - end - - def handle_issue_order - reverse_if_needed - - if TYPE_PRECEDES == relation_type - self.delay ||= 0 - else - self.delay = nil - end - set_issue_to_dates - end - - def set_issue_to_dates - soonest_start = self.successor_soonest_start - if soonest_start && issue_to - issue_to.reschedule_after(soonest_start) - end - end - - def successor_soonest_start - if (TYPE_PRECEDES == self.relation_type) && delay && issue_from && (issue_from.start_date || issue_from.due_date) - (issue_from.due_date || issue_from.start_date) + 1 + delay - end - end - - def <=>(relation) - TYPES[self.relation_type][:order] <=> TYPES[relation.relation_type][:order] - end - - private - - # Reverses the relation if needed so that it gets stored in the proper way - # Should not be reversed before validation so that it can be displayed back - # as entered on new relation form - def reverse_if_needed - if TYPES.has_key?(relation_type) && TYPES[relation_type][:reverse] - issue_tmp = issue_to - self.issue_to = issue_from - self.issue_from = issue_tmp - self.relation_type = TYPES[relation_type][:reverse] - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/3c/3c9db866c7acbdda129e19ec3d3a7067e2b8c96c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3c/3c9db866c7acbdda129e19ec3d3a7067e2b8c96c.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,1102 @@ +# French translations for Ruby on Rails +# by Christian Lescuyer (christian@flyingcoders.com) +# contributor: Sebastien Grosjean - ZenCocoon.com +# contributor: Thibaut Cuvelier - Developpez.com + +fr: + direction: ltr + date: + formats: + default: "%d/%m/%Y" + short: "%e %b" + long: "%e %B %Y" + long_ordinal: "%e %B %Y" + only_day: "%e" + + day_names: [dimanche, lundi, mardi, mercredi, jeudi, vendredi, samedi] + abbr_day_names: [dim, lun, mar, mer, jeu, ven, sam] + month_names: [~, janvier, février, mars, avril, mai, juin, juillet, août, septembre, octobre, novembre, décembre] + abbr_month_names: [~, jan., fév., mar., avr., mai, juin, juil., août, sept., oct., nov., déc.] + order: + - :day + - :month + - :year + + time: + formats: + default: "%d/%m/%Y %H:%M" + time: "%H:%M" + short: "%d %b %H:%M" + long: "%A %d %B %Y %H:%M:%S %Z" + long_ordinal: "%A %d %B %Y %H:%M:%S %Z" + only_second: "%S" + am: 'am' + pm: 'pm' + + datetime: + distance_in_words: + half_a_minute: "30 secondes" + less_than_x_seconds: + zero: "moins d'une seconde" + one: "moins d'une seconde" + other: "moins de %{count} secondes" + x_seconds: + one: "1 seconde" + other: "%{count} secondes" + less_than_x_minutes: + zero: "moins d'une minute" + one: "moins d'une minute" + other: "moins de %{count} minutes" + x_minutes: + one: "1 minute" + other: "%{count} minutes" + about_x_hours: + one: "environ une heure" + other: "environ %{count} heures" + x_hours: + one: "une heure" + other: "%{count} heures" + x_days: + one: "un jour" + other: "%{count} jours" + about_x_months: + one: "environ un mois" + other: "environ %{count} mois" + x_months: + one: "un mois" + other: "%{count} mois" + about_x_years: + one: "environ un an" + other: "environ %{count} ans" + over_x_years: + one: "plus d'un an" + other: "plus de %{count} ans" + almost_x_years: + one: "presqu'un an" + other: "presque %{count} ans" + prompts: + year: "Année" + month: "Mois" + day: "Jour" + hour: "Heure" + minute: "Minute" + second: "Seconde" + + number: + format: + precision: 3 + separator: ',' + delimiter: ' ' + currency: + format: + unit: '€' + precision: 2 + format: '%n %u' + human: + format: + precision: 3 + storage_units: + format: "%n %u" + units: + byte: + one: "octet" + other: "octet" + kb: "ko" + mb: "Mo" + gb: "Go" + tb: "To" + + support: + array: + sentence_connector: 'et' + skip_last_comma: true + word_connector: ", " + two_words_connector: " et " + last_word_connector: " et " + + activerecord: + errors: + template: + header: + one: "Impossible d'enregistrer %{model} : une erreur" + other: "Impossible d'enregistrer %{model} : %{count} erreurs." + body: "Veuillez vérifier les champs suivants :" + messages: + inclusion: "n'est pas inclus(e) dans la liste" + exclusion: "n'est pas disponible" + invalid: "n'est pas valide" + confirmation: "ne concorde pas avec la confirmation" + accepted: "doit être accepté(e)" + empty: "doit être renseigné(e)" + blank: "doit être renseigné(e)" + too_long: "est trop long (pas plus de %{count} caractères)" + too_short: "est trop court (au moins %{count} caractères)" + wrong_length: "ne fait pas la bonne longueur (doit comporter %{count} caractères)" + taken: "est déjà utilisé" + not_a_number: "n'est pas un nombre" + not_a_date: "n'est pas une date valide" + greater_than: "doit être supérieur à %{count}" + greater_than_or_equal_to: "doit être supérieur ou égal à %{count}" + equal_to: "doit être égal à %{count}" + less_than: "doit être inférieur à %{count}" + less_than_or_equal_to: "doit être inférieur ou égal à %{count}" + odd: "doit être impair" + even: "doit être pair" + greater_than_start_date: "doit être postérieure à la date de début" + not_same_project: "n'appartient pas au même projet" + circular_dependency: "Cette relation créerait une dépendance circulaire" + cant_link_an_issue_with_a_descendant: "Une demande ne peut pas être liée à l'une de ses sous-tâches" + + actionview_instancetag_blank_option: Choisir + + general_text_No: 'Non' + general_text_Yes: 'Oui' + general_text_no: 'non' + general_text_yes: 'oui' + general_lang_name: 'Français' + general_csv_separator: ';' + general_csv_decimal_separator: ',' + general_csv_encoding: ISO-8859-1 + general_pdf_encoding: UTF-8 + general_first_day_of_week: '1' + + notice_account_updated: Le compte a été mis à jour avec succès. + notice_account_invalid_creditentials: Identifiant ou mot de passe invalide. + notice_account_password_updated: Mot de passe mis à jour avec succès. + notice_account_wrong_password: Mot de passe incorrect + notice_account_register_done: Un message contenant les instructions pour activer votre compte vous a été envoyé. + notice_account_unknown_email: Aucun compte ne correspond à cette adresse. + notice_can_t_change_password: Ce compte utilise une authentification externe. Impossible de changer le mot de passe. + notice_account_lost_email_sent: Un message contenant les instructions pour choisir un nouveau mot de passe vous a été envoyé. + notice_account_activated: Votre compte a été activé. Vous pouvez à présent vous connecter. + notice_successful_create: Création effectuée avec succès. + notice_successful_update: Mise à jour effectuée avec succès. + notice_successful_delete: Suppression effectuée avec succès. + notice_successful_connection: Connexion réussie. + notice_file_not_found: "La page à laquelle vous souhaitez accéder n'existe pas ou a été supprimée." + notice_locking_conflict: Les données ont été mises à jour par un autre utilisateur. Mise à jour impossible. + notice_not_authorized: "Vous n'êtes pas autorisé à accéder à cette page." + notice_not_authorized_archived_project: Le projet auquel vous tentez d'accéder a été archivé. + notice_email_sent: "Un email a été envoyé à %{value}" + notice_email_error: "Erreur lors de l'envoi de l'email (%{value})" + notice_feeds_access_key_reseted: "Votre clé d'accès aux flux RSS a été réinitialisée." + notice_failed_to_save_issues: "%{count} demande(s) sur les %{total} sélectionnées n'ont pas pu être mise(s) à jour : %{ids}." + notice_failed_to_save_time_entries: "%{count} temps passé(s) sur les %{total} sélectionnés n'ont pas pu être mis à jour: %{ids}." + 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. + notice_issue_done_ratios_updated: L'avancement des demandes a été mis à jour. + notice_api_access_key_reseted: Votre clé d'accès API a été réinitialisée. + notice_gantt_chart_truncated: "Le diagramme a été tronqué car il excède le nombre maximal d'éléments pouvant être affichés (%{max})" + notice_issue_successful_create: "Demande %{id} créée." + notice_issue_update_conflict: "La demande a été mise à jour par un autre utilisateur pendant que vous la modifiez." + notice_account_deleted: "Votre compte a été définitivement supprimé." + notice_user_successful_create: "Utilisateur %{id} créé." + + error_can_t_load_default_data: "Une erreur s'est produite lors du chargement du paramétrage : %{value}" + error_scm_not_found: "L'entrée et/ou la révision demandée n'existe pas dans le dépôt." + error_scm_command_failed: "Une erreur s'est produite lors de l'accès au dépôt : %{value}" + 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" + error_can_not_reopen_issue_on_closed_version: 'Une demande assignée à une version fermée ne peut pas être réouverte' + error_can_not_archive_project: "Ce projet ne peut pas être archivé" + error_workflow_copy_source: 'Veuillez sélectionner un tracker et/ou un rôle source' + error_workflow_copy_target: 'Veuillez sélectionner les trackers et rôles cibles' + error_issue_done_ratios_not_updated: L'avancement des demandes n'a pas pu être mis à jour. + error_attachment_too_big: Ce fichier ne peut pas être attaché car il excède la taille maximale autorisée (%{max_size}) + error_session_expired: "Votre session a expiré. Veuillez vous reconnecter." + + warning_attachments_not_saved: "%{count} fichier(s) n'ont pas pu être sauvegardés." + + mail_subject_lost_password: "Votre mot de passe %{value}" + mail_body_lost_password: 'Pour changer votre mot de passe, cliquez sur le lien suivant :' + mail_subject_register: "Activation de votre compte %{value}" + mail_body_register: 'Pour activer votre compte, cliquez sur le lien suivant :' + mail_body_account_information_external: "Vous pouvez utiliser votre compte %{value} pour vous connecter." + mail_body_account_information: Paramètres de connexion de votre compte + mail_subject_account_activation_request: "Demande d'activation d'un compte %{value}" + mail_body_account_activation_request: "Un nouvel utilisateur (%{value}) s'est inscrit. Son compte nécessite votre approbation :" + mail_subject_reminder: "%{count} demande(s) arrivent à échéance (%{days})" + mail_body_reminder: "%{count} demande(s) qui vous sont assignées arrivent à échéance dans les %{days} prochains jours :" + mail_subject_wiki_content_added: "Page wiki '%{id}' ajoutée" + mail_body_wiki_content_added: "La page wiki '%{id}' a été ajoutée par %{author}." + mail_subject_wiki_content_updated: "Page wiki '%{id}' mise à jour" + mail_body_wiki_content_updated: "La page wiki '%{id}' a été mise à jour par %{author}." + + + field_name: Nom + field_description: Description + field_summary: Résumé + field_is_required: Obligatoire + field_firstname: Prénom + field_lastname: Nom + field_mail: "Email " + field_filename: Fichier + field_filesize: Taille + field_downloads: Téléchargements + field_author: Auteur + field_created_on: "Créé " + field_updated_on: "Mis-à-jour " + field_closed_on: Fermé + field_field_format: Format + field_is_for_all: Pour tous les projets + field_possible_values: Valeurs possibles + field_regexp: Expression régulière + field_min_length: Longueur minimum + field_max_length: Longueur maximum + field_value: Valeur + field_category: Catégorie + field_title: Titre + field_project: Projet + field_issue: Demande + field_status: Statut + field_notes: Notes + field_is_closed: Demande fermée + field_is_default: Valeur par défaut + field_tracker: Tracker + field_subject: Sujet + field_due_date: Echéance + field_assigned_to: Assigné à + field_priority: Priorité + field_fixed_version: Version cible + field_user: Utilisateur + field_role: Rôle + field_homepage: "Site web " + field_is_public: Public + field_parent: Sous-projet de + field_is_in_roadmap: Demandes affichées dans la roadmap + field_login: "Identifiant " + field_mail_notification: Notifications par mail + field_admin: Administrateur + field_last_login_on: "Dernière connexion " + field_language: Langue + field_effective_date: Date + field_password: Mot de passe + field_new_password: Nouveau mot de passe + field_password_confirmation: Confirmation + field_version: Version + field_type: Type + field_host: Hôte + field_port: Port + field_account: Compte + field_base_dn: Base DN + field_attr_login: Attribut Identifiant + field_attr_firstname: Attribut Prénom + field_attr_lastname: Attribut Nom + field_attr_mail: Attribut Email + field_onthefly: Création des utilisateurs à la volée + field_start_date: Début + field_done_ratio: "% réalisé" + field_auth_source: Mode d'authentification + field_hide_mail: Cacher mon adresse mail + field_comments: Commentaire + field_url: URL + field_start_page: Page de démarrage + field_subproject: Sous-projet + field_hours: Heures + field_activity: Activité + field_spent_on: Date + field_identifier: Identifiant + field_is_filter: Utilisé comme filtre + field_issue_to: Demande liée + field_delay: Retard + field_assignable: Demandes assignables à ce rôle + field_redirect_existing_links: Rediriger les liens existants + field_estimated_hours: Temps estimé + field_column_names: Colonnes + field_time_zone: Fuseau horaire + field_searchable: Utilisé pour les recherches + field_default_value: Valeur par défaut + field_comments_sorting: Afficher les commentaires + field_parent_title: Page parent + field_editable: Modifiable + field_watcher: Observateur + field_identity_url: URL OpenID + field_content: Contenu + field_group_by: Grouper par + field_sharing: Partage + field_active: Actif + field_parent_issue: Tâche parente + field_visible: Visible + field_warn_on_leaving_unsaved: "M'avertir lorsque je quitte une page contenant du texte non sauvegardé" + field_issues_visibility: Visibilité des demandes + field_is_private: Privée + field_commit_logs_encoding: Encodage des messages de commit + field_repository_is_default: Dépôt principal + field_multiple: Valeurs multiples + field_auth_source_ldap_filter: Filtre LDAP + field_core_fields: Champs standards + field_timeout: "Timeout (en secondes)" + field_board_parent: Forum parent + field_private_notes: Notes privées + field_inherit_members: Hériter les membres + + setting_app_title: Titre de l'application + setting_app_subtitle: Sous-titre de l'application + setting_welcome_text: Texte d'accueil + setting_default_language: Langue par défaut + setting_login_required: Authentification obligatoire + setting_self_registration: Inscription des nouveaux utilisateurs + setting_attachment_max_size: Taille maximale des fichiers + setting_issues_export_limit: Limite d'exportation des demandes + setting_mail_from: Adresse d'émission + setting_bcc_recipients: Destinataires en copie cachée (cci) + setting_plain_text_mail: Mail en texte brut (non HTML) + setting_host_name: Nom d'hôte et chemin + setting_text_formatting: Formatage du texte + setting_wiki_compression: Compression de l'historique des pages wiki + setting_feeds_limit: Nombre maximal d'éléments dans les flux Atom + setting_default_projects_public: Définir les nouveaux projets comme publics par défaut + setting_autofetch_changesets: Récupération automatique des commits + setting_sys_api_enabled: Activer les WS pour la gestion des dépôts + setting_commit_ref_keywords: Mots-clés de référencement + setting_commit_fix_keywords: Mots-clés de résolution + setting_autologin: Durée maximale de connexion automatique + setting_date_format: Format de date + setting_time_format: Format d'heure + setting_cross_project_issue_relations: Autoriser les relations entre demandes de différents projets + setting_cross_project_subtasks: Autoriser les sous-tâches dans des projets différents + setting_issue_list_default_columns: Colonnes affichées par défaut sur la liste des demandes + setting_emails_footer: Pied-de-page des emails + setting_protocol: Protocole + setting_per_page_options: Options d'objets affichés par page + setting_user_format: Format d'affichage des utilisateurs + setting_activity_days_default: Nombre de jours affichés sur l'activité des projets + setting_display_subprojects_issues: Afficher par défaut les demandes des sous-projets sur les projets principaux + setting_enabled_scm: SCM activés + setting_mail_handler_body_delimiters: "Tronquer les emails après l'une de ces lignes" + 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_gravatar_enabled: Afficher les Gravatar des utilisateurs + setting_diff_max_lines_displayed: Nombre maximum de lignes de diff affichées + setting_file_max_size_displayed: Taille maximum des fichiers texte affichés en ligne + setting_repository_log_display_limit: "Nombre maximum de révisions affichées sur l'historique d'un fichier" + setting_openid: "Autoriser l'authentification et l'enregistrement OpenID" + setting_password_min_length: Longueur minimum des mots de passe + setting_new_project_user_role_id: Rôle donné à un utilisateur non-administrateur qui crée un projet + setting_default_projects_modules: Modules activés par défaut pour les nouveaux projets + setting_issue_done_ratio: Calcul de l'avancement des demandes + setting_issue_done_ratio_issue_status: Utiliser le statut + setting_issue_done_ratio_issue_field: 'Utiliser le champ % effectué' + setting_rest_api_enabled: Activer l'API REST + setting_gravatar_default: Image Gravatar par défaut + setting_start_of_week: Jour de début des calendriers + setting_cache_formatted_text: Mettre en cache le texte formaté + setting_commit_logtime_enabled: Permettre la saisie de temps + setting_commit_logtime_activity_id: Activité pour le temps saisi + setting_gantt_items_limit: Nombre maximum d'éléments affichés sur le gantt + setting_issue_group_assignment: Permettre l'assignement des demandes aux groupes + setting_default_issue_start_date_to_creation_date: Donner à la date de début d'une nouvelle demande la valeur de la date du jour + setting_commit_cross_project_ref: Permettre le référencement et la résolution des demandes de tous les autres projets + setting_unsubscribe: Permettre aux utilisateurs de supprimer leur propre compte + setting_session_lifetime: Durée de vie maximale des sessions + setting_session_timeout: Durée maximale d'inactivité + setting_thumbnails_enabled: Afficher les vignettes des images + setting_thumbnails_size: Taille des vignettes (en pixels) + setting_non_working_week_days: Jours non travaillés + setting_jsonp_enabled: Activer le support JSONP + setting_default_projects_tracker_ids: Trackers par défaut pour les nouveaux projets + + permission_add_project: Créer un projet + permission_add_subprojects: Créer des sous-projets + permission_edit_project: Modifier le projet + permission_close_project: Fermer / réouvrir le projet + permission_select_project_modules: Choisir les modules + permission_manage_members: Gérer les membres + permission_manage_versions: Gérer les versions + permission_manage_categories: Gérer les catégories de demandes + permission_view_issues: Voir les demandes + permission_add_issues: Créer des demandes + permission_edit_issues: Modifier les demandes + permission_manage_issue_relations: Gérer les relations + permission_set_issues_private: Rendre les demandes publiques ou privées + permission_set_own_issues_private: Rendre ses propres demandes publiques ou privées + permission_add_issue_notes: Ajouter des notes + permission_edit_issue_notes: Modifier les notes + permission_edit_own_issue_notes: Modifier ses propres notes + permission_view_private_notes: Voir les notes privées + permission_set_notes_private: Rendre les notes privées + 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_delete_issue_watchers: Supprimer 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_view_documents: Voir les documents + permission_add_documents: Ajouter des documents + permission_edit_documents: Modifier les documents + permission_delete_documents: Supprimer 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 + permission_export_wiki_pages: Exporter les pages + permission_manage_project_activities: Gérer les activités + permission_manage_subtasks: Gérer les sous-tâches + permission_manage_related_issues: Gérer les demandes associées + + project_module_issue_tracking: Suivi des demandes + project_module_time_tracking: Suivi du temps passé + project_module_news: Publication d'annonces + project_module_documents: Publication de documents + project_module_files: Publication de fichiers + project_module_wiki: Wiki + project_module_repository: Dépôt de sources + project_module_boards: Forums de discussion + + label_user: Utilisateur + label_user_plural: Utilisateurs + label_user_new: Nouvel utilisateur + label_user_anonymous: Anonyme + label_project: Projet + label_project_new: Nouveau projet + label_project_plural: Projets + label_x_projects: + zero: aucun projet + one: un projet + other: "%{count} projets" + label_project_all: Tous les projets + label_project_latest: Derniers projets + label_issue: Demande + label_issue_new: Nouvelle demande + label_issue_plural: Demandes + label_issue_view_all: Voir toutes les demandes + label_issue_added: Demande ajoutée + label_issue_updated: Demande mise à jour + label_issue_note_added: Note ajoutée + label_issue_status_updated: Statut changé + label_issue_priority_updated: Priorité changée + label_issues_by: "Demandes par %{value}" + label_document: Document + label_document_new: Nouveau document + label_document_plural: Documents + label_document_added: Document ajouté + label_role: Rôle + label_role_plural: Rôles + label_role_new: Nouveau rôle + label_role_and_permissions: Rôles et permissions + label_role_anonymous: Anonyme + label_role_non_member: Non membre + label_member: Membre + label_member_new: Nouveau membre + label_member_plural: Membres + label_tracker: Tracker + label_tracker_plural: Trackers + label_tracker_new: Nouveau tracker + label_workflow: Workflow + label_issue_status: Statut de demandes + label_issue_status_plural: Statuts de demandes + label_issue_status_new: Nouveau statut + label_issue_category: Catégorie de demandes + label_issue_category_plural: Catégories de demandes + label_issue_category_new: Nouvelle catégorie + label_custom_field: Champ personnalisé + label_custom_field_plural: Champs personnalisés + label_custom_field_new: Nouveau champ personnalisé + label_enumerations: Listes de valeurs + label_enumeration_new: Nouvelle valeur + label_information: Information + label_information_plural: Informations + label_please_login: Identification + label_register: S'enregistrer + label_login_with_open_id_option: S'authentifier avec OpenID + label_password_lost: Mot de passe perdu + label_home: Accueil + label_my_page: Ma page + label_my_account: Mon compte + label_my_projects: Mes projets + label_my_page_block: Blocs disponibles + label_administration: Administration + label_login: Connexion + label_logout: Déconnexion + label_help: Aide + label_reported_issues: "Demandes soumises " + label_assigned_to_me_issues: Demandes qui me sont assignées + label_last_login: "Dernière connexion " + label_registered_on: "Inscrit le " + label_activity: Activité + label_overall_activity: Activité globale + label_user_activity: "Activité de %{value}" + label_new: Nouveau + label_logged_as: Connecté en tant que + label_environment: Environnement + label_authentication: Authentification + label_auth_source: Mode d'authentification + label_auth_source_new: Nouveau mode d'authentification + label_auth_source_plural: Modes d'authentification + label_subproject_plural: Sous-projets + label_subproject_new: Nouveau sous-projet + label_and_its_subprojects: "%{value} et ses sous-projets" + label_min_max_length: Longueurs mini - maxi + label_list: Liste + label_date: Date + label_integer: Entier + label_float: Nombre décimal + label_boolean: Booléen + label_string: Texte + label_text: Texte long + label_attribute: Attribut + label_attribute_plural: Attributs + label_no_data: Aucune donnée à afficher + label_change_status: Changer le statut + label_history: Historique + label_attachment: Fichier + label_attachment_new: Nouveau fichier + label_attachment_delete: Supprimer le fichier + label_attachment_plural: Fichiers + label_file_added: Fichier ajouté + label_report: Rapport + label_report_plural: Rapports + label_news: Annonce + label_news_new: Nouvelle annonce + label_news_plural: Annonces + label_news_latest: Dernières annonces + label_news_view_all: Voir toutes les annonces + label_news_added: Annonce ajoutée + label_news_comment_added: Commentaire ajouté à une annonce + label_settings: Configuration + label_overview: Aperçu + label_version: Version + label_version_new: Nouvelle version + label_version_plural: Versions + label_confirmation: Confirmation + label_export_to: 'Formats disponibles :' + label_read: Lire... + label_public_projects: Projets publics + label_open_issues: ouvert + label_open_issues_plural: ouverts + label_closed_issues: fermé + label_closed_issues_plural: fermés + label_x_open_issues_abbr_on_total: + zero: 0 ouverte sur %{total} + one: 1 ouverte sur %{total} + other: "%{count} ouvertes sur %{total}" + label_x_open_issues_abbr: + zero: 0 ouverte + one: 1 ouverte + other: "%{count} ouvertes" + label_x_closed_issues_abbr: + zero: 0 fermée + one: 1 fermée + other: "%{count} fermées" + label_x_issues: + zero: 0 demande + one: 1 demande + other: "%{count} demandes" + label_total: Total + label_total_time: Temps total + label_permissions: Permissions + label_current_status: Statut actuel + label_new_statuses_allowed: Nouveaux statuts autorisés + label_all: tous + label_any: tous + label_none: aucun + label_nobody: personne + label_next: Suivant + label_previous: Précédent + label_used_by: Utilisé par + label_details: Détails + label_add_note: Ajouter une note + label_per_page: Par page + label_calendar: Calendrier + label_months_from: mois depuis + label_gantt: Gantt + label_internal: Interne + label_last_changes: "%{count} derniers changements" + label_change_view_all: Voir tous les changements + label_personalize_page: Personnaliser cette page + label_comment: Commentaire + label_comment_plural: Commentaires + label_x_comments: + zero: aucun commentaire + one: un commentaire + other: "%{count} commentaires" + label_comment_add: Ajouter un commentaire + label_comment_added: Commentaire ajouté + label_comment_delete: Supprimer les commentaires + label_query: Rapport personnalisé + label_query_plural: Rapports personnalisés + label_query_new: Nouveau rapport + label_my_queries: Mes rapports personnalisés + label_filter_add: "Ajouter le filtre " + label_filter_plural: Filtres + label_equals: égal + label_not_equals: différent + label_in_less_than: dans moins de + label_in_more_than: dans plus de + label_in_the_next_days: dans les prochains jours + label_in_the_past_days: dans les derniers jours + label_in: dans + label_today: aujourd'hui + label_all_time: toute la période + label_yesterday: hier + label_this_week: cette semaine + label_last_week: la semaine dernière + label_last_n_weeks: "les %{count} dernières semaines" + label_last_n_days: "les %{count} derniers jours" + label_this_month: ce mois-ci + label_last_month: le mois dernier + label_this_year: cette année + label_date_range: Période + label_less_than_ago: il y a moins de + label_more_than_ago: il y a plus de + label_ago: il y a + label_contains: contient + label_not_contains: ne contient pas + label_any_issues_in_project: une demande du projet + label_any_issues_not_in_project: une demande hors du projet + label_no_issues_in_project: aucune demande du projet + label_day_plural: jours + label_repository: Dépôt + label_repository_new: Nouveau dépôt + label_repository_plural: Dépôts + label_browse: Parcourir + label_revision: "Révision " + label_revision_plural: Révisions + label_associated_revisions: Révisions associées + label_added: ajouté + label_modified: modifié + label_copied: copié + label_renamed: renommé + label_deleted: supprimé + label_latest_revision: Dernière révision + label_latest_revision_plural: Dernières révisions + label_view_revisions: Voir les révisions + label_max_size: Taille maximale + label_sort_highest: Remonter en premier + label_sort_higher: Remonter + label_sort_lower: Descendre + label_sort_lowest: Descendre en dernier + label_roadmap: Roadmap + label_roadmap_due_in: "Échéance dans %{value}" + label_roadmap_overdue: "En retard de %{value}" + label_roadmap_no_issues: Aucune demande pour cette version + label_search: "Recherche " + label_result_plural: Résultats + label_all_words: Tous les mots + label_wiki: Wiki + label_wiki_edit: Révision wiki + label_wiki_edit_plural: Révisions wiki + label_wiki_page: Page wiki + label_wiki_page_plural: Pages wiki + label_index_by_title: Index par titre + label_index_by_date: Index par date + label_current_version: Version actuelle + label_preview: Prévisualisation + label_feed_plural: Flux RSS + label_changes_details: Détails de tous les changements + label_issue_tracking: Suivi des demandes + label_spent_time: Temps passé + label_f_hour: "%{value} heure" + label_f_hour_plural: "%{value} heures" + label_time_tracking: Suivi du temps + label_change_plural: Changements + label_statistics: Statistiques + label_commits_per_month: Commits par mois + label_commits_per_author: Commits par auteur + label_view_diff: Voir les différences + label_diff_inline: en ligne + label_diff_side_by_side: côte à côte + label_options: Options + label_copy_workflow_from: Copier le workflow de + label_permissions_report: Synthèse des permissions + label_watched_issues: Demandes surveillées + label_related_issues: Demandes liées + label_applied_status: Statut appliqué + label_loading: Chargement... + label_relation_new: Nouvelle relation + label_relation_delete: Supprimer la relation + label_relates_to: Lié à + label_duplicates: Duplique + label_duplicated_by: Dupliqué par + label_blocks: Bloque + label_blocked_by: Bloqué par + label_precedes: Précède + label_follows: Suit + label_copied_to: Copié vers + label_copied_from: Copié depuis + label_end_to_start: fin à début + label_end_to_end: fin à fin + label_start_to_start: début à début + label_start_to_end: début à fin + label_stay_logged_in: Rester connecté + label_disabled: désactivé + label_show_completed_versions: Voir les versions passées + label_me: moi + label_board: Forum + label_board_new: Nouveau forum + label_board_plural: Forums + label_topic_plural: Discussions + label_message_plural: Messages + label_message_last: Dernier message + label_message_new: Nouveau message + label_message_posted: Message ajouté + label_reply_plural: Réponses + label_send_information: Envoyer les informations à l'utilisateur + label_year: Année + label_month: Mois + label_week: Semaine + label_date_from: Du + label_date_to: Au + label_language_based: Basé sur la langue de l'utilisateur + label_sort_by: "Trier par %{value}" + 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 %{value}" + label_module_plural: Modules + label_added_time_by: "Ajouté par %{author} il y a %{age}" + label_updated_time_by: "Mis à jour par %{author} il y a %{age}" + label_updated_time: "Mis à jour il y a %{value}" + label_jump_to_a_project: Aller à un projet... + label_file_plural: Fichiers + label_changeset_plural: Révisions + label_default_columns: Colonnes par défaut + label_no_change_option: (Pas de changement) + label_bulk_edit_selected_issues: Modifier les demandes sélectionnées + label_theme: Thème + label_default: Défaut + label_search_titles_only: Uniquement dans les titres + label_user_mail_option_all: "Pour tous les événements de tous mes projets" + label_user_mail_option_selected: "Pour tous les événements des projets sélectionnés..." + label_user_mail_no_self_notified: "Je ne veux pas être notifié des changements que j'effectue" + label_registration_activation_by_email: activation du compte par email + label_registration_manual_activation: activation manuelle du compte + label_registration_automatic_activation: activation automatique du compte + label_display_per_page: "Par page : %{value}" + label_age: Âge + label_change_properties: Changer les propriétés + label_general: Général + label_more: Plus + label_scm: SCM + label_plugins: Plugins + label_ldap_authentication: Authentification LDAP + label_downloads_abbr: D/L + label_optional_description: Description facultative + label_add_another_file: Ajouter un autre fichier + label_preferences: Préférences + label_chronological_order: Dans l'ordre chronologique + label_reverse_chronological_order: Dans l'ordre chronologique inverse + label_planning: Planning + label_incoming_emails: Emails entrants + label_generate_key: Générer une clé + label_issue_watchers: Observateurs + label_example: Exemple + label_display: Affichage + label_sort: Tri + label_ascending: Croissant + label_descending: Décroissant + label_date_from_to: Du %{start} au %{end} + label_wiki_content_added: Page wiki ajoutée + label_wiki_content_updated: Page wiki mise à jour + label_group_plural: Groupes + label_group: Groupe + label_group_new: Nouveau groupe + label_time_entry_plural: Temps passé + label_version_sharing_none: Non partagé + label_version_sharing_descendants: Avec les sous-projets + label_version_sharing_hierarchy: Avec toute la hiérarchie + label_version_sharing_tree: Avec tout l'arbre + label_version_sharing_system: Avec tous les projets + label_copy_source: Source + label_copy_target: Cible + label_copy_same_as_target: Comme la cible + label_update_issue_done_ratios: Mettre à jour l'avancement des demandes + label_display_used_statuses_only: N'afficher que les statuts utilisés dans ce tracker + label_api_access_key: Clé d'accès API + label_api_access_key_created_on: Clé d'accès API créée il y a %{value} + label_feeds_access_key: Clé d'accès RSS + label_missing_api_access_key: Clé d'accès API manquante + label_missing_feeds_access_key: Clé d'accès RSS manquante + label_close_versions: Fermer les versions terminées + label_revision_id: Révision %{value} + label_profile: Profil + label_subtask_plural: Sous-tâches + label_project_copy_notifications: Envoyer les notifications durant la copie du projet + label_principal_search: "Rechercher un utilisateur ou un groupe :" + label_user_search: "Rechercher un utilisateur :" + label_additional_workflow_transitions_for_author: Autorisations supplémentaires lorsque l'utilisateur a créé la demande + label_additional_workflow_transitions_for_assignee: Autorisations supplémentaires lorsque la demande est assignée à l'utilisateur + label_issues_visibility_all: Toutes les demandes + label_issues_visibility_public: Toutes les demandes non privées + label_issues_visibility_own: Demandes créées par ou assignées à l'utilisateur + label_export_options: Options d'exportation %{export_format} + label_copy_attachments: Copier les fichiers + label_copy_subtasks: Copier les sous-tâches + label_item_position: "%{position} sur %{count}" + label_completed_versions: Versions passées + label_session_expiration: Expiration des sessions + label_show_closed_projects: Voir les projets fermés + label_status_transitions: Changements de statut + label_fields_permissions: Permissions sur les champs + label_readonly: Lecture + label_required: Obligatoire + label_attribute_of_project: "%{name} du projet" + label_attribute_of_issue: "%{name} de la demande" + label_attribute_of_author: "%{name} de l'auteur" + label_attribute_of_assigned_to: "%{name} de l'assigné" + label_attribute_of_user: "%{name} de l'utilisateur" + label_attribute_of_fixed_version: "%{name} de la version cible" + label_cross_project_descendants: Avec les sous-projets + label_cross_project_tree: Avec tout l'arbre + label_cross_project_hierarchy: Avec toute la hiérarchie + label_cross_project_system: Avec tous les projets + label_gantt_progress_line: Ligne de progression + + button_login: Connexion + button_submit: Soumettre + button_save: Sauvegarder + button_check_all: Tout cocher + button_uncheck_all: Tout décocher + button_collapse_all: Plier tout + button_expand_all: Déplier tout + button_delete: Supprimer + button_create: Créer + button_create_and_continue: Créer et continuer + button_test: Tester + button_edit: Modifier + button_add: Ajouter + button_change: Changer + button_apply: Appliquer + button_clear: Effacer + button_lock: Verrouiller + button_unlock: Déverrouiller + button_download: Télécharger + button_list: Lister + button_view: Voir + button_move: Déplacer + button_move_and_follow: Déplacer et suivre + button_back: Retour + button_cancel: Annuler + button_activate: Activer + button_sort: Trier + button_log_time: Saisir temps + button_rollback: Revenir à cette version + button_watch: Surveiller + button_unwatch: Ne plus surveiller + button_reply: Répondre + button_archive: Archiver + button_unarchive: Désarchiver + button_reset: Réinitialiser + button_rename: Renommer + button_change_password: Changer de mot de passe + button_copy: Copier + button_copy_and_follow: Copier et suivre + button_annotate: Annoter + button_update: Mettre à jour + button_configure: Configurer + button_quote: Citer + button_duplicate: Dupliquer + button_show: Afficher + button_hide: Cacher + button_edit_section: Modifier cette section + button_export: Exporter + button_delete_my_account: Supprimer mon compte + button_close: Fermer + button_reopen: Réouvrir + + status_active: actif + status_registered: enregistré + status_locked: verrouillé + + project_status_active: actif + project_status_closed: fermé + project_status_archived: archivé + + version_status_open: ouvert + version_status_locked: verrouillé + version_status_closed: fermé + + text_select_mail_notifications: Actions pour lesquelles une notification par e-mail est envoyée + text_regexp_info: ex. ^[A-Z0-9]+$ + text_min_max_length_info: 0 pour aucune restriction + text_project_destroy_confirmation: Êtes-vous sûr de vouloir supprimer ce projet et toutes ses données ? + text_subprojects_destroy_warning: "Ses sous-projets : %{value} seront également supprimés." + text_workflow_edit: Sélectionner un tracker et un rôle pour éditer le workflow + text_are_you_sure: Êtes-vous sûr ? + text_tip_issue_begin_day: tâche commençant ce jour + text_tip_issue_end_day: tâche finissant ce jour + text_tip_issue_begin_end_day: tâche commençant et finissant ce jour + text_project_identifier_info: 'Seuls les lettres minuscules (a-z), chiffres, tirets et underscore sont autorisés, doit commencer par une minuscule.
Un fois sauvegardé, l''identifiant ne pourra plus être modifié.' + text_caracters_maximum: "%{count} caractères maximum." + text_caracters_minimum: "%{count} caractères minimum." + text_length_between: "Longueur comprise entre %{min} et %{max} caractères." + text_tracker_no_workflow: Aucun worflow n'est défini pour ce tracker + text_unallowed_characters: Caractères non autorisés + text_comma_separated: Plusieurs valeurs possibles (séparées par des virgules). + text_line_separated: Plusieurs valeurs possibles (une valeur par ligne). + text_issues_ref_in_commit_messages: Référencement et résolution des demandes dans les commentaires de commits + text_issue_added: "La demande %{id} a été soumise par %{author}." + text_issue_updated: "La demande %{id} a été mise à jour par %{author}." + text_wiki_destroy_confirmation: Etes-vous sûr de vouloir supprimer ce wiki et tout son contenu ? + text_issue_category_destroy_question: "%{count} demandes sont affectées à cette catégorie. Que voulez-vous faire ?" + text_issue_category_destroy_assignments: N'affecter les demandes à aucune autre catégorie + text_issue_category_reassign_to: Réaffecter les demandes à cette catégorie + text_user_mail_option: "Pour les projets non sélectionnés, vous recevrez seulement des notifications pour ce que vous surveillez ou à quoi vous participez (exemple: demandes dont vous êtes l'auteur ou la personne assignée)." + text_no_configuration_data: "Les rôles, trackers, statuts et le workflow ne sont pas encore paramétrés.\nIl est vivement recommandé de charger le paramétrage par defaut. Vous pourrez le modifier une fois chargé." + text_load_default_configuration: Charger le paramétrage par défaut + text_status_changed_by_changeset: "Appliqué par commit %{value}." + text_time_logged_by_changeset: "Appliqué par commit %{value}" + text_issues_destroy_confirmation: 'Êtes-vous sûr de vouloir supprimer la ou les demandes(s) selectionnée(s) ?' + text_issues_destroy_descendants_confirmation: "Cela entrainera également la suppression de %{count} sous-tâche(s)." + text_select_project_modules: 'Sélectionner les modules à activer pour ce projet :' + 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: "%{hours} heures ont été enregistrées sur les demandes à supprimer. Que voulez-vous faire ?" + text_destroy_time_entries: Supprimer les heures + text_assign_time_entries_to_project: Reporter les heures sur le projet + text_reassign_time_entries: 'Reporter les heures sur cette demande:' + text_user_wrote: "%{value} a écrit :" + text_enumeration_destroy_question: "Cette valeur est affectée à %{count} 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/configuration.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' + text_wiki_page_destroy_question: "Cette page possède %{descendants} sous-page(s) et descendante(s). Que voulez-vous faire ?" + text_wiki_page_nullify_children: "Conserver les sous-pages en tant que pages racines" + text_wiki_page_destroy_children: "Supprimer les sous-pages et toutes leurs descedantes" + text_wiki_page_reassign_children: "Réaffecter les sous-pages à cette page" + text_own_membership_delete_confirmation: "Vous allez supprimer tout ou partie de vos permissions sur ce projet et ne serez peut-être plus autorisé à modifier ce projet.\nEtes-vous sûr de vouloir continuer ?" + text_warn_on_leaving_unsaved: "Cette page contient du texte non sauvegardé qui sera perdu si vous quittez la page." + text_issue_conflict_resolution_overwrite: "Appliquer quand même ma mise à jour (les notes précédentes seront conservées mais des changements pourront être écrasés)" + text_issue_conflict_resolution_add_notes: "Ajouter mes notes et ignorer mes autres changements" + text_issue_conflict_resolution_cancel: "Annuler ma mise à jour et réafficher %{link}" + text_account_destroy_confirmation: "Êtes-vous sûr de vouloir continuer ?\nVotre compte sera définitivement supprimé, sans aucune possibilité de le réactiver." + text_session_expiration_settings: "Attention : le changement de ces paramètres peut entrainer l'expiration des sessions utilisateurs en cours, y compris la vôtre." + text_project_closed: Ce projet est fermé et accessible en lecture seule. + text_turning_multiple_off: "Si vous désactivez les valeurs multiples, les valeurs multiples seront supprimées pour n'en conserver qu'une par objet." + + default_role_manager: "Manager " + default_role_developer: "Développeur " + default_role_reporter: "Rapporteur " + default_tracker_bug: Anomalie + default_tracker_feature: Evolution + default_tracker_support: Assistance + default_issue_status_new: Nouveau + default_issue_status_in_progress: En cours + default_issue_status_resolved: Résolu + default_issue_status_feedback: Commentaire + default_issue_status_closed: Fermé + default_issue_status_rejected: Rejeté + default_doc_category_user: Documentation utilisateur + default_doc_category_tech: Documentation technique + default_priority_low: Bas + default_priority_normal: Normal + default_priority_high: Haut + default_priority_urgent: Urgent + default_priority_immediate: Immédiat + default_activity_design: Conception + 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) + label_greater_or_equal: ">=" + label_less_or_equal: "<=" + label_between: entre + label_view_all_revisions: Voir toutes les révisions + label_tag: Tag + label_branch: Branche + error_no_tracker_in_project: "Aucun tracker n'est associé à ce projet. Vérifier la configuration du projet." + error_no_default_issue_status: "Aucun statut de demande n'est défini par défaut. Vérifier votre configuration (Administration -> Statuts de demandes)." + text_journal_changed: "%{label} changé de %{old} à %{new}" + text_journal_changed_no_detail: "%{label} mis à jour" + text_journal_set_to: "%{label} mis à %{value}" + text_journal_deleted: "%{label} %{old} supprimé" + text_journal_added: "%{label} %{value} ajouté" + enumeration_system_activity: Activité système + label_board_sticky: Sticky + label_board_locked: Verrouillé + error_unable_delete_issue_status: Impossible de supprimer le statut de demande + error_can_not_delete_custom_field: Impossible de supprimer le champ personnalisé + error_unable_to_connect: Connexion impossible (%{value}) + error_can_not_remove_role: Ce rôle est utilisé et ne peut pas être supprimé. + error_can_not_delete_tracker: Ce tracker contient des demandes et ne peut pas être supprimé. + field_principal: Principal + notice_failed_to_save_members: "Erreur lors de la sauvegarde des membres: %{errors}." + text_zoom_out: Zoom arrière + text_zoom_in: Zoom avant + notice_unable_delete_time_entry: Impossible de supprimer le temps passé. + label_overall_spent_time: Temps passé global + field_time_entries: Temps passé + project_module_gantt: Gantt + project_module_calendar: Calendrier + button_edit_associated_wikipage: "Modifier la page wiki associée: %{page_title}" + field_text: Champ texte + label_user_mail_option_only_owner: Seulement pour ce que j'ai créé + setting_default_notification_option: Option de notification par défaut + label_user_mail_option_only_my_events: Seulement pour ce que je surveille + label_user_mail_option_only_assigned: Seulement pour ce qui m'est assigné + label_user_mail_option_none: Aucune notification + field_member_of_group: Groupe de l'assigné + field_assigned_to_role: Rôle de l'assigné + setting_emails_header: En-tête des emails + label_bulk_edit_selected_time_entries: Modifier les temps passés sélectionnés + text_time_entries_destroy_confirmation: "Etes-vous sûr de vouloir supprimer les temps passés sélectionnés ?" + field_scm_path_encoding: Encodage des chemins + text_scm_path_encoding_note: "Défaut : UTF-8" + field_path_to_repository: Chemin du dépôt + field_root_directory: Répertoire racine + field_cvs_module: Module + field_cvsroot: CVSROOT + text_mercurial_repository_note: "Dépôt local (exemples : /hgrepo, c:\\hgrepo)" + text_scm_command: Commande + text_scm_command_version: Version + label_git_report_last_commit: Afficher le dernier commit des fichiers et répertoires + text_scm_config: Vous pouvez configurer les commandes des SCM dans config/configuration.yml. Redémarrer l'application après modification. + text_scm_command_not_available: Ce SCM n'est pas disponible. Vérifier les paramètres dans la section administration. + label_diff: diff + text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) + description_query_sort_criteria_direction: Ordre de tri + description_project_scope: Périmètre de recherche + description_filter: Filtre + description_user_mail_notification: Option de notification + description_date_from: Date de début + description_message_content: Contenu du message + description_available_columns: Colonnes disponibles + description_all_columns: Toutes les colonnes + description_date_range_interval: Choisir une période + description_issue_category_reassign: Choisir une catégorie + description_search: Champ de recherche + description_notes: Notes + description_date_range_list: Choisir une période prédéfinie + description_choose_project: Projets + description_date_to: Date de fin + description_query_sort_criteria_attribute: Critère de tri + description_wiki_subpages_reassign: Choisir une nouvelle page parent + description_selected_columns: Colonnes sélectionnées + label_parent_revision: Parent + label_child_revision: Enfant + error_scm_annotate_big_text_file: Cette entrée ne peut pas être annotée car elle excède la taille maximale. + setting_repositories_encodings: Encodages des fichiers et des dépôts + label_search_for_watchers: Rechercher des observateurs + text_repository_identifier_info: 'Seuls les lettres minuscules (a-z), chiffres, tirets et underscore sont autorisés.
Un fois sauvegardé, l''identifiant ne pourra plus être modifié.' diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/3c/3cb3c42447393d4203d371cda0ccf19d942248be.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3c/3cb3c42447393d4203d371cda0ccf19d942248be.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,99 @@ +<%= error_messages_for 'custom_field' %> + +
+

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

+

<%= f.select :field_format, custom_field_formats_for_select(@custom_field), {}, :disabled => !@custom_field.new_record? %>

+ +<% if @custom_field.format_in? 'list', 'user', 'version' %> +

+ <%= f.check_box :multiple %> + <% if !@custom_field.new_record? && @custom_field.multiple %> + <%= l(:text_turning_multiple_off) %> + <% end %> +

+<% end %> + +<% unless @custom_field.format_in? 'list', 'bool', 'date', 'user', 'version' %> +

+ <%= f.text_field :min_length, :size => 5, :no_label => true %> - + <%= f.text_field :max_length, :size => 5, :no_label => true %>
(<%=l(:text_min_max_length_info)%>)

+

<%= f.text_field :regexp, :size => 50 %>
(<%=l(:text_regexp_info)%>)

+<% end %> + +<% if @custom_field.format_in? 'list' %> +

+ <%= f.text_area :possible_values, :value => @custom_field.possible_values.to_a.join("\n"), :rows => 15 %> + <%= l(:text_custom_field_possible_values_info) %> +

+<% end %> + +<% case @custom_field.field_format %> +<% when 'bool' %> +

<%= f.check_box(:default_value) %>

+<% when 'text' %> +

<%= f.text_area(:default_value, :rows => 8) %>

+<% when 'date' %> +

<%= f.text_field(:default_value, :size => 10) %>

+ <%= calendar_for('custom_field_default_value') %> +<% when 'user', 'version' %> +<% else %> +

<%= f.text_field(:default_value) %>

+<% end %> + +<%= call_hook(:view_custom_fields_form_upper_box, :custom_field => @custom_field, :form => f) %> +
+ +
+<% case @custom_field.class.name +when "IssueCustomField" %> + +
<%=l(:label_tracker_plural)%> + <% Tracker.sorted.all.each do |tracker| %> + <%= check_box_tag "custom_field[tracker_ids][]", + tracker.id, + (@custom_field.trackers.include? tracker), + :id => "custom_field_tracker_ids_#{tracker.id}" %> + + <% end %> + <%= hidden_field_tag "custom_field[tracker_ids][]", '' %> +
+   +

<%= f.check_box :is_required %>

+

<%= f.check_box :is_for_all %>

+

<%= f.check_box :is_filter %>

+

<%= f.check_box :searchable %>

+ +<% when "UserCustomField" %> +

<%= f.check_box :is_required %>

+

<%= f.check_box :visible %>

+

<%= f.check_box :editable %>

+

<%= f.check_box :is_filter %>

+ +<% when "ProjectCustomField" %> +

<%= f.check_box :is_required %>

+

<%= f.check_box :visible %>

+

<%= f.check_box :searchable %>

+

<%= f.check_box :is_filter %>

+ +<% when "VersionCustomField" %> +

<%= f.check_box :is_required %>

+

<%= f.check_box :is_filter %>

+ +<% when "GroupCustomField" %> +

<%= f.check_box :is_required %>

+

<%= f.check_box :is_filter %>

+ +<% when "TimeEntryCustomField" %> +

<%= f.check_box :is_required %>

+

<%= f.check_box :is_filter %>

+ +<% else %> +

<%= f.check_box :is_required %>

+ +<% end %> +<%= call_hook(:"view_custom_fields_form_#{@custom_field.type.to_s.underscore}", :custom_field => @custom_field, :form => f) %> +
+ +<% include_calendar_headers_tags %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/3c/3cbf045871f4a74462d238faefdda2d93e946bc2.svn-base --- a/.svn/pristine/3c/3cbf045871f4a74462d238faefdda2d93e946bc2.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,8 +0,0 @@ -

<%=l(:label_project_new)%>

- -<% labelled_tabular_form_for @project do |f| %> -<%= render :partial => 'form', :locals => { :f => f } %> -<%= submit_tag l(:button_create) %> -<%= submit_tag l(:button_create_and_continue), :name => 'continue' %> -<%= javascript_tag "Form.Element.focus('project_name');" %> -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/3c/3cc2762940ee93762d4d782147803b7d0198ed51.svn-base --- a/.svn/pristine/3c/3cc2762940ee93762d4d782147803b7d0198ed51.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4 +0,0 @@ -ActionController::Routing::Routes.draw do |map| - map.connect 'routes/:action', :controller => "test_routing" - map.plugin_route 'somespace/routes/:action', :controller => "namespace/test_routing" -end \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/3c/3cd8e88573cd0b42429f6ee4864dcc592f6b54a1.svn-base --- a/.svn/pristine/3c/3cd8e88573cd0b42429f6ee4864dcc592f6b54a1.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,29 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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 #:nodoc: - module CoreExtensions #:nodoc: - module String #:nodoc: - # Custom string inflections - module Inflections - def with_leading_slash - starts_with?('/') ? self : "/#{ self }" - end - end - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/3c/3cdf10eb8093cfc847c369901c8cec7bae62db8a.svn-base --- a/.svn/pristine/3c/3cdf10eb8093cfc847c369901c8cec7bae62db8a.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,125 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) - -class CustomValueTest < ActiveSupport::TestCase - fixtures :custom_fields, :custom_values, :users - - def test_string_field_validation_with_blank_value - f = CustomField.new(:field_format => 'string') - v = CustomValue.new(:custom_field => f) - - v.value = nil - assert v.valid? - v.value = '' - assert v.valid? - - f.is_required = true - v.value = nil - assert !v.valid? - v.value = '' - assert !v.valid? - end - - def test_string_field_validation_with_min_and_max_lengths - f = CustomField.new(:field_format => 'string', :min_length => 2, :max_length => 5) - v = CustomValue.new(:custom_field => f, :value => '') - assert v.valid? - v.value = 'a' - assert !v.valid? - v.value = 'a' * 2 - assert v.valid? - v.value = 'a' * 6 - assert !v.valid? - end - - def test_string_field_validation_with_regexp - f = CustomField.new(:field_format => 'string', :regexp => '^[A-Z0-9]*$') - v = CustomValue.new(:custom_field => f, :value => '') - assert v.valid? - v.value = 'abc' - assert !v.valid? - v.value = 'ABC' - assert v.valid? - end - - def test_date_field_validation - f = CustomField.new(:field_format => 'date') - v = CustomValue.new(:custom_field => f, :value => '') - assert v.valid? - v.value = 'abc' - assert !v.valid? - v.value = '1975-07-33' - assert !v.valid? - v.value = '1975-07-14' - assert v.valid? - end - - def test_list_field_validation - f = CustomField.new(:field_format => 'list', :possible_values => ['value1', 'value2']) - v = CustomValue.new(:custom_field => f, :value => '') - assert v.valid? - v.value = 'abc' - assert !v.valid? - v.value = 'value2' - assert v.valid? - end - - def test_int_field_validation - f = CustomField.new(:field_format => 'int') - v = CustomValue.new(:custom_field => f, :value => '') - assert v.valid? - v.value = 'abc' - assert !v.valid? - v.value = '123' - assert v.valid? - v.value = '+123' - assert v.valid? - v.value = '-123' - assert v.valid? - end - - def test_float_field_validation - v = CustomValue.new(:customized => User.find(:first), :custom_field => UserCustomField.find_by_name('Money')) - v.value = '11.2' - assert v.save - v.value = '' - assert v.save - v.value = '-6.250' - assert v.save - v.value = '6a' - assert !v.save - end - - def test_default_value - field = CustomField.find_by_default_value('Default string') - assert_not_nil field - - v = CustomValue.new(:custom_field => field) - assert_equal 'Default string', v.value - - v = CustomValue.new(:custom_field => field, :value => 'Not empty') - assert_equal 'Not empty', v.value - end - - def test_sti_polymorphic_association - # Rails uses top level sti class for polymorphic association. See #3978. - assert !User.find(4).custom_values.empty? - assert !CustomValue.find(2).customized.nil? - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/3d/3d264827557eaf8b65d3068714787c51e68d02b2.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3d/3d264827557eaf8b65d3068714787c51e68d02b2.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,22 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 DocumentCategoryCustomField < CustomField + def type_name + :enumeration_doc_categories + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/3d/3d3b405886b53d97a18acfbd6ee1f68a9fd0e1c4.svn-base --- a/.svn/pristine/3d/3d3b405886b53d97a18acfbd6ee1f68a9fd0e1c4.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -

<%=l(:button_change_password)%>

- -<%= error_messages_for 'user' %> - -<% form_tag({}, :class => "tabular") do %> -
-

-<%= password_field_tag 'password', nil, :size => 25 %>

- -

-<%= password_field_tag 'new_password', nil, :size => 25 %>
-<%= l(:text_caracters_minimum, :count => Setting.password_min_length) %>

- -

-<%= password_field_tag 'new_password_confirmation', nil, :size => 25 %>

-
-<%= submit_tag l(:button_apply) %> -<% end %> - -<% content_for :sidebar do %> -<%= render :partial => 'sidebar' %> -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/3d/3d503edadd13a313e0e3661d47e1156ded0be542.svn-base --- a/.svn/pristine/3d/3d503edadd13a313e0e3661d47e1156ded0be542.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,65 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class Watcher < ActiveRecord::Base - belongs_to :watchable, :polymorphic => true - belongs_to :user - - validates_presence_of :user - validates_uniqueness_of :user_id, :scope => [:watchable_type, :watchable_id] - - # Unwatch things that users are no longer allowed to view - def self.prune(options={}) - if options.has_key?(:user) - prune_single_user(options[:user], options) - else - pruned = 0 - User.find(:all, :conditions => "id IN (SELECT DISTINCT user_id FROM #{table_name})").each do |user| - pruned += prune_single_user(user, options) - end - pruned - end - end - - protected - - def validate - errors.add :user_id, :invalid unless user.nil? || user.active? - end - - private - - def self.prune_single_user(user, options={}) - return unless user.is_a?(User) - pruned = 0 - find(:all, :conditions => {:user_id => user.id}).each do |watcher| - next if watcher.watchable.nil? - - if options.has_key?(:project) - next unless watcher.watchable.respond_to?(:project) && watcher.watchable.project == options[:project] - end - - if watcher.watchable.respond_to?(:visible?) - unless watcher.watchable.visible?(user) - watcher.destroy - pruned += 1 - end - end - end - pruned - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/3d/3d6522340e1723225cb18ddbafc5c9dc0b5f8799.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3d/3d6522340e1723225cb18ddbafc5c9dc0b5f8799.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,48 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class IssueCategory < ActiveRecord::Base + include Redmine::SafeAttributes + belongs_to :project + belongs_to :assigned_to, :class_name => 'Principal', :foreign_key => 'assigned_to_id' + has_many :issues, :foreign_key => 'category_id', :dependent => :nullify + + validates_presence_of :name + validates_uniqueness_of :name, :scope => [:project_id] + validates_length_of :name, :maximum => 30 + + safe_attributes 'name', 'assigned_to_id' + + scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)} + + alias :destroy_without_reassign :destroy + + # Destroy the category + # If a category is specified, issues are reassigned to this category + def destroy(reassign_to = nil) + if reassign_to && reassign_to.is_a?(IssueCategory) && reassign_to.project == self.project + Issue.update_all({:category_id => reassign_to.id}, {:category_id => id}) + end + destroy_without_reassign + end + + def <=>(category) + name <=> category.name + end + + def to_s; name end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/3d/3d7003238eceffbbaba3bf34003b59ea441ffc00.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3d/3d7003238eceffbbaba3bf34003b59ea441ffc00.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,46 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +module Redmine + module Activity + + mattr_accessor :available_event_types, :default_event_types, :providers + + @@available_event_types = [] + @@default_event_types = [] + @@providers = Hash.new {|h,k| h[k]=[] } + + class << self + def map(&block) + yield self + end + + # Registers an activity provider + def register(event_type, options={}) + options.assert_valid_keys(:class_name, :default) + + event_type = event_type.to_s + providers = options[:class_name] || event_type.classify + providers = ([] << providers) unless providers.is_a?(Array) + + @@available_event_types << event_type unless @@available_event_types.include?(event_type) + @@default_event_types << event_type unless options[:default] == false + @@providers[event_type] += providers + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/3e/3e17a1388b20cd76503961d40ea60716108de247.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3e/3e17a1388b20cd76503961d40ea60716108de247.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,147 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class IssueRelationsControllerTest < ActionController::TestCase + fixtures :projects, + :users, + :roles, + :members, + :member_roles, + :issues, + :issue_statuses, + :issue_relations, + :enabled_modules, + :enumerations, + :trackers, + :projects_trackers + + def setup + User.current = nil + @request.session[:user_id] = 3 + end + + def test_create + assert_difference 'IssueRelation.count' do + post :create, :issue_id => 1, + :relation => {:issue_to_id => '2', :relation_type => 'relates', :delay => ''} + end + relation = IssueRelation.first(:order => 'id DESC') + assert_equal 1, relation.issue_from_id + assert_equal 2, relation.issue_to_id + assert_equal 'relates', relation.relation_type + end + + def test_create_xhr + assert_difference 'IssueRelation.count' do + xhr :post, :create, :issue_id => 3, :relation => {:issue_to_id => '1', :relation_type => 'relates', :delay => ''} + assert_response :success + assert_template 'create' + assert_equal 'text/javascript', response.content_type + end + relation = IssueRelation.first(:order => 'id DESC') + assert_equal 3, relation.issue_from_id + assert_equal 1, relation.issue_to_id + + assert_match /Bug #1/, response.body + end + + def test_create_should_accept_id_with_hash + assert_difference 'IssueRelation.count' do + post :create, :issue_id => 1, + :relation => {:issue_to_id => '#2', :relation_type => 'relates', :delay => ''} + end + relation = IssueRelation.first(:order => 'id DESC') + assert_equal 2, relation.issue_to_id + end + + def test_create_should_strip_id + assert_difference 'IssueRelation.count' do + post :create, :issue_id => 1, + :relation => {:issue_to_id => ' 2 ', :relation_type => 'relates', :delay => ''} + end + relation = IssueRelation.first(:order => 'id DESC') + assert_equal 2, relation.issue_to_id + end + + def test_create_should_not_break_with_non_numerical_id + assert_no_difference 'IssueRelation.count' do + assert_nothing_raised do + post :create, :issue_id => 1, + :relation => {:issue_to_id => 'foo', :relation_type => 'relates', :delay => ''} + end + end + end + + def test_create_follows_relation_should_update_relations_list + issue1 = Issue.generate!(:subject => 'Followed issue', :start_date => Date.yesterday, :due_date => Date.today) + issue2 = Issue.generate! + + assert_difference 'IssueRelation.count' do + xhr :post, :create, :issue_id => issue2.id, + :relation => {:issue_to_id => issue1.id, :relation_type => 'follows', :delay => ''} + end + assert_match /Followed issue/, response.body + 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 + post :create, :issue_id => 1, + :relation => {:issue_to_id => '4', :relation_type => 'relates', :delay => ''} + end + end + + should "prevent relation creation when there's a circular dependency" + + def test_create_xhr_with_failure + assert_no_difference 'IssueRelation.count' do + xhr :post, :create, :issue_id => 3, :relation => {:issue_to_id => '999', :relation_type => 'relates', :delay => ''} + + assert_response :success + assert_template 'create' + assert_equal 'text/javascript', response.content_type + end + + assert_match /errorExplanation/, response.body + end + + def test_destroy + assert_difference 'IssueRelation.count', -1 do + delete :destroy, :id => '2' + end + end + + def test_destroy_xhr + IssueRelation.create!(:relation_type => IssueRelation::TYPE_RELATES) do |r| + r.issue_from_id = 3 + r.issue_to_id = 1 + end + + assert_difference 'IssueRelation.count', -1 do + xhr :delete, :destroy, :id => '2' + + assert_response :success + assert_template 'destroy' + assert_equal 'text/javascript', response.content_type + assert_match /relation-2/, response.body + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/3e/3e2401296aa57bb537864ac16aaa5ce36ba06138.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3e/3e2401296aa57bb537864ac16aaa5ce36ba06138.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,200 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class Redmine::ApiTest::MembershipsTest < Redmine::ApiTest::Base + fixtures :projects, :users, :roles, :members, :member_roles + + def setup + Setting.rest_api_enabled = '1' + end + + context "/projects/:project_id/memberships" do + context "GET" do + context "xml" do + should "return memberships" do + get '/projects/1/memberships.xml', {}, credentials('jsmith') + + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_tag :tag => 'memberships', + :attributes => {:type => 'array'}, + :child => { + :tag => 'membership', + :child => { + :tag => 'id', + :content => '2', + :sibling => { + :tag => 'user', + :attributes => {:id => '3', :name => 'Dave Lopper'}, + :sibling => { + :tag => 'roles', + :child => { + :tag => 'role', + :attributes => {:id => '2', :name => 'Developer'} + } + } + } + } + } + end + end + + context "json" do + should "return memberships" do + get '/projects/1/memberships.json', {}, credentials('jsmith') + + assert_response :success + assert_equal 'application/json', @response.content_type + json = ActiveSupport::JSON.decode(response.body) + assert_equal({ + "memberships" => + [{"id"=>1, + "project" => {"name"=>"eCookbook", "id"=>1}, + "roles" => [{"name"=>"Manager", "id"=>1}], + "user" => {"name"=>"John Smith", "id"=>2}}, + {"id"=>2, + "project" => {"name"=>"eCookbook", "id"=>1}, + "roles" => [{"name"=>"Developer", "id"=>2}], + "user" => {"name"=>"Dave Lopper", "id"=>3}}], + "limit" => 25, + "total_count" => 2, + "offset" => 0}, + json) + end + end + end + + context "POST" do + context "xml" do + should "create membership" do + assert_difference 'Member.count' do + post '/projects/1/memberships.xml', {:membership => {:user_id => 7, :role_ids => [2,3]}}, credentials('jsmith') + + assert_response :created + end + end + + should "return errors on failure" do + assert_no_difference 'Member.count' do + post '/projects/1/memberships.xml', {:membership => {:role_ids => [2,3]}}, credentials('jsmith') + + assert_response :unprocessable_entity + assert_equal 'application/xml', @response.content_type + assert_tag 'errors', :child => {:tag => 'error', :content => "Principal can't be blank"} + end + end + end + end + end + + context "/memberships/:id" do + context "GET" do + context "xml" do + should "return the membership" do + get '/memberships/2.xml', {}, credentials('jsmith') + + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_tag :tag => 'membership', + :child => { + :tag => 'id', + :content => '2', + :sibling => { + :tag => 'user', + :attributes => {:id => '3', :name => 'Dave Lopper'}, + :sibling => { + :tag => 'roles', + :child => { + :tag => 'role', + :attributes => {:id => '2', :name => 'Developer'} + } + } + } + } + end + end + + context "json" do + should "return the membership" do + get '/memberships/2.json', {}, credentials('jsmith') + + assert_response :success + assert_equal 'application/json', @response.content_type + json = ActiveSupport::JSON.decode(response.body) + assert_equal( + {"membership" => { + "id" => 2, + "project" => {"name"=>"eCookbook", "id"=>1}, + "roles" => [{"name"=>"Developer", "id"=>2}], + "user" => {"name"=>"Dave Lopper", "id"=>3}} + }, + json) + end + end + end + + context "PUT" do + context "xml" do + should "update membership" do + assert_not_equal [1,2], Member.find(2).role_ids.sort + assert_no_difference 'Member.count' do + put '/memberships/2.xml', {:membership => {:user_id => 3, :role_ids => [1,2]}}, credentials('jsmith') + + assert_response :ok + assert_equal '', @response.body + end + member = Member.find(2) + assert_equal [1,2], member.role_ids.sort + end + + should "return errors on failure" do + put '/memberships/2.xml', {:membership => {:user_id => 3, :role_ids => [99]}}, credentials('jsmith') + + assert_response :unprocessable_entity + assert_equal 'application/xml', @response.content_type + assert_tag 'errors', :child => {:tag => 'error', :content => /member_roles is invalid/} + end + end + end + + context "DELETE" do + context "xml" do + should "destroy membership" do + assert_difference 'Member.count', -1 do + delete '/memberships/2.xml', {}, credentials('jsmith') + + assert_response :ok + assert_equal '', @response.body + end + assert_nil Member.find_by_id(2) + end + + should "respond with 422 on failure" do + assert_no_difference 'Member.count' do + # A membership with an inherited role can't be deleted + Member.find(2).member_roles.first.update_attribute :inherited_from, 99 + delete '/memberships/2.xml', {}, credentials('jsmith') + + assert_response :unprocessable_entity + end + end + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/3e/3e2856705bc7c004aa221bf2a7acea9b1ba0b99e.svn-base --- a/.svn/pristine/3e/3e2856705bc7c004aa221bf2a7acea9b1ba0b99e.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,554 +0,0 @@ -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: - # * #move_to_child_of - # * #move_to_right_of - # * #move_to_left_of - # - 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: acts_as_nested_set :scope => [:notable_id, :notable_type] - # * +: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| - (root.left > left && root.right > right).tap 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!(force=false) - # Don't rebuild a valid tree. - # valid? doesn't strictly validate the tree - return true if !force && valid? - - scope = lambda{|node|} - 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? - new_record? || (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 => "#{self.class.table_name}.#{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? || !self.class.exists?(id) - - self.class.base_class.transaction do - reload_nested_set - if acts_as_nested_set_options[:dependent] == :destroy - children.each(&:destroy) - else - nested_set_scope.send(:delete_all, - ["#{quoted_left_column_name} > ? AND #{quoted_right_column_name} < ?", - left, right] - ) - end - reload_nested_set - diff = right - left + 1 - 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 - - # Reload is needed because children may have updated their parent (self) during deletion. - reload - 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 -r 0a574315af3e -r 4f746d8966dd .svn/pristine/3e/3e497523d528dce449385801e7d571e2b8c31896.svn-base --- a/.svn/pristine/3e/3e497523d528dce449385801e7d571e2b8c31896.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1006 +0,0 @@ -bg: - # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl) - direction: ltr - date: - formats: - # Use the strftime parameters for formats. - # When no format has been given, it uses default. - # You can provide other formats here if you like! - default: "%d-%m-%Y" - short: "%b %d" - long: "%B %d, %Y" - - day_names: [ÐеделÑ, Понеделник, Вторник, СрÑда, Четвъртък, Петък, Събота] - abbr_day_names: [Ðед, Пон, Вто, СрÑ, Чет, Пет, Съб] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, Януари, Февруари, Март, Ðприл, Май, Юни, Юли, ÐвгуÑÑ‚, Септември, Октомври, Ðоември, Декември] - abbr_month_names: [~, Яну, Фев, Мар, Ðпр, Май, Юни, Юли, Ðвг, Сеп, Окт, Ðое, Дек] - # Used in date_select and datime_select. - order: - - :year - - :month - - :day - - time: - formats: - default: "%a, %d %b %Y %H:%M:%S %z" - time: "%H:%M" - short: "%d %b %H:%M" - long: "%B %d, %Y %H:%M" - am: "am" - pm: "pm" - - datetime: - distance_in_words: - half_a_minute: "half a minute" - less_than_x_seconds: - one: "по-малко от 1 Ñекунда" - other: "по-малко от %{count} Ñекунди" - x_seconds: - one: "1 Ñекунда" - other: "%{count} Ñекунди" - less_than_x_minutes: - one: "по-малко от 1 минута" - other: "по-малко от %{count} минути" - x_minutes: - one: "1 минута" - other: "%{count} минути" - about_x_hours: - one: "около 1 чаÑ" - other: "около %{count} чаÑа" - x_days: - one: "1 ден" - other: "%{count} дена" - about_x_months: - one: "около 1 меÑец" - other: "около %{count} меÑеца" - x_months: - one: "1 меÑец" - other: "%{count} меÑеца" - about_x_years: - one: "около 1 година" - other: "около %{count} години" - over_x_years: - one: "над 1 година" - other: "над %{count} години" - almost_x_years: - one: "почти 1 година" - other: "почти %{count} години" - - number: - format: - separator: "." - delimiter: "" - precision: 3 - - human: - format: - precision: 1 - delimiter: "" - storage_units: - format: "%n %u" - units: - byte: - one: Byte - other: Bytes - kb: "KB" - mb: "MB" - gb: "GB" - tb: "TB" - -# Used in array.to_sentence. - support: - array: - sentence_connector: "и" - skip_last_comma: false - - activerecord: - errors: - template: - header: - one: "1 грешка попречи този %{model} да бъде запиÑан" - other: "%{count} грешки попречиха този %{model} да бъде запиÑан" - messages: - inclusion: "не ÑъщеÑтвува в ÑпиÑъка" - exclusion: "е запазено" - invalid: "е невалидно" - confirmation: "липÑва одобрение" - accepted: "трÑбва да Ñе приеме" - empty: "не може да е празно" - blank: "не може да е празно" - too_long: "е прекалено дълго" - too_short: "е прекалено къÑо" - wrong_length: "е Ñ Ð³Ñ€ÐµÑˆÐ½Ð° дължина" - taken: "вече ÑъщеÑтвува" - not_a_number: "не е чиÑло" - not_a_date: "е невалидна дата" - greater_than: "трÑбва да бъде по-голÑм[a/о] от %{count}" - greater_than_or_equal_to: "трÑбва да бъде по-голÑм[a/о] от или равен[a/o] на %{count}" - equal_to: "трÑбва да бъде равен[a/o] на %{count}" - less_than: "трÑбва да бъде по-малък[a/o] от %{count}" - less_than_or_equal_to: "трÑбва да бъде по-малък[a/o] от или равен[a/o] на %{count}" - odd: "трÑбва да бъде нечетен[a/o]" - even: "трÑбва да бъде четен[a/o]" - greater_than_start_date: "трÑбва да е Ñлед началната дата" - not_same_project: "не е от ÑÑŠÑ‰Ð¸Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚" - circular_dependency: "Тази Ñ€ÐµÐ»Ð°Ñ†Ð¸Ñ Ñ‰Ðµ доведе до безкрайна завиÑимоÑÑ‚" - cant_link_an_issue_with_a_descendant: "Една задача не може да бъде Ñвързвана към ÑÐ²Ð¾Ñ Ð¿Ð¾Ð´Ð·Ð°Ð´Ð°Ñ‡Ð°" - - actionview_instancetag_blank_option: Изберете - - general_text_No: 'Ðе' - general_text_Yes: 'Да' - general_text_no: 'не' - general_text_yes: 'да' - general_lang_name: 'Bulgarian (БългарÑки)' - general_csv_separator: ',' - general_csv_decimal_separator: '.' - general_csv_encoding: UTF-8 - general_pdf_encoding: UTF-8 - general_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: Ðепознат e-mail. - notice_can_t_change_password: Този профил е Ñ Ð²ÑŠÐ½ÑˆÐµÐ½ метод за оторизациÑ. Ðевъзможна ÑмÑна на паролата. - notice_account_lost_email_sent: Изпратен ви е e-mail Ñ Ð¸Ð½Ñтрукции за избор на нова парола. - notice_account_activated: Профилът ви е активиран. Вече може да влезете в ÑиÑтемата. - notice_successful_create: УÑпешно Ñъздаване. - notice_successful_update: УÑпешно обновÑване. - notice_successful_delete: УÑпешно изтриване. - notice_successful_connection: УÑпешно Ñвързване. - notice_file_not_found: ÐеÑъщеÑтвуваща или премеÑтена Ñтраница. - notice_locking_conflict: Друг потребител Ð¿Ñ€Ð¾Ð¼ÐµÐ½Ñ Ñ‚ÐµÐ·Ð¸ данни в момента. - notice_not_authorized: ÐÑмате право на доÑтъп до тази Ñтраница. - notice_not_authorized_archived_project: Проектът, който Ñе опитвате да видите е архивиран. Ðко ÑмÑтате, че това не е правилно, обърнете Ñе към админиÑтратора за разархивиране. - notice_email_sent: "Изпратен e-mail на %{value}" - notice_email_error: "Грешка при изпращане на e-mail (%{value})" - notice_feeds_access_key_reseted: Ð’Ð°ÑˆÐ¸Ñ ÐºÐ»ÑŽÑ‡ за RSS доÑтъп беше променен. - notice_api_access_key_reseted: ВашиÑÑ‚ API ключ за доÑтъп беше изчиÑтен. - notice_failed_to_save_issues: "ÐеуÑпешен Ð·Ð°Ð¿Ð¸Ñ Ð½Ð° %{count} задачи от %{total} избрани: %{ids}." - notice_failed_to_save_members: "ÐевъзможноÑÑ‚ за Ð·Ð°Ð¿Ð¸Ñ Ð½Ð° член(ове): %{errors}." - notice_no_issue_selected: "ÐÑма избрани задачи." - notice_account_pending: "Профилът Ви е Ñъздаден и очаква одобрение от админиÑтратор." - notice_default_data_loaded: Примерната Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ðµ заредена уÑпешно. - notice_unable_delete_version: ÐевъзможноÑÑ‚ за изтриване на верÑÐ¸Ñ - notice_unable_delete_time_entry: ÐевъзможноÑÑ‚ за изтриване на Ð·Ð°Ð¿Ð¸Ñ Ð½Ð° time log. - notice_issue_done_ratios_updated: Обновен процент на завършените задачи. - notice_gantt_chart_truncated: МрежовиÑÑ‚ график е Ñъкратен, понеже броÑÑ‚ на обектите, които могат да бъдат показани е твърде голÑм (%{max}) - notice_issue_successful_create: Задача %{id} е Ñъздадена. - - error_can_t_load_default_data: "Грешка при зареждане на примерната информациÑ: %{value}" - error_scm_not_found: ÐеÑъщеÑтвуващ обект в хранилището. - error_scm_command_failed: "Грешка при опит за ÐºÐ¾Ð¼ÑƒÐ½Ð¸ÐºÐ°Ñ†Ð¸Ñ Ñ Ñ…Ñ€Ð°Ð½Ð¸Ð»Ð¸Ñ‰Ðµ: %{value}" - error_scm_annotate: "Обектът не ÑъщеÑтвува или не може да бъде анотиран." - error_scm_annotate_big_text_file: "Файлът не може да бъде анотиран, понеже Ð½Ð°Ð´Ñ…Ð²ÑŠÑ€Ð»Ñ Ð¼Ð°ÐºÑÐ¸Ð¼Ð°Ð»Ð½Ð¸Ñ Ñ€Ð°Ð·Ð¼ÐµÑ€ за текÑтови файлове." - error_issue_not_found_in_project: 'Задачата не е намерена или не принадлежи на този проект' - error_no_tracker_in_project: ÐÑма аÑоциирани тракери Ñ Ñ‚Ð¾Ð·Ð¸ проект. Проверете наÑтройките на проекта. - error_no_default_issue_status: ÐÑма уÑтановено подразбиращо Ñе ÑÑŠÑтоÑние за задачите. ÐœÐ¾Ð»Ñ Ð¿Ñ€Ð¾Ð²ÐµÑ€ÐµÑ‚Ðµ вашата ÐºÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ (Вижте "ÐдминиÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ -> СъÑтоÑÐ½Ð¸Ñ Ð½Ð° задачи"). - error_can_not_delete_custom_field: ÐевъзможноÑÑ‚ за изтриване на потребителÑко поле - error_can_not_delete_tracker: Този тракер Ñъдържа задачи и не може да бъде изтрит. - error_can_not_remove_role: Тази Ñ€Ð¾Ð»Ñ Ñе използва и не може да бъде изтрита. - error_can_not_reopen_issue_on_closed_version: Задача, аÑоциирана ÑÑŠÑ Ð·Ð°Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð° верÑÐ¸Ñ Ð½Ðµ може да бъде отворена отново - error_can_not_archive_project: Този проект не може да бъде архивиран - error_issue_done_ratios_not_updated: Процентът на завършените задачи не е обновен. - error_workflow_copy_source: ÐœÐ¾Ð»Ñ Ð¸Ð·Ð±ÐµÑ€ÐµÑ‚Ðµ source тракер или Ñ€Ð¾Ð»Ñ - error_workflow_copy_target: ÐœÐ¾Ð»Ñ Ð¸Ð·Ð±ÐµÑ€ÐµÑ‚Ðµ тракер(и) и Ñ€Ð¾Ð»Ñ (роли). - error_unable_delete_issue_status: ÐевъзможноÑÑ‚ за изтриване на ÑÑŠÑтоÑние на задача - error_unable_to_connect: ÐевъзможноÑÑ‚ за Ñвързване Ñ (%{value}) - error_attachment_too_big: Този файл не може да бъде качен, понеже Ð½Ð°Ð´Ñ…Ð²ÑŠÑ€Ð»Ñ Ð¼Ð°ÐºÑималната възможна големина (%{max_size}) - warning_attachments_not_saved: "%{count} файла не бÑха запиÑани." - - mail_subject_lost_password: "Вашата парола (%{value})" - mail_body_lost_password: 'За да Ñмените паролата Ñи, използвайте ÑÐ»ÐµÐ´Ð½Ð¸Ñ Ð»Ð¸Ð½Ðº:' - mail_subject_register: "ÐÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ñ Ð½Ð° профил (%{value})" - mail_body_register: 'За да активирате профила Ñи използвайте ÑÐ»ÐµÐ´Ð½Ð¸Ñ Ð»Ð¸Ð½Ðº:' - mail_body_account_information_external: "Можете да използвате Ð²Ð°ÑˆÐ¸Ñ %{value} профил за вход." - mail_body_account_information: ИнформациÑта за профила ви - mail_subject_account_activation_request: "ЗаÑвка за активиране на профил в %{value}" - mail_body_account_activation_request: "Има новорегиÑтриран потребител (%{value}), очакващ вашето одобрение:" - mail_subject_reminder: "%{count} задачи Ñ ÐºÑ€Ð°ÐµÐ½ Ñрок Ñ Ñледващите %{days} дни" - mail_body_reminder: "%{count} задачи, назначени на Ð²Ð°Ñ Ñа Ñ ÐºÑ€Ð°ÐµÐ½ Ñрок в Ñледващите %{days} дни:" - mail_subject_wiki_content_added: "Wiki Ñтраницата '%{id}' беше добавена" - mail_body_wiki_content_added: Wiki Ñтраницата '%{id}' беше добавена от %{author}. - mail_subject_wiki_content_updated: "Wiki Ñтраницата '%{id}' беше обновена" - mail_body_wiki_content_updated: Wiki Ñтраницата '%{id}' беше обновена от %{author}. - - gui_validation_error: 1 грешка - gui_validation_error_plural: "%{count} грешки" - - field_name: Име - field_description: ОпиÑание - field_summary: ÐÐ½Ð¾Ñ‚Ð°Ñ†Ð¸Ñ - field_is_required: Задължително - field_firstname: Име - field_lastname: Ð¤Ð°Ð¼Ð¸Ð»Ð¸Ñ - field_mail: Email - 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: РегулÑрен израз - field_min_length: Мин. дължина - field_max_length: МакÑ. дължина - field_value: СтойноÑÑ‚ - field_category: ÐšÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñ - field_title: Заглавие - field_project: Проект - field_issue: Задача - field_status: СъÑтоÑние - field_notes: Бележка - field_is_closed: Затворена задача - field_is_default: СъÑтоÑние по подразбиране - field_tracker: Тракер - field_subject: ОтноÑно - field_due_date: Крайна дата - field_assigned_to: Възложена на - field_priority: Приоритет - field_fixed_version: Планувана верÑÐ¸Ñ - field_user: Потребител - field_principal: Principal - field_role: Ð Ð¾Ð»Ñ - field_homepage: Ðачална Ñтраница - field_is_public: Публичен - field_parent: Подпроект на - field_is_in_roadmap: Да Ñе вижда ли в Пътна карта - field_login: Потребител - field_mail_notification: ИзвеÑÑ‚Ð¸Ñ Ð¿Ð¾ пощата - field_admin: ÐдминиÑтратор - field_last_login_on: ПоÑледно Ñвързване - field_language: Език - field_effective_date: Дата - field_password: Парола - field_new_password: Ðова парола - field_password_confirmation: Потвърждение - field_version: ВерÑÐ¸Ñ - field_type: Тип - field_host: ХоÑÑ‚ - field_port: Порт - field_account: Профил - field_base_dn: Base DN - field_attr_login: Ðтрибут Login - field_attr_firstname: Ðтрибут Първо име (Firstname) - field_attr_lastname: Ðтрибут Ð¤Ð°Ð¼Ð¸Ð»Ð¸Ñ (Lastname) - field_attr_mail: Ðтрибут Email - field_onthefly: Динамично Ñъздаване на потребител - field_start_date: Ðачална дата - field_done_ratio: "% ПрогреÑ" - field_auth_source: Ðачин на Ð¾Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ð¸Ñ - field_hide_mail: Скрий e-mail адреÑа ми - field_comments: Коментар - field_url: ÐÐ´Ñ€ÐµÑ - field_start_page: Ðачална Ñтраница - field_subproject: Подпроект - field_hours: ЧаÑове - field_activity: ДейноÑÑ‚ - field_spent_on: Дата - field_identifier: Идентификатор - field_is_filter: Използва Ñе за филтър - field_issue_to: Свързана задача - field_delay: ОтмеÑтване - field_assignable: Възможно е възлагане на задачи за тази Ñ€Ð¾Ð»Ñ - field_redirect_existing_links: ПренаÑочване на ÑъщеÑтвуващи линкове - field_estimated_hours: ИзчиÑлено време - field_column_names: Колони - field_time_entries: Log time - field_time_zone: ЧаÑова зона - field_searchable: С възможноÑÑ‚ за търÑене - field_default_value: СтойноÑÑ‚ по подразбиране - field_comments_sorting: Сортиране на коментарите - field_parent_title: РодителÑка Ñтраница - field_editable: Editable - field_watcher: Ðаблюдател - field_identity_url: OpenID URL - field_content: Съдържание - field_group_by: Групиране на резултатите по - field_sharing: Sharing - field_parent_issue: РодителÑка задача - field_member_of_group: Член на група - field_assigned_to_role: Assignee's role - field_text: ТекÑтово поле - field_visible: Видим - field_warn_on_leaving_unsaved: Предупреди ме, когато напуÑкам Ñтраница Ñ Ð½ÐµÐ·Ð°Ð¿Ð¸Ñано Ñъдържание - field_issues_visibility: ВидимоÑÑ‚ на задачите - field_is_private: Лична - field_commit_logs_encoding: Кодова таблица на ÑъобщениÑта при поверÑване - field_scm_path_encoding: Кодова таблица на пътищата (path) - field_path_to_repository: Път до хранилището - field_root_directory: Коренна Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ (папка) - field_cvsroot: CVSROOT - field_cvs_module: Модул - - setting_app_title: Заглавие - setting_app_subtitle: ОпиÑание - setting_welcome_text: Допълнителен текÑÑ‚ - setting_default_language: Език по подразбиране - setting_login_required: ИзиÑкване за вход в ÑиÑтемата - setting_self_registration: РегиÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ Ð¾Ñ‚ потребители - setting_attachment_max_size: МакÑимална големина на прикачен файл - setting_issues_export_limit: МакÑимален брой задачи за екÑпорт - setting_mail_from: E-mail Ð°Ð´Ñ€ÐµÑ Ð·Ð° емиÑии - setting_bcc_recipients: Получатели на Ñкрито копие (bcc) - setting_plain_text_mail: Ñамо чиÑÑ‚ текÑÑ‚ (без HTML) - setting_host_name: ХоÑÑ‚ - setting_text_formatting: Форматиране на текÑта - setting_wiki_compression: КомпреÑиране на Wiki иÑториÑта - setting_feeds_limit: МакÑимален брой запиÑи в ATOM емиÑии - setting_default_projects_public: Ðовите проекти Ñа публични по подразбиране - setting_autofetch_changesets: Ðвтоматично извличане на ревизиите - setting_sys_api_enabled: Разрешаване на WS за управление - setting_commit_ref_keywords: ОтбелÑзващи ключови думи - setting_commit_fix_keywords: Приключващи ключови думи - setting_autologin: Ðвтоматичен вход - setting_date_format: Формат на датата - setting_time_format: Формат на чаÑа - setting_cross_project_issue_relations: Релации на задачи между проекти - setting_issue_list_default_columns: Показвани колони по подразбиране - setting_repositories_encodings: Кодова таблица на прикачените файлове и хранилищата - setting_emails_header: Emails header - setting_emails_footer: ПодтекÑÑ‚ за e-mail - setting_protocol: Протокол - setting_per_page_options: Опции за Ñтраниране - setting_user_format: ПотребителÑки формат - setting_activity_days_default: Брой дни показвани на таб ДейноÑÑ‚ - setting_display_subprojects_issues: Задачите от подпроектите по подразбиране Ñе показват в главните проекти - setting_enabled_scm: Разрешена SCM - setting_mail_handler_body_delimiters: ОтрÑзване на e-mail-ите Ñлед един от тези редове - setting_mail_handler_api_enabled: Разрешаване на WS за входÑщи e-mail-и - setting_mail_handler_api_key: API ключ - setting_sequential_project_identifiers: Генериране на поÑледователни проектни идентификатори - setting_gravatar_enabled: Използване на портребителÑки икони от Gravatar - setting_gravatar_default: Подразбиращо Ñе изображение от Gravatar - setting_diff_max_lines_displayed: МакÑимален брой показвани diff редове - setting_file_max_size_displayed: МакÑимален размер на текÑтовите файлове, показвани inline - setting_repository_log_display_limit: МакÑимален брой на показванете ревизии в лог файла - setting_openid: Рарешаване на OpenID вход и региÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ - setting_password_min_length: Минимална дължина на парола - setting_new_project_user_role_id: РолÑ, давана на потребител, Ñъздаващ проекти, който не е админиÑтратор - setting_default_projects_modules: Ðктивирани модули по подразбиране за нов проект - setting_issue_done_ratio: ИзчиÑление на процента на готови задачи Ñ - setting_issue_done_ratio_issue_field: Използване на поле '% ПрогреÑ' - setting_issue_done_ratio_issue_status: Използване на ÑÑŠÑтоÑнието на задачите - setting_start_of_week: Първи ден на Ñедмицата - setting_rest_api_enabled: Разрешаване на REST web ÑÑŠÑ€Ð²Ð¸Ñ - setting_cache_formatted_text: Кеширане на форматираните текÑтове - setting_default_notification_option: Подразбиращ Ñе начин за извеÑÑ‚Ñване - setting_commit_logtime_enabled: Разрешаване на отчитането на работното време - setting_commit_logtime_activity_id: ДейноÑÑ‚ при отчитане на работното време - setting_gantt_items_limit: МакÑимален брой обекти, които да Ñе показват в мрежов график - setting_issue_group_assignment: Разрешено назначаването на задачи на групи - setting_default_issue_start_date_to_creation_date: Ðачална дата на новите задачи по подразбиране да бъде днешната дата - - permission_add_project: Създаване на проект - permission_add_subprojects: Създаване на подпроекти - permission_edit_project: Редактиране на проект - permission_select_project_modules: Избор на проектни модули - permission_manage_members: Управление на членовете (на екип) - permission_manage_project_activities: Управление на дейноÑтите на проекта - permission_manage_versions: Управление на верÑиите - permission_manage_categories: Управление на категориите - permission_view_issues: Разглеждане на задачите - permission_add_issues: ДобавÑне на задачи - permission_edit_issues: Редактиране на задачи - permission_manage_issue_relations: Управление на връзките между задачите - permission_set_own_issues_private: УÑтановÑване на ÑобÑтвените задачи публични или лични - permission_set_issues_private: УÑтановÑване на задачите публични или лични - 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: Ð—Ð°Ð¿Ð¸Ñ Ð½Ð° Ð·Ð°Ð¿Ð¸Ñ‚Ð²Ð°Ð½Ð¸Ñ (queries) - permission_view_gantt: Разглеждане на мрежов график - permission_view_calendar: Разглеждане на календари - permission_view_issue_watchers: Разглеждане на ÑпиÑък Ñ Ð½Ð°Ð±Ð»ÑŽÐ´Ð°Ñ‚ÐµÐ»Ð¸ - permission_add_issue_watchers: ДобавÑне на наблюдатели - permission_delete_issue_watchers: Изтриване на наблюдатели - permission_log_time: Log spent time - permission_view_time_entries: Разглеждане на изразходваното време - permission_edit_time_entries: Редактиране на time logs - permission_edit_own_time_entries: Редактиране на ÑобÑтвените time logs - 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: Изтриване на прикачени файлове към wiki Ñтраници - permission_protect_wiki_pages: Заключване на wiki Ñтраници - permission_manage_repository: Управление на хранилища - permission_browse_repository: Разглеждане на хранилища - permission_view_changesets: Разглеждане на changesets - permission_commit_access: ПоверÑване - permission_manage_boards: Управление на boards - permission_view_messages: Разглеждане на ÑÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ - permission_add_messages: Публикуване на ÑÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ - permission_edit_messages: Редактиране на ÑÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ - permission_edit_own_messages: Редактиране на ÑобÑтвени ÑÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ - permission_delete_messages: Изтриване на ÑÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ - permission_delete_own_messages: Изтриване на ÑобÑтвени ÑÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ - permission_export_wiki_pages: ЕкÑпорт на wiki Ñтраници - permission_manage_subtasks: Управление на подзадачите - - project_module_issue_tracking: Тракинг - project_module_time_tracking: ОтделÑне на време - project_module_news: Ðовини - project_module_documents: Документи - project_module_files: Файлове - project_module_wiki: Wiki - project_module_repository: Хранилище - project_module_boards: Форуми - project_module_calendar: Календар - project_module_gantt: Мрежов график - - label_user: Потребител - label_user_plural: Потребители - label_user_new: Ðов потребител - label_user_anonymous: Ðнонимен - label_project: Проект - label_project_new: Ðов проект - label_project_plural: Проекти - label_x_projects: - zero: 0 проекта - one: 1 проект - other: "%{count} проекта" - label_project_all: Ð’Ñички проекти - label_project_latest: ПоÑледни проекти - label_issue: Задача - label_issue_new: Ðова задача - label_issue_plural: Задачи - label_issue_view_all: Ð’Ñички задачи - label_issues_by: "Задачи по %{value}" - label_issue_added: Добавена задача - label_issue_updated: Обновена задача - label_issue_note_added: Добавена бележка - label_issue_status_updated: Обновено ÑÑŠÑтоÑние - label_issue_priority_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_role_anonymous: Ðнонимен - label_role_non_member: Ðе член - label_member: Член - label_member_new: Ðов член - label_member_plural: Членове - label_tracker: Тракер - label_tracker_plural: Тракери - label_tracker_new: Ðов тракер - label_workflow: Работен Ð¿Ñ€Ð¾Ñ†ÐµÑ - label_issue_status: СъÑтоÑние на задача - label_issue_status_plural: СъÑтоÑÐ½Ð¸Ñ Ð½Ð° задачи - label_issue_status_new: Ðово ÑÑŠÑтоÑние - label_issue_category: ÐšÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° - label_issue_category_plural: Категории задачи - label_issue_category_new: Ðова ÐºÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñ - label_custom_field: ПотребителÑко поле - label_custom_field_plural: ПотребителÑки полета - label_custom_field_new: Ðово потребителÑко поле - label_enumerations: СпиÑъци - label_enumeration_new: Ðова ÑтойноÑÑ‚ - label_information: Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ - label_information_plural: Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ - label_please_login: Вход - label_register: РегиÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ - label_login_with_open_id_option: или вход чрез OpenID - label_password_lost: Забравена парола - label_home: Ðачало - label_my_page: Лична Ñтраница - label_my_account: Профил - label_my_projects: Проекти, в които учаÑтвам - label_my_page_block: Блокове в личната Ñтраница - label_administration: ÐдминиÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ - label_login: Вход - label_logout: Изход - label_help: Помощ - label_reported_issues: Публикувани задачи - label_assigned_to_me_issues: Възложени на мен - label_last_login: ПоÑледно Ñвързване - label_registered_on: РегиÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ - label_activity: ДейноÑÑ‚ - label_overall_activity: ЦÑлоÑтна дейноÑÑ‚ - label_user_activity: "ÐктивноÑÑ‚ на %{value}" - label_new: Ðов - label_logged_as: Здравейте, - label_environment: Среда - label_authentication: ÐžÑ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ð¸Ñ - label_auth_source: Ðачин на Ð¾Ñ‚Ð¾Ñ€Ð¾Ð·Ð°Ñ†Ð¸Ñ - label_auth_source_new: Ðов начин на Ð¾Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ð¸Ñ - label_auth_source_plural: Ðачини на Ð¾Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ð¸Ñ - label_subproject_plural: Подпроекти - label_subproject_new: Ðов подпроект - label_and_its_subprojects: "%{value} и неговите подпроекти" - label_min_max_length: Минимална - макÑимална дължина - label_list: СпиÑък - label_date: Дата - label_integer: ЦелочиÑлен - label_float: Дробно - label_boolean: Ð§ÐµÐºÐ±Ð¾ÐºÑ - label_string: ТекÑÑ‚ - label_text: Дълъг текÑÑ‚ - label_attribute: Ðтрибут - label_attribute_plural: Ðтрибути - label_download: "%{count} изтеглÑне" - label_download_plural: "%{count} изтеглÑниÑ" - 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_news_comment_added: Добавен коментар към новина - label_settings: ÐаÑтройки - label_overview: Общ изглед - label_version: ВерÑÐ¸Ñ - label_version_new: Ðова верÑÐ¸Ñ - label_version_plural: ВерÑии - label_close_versions: ЗатварÑне на завършените верÑии - label_confirmation: Одобрение - label_export_to: ЕкÑпорт към - label_read: Read... - label_public_projects: Публични проекти - label_open_issues: отворена - label_open_issues_plural: отворени - label_closed_issues: затворена - label_closed_issues_plural: затворени - label_x_open_issues_abbr_on_total: - zero: 0 отворени / %{total} - one: 1 отворена / %{total} - other: "%{count} отворени / %{total}" - label_x_open_issues_abbr: - zero: 0 отворени - one: 1 отворена - other: "%{count} отворени" - label_x_closed_issues_abbr: - zero: 0 затворени - one: 1 затворена - other: "%{count} затворени" - 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: Мрежов график - label_internal: Вътрешен - label_last_changes: "поÑледни %{count} промени" - label_change_view_all: Виж вÑички промени - label_personalize_page: ПерÑонализиране - label_comment: Коментар - label_comment_plural: Коментари - label_x_comments: - zero: 0 коментари - one: 1 коментар - other: "%{count} коментари" - label_comment_add: ДобавÑне на коментар - label_comment_added: Добавен коментар - label_comment_delete: Изтриване на коментари - label_query: ПотребителÑка Ñправка - label_query_plural: ПотребителÑки Ñправки - label_query_new: Ðова заÑвка - label_my_queries: Моите заÑвки - label_filter_add: Добави филтър - label_filter_plural: Филтри - label_equals: е - label_not_equals: не е - label_in_less_than: Ñлед по-малко от - label_in_more_than: Ñлед повече от - label_greater_or_equal: ">=" - label_less_or_equal: <= - label_between: между - label_in: в Ñледващите - label_today: Ð´Ð½ÐµÑ - label_all_time: вÑички - label_yesterday: вчера - label_this_week: тази Ñедмица - label_last_week: поÑледната Ñедмица - label_last_n_days: "поÑледните %{count} дни" - label_this_month: Ñ‚ÐµÐºÑƒÑ‰Ð¸Ñ Ð¼ÐµÑец - label_last_month: поÑÐ»ÐµÐ´Ð½Ð¸Ñ Ð¼ÐµÑец - label_this_year: текущата година - label_date_range: Период - label_less_than_ago: преди по-малко от - label_more_than_ago: преди повече от - label_ago: преди - label_contains: Ñъдържа - label_not_contains: не Ñъдържа - label_day_plural: дни - label_repository: Хранилище - label_repository_plural: Хранилища - label_browse: Разглеждане - label_modification: "%{count} промÑна" - label_modification_plural: "%{count} промени" - label_branch: работен вариант - label_tag: ВерÑÐ¸Ñ - label_revision: Ð ÐµÐ²Ð¸Ð·Ð¸Ñ - label_revision_plural: Ревизии - label_revision_id: Ð ÐµÐ²Ð¸Ð·Ð¸Ñ %{value} - label_associated_revisions: ÐÑоциирани ревизии - label_added: добавено - label_modified: променено - label_copied: копирано - label_renamed: преименувано - label_deleted: изтрито - label_latest_revision: ПоÑледна Ñ€ÐµÐ²Ð¸Ð·Ð¸Ñ - label_latest_revision_plural: ПоÑледни ревизии - label_view_revisions: Виж ревизиите - label_view_all_revisions: Разглеждане на вÑички ревизии - label_max_size: МакÑимална големина - label_sort_highest: ПремеÑти най-горе - label_sort_higher: ПремеÑти по-горе - label_sort_lower: ПремеÑти по-долу - label_sort_lowest: ПремеÑти най-долу - label_roadmap: Пътна карта - label_roadmap_due_in: "Излиза Ñлед %{value}" - label_roadmap_overdue: "%{value} закъÑнение" - label_roadmap_no_issues: ÐÑма задачи за тази верÑÐ¸Ñ - label_search: ТърÑене - label_result_plural: Pезултати - label_all_words: Ð’Ñички думи - label_wiki: Wiki - label_wiki_edit: Wiki Ñ€ÐµÐ´Ð°ÐºÑ†Ð¸Ñ - label_wiki_edit_plural: Wiki редакции - label_wiki_page: Wiki Ñтраница - label_wiki_page_plural: Wiki Ñтраници - label_index_by_title: Ð˜Ð½Ð´ÐµÐºÑ - label_index_by_date: Ð˜Ð½Ð´ÐµÐºÑ Ð¿Ð¾ дата - label_current_version: Текуща верÑÐ¸Ñ - label_preview: Преглед - label_feed_plural: ЕмиÑии - label_changes_details: Подробни промени - label_issue_tracking: Тракинг - label_spent_time: Отделено време - label_overall_spent_time: Общо употребено време - label_f_hour: "%{value} чаÑ" - label_f_hour_plural: "%{value} чаÑа" - label_time_tracking: ОтделÑне на време - label_change_plural: Промени - label_statistics: СтатиÑтики - label_commits_per_month: Ревизии по меÑеци - label_commits_per_author: Ревизии по автор - label_diff: diff - label_view_diff: Виж разликите - label_diff_inline: хоризонтално - label_diff_side_by_side: вертикално - label_options: Опции - label_copy_workflow_from: Копирай Ñ€Ð°Ð±Ð¾Ñ‚Ð½Ð¸Ñ Ð¿Ñ€Ð¾Ñ†ÐµÑ Ð¾Ñ‚ - label_permissions_report: Справка за права - label_watched_issues: Ðаблюдавани задачи - label_related_issues: Свързани задачи - label_applied_status: УÑтановено ÑÑŠÑтоÑние - label_loading: Зареждане... - label_relation_new: Ðова Ñ€ÐµÐ»Ð°Ñ†Ð¸Ñ - label_relation_delete: Изтриване на Ñ€ÐµÐ»Ð°Ñ†Ð¸Ñ - label_relates_to: Ñвързана ÑÑŠÑ - label_duplicates: дублира - label_duplicated_by: дублирана от - label_blocks: блокира - label_blocked_by: блокирана от - label_precedes: предшеÑтва - label_follows: изпълнÑва Ñе Ñлед - label_end_to_start: край към начало - label_end_to_end: край към край - label_start_to_start: начало към начало - label_start_to_end: начало към край - label_stay_logged_in: Запомни ме - label_disabled: забранено - label_show_completed_versions: Показване на реализирани верÑии - label_me: аз - label_board: Форум - label_board_new: Ðов форум - label_board_plural: Форуми - label_board_locked: Заключена - label_board_sticky: Sticky - label_topic_plural: Теми - label_message_plural: Ð¡ÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ - label_message_last: ПоÑледно Ñъобщение - label_message_new: Ðова тема - label_message_posted: Добавено Ñъобщение - label_reply_plural: Отговори - label_send_information: Изпращане на информациÑта до Ð¿Ð¾Ñ‚Ñ€ÐµÐ±Ð¸Ñ‚ÐµÐ»Ñ - label_year: Година - label_month: МеÑец - label_week: Седмица - label_date_from: От - label_date_to: До - label_language_based: Ð’ завиÑимоÑÑ‚ от езика - label_sort_by: "Сортиране по %{value}" - label_send_test_email: Изпращане на теÑтов e-mail - label_feeds_access_key: RSS access ключ - label_missing_feeds_access_key: ЛипÑващ RSS ключ за доÑтъп - label_feeds_access_key_created_on: "%{value} от Ñъздаването на RSS ключа" - label_module_plural: Модули - label_added_time_by: "Публикувана от %{author} преди %{age}" - label_updated_time_by: "Обновена от %{author} преди %{age}" - label_updated_time: "Обновена преди %{value}" - label_jump_to_a_project: Проект... - label_file_plural: Файлове - label_changeset_plural: Ревизии - label_default_columns: По подразбиране - label_no_change_option: (Без промÑна) - label_bulk_edit_selected_issues: Групово редактиране на задачи - label_bulk_edit_selected_time_entries: Групово редактиране на запиÑи за използвано време - 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_option_only_my_events: Само за неща, в които Ñъм включен/а - label_user_mail_option_only_assigned: Само за неща, назначени на мен - label_user_mail_option_only_owner: Само за неща, на които аз Ñъм ÑобÑтвеник - label_user_mail_no_self_notified: "Ðе иÑкам извеÑÑ‚Ð¸Ñ Ð·Ð° извършени от мен промени" - label_registration_activation_by_email: активиране на профила по email - label_registration_manual_activation: ръчно активиране - label_registration_automatic_activation: автоматично активиране - label_display_per_page: "Ðа Ñтраница по: %{value}" - label_age: ВъзраÑÑ‚ - label_change_properties: ПромÑна на наÑтройки - label_general: ОÑновни - label_more: Още - label_scm: SCM (СиÑтема за контрол на верÑиите) - label_plugins: Плъгини - label_ldap_authentication: LDAP Ð¾Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ð¸Ñ - label_downloads_abbr: D/L - label_optional_description: Ðезадължително опиÑание - label_add_another_file: ДобавÑне на друг файл - label_preferences: ÐŸÑ€ÐµÐ´Ð¿Ð¾Ñ‡Ð¸Ñ‚Ð°Ð½Ð¸Ñ - label_chronological_order: Хронологичен ред - label_reverse_chronological_order: Обратен хронологичен ред - label_planning: Планиране - label_incoming_emails: ВходÑщи e-mail-и - label_generate_key: Генериране на ключ - label_issue_watchers: Ðаблюдатели - label_example: Пример - label_display: Display - label_sort: Сортиране - label_ascending: ÐараÑтващ - label_descending: ÐамалÑващ - label_date_from_to: От %{start} до %{end} - label_wiki_content_added: Wiki Ñтраница беше добавена - label_wiki_content_updated: Wiki Ñтраница беше обновена - label_group: Група - label_group_plural: Групи - label_group_new: Ðова група - label_time_entry_plural: Използвано време - label_version_sharing_none: Ðе Ñподелен - label_version_sharing_descendants: С подпроекти - label_version_sharing_hierarchy: С проектна Ð¹ÐµÑ€Ð°Ñ€Ñ…Ð¸Ñ - label_version_sharing_tree: С дърво на проектите - label_version_sharing_system: С вÑички проекти - label_update_issue_done_ratios: ОбновÑване на процента на завършените задачи - label_copy_source: Източник - label_copy_target: Цел - label_copy_same_as_target: Също като целта - label_display_used_statuses_only: Показване Ñамо на ÑÑŠÑтоÑниÑта, използвани от този тракер - label_api_access_key: API ключ за доÑтъп - label_missing_api_access_key: ЛипÑващ API ключ - label_api_access_key_created_on: API ключ за доÑтъп е Ñъздаден преди %{value} - label_profile: Профил - label_subtask_plural: Подзадачи - label_project_copy_notifications: Изпращане на Send e-mail извеÑÑ‚Ð¸Ñ Ð¿Ð¾ време на копирането на проекта - label_principal_search: "ТърÑене на потребител или група:" - label_user_search: "ТърÑене на потребител:" - label_additional_workflow_transitions_for_author: Позволени Ñа допълнителни преходи, когато потребителÑÑ‚ е авторът - label_additional_workflow_transitions_for_assignee: Позволени Ñа допълнителни преходи, когато потребителÑÑ‚ е назначениÑÑ‚ към задачата - label_issues_visibility_all: Ð’Ñички задачи - label_issues_visibility_public: Ð’Ñички не-лични задачи - label_issues_visibility_own: Задачи, Ñъздадени от или назначени на Ð¿Ð¾Ñ‚Ñ€ÐµÐ±Ð¸Ñ‚ÐµÐ»Ñ - label_git_report_last_commit: Извеждане на поÑледното поверÑване за файлове и папки - label_parent_revision: Ð ÐµÐ²Ð¸Ð·Ð¸Ñ Ñ€Ð¾Ð´Ð¸Ñ‚ÐµÐ» - label_child_revision: Ð ÐµÐ²Ð¸Ð·Ð¸Ñ Ð½Ð°Ñледник - label_export_options: "%{export_format} опции за екÑпорт" - - button_login: Вход - button_submit: Прикачване - button_save: Ð—Ð°Ð¿Ð¸Ñ - button_check_all: Избор на вÑички - button_uncheck_all: ИзчиÑтване на вÑички - button_collapse_all: Скриване вÑички - button_expand_all: Разгъване вÑички - button_delete: Изтриване - button_create: Създаване - button_create_and_continue: Създаване и продължаване - button_test: ТеÑÑ‚ - button_edit: Ð ÐµÐ´Ð°ÐºÑ†Ð¸Ñ - button_edit_associated_wikipage: "Редактиране на аÑоциираната Wiki Ñтраница: %{page_title}" - button_add: ДобавÑне - button_change: ПромÑна - button_apply: Приложи - button_clear: ИзчиÑти - button_lock: Заключване - button_unlock: Отключване - button_download: ИзтеглÑне - button_list: СпиÑък - button_view: Преглед - button_move: ПремеÑтване - button_move_and_follow: ПремеÑтване и продължаване - button_back: Ðазад - button_cancel: Отказ - button_activate: ÐÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ñ - button_sort: Сортиране - button_log_time: ОтделÑне на време - button_rollback: Върни Ñе към тази Ñ€ÐµÐ²Ð¸Ð·Ð¸Ñ - button_watch: Ðаблюдаване - button_unwatch: Край на наблюдението - button_reply: Отговор - button_archive: Ðрхивиране - button_unarchive: Разархивиране - button_reset: Генериране наново - button_rename: Преименуване - button_change_password: ПромÑна на парола - button_copy: Копиране - button_copy_and_follow: Копиране и продължаване - button_annotate: ÐÐ½Ð¾Ñ‚Ð°Ñ†Ð¸Ñ - button_update: ОбновÑване - button_configure: Конфигуриране - button_quote: Цитат - button_duplicate: Дублиране - button_show: Показване - button_edit_section: Редактиране на тази ÑÐµÐºÑ†Ð¸Ñ - button_export: ЕкÑпорт - - status_active: активен - status_registered: региÑтриран - status_locked: заключен - - version_status_open: отворена - version_status_locked: заключена - version_status_closed: затворена - - field_active: Ðктивен - - text_select_mail_notifications: Изберете ÑÑŠÐ±Ð¸Ñ‚Ð¸Ñ Ð·Ð° изпращане на e-mail. - text_regexp_info: пр. ^[A-Z0-9]+$ - text_min_max_length_info: 0 - без Ð¾Ð³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ñ - text_project_destroy_confirmation: Сигурни ли Ñте, че иÑкате да изтриете проекта и данните в него? - text_subprojects_destroy_warning: "Ðеговите подпроекти: %{value} Ñъщо ще бъдат изтрити." - text_workflow_edit: Изберете Ñ€Ð¾Ð»Ñ Ð¸ тракер за да редактирате Ñ€Ð°Ð±Ð¾Ñ‚Ð½Ð¸Ñ Ð¿Ñ€Ð¾Ñ†ÐµÑ - text_are_you_sure: Сигурни ли Ñте? - text_are_you_sure_with_children: Изтриване на задачата и нейните подзадачи? - text_journal_changed: "%{label} променен от %{old} на %{new}" - text_journal_changed_no_detail: "%{label} променен" - text_journal_set_to: "%{label} уÑтановен на %{value}" - text_journal_deleted: "%{label} изтрит (%{old})" - text_journal_added: "Добавено %{label} %{value}" - text_tip_issue_begin_day: задача, започваща този ден - text_tip_issue_end_day: задача, завършваща този ден - text_tip_issue_begin_end_day: задача, започваща и завършваща този ден - text_project_identifier_info: 'Позволени Ñа малки букви (a-z), цифри и тирета.
Ðевъзможна промÑна Ñлед запиÑ.' - text_caracters_maximum: "До %{count} Ñимвола." - text_caracters_minimum: "Минимум %{count} Ñимвола." - text_length_between: "От %{min} до %{max} Ñимвола." - text_tracker_no_workflow: ÐÑма дефиниран работен Ð¿Ñ€Ð¾Ñ†ÐµÑ Ð·Ð° този тракер - text_unallowed_characters: Ðепозволени Ñимволи - text_comma_separated: Позволено е изброÑване (Ñ Ñ€Ð°Ð·Ð´ÐµÐ»Ð¸Ñ‚ÐµÐ» запетаÑ). - text_line_separated: Позволени Ñа много ÑтойноÑти (по едно на ред). - text_issues_ref_in_commit_messages: ОтбелÑзване и приключване на задачи от ревизии - text_issue_added: "Публикувана е нова задача Ñ Ð½Ð¾Ð¼ÐµÑ€ %{id} (от %{author})." - text_issue_updated: "Задача %{id} е обновена (от %{author})." - text_wiki_destroy_confirmation: Сигурни ли Ñте, че иÑкате да изтриете това Wiki и цÑлото му Ñъдържание? - text_issue_category_destroy_question: "Има задачи (%{count}) обвързани Ñ Ñ‚Ð°Ð·Ð¸ категориÑ. Какво ще изберете?" - text_issue_category_destroy_assignments: Премахване на връзките Ñ ÐºÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñта - text_issue_category_reassign_to: Преобвързване Ñ ÐºÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñ - text_user_mail_option: "За неизбраните проекти, ще получавате извеÑÑ‚Ð¸Ñ Ñамо за наблюдавани дейноÑти или в които учаÑтвате (Ñ‚.е. автор или назначени на мен)." - text_no_configuration_data: "Ð’Ñе още не Ñа конфигурирани Роли, тракери, ÑÑŠÑтоÑÐ½Ð¸Ñ Ð½Ð° задачи и работен процеÑ.\nСтрого Ñе препоръчва зареждането на примерната информациÑ. Веднъж заредена ще имате възможноÑÑ‚ да Ñ Ñ€ÐµÐ´Ð°ÐºÑ‚Ð¸Ñ€Ð°Ñ‚Ðµ." - text_load_default_configuration: Зареждане на примерна Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ - text_status_changed_by_changeset: "Приложено Ñ Ñ€ÐµÐ²Ð¸Ð·Ð¸Ñ %{value}." - text_time_logged_by_changeset: Приложено в Ñ€ÐµÐ²Ð¸Ð·Ð¸Ñ %{value}. - text_issues_destroy_confirmation: 'Сигурни ли Ñте, че иÑкате да изтриете избраните задачи?' - text_issues_destroy_descendants_confirmation: Тази Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñ Ñ‰Ðµ премахне и %{count} подзадача(и). - text_time_entries_destroy_confirmation: Сигурен ли Ñте, че изтриете избраните запиÑи за изразходвано време? - text_select_project_modules: 'Изберете активните модули за този проект:' - text_default_administrator_account_changed: Сменен Ñ„Ð°Ð±Ñ€Ð¸Ñ‡Ð½Ð¸Ñ Ð°Ð´Ð¼Ð¸Ð½Ð¸ÑтраторÑки профил - text_file_repository_writable: ВъзможноÑÑ‚ за пиÑане в хранилището Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð²Ðµ - text_plugin_assets_writable: Папката на приÑтавките е разрешена за Ð·Ð°Ð¿Ð¸Ñ - text_rmagick_available: Ðаличен RMagick (по избор) - text_destroy_time_entries_question: "%{hours} чаÑа Ñа отделени на задачите, които иÑкате да изтриете. Какво избирате?" - text_destroy_time_entries: Изтриване на отделеното време - text_assign_time_entries_to_project: ПрехвърлÑне на отделеното време към проект - text_reassign_time_entries: 'ПрехвърлÑне на отделеното време към задача:' - text_user_wrote: "%{value} напиÑа:" - text_enumeration_destroy_question: "%{count} обекта Ñа Ñвързани Ñ Ñ‚Ð°Ð·Ð¸ ÑтойноÑÑ‚." - text_enumeration_category_reassign_to: 'ПреÑвържете ги към тази ÑтойноÑÑ‚:' - text_email_delivery_not_configured: "Изпращането на e-mail-и не е конфигурирано и извеÑтиÑта не Ñа разрешени.\nКонфигурирайте Ð²Ð°ÑˆÐ¸Ñ SMTP Ñървър в config/configuration.yml и реÑтартирайте Redmine, за да ги разрешите." - text_repository_usernames_mapping: "Изберете или променете потребителите в Redmine, ÑъответÑтващи на потребителите в дневника на хранилището (repository).\nПотребителите Ñ ÐµÐ´Ð½Ð°ÐºÐ²Ð¸ имена в Redmine и хранилищата Ñе ÑъвмеÑÑ‚Ñват автоматично." - text_diff_truncated: '... Този diff не е пълен, понеже е Ð½Ð°Ð´Ñ…Ð²ÑŠÑ€Ð»Ñ Ð¼Ð°ÐºÑÐ¸Ð¼Ð°Ð»Ð½Ð¸Ñ Ñ€Ð°Ð·Ð¼ÐµÑ€, който може да бъде показан.' - text_custom_field_possible_values_info: 'Една ÑтойноÑÑ‚ на ред' - text_wiki_page_destroy_question: Тази Ñтраница има %{descendants} Ñтраници деца и descendant(s). Какво желаете да правите? - text_wiki_page_nullify_children: Запазване на тези Ñтраници като коренни Ñтраници - text_wiki_page_destroy_children: Изтриване на Ñтраниците деца и вÑички техни descendants - text_wiki_page_reassign_children: Преназначаване на Ñтраниците деца на тази родителÑка Ñтраница - text_own_membership_delete_confirmation: "Вие Ñте на път да премахнете нÑкои или вÑички ваши Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð¸ е възможно Ñлед това да не можете да редактирате този проект.\nСигурен ли Ñте, че иÑкате да продължите?" - text_zoom_in: Увеличаване - text_zoom_out: ÐамалÑване - text_warn_on_leaving_unsaved: Страницата Ñъдържа незапиÑано Ñъдържание, което може да бъде загубено, ако Ñ Ð½Ð°Ð¿ÑƒÑнете. - text_scm_path_encoding_note: "По подразбиране: UTF-8" - text_git_repository_note: Празно и локално хранилище (например /gitrepo, c:\gitrepo) - text_mercurial_repository_note: Локално хранилище (например /hgrepo, c:\hgrepo) - text_scm_command: SCM команда - text_scm_command_version: ВерÑÐ¸Ñ - text_scm_config: Можете да конфигурирате SCM командите в config/configuration.yml. За да активирате промените, реÑтартирайте Redmine. - text_scm_command_not_available: SCM командата не е налична или доÑтъпна. Проверете конфигурациÑта в админиÑÑ‚Ñ€Ð°Ñ‚Ð¸Ð²Ð½Ð¸Ñ Ð¿Ð°Ð½ÐµÐ». - - default_role_manager: Мениджър - default_role_developer: Разработчик - default_role_reporter: Публикуващ - default_tracker_bug: Грешка - default_tracker_feature: ФункционалноÑÑ‚ - default_tracker_support: Поддръжка - default_issue_status_new: Ðова - default_issue_status_in_progress: Изпълнение - default_issue_status_resolved: Приключена - default_issue_status_feedback: Обратна връзка - default_issue_status_closed: Затворена - default_issue_status_rejected: Отхвърлена - default_doc_category_user: Ð”Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ñ Ð·Ð° Ð¿Ð¾Ñ‚Ñ€ÐµÐ±Ð¸Ñ‚ÐµÐ»Ñ - default_doc_category_tech: ТехничеÑка Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ñ - default_priority_low: ÐиÑък - default_priority_normal: Ðормален - default_priority_high: ВиÑок - default_priority_urgent: Спешен - default_priority_immediate: Веднага - default_activity_design: Дизайн - default_activity_development: Разработка - - enumeration_issue_priorities: Приоритети на задачи - enumeration_doc_categories: Категории документи - enumeration_activities: ДейноÑти (time tracking) - enumeration_system_activity: СиÑтемна активноÑÑ‚ - description_filter: Филтър - description_search: ТърÑене - description_choose_project: Проекти - description_project_scope: Обхват на търÑенето - description_notes: Бележки - description_message_content: Съдържание на Ñъобщението - description_query_sort_criteria_attribute: Ðтрибут на Ñортиране - description_query_sort_criteria_direction: ПоÑока на Ñортиране - description_user_mail_notification: ÐšÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ Ð¸Ð·Ð²ÐµÑтиÑта по пощата - description_available_columns: Ðалични колони - description_selected_columns: Избрани колони - description_issue_category_reassign: Изберете ÐºÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñ - description_wiki_subpages_reassign: Изберете нова родителÑка Ñтраница - description_all_columns: Ð’Ñички колони - description_date_range_list: Изберете диапазон от ÑпиÑъка - description_date_range_interval: Изберете диапазон чрез задаване на начална и крайна дати - description_date_from: Въведете начална дата - description_date_to: Въведете крайна дата diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/3e/3e69b6e2154a117c66e42d50dd35e274a3a35611.svn-base --- a/.svn/pristine/3e/3e69b6e2154a117c66e42d50dd35e274a3a35611.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,16 +0,0 @@ -

<%=l(:label_confirmation)%>

-
-

<%=h @project_to_destroy %>
-<%=l(:text_project_destroy_confirmation)%> - -<% if @project_to_destroy.descendants.any? %> -
<%= l(:text_subprojects_destroy_warning, content_tag('strong', h(@project_to_destroy.descendants.collect{|p| p.to_s}.join(', ')))) %> -<% end %> -

-

- <% form_tag(project_path(@project_to_destroy), :method => :delete) do %> - - <%= submit_tag l(:button_delete) %> - <% end %> -

-
diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/3e/3ecbdf2b380b8b58c6f31309dfb99930076f3961.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3e/3ecbdf2b380b8b58c6f31309dfb99930076f3961.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,374 @@ +/* ***** 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 */ + +function jsToolBar(textarea) { + if (!document.createElement) { return; } + + if (!textarea) { return; } + + if ((typeof(document["selection"]) == "undefined") + && (typeof(textarea["setSelectionRange"]) == "undefined")) { + return; + } + + this.textarea = textarea; + + this.editor = document.createElement('div'); + this.editor.className = 'jstEditor'; + + this.textarea.parentNode.insertBefore(this.editor,this.textarea); + this.editor.appendChild(this.textarea); + + this.toolbar = document.createElement("div"); + this.toolbar.className = 'jstElements'; + this.editor.parentNode.insertBefore(this.toolbar,this.editor); + + // Dragable resizing + if (this.editor.addEventListener && navigator.appVersion.match(/\bMSIE\b/)) + { + this.handle = document.createElement('div'); + this.handle.className = 'jstHandle'; + var dragStart = this.resizeDragStart; + var This = this; + this.handle.addEventListener('mousedown',function(event) { dragStart.call(This,event); },false); + // fix memory leak in Firefox (bug #241518) + window.addEventListener('unload',function() { + var del = This.handle.parentNode.removeChild(This.handle); + delete(This.handle); + },false); + + this.editor.parentNode.insertBefore(this.handle,this.editor.nextSibling); + } + + this.context = null; + this.toolNodes = {}; // lorsque la toolbar est dessinée , cet objet est garni + // de raccourcis vers les éléments DOM correspondants aux outils. +} + +function jsButton(title, fn, scope, className) { + if(typeof jsToolBar.strings == 'undefined') { + this.title = title || null; + } else { + this.title = jsToolBar.strings[title] || title || null; + } + this.fn = fn || function(){}; + this.scope = scope || null; + this.className = className || null; +} +jsButton.prototype.draw = function() { + if (!this.scope) return null; + + var button = document.createElement('button'); + button.setAttribute('type','button'); + button.tabIndex = 200; + if (this.className) button.className = this.className; + button.title = this.title; + var span = document.createElement('span'); + span.appendChild(document.createTextNode(this.title)); + button.appendChild(span); + + if (this.icon != undefined) { + button.style.backgroundImage = 'url('+this.icon+')'; + } + if (typeof(this.fn) == 'function') { + var This = this; + button.onclick = function() { try { This.fn.apply(This.scope, arguments) } catch (e) {} return false; }; + } + return button; +} + +function jsSpace(id) { + this.id = id || null; + this.width = null; +} +jsSpace.prototype.draw = function() { + var span = document.createElement('span'); + if (this.id) span.id = this.id; + span.appendChild(document.createTextNode(String.fromCharCode(160))); + span.className = 'jstSpacer'; + if (this.width) span.style.marginRight = this.width+'px'; + + return span; +} + +function jsCombo(title, options, scope, fn, className) { + this.title = title || null; + this.options = options || null; + this.scope = scope || null; + this.fn = fn || function(){}; + this.className = className || null; +} +jsCombo.prototype.draw = function() { + if (!this.scope || !this.options) return null; + + var select = document.createElement('select'); + if (this.className) select.className = className; + select.title = this.title; + + for (var o in this.options) { + //var opt = this.options[o]; + var option = document.createElement('option'); + option.value = o; + option.appendChild(document.createTextNode(this.options[o])); + select.appendChild(option); + } + + var This = this; + select.onchange = function() { + try { + This.fn.call(This.scope, this.value); + } catch (e) { alert(e); } + + return false; + } + + return select; +} + + +jsToolBar.prototype = { + base_url: '', + mode: 'wiki', + elements: {}, + help_link: '', + + getMode: function() { + return this.mode; + }, + + setMode: function(mode) { + this.mode = mode || 'wiki'; + }, + + switchMode: function(mode) { + mode = mode || 'wiki'; + this.draw(mode); + }, + + setHelpLink: function(link) { + this.help_link = link; + }, + + button: function(toolName) { + var tool = this.elements[toolName]; + if (typeof tool.fn[this.mode] != 'function') return null; + var b = new jsButton(tool.title, tool.fn[this.mode], this, 'jstb_'+toolName); + if (tool.icon != undefined) b.icon = tool.icon; + return b; + }, + space: function(toolName) { + var tool = new jsSpace(toolName) + if (this.elements[toolName].width !== undefined) + tool.width = this.elements[toolName].width; + return tool; + }, + combo: function(toolName) { + var tool = this.elements[toolName]; + var length = tool[this.mode].list.length; + + if (typeof tool[this.mode].fn != 'function' || length == 0) { + return null; + } else { + var options = {}; + for (var i=0; i < length; i++) { + var opt = tool[this.mode].list[i]; + options[opt] = tool.options[opt]; + } + return new jsCombo(tool.title, options, this, tool[this.mode].fn); + } + }, + draw: function(mode) { + this.setMode(mode); + + // Empty toolbar + while (this.toolbar.hasChildNodes()) { + this.toolbar.removeChild(this.toolbar.firstChild) + } + this.toolNodes = {}; // vide les raccourcis DOM/**/ + + // Draw toolbar elements + var b, tool, newTool; + + for (var i in this.elements) { + b = this.elements[i]; + + var disabled = + b.type == undefined || b.type == '' + || (b.disabled != undefined && b.disabled) + || (b.context != undefined && b.context != null && b.context != this.context); + + if (!disabled && typeof this[b.type] == 'function') { + tool = this[b.type](i); + if (tool) newTool = tool.draw(); + if (newTool) { + this.toolNodes[i] = newTool; //mémorise l'accès DOM pour usage éventuel ultérieur + this.toolbar.appendChild(newTool); + } + } + } + }, + + singleTag: function(stag,etag) { + stag = stag || null; + etag = etag || stag; + + if (!stag || !etag) { return; } + + this.encloseSelection(stag,etag); + }, + + encloseLineSelection: function(prefix, suffix, fn) { + this.textarea.focus(); + + prefix = prefix || ''; + suffix = suffix || ''; + + var start, end, sel, scrollPos, subst, res; + + if (typeof(document["selection"]) != "undefined") { + sel = document.selection.createRange().text; + } else if (typeof(this.textarea["setSelectionRange"]) != "undefined") { + start = this.textarea.selectionStart; + end = this.textarea.selectionEnd; + scrollPos = this.textarea.scrollTop; + // go to the start of the line + start = this.textarea.value.substring(0, start).replace(/[^\r\n]*$/g,'').length; + // go to the end of the line + end = this.textarea.value.length - this.textarea.value.substring(end, this.textarea.value.length).replace(/^[^\r\n]*/, '').length; + sel = this.textarea.value.substring(start, end); + } + + if (sel.match(/ $/)) { // exclude ending space char, if any + sel = sel.substring(0, sel.length - 1); + suffix = suffix + " "; + } + + if (typeof(fn) == 'function') { + res = (sel) ? fn.call(this,sel) : fn(''); + } else { + res = (sel) ? sel : ''; + } + + subst = prefix + res + suffix; + + if (typeof(document["selection"]) != "undefined") { + document.selection.createRange().text = subst; + var range = this.textarea.createTextRange(); + range.collapse(false); + range.move('character', -suffix.length); + range.select(); + } else if (typeof(this.textarea["setSelectionRange"]) != "undefined") { + this.textarea.value = this.textarea.value.substring(0, start) + subst + + this.textarea.value.substring(end); + if (sel) { + this.textarea.setSelectionRange(start + subst.length, start + subst.length); + } else { + this.textarea.setSelectionRange(start + prefix.length, start + prefix.length); + } + this.textarea.scrollTop = scrollPos; + } + }, + + encloseSelection: function(prefix, suffix, fn) { + this.textarea.focus(); + + prefix = prefix || ''; + suffix = suffix || ''; + + var start, end, sel, scrollPos, subst, res; + + if (typeof(document["selection"]) != "undefined") { + sel = document.selection.createRange().text; + } else if (typeof(this.textarea["setSelectionRange"]) != "undefined") { + start = this.textarea.selectionStart; + end = this.textarea.selectionEnd; + scrollPos = this.textarea.scrollTop; + sel = this.textarea.value.substring(start, end); + } + + if (sel.match(/ $/)) { // exclude ending space char, if any + sel = sel.substring(0, sel.length - 1); + suffix = suffix + " "; + } + + if (typeof(fn) == 'function') { + res = (sel) ? fn.call(this,sel) : fn(''); + } else { + res = (sel) ? sel : ''; + } + + subst = prefix + res + suffix; + + if (typeof(document["selection"]) != "undefined") { + document.selection.createRange().text = subst; + var range = this.textarea.createTextRange(); + range.collapse(false); + range.move('character', -suffix.length); + range.select(); +// this.textarea.caretPos -= suffix.length; + } else if (typeof(this.textarea["setSelectionRange"]) != "undefined") { + this.textarea.value = this.textarea.value.substring(0, start) + subst + + this.textarea.value.substring(end); + if (sel) { + this.textarea.setSelectionRange(start + subst.length, start + subst.length); + } else { + this.textarea.setSelectionRange(start + prefix.length, start + prefix.length); + } + this.textarea.scrollTop = scrollPos; + } + }, + + stripBaseURL: function(url) { + if (this.base_url != '') { + var pos = url.indexOf(this.base_url); + if (pos == 0) { + url = url.substr(this.base_url.length); + } + } + + return url; + } +}; + +/** Resizer +-------------------------------------------------------- */ +jsToolBar.prototype.resizeSetStartH = function() { + this.dragStartH = this.textarea.offsetHeight + 0; +}; +jsToolBar.prototype.resizeDragStart = function(event) { + var This = this; + this.dragStartY = event.clientY; + this.resizeSetStartH(); + document.addEventListener('mousemove', this.dragMoveHdlr=function(event){This.resizeDragMove(event);}, false); + document.addEventListener('mouseup', this.dragStopHdlr=function(event){This.resizeDragStop(event);}, false); +}; + +jsToolBar.prototype.resizeDragMove = function(event) { + this.textarea.style.height = (this.dragStartH+event.clientY-this.dragStartY)+'px'; +}; + +jsToolBar.prototype.resizeDragStop = function(event) { + document.removeEventListener('mousemove', this.dragMoveHdlr, false); + document.removeEventListener('mouseup', this.dragStopHdlr, false); +}; diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/3e/3ee3c9a35a260aa80f2513c5c9353b2283428f2c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3e/3ee3c9a35a260aa80f2513c5c9353b2283428f2c.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,40 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class PatchesTest < ActiveSupport::TestCase + include Redmine::I18n + + context "ActiveRecord::Base.human_attribute_name" do + setup do + Setting.default_language = 'en' + end + + should "transform name to field_name" do + assert_equal l('field_last_login_on'), ActiveRecord::Base.human_attribute_name('last_login_on') + end + + should "cut extra _id suffix for better validation" do + assert_equal l('field_last_login_on'), ActiveRecord::Base.human_attribute_name('last_login_on_id') + end + + should "default to humanized value if no translation has been found (useful for custom fields)" do + assert_equal 'Patch name', ActiveRecord::Base.human_attribute_name('Patch name') + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/3e/3efd26febde35f053259781a6842c32aa0085eb7.svn-base --- a/.svn/pristine/3e/3efd26febde35f053259781a6842c32aa0085eb7.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,144 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) - -class SearchTest < ActiveSupport::TestCase - fixtures :users, - :members, - :member_roles, - :projects, - :roles, - :enabled_modules, - :issues, - :trackers, - :journals, - :journal_details, - :repositories, - :changesets - - def setup - @project = Project.find(1) - @issue_keyword = '%unable to print recipes%' - @issue = Issue.find(1) - @changeset_keyword = '%very first commit%' - @changeset = Changeset.find(100) - end - - def test_search_by_anonymous - User.current = nil - - r = Issue.search(@issue_keyword).first - assert r.include?(@issue) - r = Changeset.search(@changeset_keyword).first - assert r.include?(@changeset) - - # Removes the :view_changesets permission from Anonymous role - remove_permission Role.anonymous, :view_changesets - - r = Issue.search(@issue_keyword).first - assert r.include?(@issue) - r = Changeset.search(@changeset_keyword).first - assert !r.include?(@changeset) - - # Make the project private - @project.update_attribute :is_public, false - r = Issue.search(@issue_keyword).first - assert !r.include?(@issue) - r = Changeset.search(@changeset_keyword).first - assert !r.include?(@changeset) - end - - def test_search_by_user - User.current = User.find_by_login('rhill') - assert User.current.memberships.empty? - - r = Issue.search(@issue_keyword).first - assert r.include?(@issue) - r = Changeset.search(@changeset_keyword).first - assert r.include?(@changeset) - - # Removes the :view_changesets permission from Non member role - remove_permission Role.non_member, :view_changesets - - r = Issue.search(@issue_keyword).first - assert r.include?(@issue) - r = Changeset.search(@changeset_keyword).first - assert !r.include?(@changeset) - - # Make the project private - @project.update_attribute :is_public, false - r = Issue.search(@issue_keyword).first - assert !r.include?(@issue) - r = Changeset.search(@changeset_keyword).first - assert !r.include?(@changeset) - end - - def test_search_by_allowed_member - User.current = User.find_by_login('jsmith') - assert User.current.projects.include?(@project) - - r = Issue.search(@issue_keyword).first - assert r.include?(@issue) - r = Changeset.search(@changeset_keyword).first - assert r.include?(@changeset) - - # Make the project private - @project.update_attribute :is_public, false - r = Issue.search(@issue_keyword).first - assert r.include?(@issue) - r = Changeset.search(@changeset_keyword).first - assert r.include?(@changeset) - end - - def test_search_by_unallowed_member - # Removes the :view_changesets permission from user's and non member role - remove_permission Role.find(1), :view_changesets - remove_permission Role.non_member, :view_changesets - - User.current = User.find_by_login('jsmith') - assert User.current.projects.include?(@project) - - r = Issue.search(@issue_keyword).first - assert r.include?(@issue) - r = Changeset.search(@changeset_keyword).first - assert !r.include?(@changeset) - - # Make the project private - @project.update_attribute :is_public, false - r = Issue.search(@issue_keyword).first - assert r.include?(@issue) - r = Changeset.search(@changeset_keyword).first - assert !r.include?(@changeset) - end - - def test_search_issue_with_multiple_hits_in_journals - i = Issue.find(1) - assert_equal 2, i.journals.count(:all, :conditions => "notes LIKE '%notes%'") - - r = Issue.search('%notes%').first - assert_equal 1, r.size - assert_equal i, r.first - end - - private - - def remove_permission(role, permission) - role.permissions = role.permissions - [ permission ] - role.save - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/3f/3f0e8212628d5f669e97d1ba675898fce5586337.svn-base --- a/.svn/pristine/3f/3f0e8212628d5f669e97d1ba675898fce5586337.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,25 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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 LdapAuthSourcesController < AuthSourcesController - - protected - - def auth_source_class - AuthSourceLdap - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/3f/3f1432f5ae9fdc8b43bfc5b1f1592ae627706799.svn-base --- a/.svn/pristine/3f/3f1432f5ae9fdc8b43bfc5b1f1592ae627706799.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,48 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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 GanttsController < ApplicationController - menu_item :gantt - before_filter :find_optional_project - - rescue_from Query::StatementInvalid, :with => :query_statement_invalid - - helper :gantt - helper :issues - helper :projects - helper :queries - include QueriesHelper - helper :sort - include SortHelper - include Redmine::Export::PDF - - def show - @gantt = Redmine::Helpers::Gantt.new(params) - @gantt.project = @project - retrieve_query - @query.group_by = nil - @gantt.query = @query if @query.valid? - - basename = (@project ? "#{@project.identifier}-" : '') + 'gantt' - - respond_to do |format| - format.html { render :action => "show", :layout => !request.xhr? } - format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{basename}.png") } if @gantt.respond_to?('to_image') - format.pdf { send_data(@gantt.to_pdf, :type => 'application/pdf', :filename => "#{basename}.pdf") } - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/3f/3f7e33eb719b73cf1721b389cca78ecdef336acd.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3f/3f7e33eb719b73cf1721b389cca78ecdef336acd.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,46 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2013 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 JournalsHelper + def render_notes(issue, journal, options={}) + content = '' + editable = User.current.logged? && (User.current.allowed_to?(:edit_issue_notes, issue.project) || (journal.user == User.current && User.current.allowed_to?(:edit_own_issue_notes, issue.project))) + links = [] + if !journal.notes.blank? + links << link_to(image_tag('comment.png'), + {:controller => 'journals', :action => 'new', :id => issue, :journal_id => journal}, + :remote => true, + :method => 'post', + :title => l(:button_quote)) if options[:reply_links] + links << link_to_in_place_notes_editor(image_tag('edit.png'), "journal-#{journal.id}-notes", + { :controller => 'journals', :action => 'edit', :id => journal, :format => 'js' }, + :title => l(:button_edit)) if editable + end + content << content_tag('div', links.join(' ').html_safe, :class => 'contextual') unless links.empty? + content << textilizable(journal, :notes) + css_classes = "wiki" + css_classes << " editable" if editable + content_tag('div', content.html_safe, :id => "journal-#{journal.id}-notes", :class => css_classes) + end + + def link_to_in_place_notes_editor(text, field_id, url, options={}) + onclick = "$.ajax({url: '#{url_for(url)}', type: 'get'}); return false;" + link_to text, '#', options.merge(:onclick => onclick) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/3f/3fac3fc235a53c2e2d4d021dffb9a965b1506411.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/3f/3fac3fc235a53c2e2d4d021dffb9a965b1506411.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,9 @@ +class AddQueriesType < ActiveRecord::Migration + def up + add_column :queries, :type, :string + end + + def down + remove_column :queries, :type + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/3f/3ff64c6f7b8c7d1560b577e6e864916cca4fbd1d.svn-base --- a/.svn/pristine/3f/3ff64c6f7b8c7d1560b577e6e864916cca4fbd1d.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +0,0 @@ -#!/usr/bin/env ruby -require File.expand_path('../../config/boot', __FILE__) -require 'commands/process/inspector' diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/40/4020a77b0d5d691da1ac32e725f8f600aff372b8.svn-base --- a/.svn/pristine/40/4020a77b0d5d691da1ac32e725f8f600aff372b8.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -

<%= class_name %>#<%= action %>

diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/40/4038e83eefe53cf4e2ba6b2f10fddf413b1d2edb.svn-base --- a/.svn/pristine/40/4038e83eefe53cf4e2ba6b2f10fddf413b1d2edb.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,128 +0,0 @@ -// ** I18N - -// Calendar GL (galician) language -// Author: Martín Vázquez Cabanas, -// 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 -r 0a574315af3e -r 4f746d8966dd .svn/pristine/40/40689c0c3542244c3df167e6c12a7f9af9376e9b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/40/40689c0c3542244c3df167e6c12a7f9af9376e9b.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,21 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +module DocumentsHelper +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/40/406f7b24c26c10e7c0b7a7dfc884f38ff3b10cd2.svn-base --- a/.svn/pristine/40/406f7b24c26c10e7c0b7a7dfc884f38ff3b10cd2.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,383 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -require 'SVG/Graph/Bar' -require 'SVG/Graph/BarHorizontal' -require 'digest/sha1' - -class ChangesetNotFound < Exception; end -class InvalidRevisionParam < Exception; end - -class RepositoriesController < ApplicationController - menu_item :repository - menu_item :settings, :only => :edit - default_search_scope :changesets - - before_filter :find_repository, :except => :edit - before_filter :find_project, :only => :edit - before_filter :authorize - accept_rss_auth :revisions - - rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed - - def edit - @repository = @project.repository - if !@repository && !params[:repository_scm].blank? - @repository = Repository.factory(params[:repository_scm]) - @repository.project = @project if @repository - end - if request.post? && @repository - p1 = params[:repository] - p = {} - p_extra = {} - p1.each do |k, v| - if k =~ /^extra_/ - p_extra[k] = v - else - p[k] = v - end - end - @repository.attributes = p - @repository.merge_extra_info(p_extra) - @repository.save - end - render(:update) do |page| - page.replace_html "tab-content-repository", - :partial => 'projects/settings/repository' - if @repository && !@project.repository - @project.reload # needed to reload association - page.replace_html "main-menu", render_main_menu(@project) - end - end - 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' - end - - def show - @repository.fetch_changesets if Setting.autofetch_changesets? && @path.empty? - - @entries = @repository.entries(@path, @rev) - @changeset = @repository.find_changeset_by_name(@rev) - if request.xhr? - @entries ? render(:partial => 'dir_list_content') : render(:nothing => true) - else - (show_error_not_found; return) unless @entries - @changesets = @repository.latest_changesets(@path, @rev) - @properties = @repository.properties(@path, @rev) - render :action => 'show' - end - end - - alias_method :browse, :show - - def changes - @entry = @repository.entry(@path, @rev) - (show_error_not_found; return) unless @entry - @changesets = @repository.latest_changesets(@path, @rev, Setting.repository_log_display_limit.to_i) - @properties = @repository.properties(@path, @rev) - @changeset = @repository.find_changeset_by_name(@rev) - end - - def revisions - @changeset_count = @repository.changesets.count - @changeset_pages = Paginator.new self, @changeset_count, - per_page_option, - params['page'] - @changesets = @repository.changesets.find(:all, - :limit => @changeset_pages.items_per_page, - :offset => @changeset_pages.current.offset, - :include => [:user, :repository, :parents]) - - respond_to do |format| - format.html { render :layout => false if request.xhr? } - format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") } - end - end - - def entry - @entry = @repository.entry(@path, @rev) - (show_error_not_found; return) unless @entry - - # If the entry is a dir, show the browser - (show; return) if @entry.is_dir? - - @content = @repository.cat(@path, @rev) - (show_error_not_found; return) unless @content - if 'raw' == params[:format] || - (@content.size && @content.size > Setting.file_max_size_displayed.to_i.kilobyte) || - ! is_entry_text_data?(@content, @path) - # Force the download - send_opt = { :filename => filename_for_content_disposition(@path.split('/').last) } - send_type = Redmine::MimeType.of(@path) - send_opt[:type] = send_type.to_s if send_type - send_data @content, send_opt - else - # Prevent empty lines when displaying a file with Windows style eol - # TODO: UTF-16 - # Is this needs? AttachmentsController reads file simply. - @content.gsub!("\r\n", "\n") - @changeset = @repository.find_changeset_by_name(@rev) - end - end - - def is_entry_text_data?(ent, path) - # UTF-16 contains "\x00". - # It is very strict that file contains less than 30% of ascii symbols - # in non Western Europe. - return true if Redmine::MimeType.is_type?('text', path) - # Ruby 1.8.6 has a bug of integer divisions. - # http://apidock.com/ruby/v1_8_6_287/String/is_binary_data%3F - return false if ent.is_binary_data? - true - end - private :is_entry_text_data? - - def annotate - @entry = @repository.entry(@path, @rev) - (show_error_not_found; return) unless @entry - - @annotate = @repository.scm.annotate(@path, @rev) - if @annotate.nil? || @annotate.empty? - (render_error l(:error_scm_annotate); return) - end - ann_buf_size = 0 - @annotate.lines.each do |buf| - ann_buf_size += buf.size - end - if ann_buf_size > Setting.file_max_size_displayed.to_i.kilobyte - (render_error l(:error_scm_annotate_big_text_file); return) - end - @changeset = @repository.find_changeset_by_name(@rev) - end - - def revision - raise ChangesetNotFound if @rev.blank? - @changeset = @repository.find_changeset_by_name(@rev) - raise ChangesetNotFound unless @changeset - - respond_to do |format| - format.html - format.js {render :layout => false} - end - rescue ChangesetNotFound - show_error_not_found - end - - def diff - if params[:format] == 'diff' - @diff = @repository.diff(@path, @rev, @rev_to) - (show_error_not_found; return) unless @diff - filename = "changeset_r#{@rev}" - filename << "_r#{@rev_to}" if @rev_to - send_data @diff.join, :filename => "#{filename}.diff", - :type => 'text/x-patch', - :disposition => 'attachment' - else - @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline' - @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type) - - # Save diff type as user preference - if User.current.logged? && @diff_type != User.current.pref[:diff_type] - User.current.pref[:diff_type] = @diff_type - User.current.preference.save - end - @cache_key = "repositories/diff/#{@repository.id}/" + - Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}-#{current_language}") - unless read_fragment(@cache_key) - @diff = @repository.diff(@path, @rev, @rev_to) - show_error_not_found unless @diff - end - - @changeset = @repository.find_changeset_by_name(@rev) - @changeset_to = @rev_to ? @repository.find_changeset_by_name(@rev_to) : nil - @diff_format_revisions = @repository.diff_format_revisions(@changeset, @changeset_to) - end - end - - def stats - end - - def graph - data = nil - case params[:graph] - when "commits_per_month" - data = graph_commits_per_month(@repository) - when "commits_per_author" - data = graph_commits_per_author(@repository) - end - if data - headers["Content-Type"] = "image/svg+xml" - send_data(data, :type => "image/svg+xml", :disposition => "inline") - else - render_404 - end - end - - private - - REV_PARAM_RE = %r{\A[a-f0-9]*\Z}i - - def find_repository - @project = Project.find(params[:id]) - @repository = @project.repository - (render_404; return false) unless @repository - @path = params[:path].join('/') unless params[:path].nil? - @path ||= '' - @rev = params[:rev].blank? ? @repository.default_branch : params[:rev].to_s.strip - @rev_to = params[:rev_to] - - unless @rev.to_s.match(REV_PARAM_RE) && @rev_to.to_s.match(REV_PARAM_RE) - if @repository.branches.blank? - raise InvalidRevisionParam - end - end - rescue ActiveRecord::RecordNotFound - render_404 - rescue InvalidRevisionParam - show_error_not_found - end - - def show_error_not_found - render_error :message => l(:error_scm_not_found), :status => 404 - end - - # Handler for Redmine::Scm::Adapters::CommandFailed exception - def show_error_command_failed(exception) - render_error l(:error_scm_command_failed, exception.message) - end - - def graph_commits_per_month(repository) - @date_to = Date.today - @date_from = @date_to << 11 - @date_from = Date.civil(@date_from.year, @date_from.month, 1) - commits_by_day = repository.changesets.count( - :all, :group => :commit_date, - :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to]) - commits_by_month = [0] * 12 - commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last } - - changes_by_day = repository.changes.count( - :all, :group => :commit_date, - :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to]) - changes_by_month = [0] * 12 - changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last } - - fields = [] - 12.times {|m| fields << month_name(((Date.today.month - 1 - m) % 12) + 1)} - - graph = SVG::Graph::Bar.new( - :height => 300, - :width => 800, - :fields => fields.reverse, - :stack => :side, - :scale_integers => true, - :step_x_labels => 2, - :show_data_values => false, - :graph_title => l(:label_commits_per_month), - :show_graph_title => true - ) - - graph.add_data( - :data => commits_by_month[0..11].reverse, - :title => l(:label_revision_plural) - ) - - graph.add_data( - :data => changes_by_month[0..11].reverse, - :title => l(:label_change_plural) - ) - - graph.burn - end - - def graph_commits_per_author(repository) - commits_by_author = repository.changesets.count(:all, :group => :committer) - commits_by_author.to_a.sort! {|x, y| x.last <=> y.last} - - changes_by_author = repository.changes.count(:all, :group => :committer) - h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o} - - fields = commits_by_author.collect {|r| r.first} - commits_data = commits_by_author.collect {|r| r.last} - changes_data = commits_by_author.collect {|r| h[r.first] || 0} - - fields = fields + [""]*(10 - fields.length) if fields.length<10 - commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10 - changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10 - - # Remove email adress in usernames - fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') } - - graph = SVG::Graph::BarHorizontal.new( - :height => 400, - :width => 800, - :fields => fields, - :stack => :side, - :scale_integers => true, - :show_data_values => false, - :rotate_y_labels => false, - :graph_title => l(:label_commits_per_author), - :show_graph_title => true - ) - graph.add_data( - :data => commits_data, - :title => l(:label_revision_plural) - ) - graph.add_data( - :data => changes_data, - :title => l(:label_change_plural) - ) - graph.burn - end -end - -class Date - def months_ago(date = Date.today) - (date.year - self.year)*12 + (date.month - self.month) - end - - def weeks_ago(date = Date.today) - (date.year - self.year)*52 + (date.cweek - self.cweek) - end -end - -class String - def with_leading_slash - starts_with?('/') ? self : "/#{self}" - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/40/407f7e2d4e08a32e78ca65cbc6b2ee007be59736.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/40/407f7e2d4e08a32e78ca65cbc6b2ee007be59736.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,73 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 WorkflowRule < ActiveRecord::Base + self.table_name = "#{table_name_prefix}workflows#{table_name_suffix}" + + belongs_to :role + belongs_to :tracker + belongs_to :old_status, :class_name => 'IssueStatus', :foreign_key => 'old_status_id' + belongs_to :new_status, :class_name => 'IssueStatus', :foreign_key => 'new_status_id' + + validates_presence_of :role, :tracker, :old_status + + # Copies workflows from source to targets + def self.copy(source_tracker, source_role, target_trackers, target_roles) + unless source_tracker.is_a?(Tracker) || source_role.is_a?(Role) + raise ArgumentError.new("source_tracker or source_role must be specified") + end + + target_trackers = [target_trackers].flatten.compact + target_roles = [target_roles].flatten.compact + + target_trackers = Tracker.sorted.all if target_trackers.empty? + target_roles = Role.all if target_roles.empty? + + target_trackers.each do |target_tracker| + target_roles.each do |target_role| + copy_one(source_tracker || target_tracker, + source_role || target_role, + target_tracker, + target_role) + end + end + end + + # Copies a single set of workflows from source to target + def self.copy_one(source_tracker, source_role, target_tracker, target_role) + unless source_tracker.is_a?(Tracker) && !source_tracker.new_record? && + source_role.is_a?(Role) && !source_role.new_record? && + target_tracker.is_a?(Tracker) && !target_tracker.new_record? && + target_role.is_a?(Role) && !target_role.new_record? + + raise ArgumentError.new("arguments can not be nil or unsaved objects") + end + + if source_tracker == target_tracker && source_role == target_role + false + else + transaction do + delete_all :tracker_id => target_tracker.id, :role_id => target_role.id + connection.insert "INSERT INTO #{WorkflowRule.table_name} (tracker_id, role_id, old_status_id, new_status_id, author, assignee, field_name, #{connection.quote_column_name 'rule'}, type)" + + " SELECT #{target_tracker.id}, #{target_role.id}, old_status_id, new_status_id, author, assignee, field_name, #{connection.quote_column_name 'rule'}, type" + + " FROM #{WorkflowRule.table_name}" + + " WHERE tracker_id = #{source_tracker.id} AND role_id = #{source_role.id}" + end + true + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/40/40d857531cd6b4668cb1e019211d87ead6d54850.svn-base --- a/.svn/pristine/40/40d857531cd6b4668cb1e019211d87ead6d54850.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,15 +0,0 @@ -require 'test/unit' - -$VERBOSE = $CODERAY_DEBUG = true -$:.unshift File.expand_path('../../../lib', __FILE__) -require 'coderay' - -mydir = File.dirname(__FILE__) -suite = Dir[File.join(mydir, '*.rb')]. - map { |tc| File.basename(tc).sub(/\.rb$/, '') } - %w'suite for_redcloth' - -puts "Running basic CodeRay #{CodeRay::VERSION} tests: #{suite.join(', ')}" - -for test_case in suite - load File.join(mydir, test_case + '.rb') -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/40/40fb7c915312a0bec722da5f4b2c6582d58a02b9.svn-base --- a/.svn/pristine/40/40fb7c915312a0bec722da5f4b2c6582d58a02b9.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,174 +0,0 @@ -require 'active_support' -require File.join(File.dirname(__FILE__), 'engines/plugin') -require File.join(File.dirname(__FILE__), 'engines/plugin/list') -require File.join(File.dirname(__FILE__), 'engines/plugin/loader') -require File.join(File.dirname(__FILE__), 'engines/plugin/locator') -require File.join(File.dirname(__FILE__), 'engines/assets') -require File.join(File.dirname(__FILE__), 'engines/rails_extensions/rails') - -# == Parameters -# -# The Engines module has a number of public configuration parameters: -# -# [+public_directory+] The directory into which plugin assets should be -# mirrored. Defaults to RAILS_ROOT/public/plugin_assets. -# [+schema_info_table+] The table to use when storing plugin migration -# version information. Defaults to +plugin_schema_info+. -# -# Additionally, there are a few flags which control the behaviour of -# some of the features the engines plugin adds to Rails: -# -# [+disable_application_view_loading+] A boolean flag determining whether -# or not views should be loaded from -# the main app/views directory. -# Defaults to false; probably only -# useful when testing your plugin. -# [+disable_application_code_loading+] A boolean flag determining whether -# or not to load controllers/helpers -# from the main +app+ directory, -# if corresponding code exists within -# a plugin. Defaults to false; again, -# probably only useful when testing -# your plugin. -# [+disable_code_mixing+] A boolean flag indicating whether all plugin -# copies of a particular controller/helper should -# be loaded and allowed to override each other, -# or if the first matching file should be loaded -# instead. Defaults to false. -# -module Engines - # The set of all loaded plugins - mattr_accessor :plugins - self.plugins = Engines::Plugin::List.new - - # List of extensions to load, can be changed in init.rb before calling Engines.init - mattr_accessor :rails_extensions - self.rails_extensions = %w(asset_helpers form_tag_helpers migrations dependencies) - - # The name of the public directory to mirror public engine assets into. - # Defaults to RAILS_ROOT/public/plugin_assets. - mattr_accessor :public_directory - self.public_directory = File.join(RAILS_ROOT, 'public', 'plugin_assets') - - # The table in which to store plugin schema information. Defaults to - # "plugin_schema_info". - mattr_accessor :schema_info_table - self.schema_info_table = "plugin_schema_info" - - #-- - # These attributes control the behaviour of the engines extensions - #++ - - # Set this to true if views should *only* be loaded from plugins - mattr_accessor :disable_application_view_loading - self.disable_application_view_loading = false - - # Set this to true if controller/helper code shouldn't be loaded - # from the application - mattr_accessor :disable_application_code_loading - self.disable_application_code_loading = false - - # Set this to true if code should not be mixed (i.e. it will be loaded - # from the first valid path on $LOAD_PATH) - mattr_accessor :disable_code_mixing - self.disable_code_mixing = false - - # This is used to determine which files are candidates for the "code - # mixing" feature that the engines plugin provides, where classes from - # plugins can be loaded, and then code from the application loaded - # on top of that code to override certain methods. - mattr_accessor :code_mixing_file_types - self.code_mixing_file_types = %w(controller helper) - - class << self - def init(initializer) - load_extensions - Engines::Assets.initialize_base_public_directory - end - - def logger - RAILS_DEFAULT_LOGGER - end - - def load_extensions - rails_extensions.each { |name| require "engines/rails_extensions/#{name}" } - # load the testing extensions, if we are in the test environment. - require "engines/testing" if RAILS_ENV == "test" - end - - def select_existing_paths(paths) - paths.select { |path| File.directory?(path) } - end - - # The engines plugin will, by default, mix code from controllers and helpers, - # allowing application code to override specific methods in the corresponding - # controller or helper classes and modules. However, if other file types should - # also be mixed like this, they can be added by calling this method. For example, - # if you want to include "things" within your plugin and override them from - # your applications, you should use the following layout: - # - # app/ - # +-- things/ - # | +-- one_thing.rb - # | +-- another_thing.rb - # ... - # vendor/ - # +-- plugins/ - # +-- my_plugin/ - # +-- app/ - # +-- things/ - # +-- one_thing.rb - # +-- another_thing.rb - # - # The important point here is that your "things" are named _thing.rb, - # and that they are placed within plugin/app/things (the pluralized form of 'thing'). - # - # It's important to note that you'll also want to ensure that the "things" are - # on your load path by including them in Rails load path mechanism, e.g. in init.rb: - # - # ActiveSupport::Dependencies.load_paths << File.join(File.dirname(__FILE__), 'app', 'things')) - # - def mix_code_from(*types) - self.code_mixing_file_types += types.map { |x| x.to_s.singularize } - end - - # A general purpose method to mirror a directory (+source+) into a destination - # directory, including all files and subdirectories. Files will not be mirrored - # if they are identical already (checked via FileUtils#identical?). - def mirror_files_from(source, destination) - return unless File.directory?(source) - - # TODO: use Rake::FileList#pathmap? - source_files = Dir[source + "/**/*"] - source_dirs = source_files.select { |d| File.directory?(d) } - source_files -= source_dirs - - unless source_files.empty? - base_target_dir = File.join(destination, File.dirname(source_files.first).gsub(source, '')) - FileUtils.mkdir_p(base_target_dir) - end - - source_dirs.each do |dir| - # strip down these paths so we have simple, relative paths we can - # add to the destination - target_dir = File.join(destination, dir.gsub(source, '')) - begin - FileUtils.mkdir_p(target_dir) - rescue Exception => e - raise "Could not create directory #{target_dir}: \n" + e - end - end - - source_files.each do |file| - begin - target = File.join(destination, file.gsub(source, '')) - unless File.exist?(target) && FileUtils.identical?(file, target) - FileUtils.cp(file, target) - end - rescue Exception => e - raise "Could not copy #{file} to #{target}: \n" + e - end - end - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/41/413080d862dfb1417751d3ce6800014b0d3f9e36.svn-base --- a/.svn/pristine/41/413080d862dfb1417751d3ce6800014b0d3f9e36.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,128 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) - -class JournalObserverTest < ActiveSupport::TestCase - fixtures :issues, :issue_statuses, :journals, :journal_details - - def setup - ActionMailer::Base.deliveries.clear - @journal = Journal.find 1 - end - - # context: issue_updated notified_events - def test_create_should_send_email_notification_with_issue_updated - Setting.notified_events = ['issue_updated'] - issue = Issue.find(:first) - user = User.find(:first) - journal = issue.init_journal(user, issue) - - assert journal.save - assert_equal 1, ActionMailer::Base.deliveries.size - end - - def test_create_should_not_send_email_notification_with_notify_set_to_false - Setting.notified_events = ['issue_updated'] - issue = Issue.find(:first) - user = User.find(:first) - journal = issue.init_journal(user, issue) - journal.notify = false - - assert journal.save - assert_equal 0, ActionMailer::Base.deliveries.size - end - - def test_create_should_not_send_email_notification_without_issue_updated - Setting.notified_events = [] - issue = Issue.find(:first) - user = User.find(:first) - journal = issue.init_journal(user, issue) - - assert journal.save - assert_equal 0, ActionMailer::Base.deliveries.size - end - - # context: issue_note_added notified_events - def test_create_should_send_email_notification_with_issue_note_added - Setting.notified_events = ['issue_note_added'] - issue = Issue.find(:first) - user = User.find(:first) - journal = issue.init_journal(user, issue) - journal.notes = 'This update has a note' - - assert journal.save - assert_equal 1, ActionMailer::Base.deliveries.size - end - - def test_create_should_not_send_email_notification_without_issue_note_added - Setting.notified_events = [] - issue = Issue.find(:first) - user = User.find(:first) - journal = issue.init_journal(user, issue) - journal.notes = 'This update has a note' - - assert journal.save - assert_equal 0, ActionMailer::Base.deliveries.size - end - - # context: issue_status_updated notified_events - def test_create_should_send_email_notification_with_issue_status_updated - Setting.notified_events = ['issue_status_updated'] - issue = Issue.find(:first) - user = User.find(:first) - issue.init_journal(user, issue) - issue.status = IssueStatus.last - - assert issue.save - assert_equal 1, ActionMailer::Base.deliveries.size - end - - def test_create_should_not_send_email_notification_without_issue_status_updated - Setting.notified_events = [] - issue = Issue.find(:first) - user = User.find(:first) - issue.init_journal(user, issue) - issue.status = IssueStatus.last - - assert issue.save - assert_equal 0, ActionMailer::Base.deliveries.size - end - - # context: issue_priority_updated notified_events - def test_create_should_send_email_notification_with_issue_priority_updated - Setting.notified_events = ['issue_priority_updated'] - issue = Issue.find(:first) - user = User.find(:first) - issue.init_journal(user, issue) - issue.priority = IssuePriority.last - - assert issue.save - assert_equal 1, ActionMailer::Base.deliveries.size - end - - def test_create_should_not_send_email_notification_without_issue_priority_updated - Setting.notified_events = [] - issue = Issue.find(:first) - user = User.find(:first) - issue.init_journal(user, issue) - issue.priority = IssuePriority.last - - assert issue.save - assert_equal 0, ActionMailer::Base.deliveries.size - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/41/414d847bcbb5d1b481c5b6e074d2d95ff30c291d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/41/414d847bcbb5d1b481c5b6e074d2d95ff30c291d.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,29 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class UsersTest < ActionController::IntegrationTest + fixtures :users + + def test_destroy_should_not_accept_get_requests + assert_no_difference 'User.count' do + get '/users/destroy/2', {}, credentials('admin') + assert_response 404 + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/41/41666c76b3a63b119e23476dd87dae257e309108.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/41/41666c76b3a63b119e23476dd87dae257e309108.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,111 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class NewsController < ApplicationController + default_search_scope :news + model_object News + before_filter :find_model_object, :except => [:new, :create, :index] + before_filter :find_project_from_association, :except => [:new, :create, :index] + before_filter :find_project_by_project_id, :only => [:new, :create] + before_filter :authorize, :except => [:index] + before_filter :find_optional_project, :only => :index + accept_rss_auth :index + accept_api_auth :index + + helper :watchers + helper :attachments + + def index + case params[:format] + when 'xml', 'json' + @offset, @limit = api_offset_and_limit + else + @limit = 10 + end + + scope = @project ? @project.news.visible : News.visible + + @news_count = scope.count + @news_pages = Paginator.new @news_count, @limit, params['page'] + @offset ||= @news_pages.offset + @newss = scope.all(:include => [:author, :project], + :order => "#{News.table_name}.created_on DESC", + :offset => @offset, + :limit => @limit) + + respond_to do |format| + format.html { + @news = News.new # for adding news inline + render :layout => false if request.xhr? + } + format.api + format.atom { render_feed(@newss, :title => (@project ? @project.name : Setting.app_title) + ": #{l(:label_news_plural)}") } + end + end + + def show + @comments = @news.comments + @comments.reverse! if User.current.wants_comments_in_reverse_order? + end + + def new + @news = News.new(:project => @project, :author => User.current) + end + + def create + @news = News.new(:project => @project, :author => User.current) + @news.safe_attributes = params[:news] + @news.save_attachments(params[:attachments]) + if @news.save + render_attachment_warning_if_needed(@news) + flash[:notice] = l(:notice_successful_create) + redirect_to project_news_index_path(@project) + else + render :action => 'new' + end + end + + def edit + end + + def update + @news.safe_attributes = params[:news] + @news.save_attachments(params[:attachments]) + if @news.save + render_attachment_warning_if_needed(@news) + flash[:notice] = l(:notice_successful_update) + redirect_to news_path(@news) + else + render :action => 'edit' + end + end + + def destroy + @news.destroy + redirect_to project_news_index_path(@project) + end + + private + + def find_optional_project + return true unless params[:project_id] + @project = Project.find(params[:project_id]) + authorize + rescue ActiveRecord::RecordNotFound + render_404 + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/41/4171661e14f170beefe360373ffc236afc3efe66.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/41/4171661e14f170beefe360373ffc236afc3efe66.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,101 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class ActivitiesHelperTest < ActionView::TestCase + include ActivitiesHelper + + class MockEvent + attr_reader :event_datetime, :event_group, :name + + def initialize(group=nil) + @@count ||= 0 + @name = "e#{@@count}" + @event_datetime = Time.now + @@count.hours + @event_group = group || self + @@count += 1 + end + + def self.clear + @@count = 0 + end + end + + def setup + MockEvent.clear + end + + def test_sort_activity_events_should_sort_by_datetime + events = [] + events << MockEvent.new + events << MockEvent.new + events << MockEvent.new + + assert_equal [ + ['e2', false], + ['e1', false], + ['e0', false] + ], sort_activity_events(events).map {|event, grouped| [event.name, grouped]} + end + + def test_sort_activity_events_should_group_events + events = [] + events << MockEvent.new + events << MockEvent.new(events[0]) + events << MockEvent.new(events[0]) + + assert_equal [ + ['e2', false], + ['e1', true], + ['e0', true] + ], sort_activity_events(events).map {|event, grouped| [event.name, grouped]} + end + + def test_sort_activity_events_with_group_not_in_set_should_group_events + e = MockEvent.new + events = [] + events << MockEvent.new(e) + events << MockEvent.new(e) + + assert_equal [ + ['e2', false], + ['e1', true] + ], sort_activity_events(events).map {|event, grouped| [event.name, grouped]} + end + + def test_sort_activity_events_should_sort_by_datetime_and_group + events = [] + events << MockEvent.new + events << MockEvent.new + events << MockEvent.new + events << MockEvent.new(events[1]) + events << MockEvent.new(events[2]) + events << MockEvent.new + events << MockEvent.new(events[2]) + + assert_equal [ + ['e6', false], + ['e4', true], + ['e2', true], + ['e5', false], + ['e3', false], + ['e1', true], + ['e0', false] + ], sort_activity_events(events).map {|event, grouped| [event.name, grouped]} + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/41/41bb9cc50bdf02df6fa0e55ba5d0a45d87f52436.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/41/41bb9cc50bdf02df6fa0e55ba5d0a45d87f52436.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,182 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class IssueRelation < ActiveRecord::Base + # Class used to represent the relations of an issue + class Relations < Array + include Redmine::I18n + + def initialize(issue, *args) + @issue = issue + super(*args) + end + + def to_s(*args) + map {|relation| "#{l(relation.label_for(@issue))} ##{relation.other_issue(@issue).id}"}.join(', ') + end + end + + belongs_to :issue_from, :class_name => 'Issue', :foreign_key => 'issue_from_id' + belongs_to :issue_to, :class_name => 'Issue', :foreign_key => 'issue_to_id' + + TYPE_RELATES = "relates" + TYPE_DUPLICATES = "duplicates" + TYPE_DUPLICATED = "duplicated" + TYPE_BLOCKS = "blocks" + TYPE_BLOCKED = "blocked" + TYPE_PRECEDES = "precedes" + TYPE_FOLLOWS = "follows" + TYPE_COPIED_TO = "copied_to" + TYPE_COPIED_FROM = "copied_from" + + TYPES = { + TYPE_RELATES => { :name => :label_relates_to, :sym_name => :label_relates_to, + :order => 1, :sym => TYPE_RELATES }, + TYPE_DUPLICATES => { :name => :label_duplicates, :sym_name => :label_duplicated_by, + :order => 2, :sym => TYPE_DUPLICATED }, + TYPE_DUPLICATED => { :name => :label_duplicated_by, :sym_name => :label_duplicates, + :order => 3, :sym => TYPE_DUPLICATES, :reverse => TYPE_DUPLICATES }, + TYPE_BLOCKS => { :name => :label_blocks, :sym_name => :label_blocked_by, + :order => 4, :sym => TYPE_BLOCKED }, + TYPE_BLOCKED => { :name => :label_blocked_by, :sym_name => :label_blocks, + :order => 5, :sym => TYPE_BLOCKS, :reverse => TYPE_BLOCKS }, + TYPE_PRECEDES => { :name => :label_precedes, :sym_name => :label_follows, + :order => 6, :sym => TYPE_FOLLOWS }, + TYPE_FOLLOWS => { :name => :label_follows, :sym_name => :label_precedes, + :order => 7, :sym => TYPE_PRECEDES, :reverse => TYPE_PRECEDES }, + TYPE_COPIED_TO => { :name => :label_copied_to, :sym_name => :label_copied_from, + :order => 8, :sym => TYPE_COPIED_FROM }, + TYPE_COPIED_FROM => { :name => :label_copied_from, :sym_name => :label_copied_to, + :order => 9, :sym => TYPE_COPIED_TO, :reverse => TYPE_COPIED_TO } + }.freeze + + validates_presence_of :issue_from, :issue_to, :relation_type + validates_inclusion_of :relation_type, :in => TYPES.keys + validates_numericality_of :delay, :allow_nil => true + validates_uniqueness_of :issue_to_id, :scope => :issue_from_id + validate :validate_issue_relation + + attr_protected :issue_from_id, :issue_to_id + before_save :handle_issue_order + + def visible?(user=User.current) + (issue_from.nil? || issue_from.visible?(user)) && (issue_to.nil? || issue_to.visible?(user)) + end + + def deletable?(user=User.current) + visible?(user) && + ((issue_from.nil? || user.allowed_to?(:manage_issue_relations, issue_from.project)) || + (issue_to.nil? || user.allowed_to?(:manage_issue_relations, issue_to.project))) + end + + def initialize(attributes=nil, *args) + super + if new_record? + if relation_type.blank? + self.relation_type = IssueRelation::TYPE_RELATES + end + end + end + + def validate_issue_relation + if issue_from && issue_to + errors.add :issue_to_id, :invalid if issue_from_id == issue_to_id + unless issue_from.project_id == issue_to.project_id || + Setting.cross_project_issue_relations? + errors.add :issue_to_id, :not_same_project + end + # detect circular dependencies depending wether the relation should be reversed + if TYPES.has_key?(relation_type) && TYPES[relation_type][:reverse] + errors.add :base, :circular_dependency if issue_from.all_dependent_issues.include? issue_to + else + errors.add :base, :circular_dependency if issue_to.all_dependent_issues.include? issue_from + end + if issue_from.is_descendant_of?(issue_to) || issue_from.is_ancestor_of?(issue_to) + errors.add :base, :cant_link_an_issue_with_a_descendant + end + end + end + + def other_issue(issue) + (self.issue_from_id == issue.id) ? issue_to : issue_from + end + + # Returns the relation type for +issue+ + def relation_type_for(issue) + if TYPES[relation_type] + if self.issue_from_id == issue.id + relation_type + else + TYPES[relation_type][:sym] + end + end + end + + def label_for(issue) + TYPES[relation_type] ? + TYPES[relation_type][(self.issue_from_id == issue.id) ? :name : :sym_name] : + :unknow + end + + def css_classes_for(issue) + "rel-#{relation_type_for(issue)}" + end + + def handle_issue_order + reverse_if_needed + + if TYPE_PRECEDES == relation_type + self.delay ||= 0 + else + self.delay = nil + end + set_issue_to_dates + end + + def set_issue_to_dates + soonest_start = self.successor_soonest_start + if soonest_start && issue_to + issue_to.reschedule_on!(soonest_start) + end + end + + def successor_soonest_start + if (TYPE_PRECEDES == self.relation_type) && delay && issue_from && + (issue_from.start_date || issue_from.due_date) + (issue_from.due_date || issue_from.start_date) + 1 + delay + end + end + + def <=>(relation) + r = TYPES[self.relation_type][:order] <=> TYPES[relation.relation_type][:order] + r == 0 ? id <=> relation.id : r + end + + private + + # Reverses the relation if needed so that it gets stored in the proper way + # Should not be reversed before validation so that it can be displayed back + # as entered on new relation form + def reverse_if_needed + if TYPES.has_key?(relation_type) && TYPES[relation_type][:reverse] + issue_tmp = issue_to + self.issue_to = issue_from + self.issue_from = issue_tmp + self.relation_type = TYPES[relation_type][:reverse] + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/42/42065d6686eb12e9679c9132fc4c1d7b2537270b.svn-base --- a/.svn/pristine/42/42065d6686eb12e9679c9132fc4c1d7b2537270b.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ -xml.instruct! -xml.feed "xmlns" => "http://www.w3.org/2005/Atom" do - xml.title @title - xml.link "rel" => "self", "href" => url_for(:format => 'atom', :key => User.current.rss_key, :only_path => false) - xml.link "rel" => "alternate", "href" => home_url(:only_path => false) - xml.id url_for(:controller => 'welcome', :only_path => false) - xml.updated((@journals.first ? @journals.first.event_datetime : Time.now).xmlschema) - xml.author { xml.name "#{Setting.app_title}" } - @journals.each do |change| - issue = change.issue - xml.entry do - xml.title "#{issue.project.name} - #{issue.tracker.name} ##{issue.id}: #{issue.subject}" - xml.link "rel" => "alternate", "href" => url_for(:controller => 'issues' , :action => 'show', :id => issue, :only_path => false) - xml.id url_for(:controller => 'issues' , :action => 'show', :id => issue, :journal_id => change, :only_path => false) - xml.updated change.created_on.xmlschema - xml.author do - xml.name change.user.name - xml.email(change.user.mail) if change.user.is_a?(User) && !change.user.mail.blank? && !change.user.pref.hide_mail - end - xml.content "type" => "html" do - xml.text! '
    ' - change.details.each do |detail| - xml.text! '
  • ' + show_detail(detail, false) + '
  • ' - end - xml.text! '
' - xml.text! textilizable(change, :notes, :only_path => false) unless change.notes.blank? - end - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/42/4222efb8f989394cbc29df18dfb204374b8d5e79.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/42/4222efb8f989394cbc29df18dfb204374b8d5e79.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,97 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class Redmine::ApiTest::NewsTest < Redmine::ApiTest::Base + fixtures :projects, :trackers, :issue_statuses, :issues, + :enumerations, :users, :issue_categories, + :projects_trackers, + :roles, + :member_roles, + :members, + :enabled_modules, + :news + + def setup + Setting.rest_api_enabled = '1' + end + + context "GET /news" do + context ".xml" do + should "return news" do + get '/news.xml' + + assert_tag :tag => 'news', + :attributes => {:type => 'array'}, + :child => { + :tag => 'news', + :child => { + :tag => 'id', + :content => '2' + } + } + end + end + + context ".json" do + should "return news" do + get '/news.json' + + json = ActiveSupport::JSON.decode(response.body) + assert_kind_of Hash, json + assert_kind_of Array, json['news'] + assert_kind_of Hash, json['news'].first + assert_equal 2, json['news'].first['id'] + end + end + end + + context "GET /projects/:project_id/news" do + context ".xml" do + should_allow_api_authentication(:get, "/projects/onlinestore/news.xml") + + should "return news" do + get '/projects/ecookbook/news.xml' + + assert_tag :tag => 'news', + :attributes => {:type => 'array'}, + :child => { + :tag => 'news', + :child => { + :tag => 'id', + :content => '2' + } + } + end + end + + context ".json" do + should_allow_api_authentication(:get, "/projects/onlinestore/news.json") + + should "return news" do + get '/projects/ecookbook/news.json' + + json = ActiveSupport::JSON.decode(response.body) + assert_kind_of Hash, json + assert_kind_of Array, json['news'] + assert_kind_of Hash, json['news'].first + assert_equal 2, json['news'].first['id'] + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/42/427b264a64a6d9342fb25880cb15db8377d4ab62.svn-base --- a/.svn/pristine/42/427b264a64a6d9342fb25880cb15db8377d4ab62.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +0,0 @@ -<%= l(:text_issue_added, :id => "##{@issue.id}", :author => h(@issue.author)) %> -
-<%= render :partial => "issue.html.erb", :locals => { :issue => @issue, :issue_url => @issue_url } %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/42/427c435973b09320d012f4a307265921e2e2fd84.svn-base --- a/.svn/pristine/42/427c435973b09320d012f4a307265921e2e2fd84.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,25 +0,0 @@ -#!/usr/bin/perl -# -# redMine 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. - -# modify to suit your repository base -my $repos_base = '/var/svn'; - -my $path = '/usr/bin/'; -my %kwown_commands = map { $_ => 1 } qw/svnserve/; - -umask 0002; - -exec ('/usr/bin/svnserve', '-r', $repos_base, '-t'); diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/42/428612395d9016270f63f32ed9dffa1b755e4922.svn-base --- a/.svn/pristine/42/428612395d9016270f63f32ed9dffa1b755e4922.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,960 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class Issue < ActiveRecord::Base - include Redmine::SafeAttributes - - belongs_to :project - belongs_to :tracker - belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id' - belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' - belongs_to :assigned_to, :class_name => 'Principal', :foreign_key => 'assigned_to_id' - belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id' - belongs_to :priority, :class_name => 'IssuePriority', :foreign_key => 'priority_id' - belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id' - - has_many :journals, :as => :journalized, :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_nested_set :scope => 'root_id', :dependent => :destroy - acts_as_attachable :after_add => :attachment_added, :after_remove => :attachment_removed - acts_as_customizable - acts_as_watchable - acts_as_searchable :columns => ['subject', "#{table_name}.description", "#{Journal.table_name}.notes"], - :include => [:project, :journals], - # sort by id so that limited eager loading doesn't break with postgresql - :order_column => "#{table_name}.id" - acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id} (#{o.status}): #{o.subject}"}, - :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]}, - :author_key => :author_id - - DONE_RATIO_OPTIONS = %w(issue_field issue_status) - - attr_reader :current_journal - - 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 - validate :validate_issue - - named_scope :visible, lambda {|*args| { :include => :project, - :conditions => Issue.visible_condition(args.shift || User.current, *args) } } - - named_scope :open, :conditions => ["#{IssueStatus.table_name}.is_closed = ?", false], :include => :status - - named_scope :recently_updated, :order => "#{Issue.table_name}.updated_on DESC" - named_scope :with_limit, lambda { |limit| { :limit => limit} } - named_scope :on_active_project, :include => [:status, :project, :tracker], - :conditions => ["#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"] - - named_scope :without_version, lambda { - { - :conditions => { :fixed_version_id => nil} - } - } - - named_scope :with_query, lambda {|query| - { - :conditions => Query.merge_conditions(query.statement) - } - } - - before_create :default_assign - before_save :close_duplicates, :update_done_ratio_from_issue_status - after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes, :create_journal - after_destroy :update_parent_attributes - - # Returns a SQL conditions string used to find all issues visible by the specified user - def self.visible_condition(user, options={}) - Project.allowed_to_condition(user, :view_issues, options) do |role, user| - case role.issues_visibility - when 'all' - nil - when 'default' - user_ids = [user.id] + user.groups.map(&:id) - "(#{table_name}.is_private = #{connection.quoted_false} OR #{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))" - when 'own' - user_ids = [user.id] + user.groups.map(&:id) - "(#{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))" - else - '1=0' - end - end - end - - # 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) do |role, user| - case role.issues_visibility - when 'all' - true - when 'default' - !self.is_private? || self.author == user || user.is_or_belongs_to?(assigned_to) - when 'own' - self.author == user || user.is_or_belongs_to?(assigned_to) - else - false - end - end - end - - def after_initialize - if new_record? - # set default values for new records only - self.status ||= IssueStatus.default - self.priority ||= IssuePriority.default - end - end - - # Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields - def available_custom_fields - (project && tracker) ? (project.all_issue_custom_fields & tracker.custom_fields.all) : [] - end - - def copy_from(arg) - issue = arg.is_a?(Issue) ? arg : Issue.visible.find(arg) - self.attributes = issue.attributes.dup.except("id", "root_id", "parent_id", "lft", "rgt", "created_on", "updated_on") - self.custom_field_values = issue.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h} - self.status = issue.status - self - end - - # Moves/copies an issue to a new project and tracker - # Returns the moved/copied issue on success, false on failure - def move_to_project(*args) - ret = Issue.transaction do - move_to_project_without_transaction(*args) || raise(ActiveRecord::Rollback) - end || false - end - - def move_to_project_without_transaction(new_project, new_tracker = nil, options = {}) - options ||= {} - issue = options[:copy] ? self.class.new.copy_from(self) : self - - if new_project && issue.project_id != new_project.id - # delete issue relations - unless Setting.cross_project_issue_relations? - 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 = issue.category.nil? ? nil : new_project.issue_categories.find_by_name(issue.category.name) - issue.category = new_category - # Keep the fixed_version if it's still valid in the new_project - unless new_project.shared_versions.include?(issue.fixed_version) - issue.fixed_version = nil - end - issue.project = new_project - if issue.parent && issue.parent.project_id != issue.project_id - issue.parent_issue_id = nil - end - end - if new_tracker - issue.tracker = new_tracker - issue.reset_custom_values! - end - if options[:copy] - issue.author = User.current - issue.custom_field_values = self.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h} - issue.status = if options[:attributes] && options[:attributes][:status_id] - IssueStatus.find_by_id(options[:attributes][:status_id]) - else - self.status - end - end - # Allow bulk setting of attributes on the issue - if options[:attributes] - issue.attributes = options[:attributes] - end - if issue.save - if options[:copy] - if current_journal && current_journal.notes.present? - issue.init_journal(current_journal.user, current_journal.notes) - issue.current_journal.notify = false - issue.save - end - else - # Manually update project_id on related time entries - TimeEntry.update_all("project_id = #{new_project.id}", {:issue_id => id}) - - issue.children.each do |child| - unless child.move_to_project_without_transaction(new_project) - # Move failed and transaction was rollback'd - return false - end - end - end - else - return false - end - issue - end - - def status_id=(sid) - self.status = nil - write_attribute(:status_id, sid) - end - - def priority_id=(pid) - self.priority = nil - write_attribute(:priority_id, pid) - end - - def tracker_id=(tid) - self.tracker = nil - result = write_attribute(:tracker_id, tid) - @custom_field_values = nil - result - end - - def description=(arg) - if arg.is_a?(String) - arg = arg.gsub(/(\r\n|\n|\r)/, "\r\n") - end - write_attribute(:description, arg) - end - - # Overrides attributes= so that tracker_id gets assigned first - def attributes_with_tracker_first=(new_attributes, *args) - return if new_attributes.nil? - new_tracker_id = new_attributes['tracker_id'] || new_attributes[:tracker_id] - if new_tracker_id - self.tracker_id = new_tracker_id - end - send :attributes_without_tracker_first=, new_attributes, *args - end - # Do not redefine alias chain on reload (see #4838) - alias_method_chain(:attributes=, :tracker_first) unless method_defined?(:attributes_without_tracker_first=) - - def estimated_hours=(h) - write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h) - end - - safe_attributes 'tracker_id', - 'status_id', - 'parent_issue_id', - 'category_id', - 'assigned_to_id', - 'priority_id', - 'fixed_version_id', - 'subject', - 'description', - 'start_date', - 'due_date', - 'done_ratio', - 'estimated_hours', - 'custom_field_values', - 'custom_fields', - 'lock_version', - :if => lambda {|issue, user| issue.new_record? || user.allowed_to?(:edit_issues, issue.project) } - - safe_attributes 'status_id', - 'assigned_to_id', - 'fixed_version_id', - 'done_ratio', - :if => lambda {|issue, user| issue.new_statuses_allowed_to(user).any? } - - safe_attributes 'is_private', - :if => lambda {|issue, user| - user.allowed_to?(:set_issues_private, issue.project) || - (issue.author == user && user.allowed_to?(:set_own_issues_private, issue.project)) - } - - # Safely sets attributes - # Should be called from controllers instead of #attributes= - # attr_accessible is too rough because we still want things like - # Issue.new(:project => foo) to work - # TODO: move workflow/permission checks from controllers to here - def safe_attributes=(attrs, user=User.current) - return unless attrs.is_a?(Hash) - - # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed - attrs = delete_unsafe_attributes(attrs, user) - return if attrs.empty? - - # Tracker must be set before since new_statuses_allowed_to depends on it. - if t = attrs.delete('tracker_id') - self.tracker_id = t - end - - if attrs['status_id'] - unless new_statuses_allowed_to(user).collect(&:id).include?(attrs['status_id'].to_i) - attrs.delete('status_id') - end - end - - unless leaf? - attrs.reject! {|k,v| %w(priority_id done_ratio start_date due_date estimated_hours).include?(k)} - end - - if attrs.has_key?('parent_issue_id') - if !user.allowed_to?(:manage_subtasks, project) - attrs.delete('parent_issue_id') - elsif !attrs['parent_issue_id'].blank? - attrs.delete('parent_issue_id') unless Issue.visible(user).exists?(attrs['parent_issue_id'].to_i) - end - end - - self.attributes = attrs - end - - def done_ratio - if Issue.use_status_for_done_ratio? && status && status.default_done_ratio - status.default_done_ratio - else - read_attribute(:done_ratio) - end - end - - def self.use_status_for_done_ratio? - Setting.issue_done_ratio == 'issue_status' - end - - def self.use_field_for_done_ratio? - Setting.issue_done_ratio == 'issue_field' - end - - def validate_issue - if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty? - errors.add :due_date, :not_a_date - end - - if self.due_date and self.start_date and self.due_date < self.start_date - errors.add :due_date, :greater_than_start_date - end - - if start_date && soonest_start && start_date < soonest_start - errors.add :start_date, :invalid - end - - if fixed_version - if !assignable_versions.include?(fixed_version) - errors.add :fixed_version_id, :inclusion - elsif reopened? && fixed_version.closed? - errors.add :base, I18n.t(:error_can_not_reopen_issue_on_closed_version) - end - end - - # Checks that the issue can not be added/moved to a disabled tracker - if project && (tracker_id_changed? || project_id_changed?) - unless project.trackers.include?(tracker) - errors.add :tracker_id, :inclusion - end - end - - # Checks parent issue assignment - if @parent_issue - if @parent_issue.project_id != project_id - errors.add :parent_issue_id, :not_same_project - elsif !new_record? - # moving an existing issue - if @parent_issue.root_id != root_id - # we can always move to another tree - elsif move_possible?(@parent_issue) - # move accepted inside tree - else - errors.add :parent_issue_id, :not_a_valid_parent - end - end - end - end - - # Set the done_ratio using the status if that setting is set. This will keep the done_ratios - # even if the user turns off the setting later - def update_done_ratio_from_issue_status - if Issue.use_status_for_done_ratio? && status && status.default_done_ratio - self.done_ratio = status.default_done_ratio - end - end - - def init_journal(user, notes = "") - @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes) - @issue_before_change = self.clone - @issue_before_change.status = self.status - @custom_values_before_change = {} - self.custom_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value } - # Make sure updated_on is updated when adding a note. - updated_on_will_change! - @current_journal - end - - # Return true if the issue is closed, otherwise false - def closed? - self.status.is_closed? - end - - # Return true if the issue is being reopened - def reopened? - if !new_record? && status_id_changed? - status_was = IssueStatus.find_by_id(status_id_was) - status_new = IssueStatus.find_by_id(status_id) - if status_was && status_new && status_was.is_closed? && !status_new.is_closed? - return true - end - end - false - end - - # Return true if the issue is being closed - def closing? - if !new_record? && status_id_changed? - status_was = IssueStatus.find_by_id(status_id_was) - status_new = IssueStatus.find_by_id(status_id) - if status_was && status_new && !status_was.is_closed? && status_new.is_closed? - return true - end - end - false - end - - # Returns true if the issue is overdue - def overdue? - !due_date.nil? && (due_date < Date.today) && !status.is_closed? - end - - # Is the amount of work done less than it should for the due date - def behind_schedule? - return false if start_date.nil? || due_date.nil? - done_date = start_date + ((due_date - start_date+1)* done_ratio/100).floor - return done_date <= Date.today - end - - # Does this issue have children? - def children? - !leaf? - end - - # Users the issue can be assigned to - def assignable_users - users = project.assignable_users - users << author if author - users << assigned_to if assigned_to - users.uniq.sort - end - - # Versions that the issue can be assigned to - def assignable_versions - @assignable_versions ||= (project.shared_versions.open + [Version.find_by_id(fixed_version_id_was)]).compact.uniq.sort - end - - # Returns true if this issue is blocked by another issue that is still open - def blocked? - !relations_to.detect {|ir| ir.relation_type == 'blocks' && !ir.issue_from.closed?}.nil? - end - - # Returns an array of status that user is able to apply - def new_statuses_allowed_to(user, include_default=false) - statuses = status.find_new_statuses_allowed_to( - user.roles_for_project(project), - tracker, - author == user, - assigned_to_id_changed? ? assigned_to_id_was == user.id : assigned_to_id == user.id - ) - statuses << status unless statuses.empty? - statuses << IssueStatus.default if include_default - statuses = statuses.uniq.sort - blocked? ? statuses.reject {|s| s.is_closed?} : statuses - end - - # Returns the mail adresses of users that should be notified - def recipients - notified = project.notified_users - # Author and assignee are always notified unless they have been - # locked or don't want to be notified - notified << author if author && author.active? && author.notify_about?(self) - if assigned_to - if assigned_to.is_a?(Group) - notified += assigned_to.users.select {|u| u.active? && u.notify_about?(self)} - else - notified << assigned_to if assigned_to.active? && assigned_to.notify_about?(self) - end - end - notified.uniq! - # Remove users that can not view the issue - notified.reject! {|user| !visible?(user)} - notified.collect(&:mail) - end - - # Returns the total number of hours spent on this issue and its descendants - # - # Example: - # spent_hours => 0.0 - # spent_hours => 50.2 - def spent_hours - @spent_hours ||= self_and_descendants.sum("#{TimeEntry.table_name}.hours", :include => :time_entries).to_f || 0.0 - end - - def relations - @relations ||= (relations_from + relations_to).sort - end - - # Preloads relations for a collection of issues - def self.load_relations(issues) - if issues.any? - relations = IssueRelation.all(:conditions => ["issue_from_id IN (:ids) OR issue_to_id IN (:ids)", {:ids => issues.map(&:id)}]) - issues.each do |issue| - issue.instance_variable_set "@relations", relations.select {|r| r.issue_from_id == issue.id || r.issue_to_id == issue.id} - end - end - end - - # Finds an issue relation given its id. - def find_relation(relation_id) - IssueRelation.find(relation_id, :conditions => ["issue_to_id = ? OR issue_from_id = ?", id, id]) - end - - def all_dependent_issues(except=[]) - except << self - dependencies = [] - relations_from.each do |relation| - if relation.issue_to && !except.include?(relation.issue_to) - dependencies << relation.issue_to - dependencies += relation.issue_to.all_dependent_issues(except) - end - end - dependencies - end - - # Returns an array of issues that duplicate this one - def duplicates - relations_to.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.issue_from} - end - - # Returns the due date or the target due date if any - # Used on gantt chart - def due_before - due_date || (fixed_version ? fixed_version.effective_date : nil) - end - - # Returns the time scheduled for this issue. - # - # Example: - # Start Date: 2/26/09, End Date: 3/04/09 - # duration => 6 - def duration - (start_date && due_date) ? due_date - start_date : 0 - end - - def soonest_start - @soonest_start ||= ( - relations_to.collect{|relation| relation.successor_soonest_start} + - ancestors.collect(&:soonest_start) - ).compact.max - end - - def reschedule_after(date) - return if date.nil? - if leaf? - if start_date.nil? || start_date < date - self.start_date, self.due_date = date, date + duration - save - end - else - leaves.each do |leaf| - leaf.reschedule_after(date) - end - end - end - - def <=>(issue) - if issue.nil? - -1 - elsif root_id != issue.root_id - (root_id || 0) <=> (issue.root_id || 0) - else - (lft || 0) <=> (issue.lft || 0) - end - end - - def to_s - "#{tracker} ##{id}: #{subject}" - end - - # Returns a string of css classes that apply to the issue - def css_classes - s = "issue status-#{status.position} priority-#{priority.position}" - s << ' closed' if closed? - s << ' overdue' if overdue? - s << ' child' if child? - s << ' parent' unless leaf? - s << ' private' if is_private? - s << ' created-by-me' if User.current.logged? && author_id == User.current.id - s << ' assigned-to-me' if User.current.logged? && assigned_to_id == User.current.id - s - end - - # Saves an issue, time_entry, attachments, and a journal from the parameters - # Returns false if save fails - def save_issue_with_child_records(params, existing_time_entry=nil) - Issue.transaction do - if params[:time_entry] && (params[:time_entry][:hours].present? || params[:time_entry][:comments].present?) && User.current.allowed_to?(:log_time, project) - @time_entry = existing_time_entry || TimeEntry.new - @time_entry.project = project - @time_entry.issue = self - @time_entry.user = User.current - @time_entry.spent_on = User.current.today - @time_entry.attributes = params[:time_entry] - self.time_entries << @time_entry - end - - if valid? - attachments = Attachment.attach_files(self, params[:attachments]) - # TODO: Rename hook - Redmine::Hook.call_hook(:controller_issues_edit_before_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal}) - begin - if save - # TODO: Rename hook - Redmine::Hook.call_hook(:controller_issues_edit_after_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal}) - else - raise ActiveRecord::Rollback - end - rescue ActiveRecord::StaleObjectError - attachments[:files].each(&:destroy) - errors.add :base, l(:notice_locking_conflict) - raise ActiveRecord::Rollback - end - end - end - end - - # Unassigns issues from +version+ if it's no longer shared with issue's project - def self.update_versions_from_sharing_change(version) - # Update issues assigned to the version - update_versions(["#{Issue.table_name}.fixed_version_id = ?", version.id]) - end - - # Unassigns issues from versions that are no longer shared - # after +project+ was moved - def self.update_versions_from_hierarchy_change(project) - moved_project_ids = project.self_and_descendants.reload.collect(&:id) - # Update issues of the moved projects and issues assigned to a version of a moved project - Issue.update_versions(["#{Version.table_name}.project_id IN (?) OR #{Issue.table_name}.project_id IN (?)", moved_project_ids, moved_project_ids]) - end - - def parent_issue_id=(arg) - parent_issue_id = arg.blank? ? nil : arg.to_i - if parent_issue_id && @parent_issue = Issue.find_by_id(parent_issue_id) - @parent_issue.id - else - @parent_issue = nil - nil - end - end - - def parent_issue_id - if instance_variable_defined? :@parent_issue - @parent_issue.nil? ? nil : @parent_issue.id - else - parent_id - end - end - - # Extracted from the ReportsController. - def self.by_tracker(project) - count_and_group_by(:project => project, - :field => 'tracker_id', - :joins => Tracker.table_name) - end - - def self.by_version(project) - count_and_group_by(:project => project, - :field => 'fixed_version_id', - :joins => Version.table_name) - end - - def self.by_priority(project) - count_and_group_by(:project => project, - :field => 'priority_id', - :joins => IssuePriority.table_name) - end - - def self.by_category(project) - count_and_group_by(:project => project, - :field => 'category_id', - :joins => IssueCategory.table_name) - end - - def self.by_assigned_to(project) - count_and_group_by(:project => project, - :field => 'assigned_to_id', - :joins => User.table_name) - end - - def self.by_author(project) - count_and_group_by(:project => project, - :field => 'author_id', - :joins => User.table_name) - end - - def self.by_subproject(project) - ActiveRecord::Base.connection.select_all("select s.id as status_id, - s.is_closed as closed, - #{Issue.table_name}.project_id as project_id, - count(#{Issue.table_name}.id) as total - from - #{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s - where - #{Issue.table_name}.status_id=s.id - and #{Issue.table_name}.project_id = #{Project.table_name}.id - and #{visible_condition(User.current, :project => project, :with_subprojects => true)} - and #{Issue.table_name}.project_id <> #{project.id} - group by s.id, s.is_closed, #{Issue.table_name}.project_id") if project.descendants.active.any? - end - # End ReportsController extraction - - # Returns an array of projects that current user can move issues to - def self.allowed_target_projects_on_move - projects = [] - if User.current.admin? - # admin is allowed to move issues to any active (visible) project - projects = Project.visible.all - elsif User.current.logged? - if Role.non_member.allowed_to?(:move_issues) - projects = Project.visible.all - else - User.current.memberships.each {|m| projects << m.project if m.roles.detect {|r| r.allowed_to?(:move_issues)}} - end - end - projects - end - - private - - def update_nested_set_attributes - if root_id.nil? - # issue was just created - self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id) - set_default_left_and_right - Issue.update_all("root_id = #{root_id}, lft = #{lft}, rgt = #{rgt}", ["id = ?", id]) - if @parent_issue - move_to_child_of(@parent_issue) - end - reload - elsif parent_issue_id != parent_id - former_parent_id = parent_id - # moving an existing issue - if @parent_issue && @parent_issue.root_id == root_id - # inside the same tree - move_to_child_of(@parent_issue) - else - # to another tree - unless root? - move_to_right_of(root) - reload - end - old_root_id = root_id - self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id ) - target_maxright = nested_set_scope.maximum(right_column_name) || 0 - offset = target_maxright + 1 - lft - Issue.update_all("root_id = #{root_id}, lft = lft + #{offset}, rgt = rgt + #{offset}", - ["root_id = ? AND lft >= ? AND rgt <= ? ", old_root_id, lft, rgt]) - self[left_column_name] = lft + offset - self[right_column_name] = rgt + offset - if @parent_issue - move_to_child_of(@parent_issue) - end - end - reload - # delete invalid relations of all descendants - self_and_descendants.each do |issue| - issue.relations.each do |relation| - relation.destroy unless relation.valid? - end - end - # update former parent - recalculate_attributes_for(former_parent_id) if former_parent_id - end - remove_instance_variable(:@parent_issue) if instance_variable_defined?(:@parent_issue) - end - - def update_parent_attributes - recalculate_attributes_for(parent_id) if parent_id - end - - def recalculate_attributes_for(issue_id) - if issue_id && p = Issue.find_by_id(issue_id) - # priority = highest priority of children - if priority_position = p.children.maximum("#{IssuePriority.table_name}.position", :include => :priority) - p.priority = IssuePriority.find_by_position(priority_position) - end - - # start/due dates = lowest/highest dates of children - p.start_date = p.children.minimum(:start_date) - p.due_date = p.children.maximum(:due_date) - if p.start_date && p.due_date && p.due_date < p.start_date - p.start_date, p.due_date = p.due_date, p.start_date - end - - # done ratio = weighted average ratio of leaves - unless Issue.use_status_for_done_ratio? && p.status && p.status.default_done_ratio - leaves_count = p.leaves.count - if leaves_count > 0 - average = p.leaves.average(:estimated_hours).to_f - if average == 0 - average = 1 - end - done = p.leaves.sum("COALESCE(estimated_hours, #{average}) * (CASE WHEN is_closed = #{connection.quoted_true} THEN 100 ELSE COALESCE(done_ratio, 0) END)", :include => :status).to_f - progress = done / (average * leaves_count) - p.done_ratio = progress.round - end - end - - # estimate = sum of leaves estimates - p.estimated_hours = p.leaves.sum(:estimated_hours).to_f - p.estimated_hours = nil if p.estimated_hours == 0.0 - - # ancestors will be recursively updated - p.save(false) - end - end - - # Update issues so their versions are not pointing to a - # fixed_version that is not shared with the issue's project - def self.update_versions(conditions=nil) - # Only need to update issues with a fixed_version from - # a different project and that is not systemwide shared - Issue.all(:conditions => merge_conditions("#{Issue.table_name}.fixed_version_id IS NOT NULL" + - " AND #{Issue.table_name}.project_id <> #{Version.table_name}.project_id" + - " AND #{Version.table_name}.sharing <> 'system'", - conditions), - :include => [:project, :fixed_version] - ).each do |issue| - next if issue.project.nil? || issue.fixed_version.nil? - unless issue.project.shared_versions.include?(issue.fixed_version) - issue.init_journal(User.current) - issue.fixed_version = nil - issue.save - end - end - end - - # Callback on attachment deletion - def attachment_added(obj) - if @current_journal && !obj.new_record? - @current_journal.details << JournalDetail.new(:property => 'attachment', :prop_key => obj.id, :value => obj.filename) - end - end - - # 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 - - # Default assignment based on category - def default_assign - if assigned_to.nil? && category && category.assigned_to - self.assigned_to = category.assigned_to - end - end - - # Updates start/due dates of following issues - def reschedule_following_issues - if start_date_changed? || due_date_changed? - relations_from.each do |relation| - relation.set_issue_to_dates - end - end - end - - # Closes duplicates if the issue is being closed - def close_duplicates - if closing? - duplicates.each do |duplicate| - # Reload is need in case the duplicate was updated by a previous duplicate - duplicate.reload - # Don't re-close it if it's already closed - next if duplicate.closed? - # Same user and notes - if @current_journal - duplicate.init_journal(@current_journal.user, @current_journal.notes) - end - duplicate.update_attribute :status, self.status - end - end - end - - # Saves the changes in a Journal - # Called after_save - def create_journal - if @current_journal - # attributes changes - (Issue.column_names - %w(id root_id lft rgt lock_version created_on updated_on)).each {|c| - before = @issue_before_change.send(c) - after = send(c) - next if before == after || (before.blank? && after.blank?) - @current_journal.details << JournalDetail.new(:property => 'attr', - :prop_key => c, - :old_value => @issue_before_change.send(c), - :value => send(c)) - } - # custom fields changes - custom_values.each {|c| - next if (@custom_values_before_change[c.custom_field_id]==c.value || - (@custom_values_before_change[c.custom_field_id].blank? && c.value.blank?)) - @current_journal.details << JournalDetail.new(:property => 'cf', - :prop_key => c.custom_field_id, - :old_value => @custom_values_before_change[c.custom_field_id], - :value => c.value) - } - @current_journal.save - # reset current journal - init_journal @current_journal.user, @current_journal.notes - end - end - - # Query generator for selecting groups of issue counts for a project - # based on specific criteria - # - # Options - # * project - Project to search in. - # * field - String. Issue field to key off of in the grouping. - # * joins - String. The table name to join against. - def self.count_and_group_by(options) - project = options.delete(:project) - select_field = options.delete(:field) - joins = options.delete(:joins) - - where = "#{Issue.table_name}.#{select_field}=j.id" - - ActiveRecord::Base.connection.select_all("select s.id as status_id, - s.is_closed as closed, - j.id as #{select_field}, - count(#{Issue.table_name}.id) as total - from - #{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s, #{joins} j - where - #{Issue.table_name}.status_id=s.id - and #{where} - and #{Issue.table_name}.project_id=#{Project.table_name}.id - and #{visible_condition(User.current, :project => project)} - group by s.id, s.is_closed, j.id") - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/42/428ba80948c53d51011877b4e85f463b9e15c19b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/42/428ba80948c53d51011877b4e85f463b9e15c19b.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,1 @@ +$('#users').html('<%= escape_javascript(render_principals_for_new_group_users(@group)) %>'); diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/42/428bfa93a4c970791698a943e257eddabf08b22f.svn-base --- a/.svn/pristine/42/428bfa93a4c970791698a943e257eddabf08b22f.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,6 +0,0 @@ -

<%= link_to h(document.title), :controller => 'documents', :action => 'show', :id => document %>

-

<%= format_time(document.updated_on) %>

- -
- <%= textilizable(truncate_lines(document.description), :object => document) %> -
diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/42/42cbd8a4f24d085f0d8c0a431c99b911f57d4034.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/42/42cbd8a4f24d085f0d8c0a431c99b911f57d4034.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,638 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class RepositoriesGitControllerTest < ActionController::TestCase + tests RepositoriesController + + fixtures :projects, :users, :roles, :members, :member_roles, + :repositories, :enabled_modules + + REPOSITORY_PATH = Rails.root.join('tmp/test/git_repository').to_s + REPOSITORY_PATH.gsub!(/\//, "\\") if Redmine::Platform.mswin? + PRJ_ID = 3 + CHAR_1_HEX = "\xc3\x9c" + NUM_REV = 28 + + ## Git, Mercurial and CVS path encodings are binary. + ## Subversion supports URL encoding for path. + ## Redmine Mercurial adapter and extension use URL encoding. + ## Git accepts only binary path in command line parameter. + ## So, there is no way to use binary command line parameter in JRuby. + JRUBY_SKIP = (RUBY_PLATFORM == 'java') + JRUBY_SKIP_STR = "TODO: This test fails in JRuby" + + def setup + @ruby19_non_utf8_pass = + (RUBY_VERSION >= '1.9' && Encoding.default_external.to_s != 'UTF-8') + + User.current = nil + @project = Project.find(PRJ_ID) + @repository = Repository::Git.create( + :project => @project, + :url => REPOSITORY_PATH, + :path_encoding => 'ISO-8859-1' + ) + assert @repository + @char_1 = CHAR_1_HEX.dup + if @char_1.respond_to?(:force_encoding) + @char_1.force_encoding('UTF-8') + end + end + + def test_create_and_update + @request.session[:user_id] = 1 + assert_difference 'Repository.count' do + post :create, :project_id => 'subproject1', + :repository_scm => 'Git', + :repository => { + :url => '/test', + :is_default => '0', + :identifier => 'test-create', + :extra_report_last_commit => '1', + } + end + assert_response 302 + repository = Repository.first(:order => 'id DESC') + assert_kind_of Repository::Git, repository + assert_equal '/test', repository.url + assert_equal true, repository.extra_report_last_commit + + put :update, :id => repository.id, + :repository => { + :extra_report_last_commit => '0' + } + assert_response 302 + repo2 = Repository.find(repository.id) + assert_equal false, repo2.extra_report_last_commit + end + + if File.directory?(REPOSITORY_PATH) + ## Ruby uses ANSI api to fork a process on Windows. + ## Japanese Shift_JIS and Traditional Chinese Big5 have 0x5c(backslash) problem + ## and these are incompatible with ASCII. + ## Git for Windows (msysGit) changed internal API from ANSI to Unicode in 1.7.10 + ## http://code.google.com/p/msysgit/issues/detail?id=80 + ## So, Latin-1 path tests fail on Japanese Windows + WINDOWS_PASS = (Redmine::Platform.mswin? && + Redmine::Scm::Adapters::GitAdapter.client_version_above?([1, 7, 10])) + WINDOWS_SKIP_STR = "TODO: This test fails in Git for Windows above 1.7.10" + + def test_get_new + @request.session[:user_id] = 1 + @project.repository.destroy + get :new, :project_id => 'subproject1', :repository_scm => 'Git' + assert_response :success + assert_template 'new' + assert_kind_of Repository::Git, assigns(:repository) + assert assigns(:repository).new_record? + end + + def test_browse_root + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + + get :show, :id => PRJ_ID + assert_response :success + assert_template 'show' + assert_not_nil assigns(:entries) + assert_equal 9, assigns(:entries).size + assert assigns(:entries).detect {|e| e.name == 'images' && e.kind == 'dir'} + assert assigns(:entries).detect {|e| e.name == 'this_is_a_really_long_and_verbose_directory_name' && e.kind == 'dir'} + assert assigns(:entries).detect {|e| e.name == 'sources' && e.kind == 'dir'} + assert assigns(:entries).detect {|e| e.name == 'README' && e.kind == 'file'} + assert assigns(:entries).detect {|e| e.name == 'copied_README' && e.kind == 'file'} + assert assigns(:entries).detect {|e| e.name == 'new_file.txt' && e.kind == 'file'} + assert assigns(:entries).detect {|e| e.name == 'renamed_test.txt' && e.kind == 'file'} + assert assigns(:entries).detect {|e| e.name == 'filemane with spaces.txt' && e.kind == 'file'} + assert assigns(:entries).detect {|e| e.name == ' filename with a leading space.txt ' && e.kind == 'file'} + assert_not_nil assigns(:changesets) + assert assigns(:changesets).size > 0 + end + + def test_browse_branch + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + get :show, :id => PRJ_ID, :rev => 'test_branch' + assert_response :success + assert_template 'show' + assert_not_nil assigns(:entries) + assert_equal 4, assigns(:entries).size + assert assigns(:entries).detect {|e| e.name == 'images' && e.kind == 'dir'} + assert assigns(:entries).detect {|e| e.name == 'sources' && e.kind == 'dir'} + assert assigns(:entries).detect {|e| e.name == 'README' && e.kind == 'file'} + assert assigns(:entries).detect {|e| e.name == 'test.txt' && e.kind == 'file'} + assert_not_nil assigns(:changesets) + assert assigns(:changesets).size > 0 + end + + def test_browse_tag + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + [ + "tag00.lightweight", + "tag01.annotated", + ].each do |t1| + get :show, :id => PRJ_ID, :rev => t1 + assert_response :success + assert_template 'show' + assert_not_nil assigns(:entries) + assert assigns(:entries).size > 0 + assert_not_nil assigns(:changesets) + assert assigns(:changesets).size > 0 + end + end + + def test_browse_directory + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + get :show, :id => PRJ_ID, :path => repository_path_hash(['images'])[:param] + assert_response :success + assert_template 'show' + assert_not_nil assigns(:entries) + assert_equal ['edit.png'], assigns(:entries).collect(&:name) + entry = assigns(:entries).detect {|e| e.name == 'edit.png'} + assert_not_nil entry + assert_equal 'file', entry.kind + assert_equal 'images/edit.png', entry.path + assert_not_nil assigns(:changesets) + assert assigns(:changesets).size > 0 + end + + def test_browse_at_given_revision + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + get :show, :id => PRJ_ID, :path => repository_path_hash(['images'])[:param], + :rev => '7234cb2750b63f47bff735edc50a1c0a433c2518' + assert_response :success + assert_template 'show' + assert_not_nil assigns(:entries) + assert_equal ['delete.png'], assigns(:entries).collect(&:name) + assert_not_nil assigns(:changesets) + assert assigns(:changesets).size > 0 + end + + def test_changes + get :changes, :id => PRJ_ID, + :path => repository_path_hash(['images', 'edit.png'])[:param] + assert_response :success + assert_template 'changes' + assert_tag :tag => 'h2', :content => 'edit.png' + end + + def test_entry_show + get :entry, :id => PRJ_ID, + :path => repository_path_hash(['sources', 'watchers_controller.rb'])[:param] + assert_response :success + assert_template 'entry' + # Line 19 + assert_tag :tag => 'th', + :content => '11', + :attributes => { :class => 'line-num' }, + :sibling => { :tag => 'td', :content => /WITHOUT ANY WARRANTY/ } + end + + def test_entry_show_latin_1 + if @ruby19_non_utf8_pass + puts_ruby19_non_utf8_pass() + elsif WINDOWS_PASS + puts WINDOWS_SKIP_STR + elsif JRUBY_SKIP + puts JRUBY_SKIP_STR + else + with_settings :repositories_encodings => 'UTF-8,ISO-8859-1' do + ['57ca437c', '57ca437c0acbbcb749821fdf3726a1367056d364'].each do |r1| + get :entry, :id => PRJ_ID, + :path => repository_path_hash(['latin-1-dir', "test-#{@char_1}.txt"])[:param], + :rev => r1 + assert_response :success + assert_template 'entry' + assert_tag :tag => 'th', + :content => '1', + :attributes => { :class => 'line-num' }, + :sibling => { :tag => 'td', + :content => /test-#{@char_1}.txt/ } + end + end + end + end + + def test_entry_download + get :entry, :id => PRJ_ID, + :path => repository_path_hash(['sources', 'watchers_controller.rb'])[:param], + :format => 'raw' + assert_response :success + # File content + assert @response.body.include?('WITHOUT ANY WARRANTY') + end + + def test_directory_entry + get :entry, :id => PRJ_ID, + :path => repository_path_hash(['sources'])[:param] + assert_response :success + assert_template 'show' + assert_not_nil assigns(:entry) + assert_equal 'sources', assigns(:entry).name + end + + def test_diff + assert_equal true, @repository.is_default + assert_nil @repository.identifier + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + # Full diff of changeset 2f9c0091 + ['inline', 'sbs'].each do |dt| + get :diff, + :id => PRJ_ID, + :rev => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7', + :type => dt + assert_response :success + assert_template 'diff' + # Line 22 removed + assert_tag :tag => 'th', + :content => /22/, + :sibling => { :tag => 'td', + :attributes => { :class => /diff_out/ }, + :content => /def remove/ } + assert_tag :tag => 'h2', :content => /2f9c0091/ + end + end + + def test_diff_with_rev_and_path + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + with_settings :diff_max_lines_displayed => 1000 do + # Full diff of changeset 2f9c0091 + ['inline', 'sbs'].each do |dt| + get :diff, + :id => PRJ_ID, + :rev => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7', + :path => repository_path_hash(['sources', 'watchers_controller.rb'])[:param], + :type => dt + assert_response :success + assert_template 'diff' + # Line 22 removed + assert_tag :tag => 'th', + :content => '22', + :sibling => { :tag => 'td', + :attributes => { :class => /diff_out/ }, + :content => /def remove/ } + assert_tag :tag => 'h2', :content => /2f9c0091/ + end + end + end + + def test_diff_truncated + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + + with_settings :diff_max_lines_displayed => 5 do + # Truncated diff of changeset 2f9c0091 + with_cache do + with_settings :default_language => 'en' do + get :diff, :id => PRJ_ID, :type => 'inline', + :rev => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7' + assert_response :success + assert @response.body.include?("... This diff was truncated") + end + with_settings :default_language => 'fr' do + get :diff, :id => PRJ_ID, :type => 'inline', + :rev => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7' + assert_response :success + assert ! @response.body.include?("... This diff was truncated") + assert @response.body.include?("... Ce diff") + end + end + end + end + + def test_diff_two_revs + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + ['inline', 'sbs'].each do |dt| + get :diff, + :id => PRJ_ID, + :rev => '61b685fbe55ab05b5ac68402d5720c1a6ac973d1', + :rev_to => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7', + :type => dt + assert_response :success + assert_template 'diff' + diff = assigns(:diff) + assert_not_nil diff + assert_tag :tag => 'h2', :content => /2f9c0091:61b685fb/ + assert_tag :tag => "form", + :attributes => { + :action => "/projects/subproject1/repository/revisions/" + + "61b685fbe55ab05b5ac68402d5720c1a6ac973d1/diff" + } + assert_tag :tag => 'input', + :attributes => { + :id => "rev_to", + :name => "rev_to", + :type => "hidden", + :value => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7' + } + end + end + + def test_diff_path_in_subrepo + repo = Repository::Git.create( + :project => @project, + :url => REPOSITORY_PATH, + :identifier => 'test-diff-path', + :path_encoding => 'ISO-8859-1' + ); + assert repo + assert_equal false, repo.is_default + assert_equal 'test-diff-path', repo.identifier + get :diff, + :id => PRJ_ID, + :repository_id => 'test-diff-path', + :rev => '61b685fbe55ab05b', + :rev_to => '2f9c0091c754a91a', + :type => 'inline' + assert_response :success + assert_template 'diff' + diff = assigns(:diff) + assert_not_nil diff + assert_tag :tag => "form", + :attributes => { + :action => "/projects/subproject1/repository/test-diff-path/" + + "revisions/61b685fbe55ab05b/diff" + } + assert_tag :tag => 'input', + :attributes => { + :id => "rev_to", + :name => "rev_to", + :type => "hidden", + :value => '2f9c0091c754a91a' + } + end + + def test_diff_latin_1 + if @ruby19_non_utf8_pass + puts_ruby19_non_utf8_pass() + else + with_settings :repositories_encodings => 'UTF-8,ISO-8859-1' do + ['57ca437c', '57ca437c0acbbcb749821fdf3726a1367056d364'].each do |r1| + ['inline', 'sbs'].each do |dt| + get :diff, :id => PRJ_ID, :rev => r1, :type => dt + assert_response :success + assert_template 'diff' + assert_tag :tag => 'thead', + :descendant => { + :tag => 'th', + :attributes => { :class => 'filename' } , + :content => /latin-1-dir\/test-#{@char_1}.txt/ , + }, + :sibling => { + :tag => 'tbody', + :descendant => { + :tag => 'td', + :attributes => { :class => /diff_in/ }, + :content => /test-#{@char_1}.txt/ + } + } + end + end + end + end + end + + def test_diff_should_show_filenames + get :diff, :id => PRJ_ID, :rev => 'deff712f05a90d96edbd70facc47d944be5897e3', :type => 'inline' + assert_response :success + assert_template 'diff' + # modified file + assert_select 'th.filename', :text => 'sources/watchers_controller.rb' + # deleted file + assert_select 'th.filename', :text => 'test.txt' + end + + def test_save_diff_type + user1 = User.find(1) + user1.pref[:diff_type] = nil + user1.preference.save + user = User.find(1) + assert_nil user.pref[:diff_type] + + @request.session[:user_id] = 1 # admin + get :diff, + :id => PRJ_ID, + :rev => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7' + assert_response :success + assert_template 'diff' + user.reload + assert_equal "inline", user.pref[:diff_type] + get :diff, + :id => PRJ_ID, + :rev => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7', + :type => 'sbs' + assert_response :success + assert_template 'diff' + user.reload + assert_equal "sbs", user.pref[:diff_type] + end + + def test_annotate + get :annotate, :id => PRJ_ID, + :path => repository_path_hash(['sources', 'watchers_controller.rb'])[:param] + assert_response :success + assert_template 'annotate' + + # Line 23, changeset 2f9c0091 + assert_select 'tr' do + assert_select 'th.line-num', :text => '23' + assert_select 'td.revision', :text => /2f9c0091/ + assert_select 'td.author', :text => 'jsmith' + assert_select 'td', :text => /remove_watcher/ + end + end + + def test_annotate_at_given_revision + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + get :annotate, :id => PRJ_ID, :rev => 'deff7', + :path => repository_path_hash(['sources', 'watchers_controller.rb'])[:param] + assert_response :success + assert_template 'annotate' + assert_tag :tag => 'h2', :content => /@ deff712f/ + end + + def test_annotate_binary_file + get :annotate, :id => PRJ_ID, + :path => repository_path_hash(['images', 'edit.png'])[:param] + assert_response 500 + assert_tag :tag => 'p', :attributes => { :id => /errorExplanation/ }, + :content => /cannot be annotated/ + end + + def test_annotate_error_when_too_big + with_settings :file_max_size_displayed => 1 do + get :annotate, :id => PRJ_ID, + :path => repository_path_hash(['sources', 'watchers_controller.rb'])[:param], + :rev => 'deff712f' + assert_response 500 + assert_tag :tag => 'p', :attributes => { :id => /errorExplanation/ }, + :content => /exceeds the maximum text file size/ + + get :annotate, :id => PRJ_ID, + :path => repository_path_hash(['README'])[:param], + :rev => '7234cb2' + assert_response :success + assert_template 'annotate' + end + end + + def test_annotate_latin_1 + if @ruby19_non_utf8_pass + puts_ruby19_non_utf8_pass() + elsif WINDOWS_PASS + puts WINDOWS_SKIP_STR + elsif JRUBY_SKIP + puts JRUBY_SKIP_STR + else + with_settings :repositories_encodings => 'UTF-8,ISO-8859-1' do + ['57ca437c', '57ca437c0acbbcb749821fdf3726a1367056d364'].each do |r1| + get :annotate, :id => PRJ_ID, + :path => repository_path_hash(['latin-1-dir', "test-#{@char_1}.txt"])[:param], + :rev => r1 + assert_tag :tag => 'th', + :content => '1', + :attributes => { :class => 'line-num' }, + :sibling => { :tag => 'td', + :content => /test-#{@char_1}.txt/ } + end + end + end + end + + def test_revisions + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + get :revisions, :id => PRJ_ID + assert_response :success + assert_template 'revisions' + assert_tag :tag => 'form', + :attributes => { + :method => 'get', + :action => '/projects/subproject1/repository/revision' + } + end + + def test_revision + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + ['61b685fbe55ab05b5ac68402d5720c1a6ac973d1', '61b685f'].each do |r| + get :revision, :id => PRJ_ID, :rev => r + assert_response :success + assert_template 'revision' + end + end + + def test_empty_revision + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + ['', ' ', nil].each do |r| + get :revision, :id => PRJ_ID, :rev => r + assert_response 404 + assert_error_tag :content => /was not found/ + end + end + + def test_destroy_valid_repository + @request.session[:user_id] = 1 # admin + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + + assert_difference 'Repository.count', -1 do + delete :destroy, :id => @repository.id + end + assert_response 302 + @project.reload + assert_nil @project.repository + end + + def test_destroy_invalid_repository + @request.session[:user_id] = 1 # admin + @project.repository.destroy + @repository = Repository::Git.create!( + :project => @project, + :url => "/invalid", + :path_encoding => 'ISO-8859-1' + ) + @repository.fetch_changesets + @repository.reload + assert_equal 0, @repository.changesets.count + + assert_difference 'Repository.count', -1 do + delete :destroy, :id => @repository.id + end + assert_response 302 + @project.reload + assert_nil @project.repository + end + + private + + def puts_ruby19_non_utf8_pass + puts "TODO: This test fails in Ruby 1.9 " + + "and Encoding.default_external is not UTF-8. " + + "Current value is '#{Encoding.default_external.to_s}'" + end + else + puts "Git test repository NOT FOUND. Skipping functional tests !!!" + def test_fake; assert true end + end + + private + def with_cache(&block) + before = ActionController::Base.perform_caching + ActionController::Base.perform_caching = true + block.call + ActionController::Base.perform_caching = before + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/43/4319c6d41e87c8e5f7a1d9a565f8d9f540e96297.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/43/4319c6d41e87c8e5f7a1d9a565f8d9f540e96297.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,19 @@ +

<%= l(:label_plugins) %>

+ +<% if @plugins.any? %> + + <% @plugins.each do |plugin| %> + + + + + + + <% end %> +
<%=h plugin.name %> + <%= 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? %> + <%= plugin.author_url.blank? ? h(plugin.author) : link_to(h(plugin.author), plugin.author_url) %><%=h plugin.version %><%= link_to(l(:button_configure), plugin_settings_path(plugin)) if plugin.configurable? %>
+<% else %> +

<%= l(:label_no_data) %>

+<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/43/431e564a1e03f4cc7d0102d7d18816df7d2cc0e3.svn-base --- a/.svn/pristine/43/431e564a1e03f4cc7d0102d7d18816df7d2cc0e3.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,117 +0,0 @@ -require File.expand_path('../../../test_helper', __FILE__) - -class ApiTest::DisabledRestApiTest < ActionController::IntegrationTest - fixtures :projects, :trackers, :issue_statuses, :issues, - :enumerations, :users, :issue_categories, - :projects_trackers, - :roles, - :member_roles, - :members, - :enabled_modules, - :workflows - - def setup - Setting.rest_api_enabled = '0' - Setting.login_required = '1' - end - - def teardown - Setting.rest_api_enabled = '1' - Setting.login_required = '0' - end - - # Using the NewsController because it's a simple API. - context "get /news with the API disabled" do - - context "in :xml format" do - context "with a valid api token" do - setup do - @user = User.generate_with_protected! - @token = Token.generate!(:user => @user, :action => 'api') - get "/news.xml?key=#{@token.value}" - end - - should_respond_with :unauthorized - should_respond_with_content_type :xml - should "not login as the user" do - assert_equal User.anonymous, User.current - end - end - - context "with a valid HTTP authentication" do - setup do - @user = User.generate_with_protected!(:password => 'my_password', :password_confirmation => 'my_password') - @authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@user.login, 'my_password') - get "/news.xml", nil, :authorization => @authorization - end - - should_respond_with :unauthorized - should_respond_with_content_type :xml - should "not login as the user" do - assert_equal User.anonymous, User.current - end - end - - context "with a valid HTTP authentication using the API token" do - setup do - @user = User.generate_with_protected! - @token = Token.generate!(:user => @user, :action => 'api') - @authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@token.value, 'X') - get "/news.xml", nil, :authorization => @authorization - end - - should_respond_with :unauthorized - should_respond_with_content_type :xml - should "not login as the user" do - assert_equal User.anonymous, User.current - end - end - end - - context "in :json format" do - context "with a valid api token" do - setup do - @user = User.generate_with_protected! - @token = Token.generate!(:user => @user, :action => 'api') - get "/news.json?key=#{@token.value}" - end - - should_respond_with :unauthorized - should_respond_with_content_type :json - should "not login as the user" do - assert_equal User.anonymous, User.current - end - end - - context "with a valid HTTP authentication" do - setup do - @user = User.generate_with_protected!(:password => 'my_password', :password_confirmation => 'my_password') - @authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@user.login, 'my_password') - get "/news.json", nil, :authorization => @authorization - end - - should_respond_with :unauthorized - should_respond_with_content_type :json - should "not login as the user" do - assert_equal User.anonymous, User.current - end - end - - context "with a valid HTTP authentication using the API token" do - setup do - @user = User.generate_with_protected! - @token = Token.generate!(:user => @user, :action => 'api') - @authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@token.value, 'DoesNotMatter') - get "/news.json", nil, :authorization => @authorization - end - - should_respond_with :unauthorized - should_respond_with_content_type :json - should "not login as the user" do - assert_equal User.anonymous, User.current - end - end - - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/43/43272555ea477da0c1b59cf98e2f63d68056c98b.svn-base --- a/.svn/pristine/43/43272555ea477da0c1b59cf98e2f63d68056c98b.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,113 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -require 'redmine/scm/adapters/darcs_adapter' - -class Repository::Darcs < Repository - validates_presence_of :url, :log_encoding - - def self.human_attribute_name(attribute_key_name) - attr_name = attribute_key_name - if attr_name == "url" - attr_name = "path_to_repository" - end - super(attr_name) - end - - def self.scm_adapter_class - Redmine::Scm::Adapters::DarcsAdapter - end - - def self.scm_name - 'Darcs' - end - - def supports_directory_revisions? - true - end - - def entry(path=nil, identifier=nil) - patch = identifier.nil? ? nil : changesets.find_by_revision(identifier) - scm.entry(path, patch.nil? ? nil : patch.scmid) - end - - def entries(path=nil, identifier=nil) - patch = nil - if ! identifier.nil? - patch = changesets.find_by_revision(identifier) - return nil if patch.nil? - end - entries = scm.entries(path, patch.nil? ? nil : patch.scmid) - if entries - entries.each do |entry| - # Search the DB for the entry's last change - if entry.lastrev && !entry.lastrev.scmid.blank? - changeset = changesets.find_by_scmid(entry.lastrev.scmid) - end - if changeset - entry.lastrev.identifier = changeset.revision - entry.lastrev.name = changeset.revision - entry.lastrev.time = changeset.committed_on - entry.lastrev.author = changeset.committer - end - end - end - entries - end - - def cat(path, identifier=nil) - patch = identifier.nil? ? nil : changesets.find_by_revision(identifier.to_s) - scm.cat(path, patch.nil? ? nil : patch.scmid) - end - - def diff(path, rev, rev_to) - patch_from = changesets.find_by_revision(rev) - return nil if patch_from.nil? - patch_to = changesets.find_by_revision(rev_to) if rev_to - if path.blank? - path = patch_from.changes.collect{|change| change.path}.join(' ') - end - patch_from ? scm.diff(path, patch_from.scmid, patch_to ? patch_to.scmid : nil) : nil - end - - def fetch_changesets - scm_info = scm.info - if scm_info - db_last_id = latest_changeset ? latest_changeset.scmid : nil - next_rev = latest_changeset ? latest_changeset.revision.to_i + 1 : 1 - # latest revision in the repository - scm_revision = scm_info.lastrev.scmid - unless changesets.find_by_scmid(scm_revision) - revisions = scm.revisions('', db_last_id, nil, :with_path => true) - transaction do - revisions.reverse_each do |revision| - changeset = Changeset.create(:repository => self, - :revision => next_rev, - :scmid => revision.scmid, - :committer => revision.author, - :committed_on => revision.time, - :comments => revision.message) - revision.paths.each do |change| - changeset.create_change(change) - end - next_rev += 1 - end if revisions - end - end - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/43/4333e2faed9865820292b92a3a8ebf203749d50e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/43/4333e2faed9865820292b92a3a8ebf203749d50e.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,43 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +module WikiHelper + + def wiki_page_options_for_select(pages, selected = nil, parent = nil, level = 0) + pages = pages.group_by(&:parent) unless pages.is_a?(Hash) + s = ''.html_safe + if pages.has_key?(parent) + pages[parent].each do |page| + attrs = "value='#{page.id}'" + attrs << " selected='selected'" if selected == page + indent = (level > 0) ? (' ' * level * 2 + '» ') : '' + + s << content_tag('option', (indent + h(page.pretty_title)).html_safe, :value => page.id.to_s, :selected => selected == page) + + wiki_page_options_for_select(pages, selected, page, level + 1) + end + end + s + end + + def wiki_page_breadcrumb(page) + breadcrumb(page.ancestors.reverse.collect {|parent| + link_to(h(parent.pretty_title), {:controller => 'wiki', :action => 'show', :id => parent.title, :project_id => parent.project, :version => nil}) + }) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/43/435d200d5b8e7f06b0e465d23cb053a76fc8f49e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/43/435d200d5b8e7f06b0e465d23cb053a76fc8f49e.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,62 @@ +
+<%= watcher_link(@news, User.current) %> +<%= link_to(l(:button_edit), + edit_news_path(@news), + :class => 'icon icon-edit', + :accesskey => accesskey(:edit), + :onclick => '$("#edit-news").show(); return false;') if User.current.allowed_to?(:manage_news, @project) %> +<%= delete_link news_path(@news) if User.current.allowed_to?(:manage_news, @project) %> +
+ +

<%= avatar(@news.author, :size => "24") %><%=h @news.title %>

+ +<% if authorize_for('news', 'edit') %> + +<% end %> + +

<% unless @news.summary.blank? %><%=h @news.summary %>
<% end %> +<%= authoring @news.created_on, @news.author %>

+
+<%= textilizable(@news, :description) %> +
+<%= link_to_attachments @news %> +
+ +
+

<%= l(:label_comment_plural) %>

+<% @comments.each do |comment| %> + <% next if comment.new_record? %> +
+ <%= link_to_if_authorized image_tag('delete.png'), {:controller => 'comments', :action => 'destroy', :id => @news, :comment_id => comment}, + :data => {:confirm => l(:text_are_you_sure)}, :method => :delete, :title => l(:button_delete) %> +
+

<%= avatar(comment.author, :size => "24") %><%= authoring comment.created_on, comment.author %>

+ <%= textilizable(comment.comments) %> +<% end if @comments.any? %> +
+ +<% if @news.commentable? %> +

<%= toggle_link l(:label_comment_add), "add_comment_form", :focus => "comment_comments" %>

+<%= form_tag({:controller => 'comments', :action => 'create', :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' %> +
+

<%= submit_tag l(:button_add) %>

+<% end %> +<% end %> + +<% html_title @news.title -%> + +<% content_for :header_tags do %> + <%= stylesheet_link_tag 'scm' %> +<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/43/437c58e98b2bf838355e6ccb0adcad2c75761185.svn-base --- a/.svn/pristine/43/437c58e98b2bf838355e6ccb0adcad2c75761185.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,52 +0,0 @@ -

<%= l(:label_search) %>

- -
-<% form_tag({}, :method => :get) do %> -<%= label_tag "search-input", l(:description_search), :class => "hidden-for-sighted" %> -

<%= text_field_tag 'q', @question, :size => 60, :id => 'search-input' %> -<%= javascript_tag "Field.focus('search-input')" %> -<%= project_select_tag %> -<%= hidden_field_tag 'all_words', '', :id => nil %> - -<%= hidden_field_tag 'titles_only', '', :id => nil %> - -

-

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

- -

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

-<% end %> -
- -<% if @results %> -
- <%= render_results_by_type(@results_by_type) unless @scope.size == 1 %> -
- -

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

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

-<% if @pagination_previous_date %> -<%= link_to_content_update("\xc2\xab " + l(:label_previous), - params.merge(:previous => 1, - :offset => @pagination_previous_date.strftime("%Y%m%d%H%M%S"))) %>  -<% end %> -<% if @pagination_next_date %> -<%= link_to_content_update(l(:label_next) + " \xc2\xbb", - params.merge(:previous => nil, - :offset => @pagination_next_date.strftime("%Y%m%d%H%M%S"))) %> -<% end %> -

- -<% html_title(l(:label_search)) -%> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/43/43815b2797ad5e2c1f98a876a3c98a313e459bdb.svn-base --- a/.svn/pristine/43/43815b2797ad5e2c1f98a876a3c98a313e459bdb.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,31 +0,0 @@ -RUBYTREE - http://rubytree.rubyforge.org -======================================== - -Copyright (c) 2006, 2007 Anupam Sengupta - -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -- Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -- Redistributions in binary form must reproduce the above copyright notice, this - list of conditions and the following disclaimer in the documentation and/or - other materials provided with the distribution. - -- Neither the name of the organization nor the names of its contributors may - be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/43/4384f149ba601f6c60fa67ac6f918ddfc90dc0b6.svn-base --- a/.svn/pristine/43/4384f149ba601f6c60fa67ac6f918ddfc90dc0b6.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,281 +0,0 @@ -# This code lets us redefine existing Rake tasks, which is extremely -# handy for modifying existing Rails rake tasks. -# Credit for the original snippet of code goes to Jeremy Kemper -# http://pastie.caboo.se/9620 -unless Rake::TaskManager.methods.include?('redefine_task') - module Rake - module TaskManager - def redefine_task(task_class, args, &block) - task_name, arg_names, deps = resolve_args([args]) - task_name = task_class.scope_name(@scope, task_name) - deps = [deps] unless deps.respond_to?(:to_ary) - deps = deps.collect {|d| d.to_s } - task = @tasks[task_name.to_s] = task_class.new(task_name, self) - task.application = self - task.add_description(@last_description) - @last_description = nil - task.enhance(deps, &block) - task - end - - end - class Task - class << self - def redefine_task(args, &block) - Rake.application.redefine_task(self, [args], &block) - end - end - end - end -end - -namespace :db do - namespace :migrate do - desc 'Migrate database and plugins to current status.' - task :all => [ 'db:migrate', 'db:migrate:plugins' ] - - desc 'Migrate plugins to current status.' - task :plugins => :environment do - Engines.plugins.each do |plugin| - next unless plugin.respond_to?(:migration_directory) - next unless File.exists? plugin.migration_directory - puts "Migrating plugin #{plugin.name} ..." - plugin.migrate - end - end - - desc 'For engines coming from Rails version < 2.0 or for those previously updated to work with Sven Fuch\'s fork of engines, you need to upgrade the schema info table' - task :upgrade_plugin_migrations => :environment do - svens_fork_table_name = 'plugin_schema_migrations' - - # Check if app was previously using Sven's fork - if ActiveRecord::Base.connection.table_exists?(svens_fork_table_name) - old_sm_table = svens_fork_table_name - else - old_sm_table = ActiveRecord::Migrator.proper_table_name(Engines.schema_info_table) - end - - unless ActiveRecord::Base.connection.table_exists?(old_sm_table) - abort "Cannot find old migration table - assuming nothing needs to be done" - end - - # There are two forms of the engines schema info - pre-fix_plugin_migrations and post - # We need to figure this out before we continue. - - results = ActiveRecord::Base.connection.select_rows( - "SELECT version, plugin_name FROM #{old_sm_table}" - ).uniq - - def insert_new_version(plugin_name, version) - version_string = "#{version}-#{plugin_name}" - new_sm_table = ActiveRecord::Migrator.schema_migrations_table_name - - # Check if the row already exists for some reason - maybe run this task more than once. - return if ActiveRecord::Base.connection.select_rows("SELECT * FROM #{new_sm_table} WHERE version = #{version_string.dump.gsub("\"", "'")}").size > 0 - - puts "Inserting new version #{version} for plugin #{plugin_name}.." - ActiveRecord::Base.connection.insert("INSERT INTO #{new_sm_table} (version) VALUES (#{version_string.dump.gsub("\"", "'")})") - end - - # We need to figure out if they already used "fix_plugin_migrations" - versions = {} - results.each do |r| - versions[r[1]] ||= [] - versions[r[1]] << r[0].to_i - end - - if versions.values.find{ |v| v.size > 1 } == nil - puts "Fixing migration info" - # We only have one listed migration per plugin - this is pre-fix_plugin_migrations, - # so we build all versions required. In this case, all migrations should - versions.each do |plugin_name, version| - version = version[0] # There is only one version - - # We have to make an assumption that numeric migrations won't get this long.. - # I'm not sure if there is a better assumption, it should work in all - # current cases.. (touch wood..) - if version.to_s.size < "YYYYMMDDHHMMSS".size - # Insert version records for each migration - (1..version).each do |v| - insert_new_version(plugin_name, v) - end - else - # If the plugin is new-format "YYYYMMDDHHMMSS", we just copy it across... - # The case in which this occurs is very rare.. - insert_new_version(plugin_name, version) - end - end - else - puts "Moving migration info" - # We have multiple migrations listed per plugin - thus we can assume they have - # already applied fix_plugin_migrations - we just copy it across verbatim - versions.each do |plugin_name, version| - version.each { |v| insert_new_version(plugin_name, v) } - end - end - - puts "Migration info successfully migrated - removing old schema info table" - ActiveRecord::Base.connection.drop_table(old_sm_table) - end - - desc 'Migrate a specified plugin.' - task(:plugin => :environment) do - name = ENV['NAME'] - if plugin = Engines.plugins[name] - version = ENV['VERSION'] - puts "Migrating #{plugin.name} to " + (version ? "version #{version}" : 'latest version') + " ..." - plugin.migrate(version ? version.to_i : nil) - else - puts "Plugin #{name} does not exist." - end - end - end -end - - -namespace :db do - namespace :fixtures do - namespace :plugins do - - desc "Load plugin fixtures into the current environment's database." - task :load => :environment do - require 'active_record/fixtures' - ActiveRecord::Base.establish_connection(RAILS_ENV.to_sym) - Dir.glob(File.join(RAILS_ROOT, 'vendor', 'plugins', ENV['PLUGIN'] || '**', - 'test', 'fixtures', '*.yml')).each do |fixture_file| - Fixtures.create_fixtures(File.dirname(fixture_file), File.basename(fixture_file, '.*')) - end - end - - end - end -end - -# this is just a modification of the original task in railties/lib/tasks/documentation.rake, -# because the default task doesn't support subdirectories like /app or -# /component. These tasks now include every file under a plugin's load paths (see -# Plugin#load_paths). -namespace :doc do - - plugins = FileList['vendor/plugins/**'].collect { |plugin| File.basename(plugin) } - - namespace :plugins do - - # Define doc tasks for each plugin - plugins.each do |plugin| - desc "Create plugin documentation for '#{plugin}'" - Rake::Task.redefine_task(plugin => :environment) do - plugin_base = RAILS_ROOT + "/vendor/plugins/#{plugin}" - options = [] - files = Rake::FileList.new - options << "-o doc/plugins/#{plugin}" - options << "--title '#{plugin.titlecase} Plugin Documentation'" - options << '--line-numbers' << '--inline-source' - options << '-T html' - - # Include every file in the plugin's load_paths (see Plugin#load_paths) - if Engines.plugins[plugin] - files.include("#{plugin_base}/{#{Engines.plugins[plugin].load_paths.join(",")}}/**/*.rb") - end - if File.exists?("#{plugin_base}/README") - files.include("#{plugin_base}/README") - options << "--main '#{plugin_base}/README'" - end - files.include("#{plugin_base}/CHANGELOG") if File.exists?("#{plugin_base}/CHANGELOG") - - if files.empty? - puts "No source files found in #{plugin_base}. No documentation will be generated." - else - options << files.to_s - sh %(rdoc #{options * ' '}) - end - end - end - end -end - - - -namespace :test do - task :warn_about_multiple_plugin_testing_with_engines do - puts %{-~============== A Moste Polite Warninge ===========================~- - -You may experience issues testing multiple plugins at once when using -the code-mixing features that the engines plugin provides. If you do -experience any problems, please test plugins individually, i.e. - - $ rake test:plugins PLUGIN=my_plugin - -or use the per-type plugin test tasks: - - $ rake test:plugins:units - $ rake test:plugins:functionals - $ rake test:plugins:integration - $ rake test:plugins:all - -Report any issues on http://dev.rails-engines.org. Thanks! - --~===============( ... as you were ... )============================~-} - end - - namespace :engines do - - def engine_plugins - Dir["vendor/plugins/*"].select { |f| File.directory?(File.join(f, "app")) }.map { |f| File.basename(f) }.join(",") - end - - desc "Run tests from within engines plugins (plugins with an 'app' directory)" - task :all => [:units, :functionals, :integration] - - desc "Run unit tests from within engines plugins (plugins with an 'app' directory)" - Rake::TestTask.new(:units => "test:plugins:setup_plugin_fixtures") do |t| - t.pattern = "vendor/plugins/{#{ENV['PLUGIN'] || engine_plugins}}/test/unit/**/*_test.rb" - t.verbose = true - end - - desc "Run functional tests from within engines plugins (plugins with an 'app' directory)" - Rake::TestTask.new(:functionals => "test:plugins:setup_plugin_fixtures") do |t| - t.pattern = "vendor/plugins/{#{ENV['PLUGIN'] || engine_plugins}}/test/functional/**/*_test.rb" - t.verbose = true - end - - desc "Run integration tests from within engines plugins (plugins with an 'app' directory)" - Rake::TestTask.new(:integration => "test:plugins:setup_plugin_fixtures") do |t| - t.pattern = "vendor/plugins/{#{ENV['PLUGIN'] || engine_plugins}}/test/integration/**/*_test.rb" - t.verbose = true - end - end - - namespace :plugins do - - desc "Run the plugin tests in vendor/plugins/**/test (or specify with PLUGIN=name)" - task :all => [:warn_about_multiple_plugin_testing_with_engines, - :units, :functionals, :integration] - - desc "Run all plugin unit tests" - Rake::TestTask.new(:units => :setup_plugin_fixtures) do |t| - t.pattern = "vendor/plugins/#{ENV['PLUGIN'] || "**"}/test/unit/**/*_test.rb" - t.verbose = true - end - - desc "Run all plugin functional tests" - Rake::TestTask.new(:functionals => :setup_plugin_fixtures) do |t| - t.pattern = "vendor/plugins/#{ENV['PLUGIN'] || "**"}/test/functional/**/*_test.rb" - t.verbose = true - end - - desc "Integration test engines" - Rake::TestTask.new(:integration => :setup_plugin_fixtures) do |t| - t.pattern = "vendor/plugins/#{ENV['PLUGIN'] || "**"}/test/integration/**/*_test.rb" - t.verbose = true - end - - desc "Mirrors plugin fixtures into a single location to help plugin tests" - task :setup_plugin_fixtures => :environment do - Engines::Testing.setup_plugin_fixtures - end - - # Patch the default plugin testing task to have setup_plugin_fixtures as a prerequisite - Rake::Task["test:plugins"].prerequisites << "test:plugins:setup_plugin_fixtures" - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/44/4427f53bdbb62ef1eca701cca69cda8b76215c5d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/44/4427f53bdbb62ef1eca701cca69cda8b76215c5d.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,21 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +module MessagesHelper +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/44/4439ef733d46df57772e77a997ac371196818a57.svn-base --- a/.svn/pristine/44/4439ef733d46df57772e77a997ac371196818a57.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -# Load the normal Rails helper -require File.expand_path(File.dirname(__FILE__) + '/../../../../test/test_helper') - -# Ensure that we are using the temporary fixture path -Engines::Testing.set_fixture_path diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/44/44669c27372c172ef04555f4bb81865f6ca83884.svn-base --- a/.svn/pristine/44/44669c27372c172ef04555f4bb81865f6ca83884.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,95 +0,0 @@ -require File.expand_path('../../test_helper', __FILE__) - -class GanttsControllerTest < ActionController::TestCase - fixtures :projects, :trackers, :issue_statuses, :issues, - :enumerations, :users, :issue_categories, - :projects_trackers, - :roles, - :member_roles, - :members, - :enabled_modules, - :workflows, - :versions - - context "#gantt" do - should "work" do - i2 = Issue.find(2) - i2.update_attribute(:due_date, 1.month.from_now) - - get :show, :project_id => 1 - assert_response :success - assert_template 'gantts/show' - assert_not_nil assigns(:gantt) - # Issue with start and due dates - i = Issue.find(1) - assert_not_nil i.due_date - assert_select "div a.issue", /##{i.id}/ - # Issue with on a targeted version should not be in the events but loaded in the html - i = Issue.find(2) - assert_select "div a.issue", /##{i.id}/ - end - - should "work without issue due dates" do - Issue.update_all("due_date = NULL") - - get :show, :project_id => 1 - assert_response :success - assert_template 'gantts/show' - assert_not_nil assigns(:gantt) - end - - should "work without issue and version due dates" do - Issue.update_all("due_date = NULL") - Version.update_all("effective_date = NULL") - - get :show, :project_id => 1 - assert_response :success - assert_template 'gantts/show' - assert_not_nil assigns(:gantt) - end - - should "work cross project" do - get :show - assert_response :success - assert_template 'gantts/show' - assert_not_nil assigns(:gantt) - assert_not_nil assigns(:gantt).query - assert_nil assigns(:gantt).project - end - - should "not disclose private projects" do - get :show - assert_response :success - assert_template 'gantts/show' - - assert_tag 'a', :content => /eCookbook/ - # Root private project - assert_no_tag 'a', {:content => /OnlineStore/} - # Private children of a public project - assert_no_tag 'a', :content => /Private child of eCookbook/ - end - - should "export to pdf" do - get :show, :project_id => 1, :format => 'pdf' - assert_response :success - assert_equal 'application/pdf', @response.content_type - assert @response.body.starts_with?('%PDF') - assert_not_nil assigns(:gantt) - end - - should "export to pdf cross project" do - get :show, :format => 'pdf' - assert_response :success - assert_equal 'application/pdf', @response.content_type - assert @response.body.starts_with?('%PDF') - assert_not_nil assigns(:gantt) - end - - should "export to png" do - get :show, :project_id => 1, :format => 'png' - assert_response :success - assert_equal 'image/png', @response.content_type - end if Object.const_defined?(:Magick) - - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/44/448dc5ae56acac81ffaeeeb288586a6544fe08b0.svn-base --- a/.svn/pristine/44/448dc5ae56acac81ffaeeeb288586a6544fe08b0.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,545 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) -require 'projects_controller' - -# Re-raise errors caught by the controller. -class ProjectsController; def rescue_action(e) raise e end; end - -class ProjectsControllerTest < ActionController::TestCase - fixtures :projects, :versions, :users, :roles, :members, :member_roles, :issues, :journals, :journal_details, - :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages, - :attachments, :custom_fields, :custom_values, :time_entries - - 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 - get :index - assert_response :success - assert_template 'index' - 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 - get :index, :format => 'atom' - assert_response :success - assert_template 'common/feed.atom' - assert_select 'feed>title', :text => 'Redmine: Latest projects' - assert_select 'feed>entry', :count => Project.count(:conditions => Project.visible_condition(User.current)) - end - - context "#index" do - context "by non-admin user with view_time_entries permission" do - setup do - @request.session[:user_id] = 3 - end - should "show overall spent time link" do - get :index - assert_template 'index' - assert_tag :a, :attributes => {:href => '/time_entries'} - end - end - - context "by non-admin user without view_time_entries permission" do - setup do - Role.find(2).remove_permission! :view_time_entries - Role.non_member.remove_permission! :view_time_entries - Role.anonymous.remove_permission! :view_time_entries - @request.session[:user_id] = 3 - end - should "not show overall spent time link" do - get :index - assert_template 'index' - assert_no_tag :a, :attributes => {:href => '/time_entries'} - end - end - end - - context "#new" do - context "by admin user" do - setup do - @request.session[:user_id] = 1 - end - - should "accept get" do - get :new - assert_response :success - assert_template 'new' - end - - end - - context "by non-admin user with add_project permission" do - setup do - Role.non_member.add_permission! :add_project - @request.session[:user_id] = 9 - end - - should "accept get" do - get :new - assert_response :success - assert_template 'new' - assert_no_tag :select, :attributes => {:name => 'project[parent_id]'} - end - end - - context "by non-admin user with add_subprojects permission" do - setup do - Role.find(1).remove_permission! :add_project - Role.find(1).add_permission! :add_subprojects - @request.session[:user_id] = 2 - end - - should "accept get" do - get :new, :parent_id => 'ecookbook' - assert_response :success - assert_template 'new' - # parent project selected - assert_tag :select, :attributes => {:name => 'project[parent_id]'}, - :child => {:tag => 'option', :attributes => {:value => '1', :selected => 'selected'}} - # no empty value - assert_no_tag :select, :attributes => {:name => 'project[parent_id]'}, - :child => {:tag => 'option', :attributes => {:value => ''}} - end - end - - end - - context "POST :create" do - context "by admin user" do - setup do - @request.session[:user_id] = 1 - end - - should "create a new project" do - post :create, - :project => { - :name => "blog", - :description => "weblog", - :homepage => 'http://weblog', - :identifier => "blog", - :is_public => 1, - :custom_field_values => { '3' => 'Beta' }, - :tracker_ids => ['1', '3'], - # an issue custom field that is not for all project - :issue_custom_field_ids => ['9'], - :enabled_module_names => ['issue_tracking', 'news', 'repository'] - } - assert_redirected_to '/projects/blog/settings' - - project = Project.find_by_name('blog') - assert_kind_of Project, project - assert project.active? - assert_equal 'weblog', project.description - assert_equal 'http://weblog', project.homepage - assert_equal true, project.is_public? - assert_nil project.parent - assert_equal 'Beta', project.custom_value_for(3).value - assert_equal [1, 3], project.trackers.map(&:id).sort - assert_equal ['issue_tracking', 'news', 'repository'], project.enabled_module_names.sort - assert project.issue_custom_fields.include?(IssueCustomField.find(9)) - end - - should "create a new subproject" do - post :create, :project => { :name => "blog", - :description => "weblog", - :identifier => "blog", - :is_public => 1, - :custom_field_values => { '3' => 'Beta' }, - :parent_id => 1 - } - assert_redirected_to '/projects/blog/settings' - - project = Project.find_by_name('blog') - assert_kind_of Project, project - assert_equal Project.find(1), project.parent - end - - should "continue" do - assert_difference 'Project.count' do - post :create, :project => {:name => "blog", :identifier => "blog"}, :continue => 'Create and continue' - end - assert_redirected_to '/projects/new?' - end - end - - context "by non-admin user with add_project permission" do - setup do - Role.non_member.add_permission! :add_project - @request.session[:user_id] = 9 - end - - should "accept create a Project" do - post :create, :project => { :name => "blog", - :description => "weblog", - :identifier => "blog", - :is_public => 1, - :custom_field_values => { '3' => 'Beta' }, - :tracker_ids => ['1', '3'], - :enabled_module_names => ['issue_tracking', 'news', 'repository'] - } - - assert_redirected_to '/projects/blog/settings' - - project = Project.find_by_name('blog') - assert_kind_of Project, project - assert_equal 'weblog', project.description - assert_equal true, project.is_public? - assert_equal [1, 3], project.trackers.map(&:id).sort - assert_equal ['issue_tracking', 'news', 'repository'], project.enabled_module_names.sort - - # User should be added as a project member - assert User.find(9).member_of?(project) - assert_equal 1, project.members.size - end - - should "fail with parent_id" do - assert_no_difference 'Project.count' do - post :create, :project => { :name => "blog", - :description => "weblog", - :identifier => "blog", - :is_public => 1, - :custom_field_values => { '3' => 'Beta' }, - :parent_id => 1 - } - end - assert_response :success - project = assigns(:project) - assert_kind_of Project, project - assert_not_nil project.errors[:parent_id] - end - end - - context "by non-admin user with add_subprojects permission" do - setup do - Role.find(1).remove_permission! :add_project - Role.find(1).add_permission! :add_subprojects - @request.session[:user_id] = 2 - end - - should "create a project with a parent_id" do - post :create, :project => { :name => "blog", - :description => "weblog", - :identifier => "blog", - :is_public => 1, - :custom_field_values => { '3' => 'Beta' }, - :parent_id => 1 - } - assert_redirected_to '/projects/blog/settings' - project = Project.find_by_name('blog') - end - - should "fail without parent_id" do - assert_no_difference 'Project.count' do - post :create, :project => { :name => "blog", - :description => "weblog", - :identifier => "blog", - :is_public => 1, - :custom_field_values => { '3' => 'Beta' } - } - end - assert_response :success - project = assigns(:project) - assert_kind_of Project, project - assert_not_nil project.errors[:parent_id] - end - - should "fail with unauthorized parent_id" do - assert !User.find(2).member_of?(Project.find(6)) - assert_no_difference 'Project.count' do - post :create, :project => { :name => "blog", - :description => "weblog", - :identifier => "blog", - :is_public => 1, - :custom_field_values => { '3' => 'Beta' }, - :parent_id => 6 - } - end - assert_response :success - project = assigns(:project) - assert_kind_of Project, project - assert_not_nil project.errors[:parent_id] - end - end - end - - def test_create_should_preserve_modules_on_validation_failure - with_settings :default_projects_modules => ['issue_tracking', 'repository'] do - @request.session[:user_id] = 1 - assert_no_difference 'Project.count' do - post :create, :project => { - :name => "blog", - :identifier => "", - :enabled_module_names => %w(issue_tracking news) - } - end - assert_response :success - project = assigns(:project) - assert_equal %w(issue_tracking news), project.enabled_module_names.sort - end - end - - def test_create_should_not_accept_get - @request.session[:user_id] = 1 - get :create - assert_response :method_not_allowed - end - - def test_show_by_id - get :show, :id => 1 - assert_response :success - assert_template 'show' - assert_not_nil assigns(:project) - end - - def test_show_by_identifier - get :show, :id => 'ecookbook' - assert_response :success - assert_template 'show' - assert_not_nil assigns(:project) - assert_equal Project.find_by_identifier('ecookbook'), assigns(:project) - - assert_tag 'li', :content => /Development status/ - end - - def test_show_should_not_display_hidden_custom_fields - ProjectCustomField.find_by_name('Development status').update_attribute :visible, false - get :show, :id => 'ecookbook' - assert_response :success - assert_template 'show' - assert_not_nil assigns(:project) - - assert_no_tag 'li', :content => /Development status/ - end - - def test_show_should_not_fail_when_custom_values_are_nil - project = Project.find_by_identifier('ecookbook') - project.custom_values.first.update_attribute(:value, nil) - get :show, :id => 'ecookbook' - assert_response :success - assert_template 'show' - assert_not_nil assigns(:project) - assert_equal Project.find_by_identifier('ecookbook'), assigns(:project) - end - - def show_archived_project_should_be_denied - project = Project.find_by_identifier('ecookbook') - project.archive! - - get :show, :id => 'ecookbook' - assert_response 403 - assert_nil assigns(:project) - assert_tag :tag => 'p', :content => /archived/ - end - - def test_private_subprojects_hidden - get :show, :id => 'ecookbook' - assert_response :success - assert_template 'show' - assert_no_tag :tag => 'a', :content => /Private child/ - end - - def test_private_subprojects_visible - @request.session[:user_id] = 2 # manager who is a member of the private subproject - get :show, :id => 'ecookbook' - assert_response :success - assert_template 'show' - assert_tag :tag => 'a', :content => /Private child/ - end - - def test_settings - @request.session[:user_id] = 2 # manager - get :settings, :id => 1 - assert_response :success - assert_template 'settings' - end - - def test_update - @request.session[:user_id] = 2 # manager - post :update, :id => 1, :project => {:name => 'Test changed name', - :issue_custom_field_ids => ['']} - assert_redirected_to '/projects/ecookbook/settings' - project = Project.find(1) - assert_equal 'Test changed name', project.name - end - - def test_modules - @request.session[:user_id] = 2 - Project.find(1).enabled_module_names = ['issue_tracking', 'news'] - - post :modules, :id => 1, :enabled_module_names => ['issue_tracking', 'repository', 'documents'] - assert_redirected_to '/projects/ecookbook/settings/modules' - assert_equal ['documents', 'issue_tracking', 'repository'], Project.find(1).enabled_module_names.sort - end - - def test_modules_should_not_allow_get - @request.session[:user_id] = 1 - get :modules, :id => 1 - assert_response :method_not_allowed - 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 - - def test_post_destroy - @request.session[:user_id] = 1 # admin - post :destroy, :id => 1, :confirm => 1 - assert_redirected_to '/admin/projects' - assert_nil Project.find_by_id(1) - 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 - @request.session[:user_id] = 1 # admin - Project.find(1).archive - post :unarchive, :id => 1 - assert_redirected_to '/admin/projects' - assert Project.find(1).active? - end - - def test_project_breadcrumbs_should_be_limited_to_3_ancestors - CustomField.delete_all - parent = nil - 6.times do |i| - p = Project.create!(:name => "Breadcrumbs #{i}", :identifier => "breadcrumbs-#{i}") - p.set_parent!(parent) - get :show, :id => p - assert_tag :h1, :parent => { :attributes => {:id => 'header'}}, - :children => { :count => [i, 3].min, - :only => { :tag => 'a' } } - - parent = p - end - end - - def test_get_copy - @request.session[:user_id] = 1 # admin - get :copy, :id => 1 - assert_response :success - assert_template 'copy' - assert assigns(:project) - assert_equal Project.find(1).description, assigns(:project).description - assert_nil assigns(:project).id - - assert_tag :tag => 'input', - :attributes => {:name => 'project[enabled_module_names][]', :value => 'issue_tracking'} - end - - def test_get_copy_without_project - @request.session[:user_id] = 1 # admin - get :copy - assert_response :redirect - assert_redirected_to :controller => 'admin', :action => 'projects' - end - - def test_post_copy_should_copy_requested_items - @request.session[:user_id] = 1 # admin - CustomField.delete_all - - assert_difference 'Project.count' do - post :copy, :id => 1, - :project => { - :name => 'Copy', - :identifier => 'unique-copy', - :tracker_ids => ['1', '2', '3', ''], - :enabled_module_names => %w(issue_tracking time_tracking) - }, - :only => %w(issues versions) - end - project = Project.find('unique-copy') - source = Project.find(1) - assert_equal %w(issue_tracking time_tracking), project.enabled_module_names.sort - - assert_equal source.versions.count, project.versions.count, "All versions were not copied" - # issues assigned to a closed version won't be copied - assert_equal source.issues.select {|i| i.fixed_version.nil? || i.fixed_version.open?}.size, - project.issues.count, "All issues were not copied" - assert_equal 0, project.members.count - end - - def test_post_copy_should_redirect_to_settings_when_successful - @request.session[:user_id] = 1 # admin - post :copy, :id => 1, :project => {:name => 'Copy', :identifier => 'unique-copy'} - assert_response :redirect - assert_redirected_to :controller => 'projects', :action => 'settings', :id => 'unique-copy' - 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 - - # A hook that is manually registered later - class ProjectBasedTemplate < Redmine::Hook::ViewListener - def view_layouts_base_html_head(context) - # Adds a project stylesheet - stylesheet_link_tag(context[:project].identifier) if context[:project] - end - end - # Don't use this hook now - Redmine::Hook.clear_listeners - - def test_hook_response - Redmine::Hook.add_listener(ProjectBasedTemplate) - get :show, :id => 1 - assert_tag :tag => 'link', :attributes => {:href => '/stylesheets/ecookbook.css'}, - :parent => {:tag => 'head'} - - Redmine::Hook.clear_listeners - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/44/44d79a17ffe7c2b64a23af9018ae26f5e13961e3.svn-base --- a/.svn/pristine/44/44d79a17ffe7c2b64a23af9018ae26f5e13961e3.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,25 +0,0 @@ -<% form_tag({:action => 'edit', :tab => 'issues'}) do %> - -
-

<%= setting_check_box :cross_project_issue_relations %>

- -

<%= setting_check_box :issue_group_assignment %>

- -

<%= setting_check_box :default_issue_start_date_to_creation_date %>

- -

<%= setting_check_box :display_subprojects_issues %>

- -

<%= setting_select :issue_done_ratio, Issue::DONE_RATIO_OPTIONS.collect {|i| [l("setting_issue_done_ratio_#{i}"), i]} %>

- -

<%= setting_text_field :issues_export_limit, :size => 6 %>

- -

<%= setting_text_field :gantt_items_limit, :size => 6 %>

-
- -
<%= l(:setting_issue_list_default_columns) %> -<%= setting_multiselect(:issue_list_default_columns, - Query.new.available_columns.collect {|c| [c.caption, c.name.to_s]}, :label => false) %> -
- -<%= submit_tag l(:button_save) %> -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/45/4537efe545b75bf585f6f1b2f82d7cbf9d1195c2.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/45/4537efe545b75bf585f6f1b2f82d7cbf9d1195c2.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,166 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 Hook + @@listener_classes = [] + @@listeners = nil + @@hook_listeners = {} + + class << self + # Adds a listener class. + # Automatically called when a class inherits from Redmine::Hook::Listener. + def add_listener(klass) + raise "Hooks must include Singleton module." unless klass.included_modules.include?(Singleton) + @@listener_classes << klass + clear_listeners_instances + end + + # Returns all the listerners instances. + def listeners + @@listeners ||= @@listener_classes.collect {|listener| listener.instance} + end + + # Returns the listeners instances for the given hook. + def hook_listeners(hook) + @@hook_listeners[hook] ||= listeners.select {|listener| listener.respond_to?(hook)} + end + + # Clears all the listeners. + def clear_listeners + @@listener_classes = [] + clear_listeners_instances + end + + # Clears all the listeners instances. + def clear_listeners_instances + @@listeners = nil + @@hook_listeners = {} + end + + # Calls a hook. + # Returns the listeners response. + def call_hook(hook, context={}) + [].tap do |response| + hls = hook_listeners(hook) + if hls.any? + hls.each {|listener| response << listener.send(hook, context)} + end + end + end + end + + # Base class for hook listeners. + class Listener + include Singleton + include Redmine::I18n + + # Registers the listener + def self.inherited(child) + Redmine::Hook.add_listener(child) + super + end + + end + + # Listener class used for views hooks. + # Listeners that inherit this class will include various helpers by default. + class ViewListener < Listener + include ERB::Util + include ActionView::Helpers::TagHelper + include ActionView::Helpers::FormHelper + include ActionView::Helpers::FormTagHelper + include ActionView::Helpers::FormOptionsHelper + include ActionView::Helpers::JavaScriptHelper + include ActionView::Helpers::NumberHelper + include ActionView::Helpers::UrlHelper + include ActionView::Helpers::AssetTagHelper + include ActionView::Helpers::TextHelper + include Rails.application.routes.url_helpers + include ApplicationHelper + + # Default to creating links using only the path. Subclasses can + # change this default as needed + def self.default_url_options + {:only_path => true } + end + + # 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| + if context[:hook_caller].respond_to?(:render) + context[:hook_caller].send(:render, {:locals => context}.merge(options)) + elsif context[:controller].is_a?(ActionController::Base) + context[:controller].send(:render_to_string, {:locals => context}.merge(options)) + else + raise "Cannot render #{self.name} hook from #{context[:hook_caller].class.name}" + end + end + end + + def controller + nil + end + + def config + ActionController::Base.config + end + end + + # Helper module included in ApplicationHelper and ActionController so that + # hooks can be called in views like this: + # + # <%= call_hook(:some_hook) %> + # <%= call_hook(:another_hook, :foo => 'bar') %> + # + # 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 + # * hook_caller => object that called the hook + # + module Helper + def call_hook(hook, context={}) + if is_a?(ActionController::Base) + default_context = {:controller => self, :project => @project, :request => request, :hook_caller => self} + Redmine::Hook.call_hook(hook, default_context.merge(context)) + else + default_context = { :project => @project, :hook_caller => self } + default_context[:controller] = controller if respond_to?(:controller) + default_context[:request] = request if respond_to?(:request) + Redmine::Hook.call_hook(hook, default_context.merge(context)).join(' ').html_safe + end + end + end + end +end + +ApplicationHelper.send(:include, Redmine::Hook::Helper) +ActionController::Base.send(:include, Redmine::Hook::Helper) diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/45/4541853cf8b57a89416ab78d9c5650a0f0ed5774.svn-base --- a/.svn/pristine/45/4541853cf8b57a89416ab78d9c5650a0f0ed5774.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -class AppAndPluginController < ApplicationController - def an_action - render_class_and_action 'from beta_plugin' - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/45/45481eeadf5fe7268354b5aabc0c5767470f2390.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/45/45481eeadf5fe7268354b5aabc0c5767470f2390.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,97 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class Wiki < ActiveRecord::Base + include Redmine::SafeAttributes + belongs_to :project + has_many :pages, :class_name => 'WikiPage', :dependent => :destroy, :order => 'title' + has_many :redirects, :class_name => 'WikiRedirect', :dependent => :delete_all + + acts_as_watchable + + validates_presence_of :start_page + validates_format_of :start_page, :with => /\A[^,\.\/\?\;\|\:]*\z/ + + safe_attributes 'start_page' + + def visible?(user=User.current) + !user.nil? && user.allowed_to?(:view_wiki_pages, project) + end + + # Returns the wiki page that acts as the sidebar content + # or nil if no such page exists + def sidebar + @sidebar ||= find_page('Sidebar', :with_redirect => false) + end + + # find the page with the given title + # if page doesn't exist, return a new page + def find_or_new_page(title) + title = start_page if title.blank? + find_page(title) || WikiPage.new(:wiki => self, :title => Wiki.titleize(title)) + end + + # find the page with the given title + def find_page(title, options = {}) + @page_found_with_redirect = false + title = start_page if title.blank? + title = Wiki.titleize(title) + page = pages.first(:conditions => ["LOWER(title) = LOWER(?)", title]) + if !page && !(options[:with_redirect] == false) + # search for a redirect + redirect = redirects.first(:conditions => ["LOWER(title) = LOWER(?)", title]) + if redirect + page = find_page(redirect.redirects_to, :with_redirect => false) + @page_found_with_redirect = true + end + end + page + end + + # Returns true if the last page was found with a redirect + def page_found_with_redirect? + @page_found_with_redirect + 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 + title = title.gsub(/\s+/, '_').delete(',./?;|:') if title + # upcase the first letter + title = (title.slice(0..0).upcase + (title.slice(1..-1) || '')) if title + title + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/45/456ef3443ff7df8c5dc761c97f3f05ad3eb5ac25.svn-base --- a/.svn/pristine/45/456ef3443ff7df8c5dc761c97f3f05ad3eb5ac25.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,101 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../../../../test_helper', __FILE__) - -class Redmine::WikiFormatting::MacrosTest < ActionView::TestCase - include ApplicationHelper - include ActionView::Helpers::TextHelper - include ActionView::Helpers::SanitizeHelper - extend ActionView::Helpers::SanitizeHelper::ClassMethods - - 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 '

{{hello_world}}

', 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 = "

\n

" - - @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 = "

\n

" - - @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 -r 0a574315af3e -r 4f746d8966dd .svn/pristine/45/457635320d7cf0fe410b58dcbd4445d01bddd1f8.svn-base --- a/.svn/pristine/45/457635320d7cf0fe410b58dcbd4445d01bddd1f8.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,285 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../../test_helper', __FILE__) -require 'pp' -class ApiTest::UsersTest < ActionController::IntegrationTest - fixtures :users - - def setup - Setting.rest_api_enabled = '1' - end - - context "GET /users" do - should_allow_api_authentication(:get, "/users.xml") - should_allow_api_authentication(:get, "/users.json") - end - - context "GET /users/2" do - context ".xml" do - should "return requested user" do - get '/users/2.xml' - - assert_tag :tag => 'user', - :child => {:tag => 'id', :content => '2'} - end - end - - context ".json" do - should "return requested user" do - get '/users/2.json' - - json = ActiveSupport::JSON.decode(response.body) - assert_kind_of Hash, json - assert_kind_of Hash, json['user'] - assert_equal 2, json['user']['id'] - end - end - end - - context "GET /users/current" do - context ".xml" do - should "require authentication" do - get '/users/current.xml' - - assert_response 401 - end - - should "return current user" do - get '/users/current.xml', {}, :authorization => credentials('jsmith') - - assert_tag :tag => 'user', - :child => {:tag => 'id', :content => '2'} - end - end - end - - context "POST /users" do - context "with valid parameters" do - setup do - @parameters = {:user => {:login => 'foo', :firstname => 'Firstname', :lastname => 'Lastname', :mail => 'foo@example.net', :password => 'secret', :mail_notification => 'only_assigned'}} - end - - context ".xml" do - should_allow_api_authentication(:post, - '/users.xml', - {:user => {:login => 'foo', :firstname => 'Firstname', :lastname => 'Lastname', :mail => 'foo@example.net', :password => 'secret'}}, - {:success_code => :created}) - - should "create a user with the attributes" do - assert_difference('User.count') do - post '/users.xml', @parameters, :authorization => credentials('admin') - end - - user = User.first(:order => 'id DESC') - assert_equal 'foo', user.login - assert_equal 'Firstname', user.firstname - assert_equal 'Lastname', user.lastname - assert_equal 'foo@example.net', user.mail - assert_equal 'only_assigned', user.mail_notification - assert !user.admin? - assert user.check_password?('secret') - - assert_response :created - assert_equal 'application/xml', @response.content_type - assert_tag 'user', :child => {:tag => 'id', :content => user.id.to_s} - end - end - - context ".json" do - should_allow_api_authentication(:post, - '/users.json', - {:user => {:login => 'foo', :firstname => 'Firstname', :lastname => 'Lastname', :mail => 'foo@example.net'}}, - {:success_code => :created}) - - should "create a user with the attributes" do - assert_difference('User.count') do - post '/users.json', @parameters, :authorization => credentials('admin') - end - - user = User.first(:order => 'id DESC') - assert_equal 'foo', user.login - assert_equal 'Firstname', user.firstname - assert_equal 'Lastname', user.lastname - assert_equal 'foo@example.net', user.mail - assert !user.admin? - - assert_response :created - assert_equal 'application/json', @response.content_type - json = ActiveSupport::JSON.decode(response.body) - assert_kind_of Hash, json - assert_kind_of Hash, json['user'] - assert_equal user.id, json['user']['id'] - end - end - end - - context "with invalid parameters" do - setup do - @parameters = {:user => {:login => 'foo', :lastname => 'Lastname', :mail => 'foo'}} - end - - context ".xml" do - should "return errors" do - assert_no_difference('User.count') do - post '/users.xml', @parameters, :authorization => credentials('admin') - end - - assert_response :unprocessable_entity - assert_equal 'application/xml', @response.content_type - assert_tag 'errors', :child => {:tag => 'error', :content => "First name can't be blank"} - end - end - - context ".json" do - should "return errors" do - assert_no_difference('User.count') do - post '/users.json', @parameters, :authorization => credentials('admin') - end - - assert_response :unprocessable_entity - assert_equal 'application/json', @response.content_type - json = ActiveSupport::JSON.decode(response.body) - assert_kind_of Hash, json - assert json.has_key?('errors') - assert_kind_of Array, json['errors'] - end - end - end - end - - context "PUT /users/2" do - context "with valid parameters" do - setup do - @parameters = {:user => {:login => 'jsmith', :firstname => 'John', :lastname => 'Renamed', :mail => 'jsmith@somenet.foo'}} - end - - context ".xml" do - should_allow_api_authentication(:put, - '/users/2.xml', - {:user => {:login => 'jsmith', :firstname => 'John', :lastname => 'Renamed', :mail => 'jsmith@somenet.foo'}}, - {:success_code => :ok}) - - should "update user with the attributes" do - assert_no_difference('User.count') do - put '/users/2.xml', @parameters, :authorization => credentials('admin') - end - - user = User.find(2) - assert_equal 'jsmith', user.login - assert_equal 'John', user.firstname - assert_equal 'Renamed', user.lastname - assert_equal 'jsmith@somenet.foo', user.mail - assert !user.admin? - - assert_response :ok - end - end - - context ".json" do - should_allow_api_authentication(:put, - '/users/2.json', - {:user => {:login => 'jsmith', :firstname => 'John', :lastname => 'Renamed', :mail => 'jsmith@somenet.foo'}}, - {:success_code => :ok}) - - should "update user with the attributes" do - assert_no_difference('User.count') do - put '/users/2.json', @parameters, :authorization => credentials('admin') - end - - user = User.find(2) - assert_equal 'jsmith', user.login - assert_equal 'John', user.firstname - assert_equal 'Renamed', user.lastname - assert_equal 'jsmith@somenet.foo', user.mail - assert !user.admin? - - assert_response :ok - end - end - end - - context "with invalid parameters" do - setup do - @parameters = {:user => {:login => 'jsmith', :firstname => '', :lastname => 'Lastname', :mail => 'foo'}} - end - - context ".xml" do - should "return errors" do - assert_no_difference('User.count') do - put '/users/2.xml', @parameters, :authorization => credentials('admin') - end - - assert_response :unprocessable_entity - assert_equal 'application/xml', @response.content_type - assert_tag 'errors', :child => {:tag => 'error', :content => "First name can't be blank"} - end - end - - context ".json" do - should "return errors" do - assert_no_difference('User.count') do - put '/users/2.json', @parameters, :authorization => credentials('admin') - end - - assert_response :unprocessable_entity - assert_equal 'application/json', @response.content_type - json = ActiveSupport::JSON.decode(response.body) - assert_kind_of Hash, json - assert json.has_key?('errors') - assert_kind_of Array, json['errors'] - end - end - end - end - - context "DELETE /users/2" do - context ".xml" do - should_allow_api_authentication(:delete, - '/users/2.xml', - {}, - {:success_code => :ok}) - - should "delete user" do - assert_difference('User.count', -1) do - delete '/users/2.xml', {}, :authorization => credentials('admin') - end - - assert_response :ok - end - end - - context ".json" do - should_allow_api_authentication(:delete, - '/users/2.xml', - {}, - {:success_code => :ok}) - - should "delete user" do - assert_difference('User.count', -1) do - delete '/users/2.json', {}, :authorization => credentials('admin') - end - - assert_response :ok - end - end - end - - def credentials(user, password=nil) - ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user) - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/45/4589164e770ce01b12657f2cebf30982504c9054.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/45/4589164e770ce01b12657f2cebf30982504c9054.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,1248 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class QueryTest < ActiveSupport::TestCase + include Redmine::I18n + + fixtures :projects, :enabled_modules, :users, :members, + :member_roles, :roles, :trackers, :issue_statuses, + :issue_categories, :enumerations, :issues, + :watchers, :custom_fields, :custom_values, :versions, + :queries, + :projects_trackers, + :custom_fields_trackers + + def test_available_filters_should_be_ordered + query = IssueQuery.new + assert_equal 0, query.available_filters.keys.index('status_id') + end + + def test_custom_fields_for_all_projects_should_be_available_in_global_queries + query = IssueQuery.new(:project => nil, :name => '_') + assert query.available_filters.has_key?('cf_1') + assert !query.available_filters.has_key?('cf_3') + end + + def test_system_shared_versions_should_be_available_in_global_queries + Version.find(2).update_attribute :sharing, 'system' + query = IssueQuery.new(:project => nil, :name => '_') + assert query.available_filters.has_key?('fixed_version_id') + assert query.available_filters['fixed_version_id'][:values].detect {|v| v.last == '2'} + end + + def test_project_filter_in_global_queries + query = IssueQuery.new(:project => nil, :name => '_') + project_filter = query.available_filters["project_id"] + assert_not_nil project_filter + project_ids = project_filter[:values].map{|p| p[1]} + assert project_ids.include?("1") #public project + assert !project_ids.include?("2") #private project user cannot see + end + + def find_issues_with_query(query) + Issue.includes([:assigned_to, :status, :tracker, :project, :priority]).where( + query.statement + ).all + end + + def assert_find_issues_with_query_is_successful(query) + assert_nothing_raised do + find_issues_with_query(query) + end + end + + def assert_query_statement_includes(query, condition) + assert_include condition, query.statement + end + + def assert_query_result(expected, query) + assert_nothing_raised do + assert_equal expected.map(&:id).sort, query.issues.map(&:id).sort + assert_equal expected.size, query.issue_count + end + end + + def test_query_should_allow_shared_versions_for_a_project_query + subproject_version = Version.find(4) + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('fixed_version_id', '=', [subproject_version.id.to_s]) + + assert query.statement.include?("#{Issue.table_name}.fixed_version_id IN ('4')") + end + + def test_query_with_multiple_custom_fields + query = IssueQuery.find(1) + assert query.valid? + assert query.statement.include?("#{CustomValue.table_name}.value IN ('MySQL')") + issues = find_issues_with_query(query) + assert_equal 1, issues.length + assert_equal Issue.find(3), issues.first + end + + def test_operator_none + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('fixed_version_id', '!*', ['']) + query.add_filter('cf_1', '!*', ['']) + assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NULL") + assert query.statement.include?("#{CustomValue.table_name}.value IS NULL OR #{CustomValue.table_name}.value = ''") + find_issues_with_query(query) + end + + def test_operator_none_for_integer + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('estimated_hours', '!*', ['']) + issues = find_issues_with_query(query) + assert !issues.empty? + assert issues.all? {|i| !i.estimated_hours} + end + + def test_operator_none_for_date + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('start_date', '!*', ['']) + issues = find_issues_with_query(query) + assert !issues.empty? + assert issues.all? {|i| i.start_date.nil?} + end + + def test_operator_none_for_string_custom_field + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('cf_2', '!*', ['']) + assert query.has_filter?('cf_2') + issues = find_issues_with_query(query) + assert !issues.empty? + assert issues.all? {|i| i.custom_field_value(2).blank?} + end + + def test_operator_all + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('fixed_version_id', '*', ['']) + query.add_filter('cf_1', '*', ['']) + assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NOT NULL") + assert query.statement.include?("#{CustomValue.table_name}.value IS NOT NULL AND #{CustomValue.table_name}.value <> ''") + find_issues_with_query(query) + end + + def test_operator_all_for_date + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('start_date', '*', ['']) + issues = find_issues_with_query(query) + assert !issues.empty? + assert issues.all? {|i| i.start_date.present?} + end + + def test_operator_all_for_string_custom_field + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('cf_2', '*', ['']) + assert query.has_filter?('cf_2') + issues = find_issues_with_query(query) + assert !issues.empty? + assert issues.all? {|i| i.custom_field_value(2).present?} + end + + def test_numeric_filter_should_not_accept_non_numeric_values + query = IssueQuery.new(:name => '_') + query.add_filter('estimated_hours', '=', ['a']) + + assert query.has_filter?('estimated_hours') + assert !query.valid? + end + + def test_operator_is_on_float + Issue.update_all("estimated_hours = 171.2", "id=2") + + query = IssueQuery.new(:name => '_') + query.add_filter('estimated_hours', '=', ['171.20']) + issues = find_issues_with_query(query) + assert_equal 1, issues.size + assert_equal 2, issues.first.id + end + + def test_operator_is_on_integer_custom_field + f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true) + CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7') + CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12') + CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '') + + query = IssueQuery.new(:name => '_') + query.add_filter("cf_#{f.id}", '=', ['12']) + issues = find_issues_with_query(query) + assert_equal 1, issues.size + assert_equal 2, issues.first.id + end + + def test_operator_is_on_integer_custom_field_should_accept_negative_value + f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true) + CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7') + CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12') + CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '') + + query = IssueQuery.new(:name => '_') + query.add_filter("cf_#{f.id}", '=', ['-12']) + assert query.valid? + issues = find_issues_with_query(query) + assert_equal 1, issues.size + assert_equal 2, issues.first.id + end + + def test_operator_is_on_float_custom_field + f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true) + CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3') + CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12.7') + CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '') + + query = IssueQuery.new(:name => '_') + query.add_filter("cf_#{f.id}", '=', ['12.7']) + issues = find_issues_with_query(query) + assert_equal 1, issues.size + assert_equal 2, issues.first.id + end + + def test_operator_is_on_float_custom_field_should_accept_negative_value + f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true) + CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3') + CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12.7') + CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '') + + query = IssueQuery.new(:name => '_') + query.add_filter("cf_#{f.id}", '=', ['-12.7']) + assert query.valid? + issues = find_issues_with_query(query) + assert_equal 1, issues.size + assert_equal 2, issues.first.id + end + + def test_operator_is_on_multi_list_custom_field + f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true, + :possible_values => ['value1', 'value2', 'value3'], :multiple => true) + CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1') + CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2') + CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1') + + query = IssueQuery.new(:name => '_') + query.add_filter("cf_#{f.id}", '=', ['value1']) + issues = find_issues_with_query(query) + assert_equal [1, 3], issues.map(&:id).sort + + query = IssueQuery.new(:name => '_') + query.add_filter("cf_#{f.id}", '=', ['value2']) + issues = find_issues_with_query(query) + assert_equal [1], issues.map(&:id).sort + end + + def test_operator_is_not_on_multi_list_custom_field + f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true, + :possible_values => ['value1', 'value2', 'value3'], :multiple => true) + CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1') + CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2') + CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1') + + query = IssueQuery.new(:name => '_') + query.add_filter("cf_#{f.id}", '!', ['value1']) + issues = find_issues_with_query(query) + assert !issues.map(&:id).include?(1) + assert !issues.map(&:id).include?(3) + + query = IssueQuery.new(:name => '_') + query.add_filter("cf_#{f.id}", '!', ['value2']) + issues = find_issues_with_query(query) + assert !issues.map(&:id).include?(1) + assert issues.map(&:id).include?(3) + end + + def test_operator_is_on_is_private_field + # is_private filter only available for those who can set issues private + User.current = User.find(2) + + query = IssueQuery.new(:name => '_') + assert query.available_filters.key?('is_private') + + query.add_filter("is_private", '=', ['1']) + issues = find_issues_with_query(query) + assert issues.any? + assert_nil issues.detect {|issue| !issue.is_private?} + ensure + User.current = nil + end + + def test_operator_is_not_on_is_private_field + # is_private filter only available for those who can set issues private + User.current = User.find(2) + + query = IssueQuery.new(:name => '_') + assert query.available_filters.key?('is_private') + + query.add_filter("is_private", '!', ['1']) + issues = find_issues_with_query(query) + assert issues.any? + assert_nil issues.detect {|issue| issue.is_private?} + ensure + User.current = nil + end + + def test_operator_greater_than + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('done_ratio', '>=', ['40']) + assert query.statement.include?("#{Issue.table_name}.done_ratio >= 40.0") + find_issues_with_query(query) + end + + def test_operator_greater_than_a_float + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('estimated_hours', '>=', ['40.5']) + assert query.statement.include?("#{Issue.table_name}.estimated_hours >= 40.5") + find_issues_with_query(query) + end + + def test_operator_greater_than_on_int_custom_field + f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true) + CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7') + CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12') + CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '') + + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter("cf_#{f.id}", '>=', ['8']) + issues = find_issues_with_query(query) + assert_equal 1, issues.size + assert_equal 2, issues.first.id + end + + def test_operator_lesser_than + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('done_ratio', '<=', ['30']) + assert query.statement.include?("#{Issue.table_name}.done_ratio <= 30.0") + find_issues_with_query(query) + end + + def test_operator_lesser_than_on_custom_field + f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true) + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter("cf_#{f.id}", '<=', ['30']) + assert_match /CAST.+ <= 30\.0/, query.statement + find_issues_with_query(query) + end + + def test_operator_lesser_than_on_date_custom_field + f = IssueCustomField.create!(:name => 'filter', :field_format => 'date', :is_filter => true, :is_for_all => true) + CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '2013-04-11') + CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '2013-05-14') + CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '') + + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter("cf_#{f.id}", '<=', ['2013-05-01']) + issue_ids = find_issues_with_query(query).map(&:id) + assert_include 1, issue_ids + assert_not_include 2, issue_ids + assert_not_include 3, issue_ids + end + + def test_operator_between + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('done_ratio', '><', ['30', '40']) + assert_include "#{Issue.table_name}.done_ratio BETWEEN 30.0 AND 40.0", query.statement + find_issues_with_query(query) + end + + def test_operator_between_on_custom_field + f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true) + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter("cf_#{f.id}", '><', ['30', '40']) + assert_match /CAST.+ BETWEEN 30.0 AND 40.0/, query.statement + find_issues_with_query(query) + end + + def test_date_filter_should_not_accept_non_date_values + query = IssueQuery.new(:name => '_') + query.add_filter('created_on', '=', ['a']) + + assert query.has_filter?('created_on') + assert !query.valid? + end + + def test_date_filter_should_not_accept_invalid_date_values + query = IssueQuery.new(:name => '_') + query.add_filter('created_on', '=', ['2011-01-34']) + + assert query.has_filter?('created_on') + assert !query.valid? + end + + def test_relative_date_filter_should_not_accept_non_integer_values + query = IssueQuery.new(:name => '_') + query.add_filter('created_on', '>t-', ['a']) + + assert query.has_filter?('created_on') + assert !query.valid? + end + + def test_operator_date_equals + query = IssueQuery.new(:name => '_') + query.add_filter('due_date', '=', ['2011-07-10']) + assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?' AND issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement + find_issues_with_query(query) + end + + def test_operator_date_lesser_than + query = IssueQuery.new(:name => '_') + query.add_filter('due_date', '<=', ['2011-07-10']) + assert_match /issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement + find_issues_with_query(query) + end + + def test_operator_date_greater_than + query = IssueQuery.new(:name => '_') + query.add_filter('due_date', '>=', ['2011-07-10']) + assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?'/, query.statement + find_issues_with_query(query) + end + + def test_operator_date_between + query = IssueQuery.new(:name => '_') + query.add_filter('due_date', '><', ['2011-06-23', '2011-07-10']) + assert_match /issues\.due_date > '2011-06-22 23:59:59(\.9+)?' AND issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement + find_issues_with_query(query) + end + + def test_operator_in_more_than + Issue.find(7).update_attribute(:due_date, (Date.today + 15)) + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('due_date', '>t+', ['15']) + 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 = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('due_date', ' Project.find(1), :name => '_') + query.add_filter('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 = IssueQuery.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))} + end + + def test_operator_in_the_past_days + Issue.find(7).update_attribute(:due_date, (Date.today - 3)) + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('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 = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('due_date', ' Project.find(1), :name => '_') + query.add_filter('due_date', 'w', ['']) + assert query.statement.match(/issues\.due_date > '2011-04-24 23:59:59(\.9+)?' AND issues\.due_date <= '2011-05-01 23:59:59(\.9+)?/), "range not found in #{query.statement}" + I18n.locale = :en + end + + def test_range_for_this_week_with_week_starting_on_sunday + I18n.locale = :en + assert_equal '7', I18n.t(:general_first_day_of_week) + + Date.stubs(:today).returns(Date.parse('2011-04-29')) + + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('due_date', 'w', ['']) + assert query.statement.match(/issues\.due_date > '2011-04-23 23:59:59(\.9+)?' AND issues\.due_date <= '2011-04-30 23:59:59(\.9+)?/), "range not found in #{query.statement}" + end + + def test_operator_does_not_contains + query = IssueQuery.new(:project => Project.find(1), :name => '_') + query.add_filter('subject', '!~', ['uNable']) + assert query.statement.include?("LOWER(#{Issue.table_name}.subject) NOT LIKE '%unable%'") + find_issues_with_query(query) + end + + def test_filter_assigned_to_me + user = User.find(2) + group = Group.find(10) + User.current = user + i1 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => user) + i2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => group) + i3 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => Group.find(11)) + group.users << user + + query = IssueQuery.new(:name => '_', :filters => { 'assigned_to_id' => {:operator => '=', :values => ['me']}}) + result = query.issues + assert_equal Issue.visible.all(:conditions => {:assigned_to_id => ([2] + user.reload.group_ids)}).sort_by(&:id), result.sort_by(&:id) + + assert result.include?(i1) + assert result.include?(i2) + assert !result.include?(i3) + end + + def test_user_custom_field_filtered_on_me + User.current = User.find(2) + cf = IssueCustomField.create!(:field_format => 'user', :is_for_all => true, :is_filter => true, :name => 'User custom field', :tracker_ids => [1]) + issue1 = Issue.create!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '2'}, :subject => 'Test', :author_id => 1) + issue2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '3'}) + + query = IssueQuery.new(:name => '_', :project => Project.find(1)) + filter = query.available_filters["cf_#{cf.id}"] + assert_not_nil filter + assert_include 'me', filter[:values].map{|v| v[1]} + + query.filters = { "cf_#{cf.id}" => {:operator => '=', :values => ['me']}} + result = query.issues + assert_equal 1, result.size + assert_equal issue1, result.first + end + + def test_filter_my_projects + User.current = User.find(2) + query = IssueQuery.new(:name => '_') + filter = query.available_filters['project_id'] + assert_not_nil filter + assert_include 'mine', filter[:values].map{|v| v[1]} + + query.filters = { 'project_id' => {:operator => '=', :values => ['mine']}} + result = query.issues + assert_nil result.detect {|issue| !User.current.member_of?(issue.project)} + end + + def test_filter_watched_issues + User.current = User.find(1) + query = IssueQuery.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 = IssueQuery.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_filter_on_project_custom_field + field = ProjectCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string') + CustomValue.create!(:custom_field => field, :customized => Project.find(3), :value => 'Foo') + CustomValue.create!(:custom_field => field, :customized => Project.find(5), :value => 'Foo') + + query = IssueQuery.new(:name => '_') + filter_name = "project.cf_#{field.id}" + assert_include filter_name, query.available_filters.keys + query.filters = {filter_name => {:operator => '=', :values => ['Foo']}} + assert_equal [3, 5], find_issues_with_query(query).map(&:project_id).uniq.sort + end + + def test_filter_on_author_custom_field + field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string') + CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo') + + query = IssueQuery.new(:name => '_') + filter_name = "author.cf_#{field.id}" + assert_include filter_name, query.available_filters.keys + query.filters = {filter_name => {:operator => '=', :values => ['Foo']}} + assert_equal [3], find_issues_with_query(query).map(&:author_id).uniq.sort + end + + def test_filter_on_assigned_to_custom_field + field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string') + CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo') + + query = IssueQuery.new(:name => '_') + filter_name = "assigned_to.cf_#{field.id}" + assert_include filter_name, query.available_filters.keys + query.filters = {filter_name => {:operator => '=', :values => ['Foo']}} + assert_equal [3], find_issues_with_query(query).map(&:assigned_to_id).uniq.sort + end + + def test_filter_on_fixed_version_custom_field + field = VersionCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string') + CustomValue.create!(:custom_field => field, :customized => Version.find(2), :value => 'Foo') + + query = IssueQuery.new(:name => '_') + filter_name = "fixed_version.cf_#{field.id}" + assert_include filter_name, query.available_filters.keys + query.filters = {filter_name => {:operator => '=', :values => ['Foo']}} + assert_equal [2], find_issues_with_query(query).map(&:fixed_version_id).uniq.sort + end + + def test_filter_on_relations_with_a_specific_issue + IssueRelation.delete_all + IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2)) + IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1)) + + query = IssueQuery.new(:name => '_') + query.filters = {"relates" => {:operator => '=', :values => ['1']}} + assert_equal [2, 3], find_issues_with_query(query).map(&:id).sort + + query = IssueQuery.new(:name => '_') + query.filters = {"relates" => {:operator => '=', :values => ['2']}} + assert_equal [1], find_issues_with_query(query).map(&:id).sort + end + + def test_filter_on_relations_with_any_issues_in_a_project + IssueRelation.delete_all + with_settings :cross_project_issue_relations => '1' do + IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first) + IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(2).issues.first) + IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(3).issues.first) + end + + query = IssueQuery.new(:name => '_') + query.filters = {"relates" => {:operator => '=p', :values => ['2']}} + assert_equal [1, 2], find_issues_with_query(query).map(&:id).sort + + query = IssueQuery.new(:name => '_') + query.filters = {"relates" => {:operator => '=p', :values => ['3']}} + assert_equal [1], find_issues_with_query(query).map(&:id).sort + + query = IssueQuery.new(:name => '_') + query.filters = {"relates" => {:operator => '=p', :values => ['4']}} + assert_equal [], find_issues_with_query(query).map(&:id).sort + end + + def test_filter_on_relations_with_any_issues_not_in_a_project + IssueRelation.delete_all + with_settings :cross_project_issue_relations => '1' do + IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first) + #IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(1).issues.first) + IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(3).issues.first) + end + + query = IssueQuery.new(:name => '_') + query.filters = {"relates" => {:operator => '=!p', :values => ['1']}} + assert_equal [1], find_issues_with_query(query).map(&:id).sort + end + + def test_filter_on_relations_with_no_issues_in_a_project + IssueRelation.delete_all + with_settings :cross_project_issue_relations => '1' do + IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first) + IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(3).issues.first) + IssueRelation.create!(:relation_type => "relates", :issue_to => Project.find(2).issues.first, :issue_from => Issue.find(3)) + end + + query = IssueQuery.new(:name => '_') + query.filters = {"relates" => {:operator => '!p', :values => ['2']}} + ids = find_issues_with_query(query).map(&:id).sort + assert_include 2, ids + assert_not_include 1, ids + assert_not_include 3, ids + end + + def test_filter_on_relations_with_no_issues + IssueRelation.delete_all + IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2)) + IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1)) + + query = IssueQuery.new(:name => '_') + query.filters = {"relates" => {:operator => '!*', :values => ['']}} + ids = find_issues_with_query(query).map(&:id) + assert_equal [], ids & [1, 2, 3] + assert_include 4, ids + end + + def test_filter_on_relations_with_any_issues + IssueRelation.delete_all + IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2)) + IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1)) + + query = IssueQuery.new(:name => '_') + query.filters = {"relates" => {:operator => '*', :values => ['']}} + assert_equal [1, 2, 3], find_issues_with_query(query).map(&:id).sort + end + + def test_statement_should_be_nil_with_no_filters + q = IssueQuery.new(:name => '_') + q.filters = {} + + assert q.valid? + assert_nil q.statement + end + + def test_default_columns + q = IssueQuery.new + assert q.columns.any? + assert q.inline_columns.any? + assert q.block_columns.empty? + end + + def test_set_column_names + q = IssueQuery.new + q.column_names = ['tracker', :subject, '', 'unknonw_column'] + assert_equal [:id, :tracker, :subject], q.columns.collect {|c| c.name} + end + + def test_has_column_should_accept_a_column_name + q = IssueQuery.new + q.column_names = ['tracker', :subject] + assert q.has_column?(:tracker) + assert !q.has_column?(:category) + end + + def test_has_column_should_accept_a_column + q = IssueQuery.new + q.column_names = ['tracker', :subject] + + tracker_column = q.available_columns.detect {|c| c.name==:tracker} + assert_kind_of QueryColumn, tracker_column + category_column = q.available_columns.detect {|c| c.name==:category} + assert_kind_of QueryColumn, category_column + + assert q.has_column?(tracker_column) + assert !q.has_column?(category_column) + end + + def test_inline_and_block_columns + q = IssueQuery.new + q.column_names = ['subject', 'description', 'tracker'] + + assert_equal [:id, :subject, :tracker], q.inline_columns.map(&:name) + assert_equal [:description], q.block_columns.map(&:name) + end + + def test_custom_field_columns_should_be_inline + q = IssueQuery.new + columns = q.available_columns.select {|column| column.is_a? QueryCustomFieldColumn} + assert columns.any? + assert_nil columns.detect {|column| !column.inline?} + end + + def test_query_should_preload_spent_hours + q = IssueQuery.new(:name => '_', :column_names => [:subject, :spent_hours]) + assert q.has_column?(:spent_hours) + issues = q.issues + assert_not_nil issues.first.instance_variable_get("@spent_hours") + end + + def test_groupable_columns_should_include_custom_fields + q = IssueQuery.new + column = q.groupable_columns.detect {|c| c.name == :cf_1} + assert_not_nil column + assert_kind_of QueryCustomFieldColumn, column + end + + def test_groupable_columns_should_not_include_multi_custom_fields + field = CustomField.find(1) + field.update_attribute :multiple, true + + q = IssueQuery.new + column = q.groupable_columns.detect {|c| c.name == :cf_1} + assert_nil column + end + + def test_groupable_columns_should_include_user_custom_fields + cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'user') + + q = IssueQuery.new + assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym} + end + + def test_groupable_columns_should_include_version_custom_fields + cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'version') + + q = IssueQuery.new + assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym} + end + + def test_grouped_with_valid_column + q = IssueQuery.new(:group_by => 'status') + assert q.grouped? + assert_not_nil q.group_by_column + assert_equal :status, q.group_by_column.name + assert_not_nil q.group_by_statement + assert_equal 'status', q.group_by_statement + end + + def test_grouped_with_invalid_column + q = IssueQuery.new(:group_by => 'foo') + assert !q.grouped? + assert_nil q.group_by_column + assert_nil q.group_by_statement + end + + def test_sortable_columns_should_sort_assignees_according_to_user_format_setting + with_settings :user_format => 'lastname_coma_firstname' do + q = IssueQuery.new + assert q.sortable_columns.has_key?('assigned_to') + assert_equal %w(users.lastname users.firstname users.id), q.sortable_columns['assigned_to'] + end + end + + def test_sortable_columns_should_sort_authors_according_to_user_format_setting + with_settings :user_format => 'lastname_coma_firstname' do + q = IssueQuery.new + assert q.sortable_columns.has_key?('author') + assert_equal %w(authors.lastname authors.firstname authors.id), q.sortable_columns['author'] + end + end + + def test_sortable_columns_should_include_custom_field + q = IssueQuery.new + assert q.sortable_columns['cf_1'] + end + + def test_sortable_columns_should_not_include_multi_custom_field + field = CustomField.find(1) + field.update_attribute :multiple, true + + q = IssueQuery.new + assert !q.sortable_columns['cf_1'] + end + + def test_default_sort + q = IssueQuery.new + assert_equal [], q.sort_criteria + end + + def test_set_sort_criteria_with_hash + q = IssueQuery.new + q.sort_criteria = {'0' => ['priority', 'desc'], '2' => ['tracker']} + assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria + end + + def test_set_sort_criteria_with_array + q = IssueQuery.new + q.sort_criteria = [['priority', 'desc'], 'tracker'] + assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria + end + + def test_create_query_with_sort + q = IssueQuery.new(:name => 'Sorted') + q.sort_criteria = [['priority', 'desc'], 'tracker'] + assert q.save + q.reload + assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria + end + + def test_sort_by_string_custom_field_asc + q = IssueQuery.new + c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' } + assert c + assert c.sortable + issues = q.issues(: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 = IssueQuery.new + c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' } + assert c + assert c.sortable + issues = q.issues(: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 = IssueQuery.new + c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'float' } + assert c + assert c.sortable + issues = q.issues(: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_invalid_query_should_raise_query_statement_invalid_error + q = IssueQuery.new + assert_raise Query::StatementInvalid do + q.issues(:conditions => "foo = 1") + end + end + + def test_issue_count + q = IssueQuery.new(:name => '_') + issue_count = q.issue_count + assert_equal q.issues.size, issue_count + end + + def test_issue_count_with_archived_issues + p = Project.generate! do |project| + project.status = Project::STATUS_ARCHIVED + end + i = Issue.generate!( :project => p, :tracker => p.trackers.first ) + assert !i.visible? + + test_issue_count + end + + def test_issue_count_by_association_group + q = IssueQuery.new(:name => '_', :group_by => 'assigned_to') + count_by_group = q.issue_count_by_group + assert_kind_of Hash, count_by_group + assert_equal %w(NilClass User), count_by_group.keys.collect {|k| k.class.name}.uniq.sort + assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq + assert count_by_group.has_key?(User.find(3)) + end + + def test_issue_count_by_list_custom_field_group + q = IssueQuery.new(:name => '_', :group_by => 'cf_1') + count_by_group = q.issue_count_by_group + assert_kind_of Hash, count_by_group + assert_equal %w(NilClass String), count_by_group.keys.collect {|k| k.class.name}.uniq.sort + assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq + assert count_by_group.has_key?('MySQL') + end + + def test_issue_count_by_date_custom_field_group + q = IssueQuery.new(:name => '_', :group_by => 'cf_8') + count_by_group = q.issue_count_by_group + assert_kind_of Hash, count_by_group + assert_equal %w(Date NilClass), count_by_group.keys.collect {|k| k.class.name}.uniq.sort + assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq + end + + def test_issue_count_with_nil_group_only + Issue.update_all("assigned_to_id = NULL") + + q = IssueQuery.new(:name => '_', :group_by => 'assigned_to') + count_by_group = q.issue_count_by_group + assert_kind_of Hash, count_by_group + assert_equal 1, count_by_group.keys.size + assert_nil count_by_group.keys.first + end + + def test_issue_ids + q = IssueQuery.new(:name => '_') + order = "issues.subject, issues.id" + issues = q.issues(:order => order) + assert_equal issues.map(&:id), q.issue_ids(:order => order) + end + + def test_label_for + set_language_if_valid 'en' + q = IssueQuery.new + assert_equal 'Assignee', q.label_for('assigned_to_id') + end + + def test_label_for_fr + set_language_if_valid 'fr' + q = IssueQuery.new + s = "Assign\xc3\xa9 \xc3\xa0" + s.force_encoding('UTF-8') if s.respond_to?(:force_encoding) + assert_equal s, q.label_for('assigned_to_id') + end + + def test_editable_by + admin = User.find(1) + manager = User.find(2) + developer = User.find(3) + + # Public query on project 1 + q = IssueQuery.find(1) + assert q.editable_by?(admin) + assert q.editable_by?(manager) + assert !q.editable_by?(developer) + + # Private query on project 1 + q = IssueQuery.find(2) + assert q.editable_by?(admin) + assert !q.editable_by?(manager) + assert q.editable_by?(developer) + + # Private query for all projects + q = IssueQuery.find(3) + assert q.editable_by?(admin) + assert !q.editable_by?(manager) + assert q.editable_by?(developer) + + # Public query for all projects + q = IssueQuery.find(4) + assert q.editable_by?(admin) + assert !q.editable_by?(manager) + assert !q.editable_by?(developer) + end + + def test_visible_scope + query_ids = IssueQuery.visible(User.anonymous).map(&:id) + + assert query_ids.include?(1), 'public query on public project was not visible' + assert query_ids.include?(4), 'public query for all projects was not visible' + assert !query_ids.include?(2), 'private query on public project was visible' + assert !query_ids.include?(3), 'private query for all projects was visible' + assert !query_ids.include?(7), 'public query on private project was visible' + end + + test "#available_filters should include users of visible projects in cross-project view" do + users = IssueQuery.new.available_filters["assigned_to_id"] + assert_not_nil users + assert users[:values].map{|u|u[1]}.include?("3") + end + + test "#available_filters should include users of subprojects" do + user1 = User.generate! + user2 = User.generate! + project = Project.find(1) + Member.create!(:principal => user1, :project => project.children.visible.first, :role_ids => [1]) + + users = IssueQuery.new(:project => project).available_filters["assigned_to_id"] + assert_not_nil users + assert users[:values].map{|u|u[1]}.include?(user1.id.to_s) + assert !users[:values].map{|u|u[1]}.include?(user2.id.to_s) + end + + test "#available_filters should include visible projects in cross-project view" do + projects = IssueQuery.new.available_filters["project_id"] + assert_not_nil projects + assert projects[:values].map{|u|u[1]}.include?("1") + end + + test "#available_filters should include 'member_of_group' filter" do + query = IssueQuery.new + assert query.available_filters.keys.include?("member_of_group") + assert_equal :list_optional, query.available_filters["member_of_group"][:type] + assert query.available_filters["member_of_group"][:values].present? + assert_equal Group.all.sort.map {|g| [g.name, g.id.to_s]}, + query.available_filters["member_of_group"][:values].sort + end + + test "#available_filters should include 'assigned_to_role' filter" do + query = IssueQuery.new + assert query.available_filters.keys.include?("assigned_to_role") + assert_equal :list_optional, query.available_filters["assigned_to_role"][:type] + + assert query.available_filters["assigned_to_role"][:values].include?(['Manager','1']) + assert query.available_filters["assigned_to_role"][:values].include?(['Developer','2']) + assert query.available_filters["assigned_to_role"][:values].include?(['Reporter','3']) + + assert ! query.available_filters["assigned_to_role"][:values].include?(['Non member','4']) + assert ! query.available_filters["assigned_to_role"][:values].include?(['Anonymous','5']) + end + + context "#statement" do + context "with 'member_of_group' filter" do + setup do + Group.destroy_all # No fixtures + @user_in_group = User.generate! + @second_user_in_group = User.generate! + @user_in_group2 = User.generate! + @user_not_in_group = User.generate! + + @group = Group.generate!.reload + @group.users << @user_in_group + @group.users << @second_user_in_group + + @group2 = Group.generate!.reload + @group2.users << @user_in_group2 + + end + + should "search assigned to for users in the group" do + @query = IssueQuery.new(:name => '_') + @query.add_filter('member_of_group', '=', [@group.id.to_s]) + + assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@group.id}')" + assert_find_issues_with_query_is_successful @query + end + + should "search not assigned to any group member (none)" do + @query = IssueQuery.new(:name => '_') + @query.add_filter('member_of_group', '!*', ['']) + + # Users not in a group + assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IS NULL OR #{Issue.table_name}.assigned_to_id NOT IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}','#{@group.id}','#{@group2.id}')" + assert_find_issues_with_query_is_successful @query + end + + should "search assigned to any group member (all)" do + @query = IssueQuery.new(:name => '_') + @query.add_filter('member_of_group', '*', ['']) + + # Only users in a group + assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}','#{@group.id}','#{@group2.id}')" + assert_find_issues_with_query_is_successful @query + end + + should "return an empty set with = empty group" do + @empty_group = Group.generate! + @query = IssueQuery.new(:name => '_') + @query.add_filter('member_of_group', '=', [@empty_group.id.to_s]) + + assert_equal [], find_issues_with_query(@query) + end + + should "return issues with ! empty group" do + @empty_group = Group.generate! + @query = IssueQuery.new(:name => '_') + @query.add_filter('member_of_group', '!', [@empty_group.id.to_s]) + + assert_find_issues_with_query_is_successful @query + end + end + + context "with 'assigned_to_role' filter" do + setup do + @manager_role = Role.find_by_name('Manager') + @developer_role = Role.find_by_name('Developer') + + @project = Project.generate! + @manager = User.generate! + @developer = User.generate! + @boss = User.generate! + @guest = User.generate! + User.add_to_project(@manager, @project, @manager_role) + User.add_to_project(@developer, @project, @developer_role) + User.add_to_project(@boss, @project, [@manager_role, @developer_role]) + + @issue1 = Issue.generate!(:project => @project, :assigned_to_id => @manager.id) + @issue2 = Issue.generate!(:project => @project, :assigned_to_id => @developer.id) + @issue3 = Issue.generate!(:project => @project, :assigned_to_id => @boss.id) + @issue4 = Issue.generate!(:project => @project, :assigned_to_id => @guest.id) + @issue5 = Issue.generate!(:project => @project) + end + + should "search assigned to for users with the Role" do + @query = IssueQuery.new(:name => '_', :project => @project) + @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s]) + + assert_query_result [@issue1, @issue3], @query + end + + should "search assigned to for users with the Role on the issue project" do + other_project = Project.generate! + User.add_to_project(@developer, other_project, @manager_role) + + @query = IssueQuery.new(:name => '_', :project => @project) + @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s]) + + assert_query_result [@issue1, @issue3], @query + end + + should "return an empty set with empty role" do + @empty_role = Role.generate! + @query = IssueQuery.new(:name => '_', :project => @project) + @query.add_filter('assigned_to_role', '=', [@empty_role.id.to_s]) + + assert_query_result [], @query + end + + should "search assigned to for users without the Role" do + @query = IssueQuery.new(:name => '_', :project => @project) + @query.add_filter('assigned_to_role', '!', [@manager_role.id.to_s]) + + assert_query_result [@issue2, @issue4, @issue5], @query + end + + should "search assigned to for users not assigned to any Role (none)" do + @query = IssueQuery.new(:name => '_', :project => @project) + @query.add_filter('assigned_to_role', '!*', ['']) + + assert_query_result [@issue4, @issue5], @query + end + + should "search assigned to for users assigned to any Role (all)" do + @query = IssueQuery.new(:name => '_', :project => @project) + @query.add_filter('assigned_to_role', '*', ['']) + + assert_query_result [@issue1, @issue2, @issue3], @query + end + + should "return issues with ! empty role" do + @empty_role = Role.generate! + @query = IssueQuery.new(:name => '_', :project => @project) + @query.add_filter('assigned_to_role', '!', [@empty_role.id.to_s]) + + assert_query_result [@issue1, @issue2, @issue3, @issue4, @issue5], @query + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/45/45b8269cf1731acb2f145d6ea43cdd52da6f4d4a.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/45/45b8269cf1731acb2f145d6ea43cdd52da6f4d4a.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,101 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class TrackersController < ApplicationController + layout 'admin' + + before_filter :require_admin, :except => :index + before_filter :require_admin_or_api_request, :only => :index + accept_api_auth :index + + def index + respond_to do |format| + format.html { + @tracker_pages, @trackers = paginate Tracker.sorted, :per_page => 25 + render :action => "index", :layout => false if request.xhr? + } + format.api { + @trackers = Tracker.sorted.all + } + end + end + + def new + @tracker ||= Tracker.new(params[:tracker]) + @trackers = Tracker.sorted.all + @projects = Project.all + end + + def create + @tracker = Tracker.new(params[:tracker]) + if @tracker.save + # workflow copy + if !params[:copy_workflow_from].blank? && (copy_from = Tracker.find_by_id(params[:copy_workflow_from])) + @tracker.workflow_rules.copy(copy_from) + end + flash[:notice] = l(:notice_successful_create) + redirect_to trackers_path + return + end + new + render :action => 'new' + end + + def edit + @tracker ||= Tracker.find(params[:id]) + @projects = Project.all + end + + def update + @tracker = Tracker.find(params[:id]) + if @tracker.update_attributes(params[:tracker]) + flash[:notice] = l(:notice_successful_update) + redirect_to trackers_path + return + end + edit + render :action => 'edit' + end + + def destroy + @tracker = Tracker.find(params[:id]) + unless @tracker.issues.empty? + flash[:error] = l(:error_can_not_delete_tracker) + else + @tracker.destroy + end + redirect_to trackers_path + end + + def fields + if request.post? && params[:trackers] + params[:trackers].each do |tracker_id, tracker_params| + tracker = Tracker.find_by_id(tracker_id) + if tracker + tracker.core_fields = tracker_params[:core_fields] + tracker.custom_field_ids = tracker_params[:custom_field_ids] + tracker.save + end + end + flash[:notice] = l(:notice_successful_update) + redirect_to fields_trackers_path + return + end + @trackers = Tracker.sorted.all + @custom_fields = IssueCustomField.all.sort + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/45/45ba0113063b6030bb97f2bd03aad9a662b0bfbf.svn-base --- a/.svn/pristine/45/45ba0113063b6030bb97f2bd03aad9a662b0bfbf.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,74 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../../../test_helper', __FILE__) - -class ThemesTest < ActionController::IntegrationTest - - def setup - @theme = Redmine::Themes.themes.last - Setting.ui_theme = @theme.id - end - - def teardown - Setting.ui_theme = '' - end - - def test_application_css - get '/' - - assert_response :success - assert_tag :tag => 'link', - :attributes => {:href => %r{^/themes/#{@theme.dir}/stylesheets/application.css}} - end - - def test_without_theme_js - get '/' - - assert_response :success - assert_no_tag :tag => 'script', - :attributes => {:src => %r{^/themes/#{@theme.dir}/javascripts/theme.js}} - end - - def test_with_theme_js - # Simulates a theme.js - @theme.javascripts << 'theme' - get '/' - - assert_response :success - assert_tag :tag => 'script', - :attributes => {:src => %r{^/themes/#{@theme.dir}/javascripts/theme.js}} - - ensure - @theme.javascripts.delete 'theme' - end - - def test_with_sub_uri - Redmine::Utils.relative_url_root = '/foo' - @theme.javascripts << 'theme' - get '/' - - assert_response :success - assert_tag :tag => 'link', - :attributes => {:href => %r{^/foo/themes/#{@theme.dir}/stylesheets/application.css}} - assert_tag :tag => 'script', - :attributes => {:src => %r{^/foo/themes/#{@theme.dir}/javascripts/theme.js}} - - ensure - Redmine::Utils.relative_url_root = '' - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/45/45daf4d07a686bd1866f0fdb820fe646d660b21c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/45/45daf4d07a686bd1866f0fdb820fe646d660b21c.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,316 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../../test_helper', __FILE__) + +class Redmine::UnifiedDiffTest < ActiveSupport::TestCase + 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 + + def test_inline_partials + diff = Redmine::UnifiedDiff.new(read_diff_fixture('partials.diff')) + assert_equal 1, diff.size + diff = diff.first + assert_equal 43, diff.size + + assert_equal [51, -1], diff[0].offsets + assert_equal [51, -1], diff[1].offsets + assert_equal 'Lorem ipsum dolor sit amet, consectetur adipiscing elit', diff[0].html_line + assert_equal 'Lorem ipsum dolor sit amet, consectetur adipiscing xx', diff[1].html_line + + assert_nil diff[2].offsets + assert_equal 'Praesent et sagittis dui. Vivamus ac diam diam', diff[2].html_line + + assert_equal [0, -14], diff[3].offsets + assert_equal [0, -14], diff[4].offsets + assert_equal 'Ut sed auctor justo', diff[3].html_line + assert_equal 'xxx auctor justo', diff[4].html_line + + assert_equal [13, -19], diff[6].offsets + assert_equal [13, -19], diff[7].offsets + + assert_equal [24, -8], diff[9].offsets + assert_equal [24, -8], diff[10].offsets + + assert_equal [37, -1], diff[12].offsets + assert_equal [37, -1], diff[13].offsets + + assert_equal [0, -38], diff[15].offsets + assert_equal [0, -38], diff[16].offsets + end + + def test_side_by_side_partials + diff = Redmine::UnifiedDiff.new(read_diff_fixture('partials.diff'), :type => 'sbs') + assert_equal 1, diff.size + diff = diff.first + assert_equal 32, diff.size + + assert_equal [51, -1], diff[0].offsets + assert_equal 'Lorem ipsum dolor sit amet, consectetur adipiscing elit', diff[0].html_line_left + assert_equal 'Lorem ipsum dolor sit amet, consectetur adipiscing xx', diff[0].html_line_right + + assert_nil diff[1].offsets + assert_equal 'Praesent et sagittis dui. Vivamus ac diam diam', diff[1].html_line_left + assert_equal 'Praesent et sagittis dui. Vivamus ac diam diam', diff[1].html_line_right + + assert_equal [0, -14], diff[2].offsets + assert_equal 'Ut sed auctor justo', diff[2].html_line_left + assert_equal 'xxx auctor justo', diff[2].html_line_right + + assert_equal [13, -19], diff[4].offsets + assert_equal [24, -8], diff[6].offsets + assert_equal [37, -1], diff[8].offsets + assert_equal [0, -38], diff[10].offsets + + end + + def test_partials_with_html_entities + raw = <<-DIFF +--- test.orig.txt Wed Feb 15 16:10:39 2012 ++++ test.new.txt Wed Feb 15 16:11:25 2012 +@@ -1,5 +1,5 @@ + Semicolons were mysteriously appearing in code diffs in the repository + +-void DoSomething(std::auto_ptr myObj) ++void DoSomething(const MyClass& myObj) + +DIFF + + diff = Redmine::UnifiedDiff.new(raw, :type => 'sbs') + assert_equal 1, diff.size + assert_equal 'void DoSomething(std::auto_ptr<MyClass> myObj)', diff.first[2].html_line_left + assert_equal 'void DoSomething(const MyClass& myObj)', diff.first[2].html_line_right + + diff = Redmine::UnifiedDiff.new(raw, :type => 'inline') + assert_equal 1, diff.size + assert_equal 'void DoSomething(std::auto_ptr<MyClass> myObj)', diff.first[2].html_line + assert_equal 'void DoSomething(const MyClass& myObj)', diff.first[3].html_line + end + + def test_line_starting_with_dashes + diff = Redmine::UnifiedDiff.new(<<-DIFF +--- old.txt Wed Nov 11 14:24:58 2009 ++++ new.txt Wed Nov 11 14:25:02 2009 +@@ -1,8 +1,4 @@ +-Lines that starts with dashes: +- +------------------------- +--- file.c +------------------------- ++A line that starts with dashes: + + and removed. + +@@ -23,4 +19,4 @@ + + + +-Another chunk of change ++Another chunk of changes + +DIFF + ) + assert_equal 1, diff.size + end + + def test_one_line_new_files + diff = Redmine::UnifiedDiff.new(<<-DIFF +diff -r 000000000000 -r ea98b14f75f0 README1 +--- /dev/null ++++ b/README1 +@@ -0,0 +1,1 @@ ++test1 +diff -r 000000000000 -r ea98b14f75f0 README2 +--- /dev/null ++++ b/README2 +@@ -0,0 +1,1 @@ ++test2 +diff -r 000000000000 -r ea98b14f75f0 README3 +--- /dev/null ++++ b/README3 +@@ -0,0 +1,3 @@ ++test4 ++test5 ++test6 +diff -r 000000000000 -r ea98b14f75f0 README4 +--- /dev/null ++++ b/README4 +@@ -0,0 +1,3 @@ ++test4 ++test5 ++test6 +DIFF + ) + assert_equal 4, diff.size + assert_equal "README1", diff[0].file_name + end + + def test_both_git_diff + diff = Redmine::UnifiedDiff.new(<<-DIFF +# HG changeset patch +# User test +# Date 1348014182 -32400 +# Node ID d1c871b8ef113df7f1c56d41e6e3bfbaff976e1f +# Parent 180b6605936cdc7909c5f08b59746ec1a7c99b3e +modify test1.txt + +diff -r 180b6605936c -r d1c871b8ef11 test1.txt +--- a/test1.txt ++++ b/test1.txt +@@ -1,1 +1,1 @@ +-test1 ++modify test1 +DIFF + ) + assert_equal 1, diff.size + assert_equal "test1.txt", diff[0].file_name + end + + def test_include_a_b_slash + diff = Redmine::UnifiedDiff.new(<<-DIFF +--- test1.txt ++++ b/test02.txt +@@ -1 +0,0 @@ +-modify test1 +DIFF + ) + assert_equal 1, diff.size + assert_equal "b/test02.txt", diff[0].file_name + + diff = Redmine::UnifiedDiff.new(<<-DIFF +--- a/test1.txt ++++ a/test02.txt +@@ -1 +0,0 @@ +-modify test1 +DIFF + ) + assert_equal 1, diff.size + assert_equal "a/test02.txt", diff[0].file_name + + diff = Redmine::UnifiedDiff.new(<<-DIFF +--- a/test1.txt ++++ test02.txt +@@ -1 +0,0 @@ +-modify test1 +DIFF + ) + assert_equal 1, diff.size + assert_equal "test02.txt", diff[0].file_name + end + + def test_utf8_ja + ja = " text_tip_issue_end_day: " + ja += "\xe3\x81\x93\xe3\x81\xae\xe6\x97\xa5\xe3\x81\xab\xe7\xb5\x82\xe4\xba\x86\xe3\x81\x99\xe3\x82\x8b\xe3\x82\xbf\xe3\x82\xb9\xe3\x82\xaf" + ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding) + with_settings :repositories_encodings => '' do + diff = Redmine::UnifiedDiff.new(read_diff_fixture('issue-12641-ja.diff'), :type => 'inline') + assert_equal 1, diff.size + assert_equal 12, diff.first.size + assert_equal ja, diff.first[4].html_line_left + end + end + + def test_utf8_ru + ru = " other: "\xd0\xbe\xd0\xba\xd0\xbe\xd0\xbb\xd0\xbe %{count} \xd1\x87\xd0\xb0\xd1\x81\xd0\xb0"" + ru.force_encoding('UTF-8') if ru.respond_to?(:force_encoding) + with_settings :repositories_encodings => '' do + diff = Redmine::UnifiedDiff.new(read_diff_fixture('issue-12641-ru.diff'), :type => 'inline') + assert_equal 1, diff.size + assert_equal 8, diff.first.size + assert_equal ru, diff.first[3].html_line_left + end + end + + def test_offset_range_ascii_1 + raw = <<-DIFF +--- a.txt 2013-04-05 14:19:39.000000000 +0900 ++++ b.txt 2013-04-05 14:19:51.000000000 +0900 +@@ -1,3 +1,3 @@ + aaaa +-abc ++abcd + bbbb +DIFF + diff = Redmine::UnifiedDiff.new(raw, :type => 'sbs') + assert_equal 1, diff.size + assert_equal 3, diff.first.size + assert_equal "abc", diff.first[1].html_line_left + assert_equal "abcd", diff.first[1].html_line_right + end + + def test_offset_range_ascii_2 + raw = <<-DIFF +--- a.txt 2013-04-05 14:19:39.000000000 +0900 ++++ b.txt 2013-04-05 14:19:51.000000000 +0900 +@@ -1,3 +1,3 @@ + aaaa +-abc ++zabc + bbbb +DIFF + diff = Redmine::UnifiedDiff.new(raw, :type => 'sbs') + assert_equal 1, diff.size + assert_equal 3, diff.first.size + assert_equal "abc", diff.first[1].html_line_left + assert_equal "zabc", diff.first[1].html_line_right + end + + def test_offset_range_japanese_1 + ja1 = "\xe6\x97\xa5\xe6\x9c\xac" + ja1.force_encoding('UTF-8') if ja1.respond_to?(:force_encoding) + ja2 = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e" + ja2.force_encoding('UTF-8') if ja2.respond_to?(:force_encoding) + with_settings :repositories_encodings => '' do + diff = Redmine::UnifiedDiff.new( + read_diff_fixture('issue-13644-1.diff'), :type => 'sbs') + assert_equal 1, diff.size + assert_equal 3, diff.first.size + assert_equal ja1, diff.first[1].html_line_left + assert_equal ja2, diff.first[1].html_line_right + end + end + + def test_offset_range_japanese_2 + ja1 = "\xe6\x97\xa5\xe6\x9c\xac" + ja1.force_encoding('UTF-8') if ja1.respond_to?(:force_encoding) + ja2 = "\xe3\x81\xab\xe3\x81\xa3\xe3\x81\xbd\xe3\x82\x93\xe6\x97\xa5\xe6\x9c\xac" + ja2.force_encoding('UTF-8') if ja2.respond_to?(:force_encoding) + with_settings :repositories_encodings => '' do + diff = Redmine::UnifiedDiff.new( + read_diff_fixture('issue-13644-2.diff'), :type => 'sbs') + assert_equal 1, diff.size + assert_equal 3, diff.first.size + assert_equal ja1, diff.first[1].html_line_left + assert_equal ja2, diff.first[1].html_line_right + end + end + + private + + def read_diff_fixture(filename) + File.new(File.join(File.dirname(__FILE__), '/../../../fixtures/diffs', filename)).read + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/45/45fedacb83fa4bf0b530842cc4c9e44d1242cd73.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/45/45fedacb83fa4bf0b530842cc4c9e44d1242cd73.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,47 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +module AttachmentsHelper + # Displays view/delete links to the attachments of the given object + # Options: + # :author -- author names are not displayed if set to false + # :thumbails -- display thumbnails if enabled in settings + def link_to_attachments(container, options = {}) + options.assert_valid_keys(:author, :thumbnails) + + if container.attachments.any? + options = {:deletable => container.attachments_deletable?, :author => true}.merge(options) + render :partial => 'attachments/links', + :locals => {:attachments => container.attachments, :options => options, :thumbnails => (options[:thumbnails] && Setting.thumbnails_enabled?)} + end + end + + def render_api_attachment(attachment, api) + api.attachment do + api.id attachment.id + api.filename attachment.filename + api.filesize attachment.filesize + api.content_type attachment.content_type + api.description attachment.description + api.content_url url_for(:controller => 'attachments', :action => 'download', :id => attachment, :filename => attachment.filename, :only_path => false) + api.author(:id => attachment.author.id, :name => attachment.author.name) if attachment.author + api.created_on attachment.created_on + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/46/460154f7f38123fe1c4a30970945d9f47767461e.svn-base --- a/.svn/pristine/46/460154f7f38123fe1c4a30970945d9f47767461e.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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) - heads_for_wiki_formatter - # Is there a simple way to link to a public resource? - url = "#{Redmine::Utils.relative_url_root}/help/wiki_syntax.html" - help_link = link_to(l(:setting_text_formatting), url, - :onclick => "window.open(\"#{ url }\", \"\", \"resizable=yes, location=no, width=300, height=640, menubar=no, status=no, scrollbars=yes\"); return false;") - - javascript_tag("var wikiToolbar = new jsToolBar($('#{field_id}')); wikiToolbar.setHelpLink('#{escape_javascript help_link}'); wikiToolbar.draw();") - end - - def initial_page_content(page) - "h1. #{@page.pretty_title}" - end - - def heads_for_wiki_formatter - unless @heads_for_wiki_formatter_included - content_for :header_tags do - javascript_include_tag('jstoolbar/jstoolbar') + - javascript_include_tag('jstoolbar/textile') + - javascript_include_tag("jstoolbar/lang/jstoolbar-#{current_language.to_s.downcase}") + - stylesheet_link_tag('jstoolbar') - end - @heads_for_wiki_formatter_included = true - end - end - end - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/46/4604849a86c8bbf40b2a1eb833e2c3ff6fe87f8f.svn-base --- a/.svn/pristine/46/4604849a86c8bbf40b2a1eb833e2c3ff6fe87f8f.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +0,0 @@ -
-<%= watcher_tag(@wiki, User.current) %> -
- -

<%= l(:label_index_by_date) %>

- -<% if @pages.empty? %> -

<%= l(:label_no_data) %>

-<% end %> - -<% @pages_by_date.keys.sort.reverse.each do |date| %> -

<%= format_date(date) %>

-
    -<% @pages_by_date[date].each do |page| %> -
  • <%= link_to h(page.pretty_title), :action => 'show', :id => page.title, :project_id => page.project %>
  • -<% end %> -
-<% end %> - -<% content_for :sidebar do %> - <%= render :partial => 'sidebar' %> -<% end %> - -<% unless @pages.empty? %> -<% other_formats_links do |f| %> - <%= f.link_to 'Atom', :url => {:controller => 'activities', :action => 'index', :id => @project, :show_wiki_edits => 1, :key => User.current.rss_key} %> - <%= f.link_to('HTML', :url => {:action => 'export'}) if User.current.allowed_to?(:export_wiki_pages, @project) %> -<% end %> -<% end %> - -<% content_for :header_tags do %> -<%= auto_discovery_link_tag(:atom, :controller => 'activities', :action => 'index', :id => @project, :show_wiki_edits => 1, :format => 'atom', :key => User.current.rss_key) %> -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/46/460823ad942a6dbaf3986f763002fe9ab659fb36.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/46/460823ad942a6dbaf3986f763002fe9ab659fb36.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,45 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 WorkflowPermission < WorkflowRule + validates_inclusion_of :rule, :in => %w(readonly required) + validate :validate_field_name + + # Replaces the workflow permissions for the given tracker and role + # + # Example: + # WorkflowPermission.replace_permissions role, tracker, {'due_date' => {'1' => 'readonly', '2' => 'required'}} + def self.replace_permissions(tracker, role, permissions) + destroy_all(:tracker_id => tracker.id, :role_id => role.id) + + permissions.each { |field, rule_by_status_id| + rule_by_status_id.each { |status_id, rule| + if rule.present? + WorkflowPermission.create(:role_id => role.id, :tracker_id => tracker.id, :old_status_id => status_id, :field_name => field, :rule => rule) + end + } + } + end + + protected + + def validate_field_name + unless Tracker::CORE_FIELDS_ALL.include?(field_name) || field_name.to_s.match(/^\d+$/) + errors.add :field_name, :invalid + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/46/46d08e5d685f85ea6b371254e247586c06beb87e.svn-base --- a/.svn/pristine/46/46d08e5d685f85ea6b371254e247586c06beb87e.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,107 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) -require 'sys_controller' -require 'mocha' - -# Re-raise errors caught by the controller. -class SysController; def rescue_action(e) raise e end; end - -class SysControllerTest < ActionController::TestCase - fixtures :projects, :repositories, :enabled_modules - - def setup - @controller = SysController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - Setting.sys_api_enabled = '1' - Setting.enabled_scm = %w(Subversion Git) - 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 } - test.assert_tag 'project', :child => {:tag => 'identifier', :sibling => {:tag => 'is-public'}} - end - assert_no_tag 'extra-info' - assert_no_tag 'extra_info' - 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 - assert_equal 'application/xml', @response.content_type - - r = Project.find(4).repository - assert r.is_a?(Repository::Subversion) - assert_equal 'file:///create/project/repository/subproject2', r.url - - assert_tag 'repository-subversion', - :child => { - :tag => 'id', :content => r.id.to_s, - :sibling => {:tag => 'url', :content => r.url} - } - assert_no_tag 'extra-info' - assert_no_tag 'extra_info' - end - - def test_fetch_changesets - Repository::Subversion.any_instance.expects(:fetch_changesets).returns(true) - get :fetch_changesets - assert_response :success - end - - def test_fetch_changesets_one_project - Repository::Subversion.any_instance.expects(:fetch_changesets).returns(true) - get :fetch_changesets, :id => 'ecookbook' - assert_response :success - end - - def test_fetch_changesets_unknown_project - get :fetch_changesets, :id => 'unknown' - assert_response 404 - end - - def test_disabled_ws_should_respond_with_403_error - with_settings :sys_api_enabled => '0' do - get :projects - assert_response 403 - end - end - - def test_api_key - with_settings :sys_api_key => 'my_secret_key' do - get :projects, :key => 'my_secret_key' - assert_response :success - end - end - - def test_wrong_key_should_respond_with_403_error - with_settings :sys_api_enabled => 'my_secret_key' do - get :projects, :key => 'wrong_key' - assert_response 403 - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/47/4715b8761d3d616a6e3ff8f7139429ffdd309875.svn-base --- a/.svn/pristine/47/4715b8761d3d616a6e3ff8f7139429ffdd309875.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1008 +0,0 @@ -sl: - # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl) - direction: ltr - date: - formats: - # Use the strftime parameters for formats. - # When no format has been given, it uses default. - # You can provide other formats here if you like! - default: "%Y-%m-%d" - short: "%b %d" - long: "%B %d, %Y" - - day_names: [Nedelja, Ponedeljek, Torek, Sreda, ÄŒetrtek, Petek, Sobota] - abbr_day_names: [Ned, Pon, To, Sr, ÄŒet, Pet, Sob] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, Januar, Februar, Marec, April, Maj, Junij, Julij, Avgust, September, Oktober, November, December] - abbr_month_names: [~, Jan, Feb, Mar, Apr, Maj, Jun, Jul, Aug, Sep, Okt, Nov, Dec] - # Used in date_select and datime_select. - order: - - :year - - :month - - :day - - time: - formats: - default: "%a, %d %b %Y %H:%M:%S %z" - time: "%H:%M" - short: "%d %b %H:%M" - long: "%B %d, %Y %H:%M" - am: "am" - pm: "pm" - - datetime: - distance_in_words: - half_a_minute: "pol minute" - less_than_x_seconds: - one: "manj kot 1. sekundo" - other: "manj kot %{count} sekund" - x_seconds: - one: "1. sekunda" - other: "%{count} sekund" - less_than_x_minutes: - one: "manj kot minuto" - other: "manj kot %{count} minut" - x_minutes: - one: "1 minuta" - other: "%{count} minut" - about_x_hours: - one: "okrog 1. ure" - other: "okrog %{count} ur" - x_days: - one: "1 dan" - other: "%{count} dni" - about_x_months: - one: "okrog 1. mesec" - other: "okrog %{count} mesecev" - x_months: - one: "1 mesec" - other: "%{count} mesecev" - about_x_years: - one: "okrog 1. leto" - other: "okrog %{count} let" - over_x_years: - one: "veÄ kot 1. leto" - other: "veÄ kot %{count} let" - almost_x_years: - one: "skoraj 1. leto" - other: "skoraj %{count} let" - - number: - format: - separator: "," - delimiter: "." - precision: 3 - human: - format: - precision: 1 - delimiter: "" - storage_units: - format: "%n %u" - units: - kb: KB - tb: TB - gb: GB - byte: - one: Byte - other: Bytes - mb: MB - -# Used in array.to_sentence. - support: - array: - sentence_connector: "in" - skip_last_comma: false - - activerecord: - errors: - template: - header: - one: "1. napaka je prepreÄila temu %{model} da bi se shranil" - other: "%{count} napak je prepreÄilo temu %{model} da bi se shranil" - messages: - inclusion: "ni vkljuÄen na seznamu" - exclusion: "je rezerviran" - invalid: "je napaÄen" - confirmation: "ne ustreza potrdilu" - accepted: "mora biti sprejet" - empty: "ne sme biti prazen" - blank: "ne sme biti neizpolnjen" - too_long: "je predolg" - too_short: "je prekratek" - wrong_length: "je napaÄne dolžine" - taken: "je že zaseden" - not_a_number: "ni Å¡tevilo" - not_a_date: "ni veljaven datum" - greater_than: "mora biti veÄji kot %{count}" - greater_than_or_equal_to: "mora biti veÄji ali enak kot %{count}" - equal_to: "mora biti enak kot %{count}" - less_than: "mora biti manjÅ¡i kot %{count}" - less_than_or_equal_to: "mora biti manjÅ¡i ali enak kot %{count}" - odd: "mora biti sodo" - even: "mora biti liho" - greater_than_start_date: "mora biti kasnejÅ¡i kot zaÄetni datum" - not_same_project: "ne pripada istemu projektu" - circular_dependency: "Ta odnos bi povzroÄil krožno odvisnost" - cant_link_an_issue_with_a_descendant: "Zahtevek ne more biti povezan s svojo podnalogo" - - actionview_instancetag_blank_option: Prosimo izberite - - 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: UTF-8 - general_pdf_encoding: UTF-8 - 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: Ustvarjanje 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 %{value}" - notice_email_error: "Ob poÅ¡iljanju e-sporoÄila je priÅ¡lo do napake (%{value})" - notice_feeds_access_key_reseted: VaÅ¡ RSS dostopni kljuÄ je bil ponastavljen. - notice_failed_to_save_issues: "Neuspelo shranjevanje %{count} zahtevka na %{total} izbranem: %{ids}." - 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: %{value}" - 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: %{value}" - 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 %{value} geslo" - mail_body_lost_password: 'Za spremembo glesla kliknite na naslednjo povezavo:' - mail_subject_register: "Aktivacija %{value} 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Å¡ %{value} raÄun." - mail_body_account_information: Informacije o vaÅ¡em raÄunu - mail_subject_account_activation_request: "%{value} zahtevek za aktivacijo raÄuna" - mail_body_account_activation_request: "Registriral se je nov uporabnik (%{value}). RaÄun Äaka na vaÅ¡o odobritev:" - mail_subject_reminder: "%{count} zahtevek(zahtevki) zapadejo v naslednjih %{days} dneh" - mail_body_reminder: "%{count} zahtevek(zahtevki), ki so vam dodeljeni bodo zapadli v naslednjih %{days} dneh:" - - gui_validation_error: 1 napaka - gui_validation_error_plural: "%{count} 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_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: 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_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_x_projects: - zero: ni projektov - one: 1 projekt - other: "%{count} projektov" - 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 %{value}" - 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_registered_on: Registriran - label_activity: Aktivnost - label_overall_activity: Celotna aktivnost - label_user_activity: "Aktivnost %{value}" - 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: "%{value} 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: "%{count} Prenos" - label_download_plural: "%{count} 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_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: odprt zahtevek - label_open_issues_plural: odprti zahtevki - label_closed_issues: zaprt zahtevek - label_closed_issues_plural: zaprti zahtevki - label_x_open_issues_abbr_on_total: - zero: 0 odprtih / %{total} - one: 1 odprt / %{total} - other: "%{count} odprtih / %{total}" - label_x_open_issues_abbr: - zero: 0 odprtih - one: 1 odprt - other: "%{count} odprtih" - label_x_closed_issues_abbr: - zero: 0 zaprtih - one: 1 zaprt - other: "%{count} zaprtih" - 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: Gantogram - label_internal: Notranji - label_last_changes: "zadnjih %{count} sprememb" - label_change_view_all: Poglej vse spremembe - label_personalize_page: Individualiziraj to stran - label_comment: Komentar - label_comment_plural: Komentarji - label_x_comments: - zero: ni komentarjev - one: 1 komentar - other: "%{count} komentarjev" - 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 %{count} 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: "%{count} sprememba" - label_modification_plural: "%{count} 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_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 %{value}" - label_roadmap_overdue: "%{value} 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: "%{value} ura" - label_f_hour_plural: "%{value} 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: Do - label_date_to: Do - label_language_based: Glede na uporabnikov jezik - label_sort_by: "Razporedi po %{value}" - label_send_test_email: PoÅ¡lji testno e-pismo - label_feeds_access_key_created_on: "RSS dostopni kljuÄ narejen %{value} nazaj" - label_module_plural: Moduli - label_added_time_by: "Dodan %{author} %{age} nazaj" - label_updated_time_by: "Posodobljen od %{author} %{age} nazaj" - label_updated_time: "Posodobljen %{value} 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_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: %{value}" - 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: Izberi 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): %{value} bodo prav tako izbrisani." - text_workflow_edit: Izberite vlogo in zahtevek za urejanje poteka dela - text_are_you_sure: Ali ste prepriÄani? - text_tip_issue_begin_day: naloga z zaÄetkom na ta dan - text_tip_issue_end_day: naloga z zakljuÄkom na ta dan - text_tip_issue_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.
Enkrat shranjen identifikator ne more biti spremenjen.' - text_caracters_maximum: "najveÄ %{count} znakov." - text_caracters_minimum: "Mora biti vsaj dolg vsaj %{count} znakov." - text_length_between: "Dolžina med %{min} in %{max} znakov." - 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 %{id} je sporoÄil(a) %{author}." - text_issue_updated: "Zahtevek %{id} je posodobil(a) %{author}." - text_wiki_destroy_confirmation: Ali ste prepriÄani da želite izbrisati ta wiki in vso njegovo vsebino? - text_issue_category_destroy_question: "Nekateri zahtevki (%{count}) 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 %{value}." - 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: "%{hours} 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: "%{value} je napisal(a):" - text_enumeration_destroy_question: "%{count} 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/configuration.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_developer: 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_in_progress: V teku - 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) - warning_attachments_not_saved: "%{count} datotek(e) ni bilo mogoÄe shraniti." - field_editable: Uredljivo - text_plugin_assets_writable: Zapisljiva mapa za vtiÄnike - label_display: Prikaz - button_create_and_continue: Ustvari in nadaljuj - text_custom_field_possible_values_info: 'Ena vrstica za vsako vrednost' - setting_repository_log_display_limit: NajveÄje Å¡tevilo prikazanih revizij v log datoteki - setting_file_max_size_displayed: NajveÄja velikost besedilnih datotek v vkljuÄenem prikazu - field_watcher: Opazovalec - setting_openid: Dovoli OpenID prijavo in registracijo - field_identity_url: OpenID URL - label_login_with_open_id_option: ali se prijavi z OpenID - field_content: Vsebina - label_descending: PadajoÄe - label_sort: Razvrsti - label_ascending: NaraÅ¡ÄajoÄe - label_date_from_to: Od %{start} do %{end} - label_greater_or_equal: ">=" - label_less_or_equal: <= - text_wiki_page_destroy_question: Ta stran ima %{descendants} podstran(i) in naslednik(ov). Kaj želite storiti? - text_wiki_page_reassign_children: Znova dodeli podstrani tej glavni strani - text_wiki_page_nullify_children: Obdrži podstrani kot glavne strani - text_wiki_page_destroy_children: IzbriÅ¡i podstrani in vse njihove naslednike - setting_password_min_length: Minimalna dolžina gesla - field_group_by: Združi rezultate po - mail_subject_wiki_content_updated: "'%{id}' wiki stran je bila posodobljena" - label_wiki_content_added: Wiki stran dodana - mail_subject_wiki_content_added: "'%{id}' wiki stran je bila dodana" - mail_body_wiki_content_added: "%{author} je dodal '%{id}' wiki stran" - label_wiki_content_updated: Wiki stran posodobljena - mail_body_wiki_content_updated: "%{author} je posodobil '%{id}' wiki stran." - permission_add_project: Ustvari projekt - setting_new_project_user_role_id: Vloga, dodeljena neadministratorskemu uporabniku, ki je ustvaril projekt - label_view_all_revisions: Poglej vse revizije - label_tag: Oznaka - label_branch: Veja - error_no_tracker_in_project: Noben sledilnik ni povezan s tem projektom. Prosimo preverite nastavitve projekta. - error_no_default_issue_status: Privzeti zahtevek ni definiran. Prosimo preverite svoje nastavitve (Pojdite na "Administracija -> Stanje zahtevkov"). - text_journal_changed: "%{label} se je spremenilo iz %{old} v %{new}" - text_journal_set_to: "%{label} nastavljeno na %{value}" - text_journal_deleted: "%{label} izbrisan (%{old})" - label_group_plural: Skupine - label_group: Skupina - label_group_new: Nova skupina - label_time_entry_plural: Porabljen Äas - text_journal_added: "%{label} %{value} dodan" - field_active: Aktiven - enumeration_system_activity: Sistemska aktivnost - permission_delete_issue_watchers: IzbriÅ¡i opazovalce - version_status_closed: zaprt - version_status_locked: zaklenjen - version_status_open: odprt - error_can_not_reopen_issue_on_closed_version: Zahtevek dodeljen zaprti verziji ne more biti ponovno odprt - label_user_anonymous: Anonimni - button_move_and_follow: Premakni in sledi - setting_default_projects_modules: Privzeti moduli za nove projekte - setting_gravatar_default: Privzeta Gravatar slika - field_sharing: Deljenje - label_version_sharing_hierarchy: S projektno hierarhijo - label_version_sharing_system: Z vsemi projekti - label_version_sharing_descendants: S podprojekti - label_version_sharing_tree: Z drevesom projekta - label_version_sharing_none: Ni deljeno - error_can_not_archive_project: Ta projekt ne more biti arhiviran - button_duplicate: Podvoji - button_copy_and_follow: Kopiraj in sledi - label_copy_source: Vir - setting_issue_done_ratio: IzraÄunaj razmerje opravljenega zahtevka z - setting_issue_done_ratio_issue_status: Uporabi stanje zahtevka - error_issue_done_ratios_not_updated: Razmerje opravljenega zahtevka ni bilo posodobljeno. - error_workflow_copy_target: Prosimo izberite ciljni(e) sledilnik(e) in vlogo(e) - setting_issue_done_ratio_issue_field: Uporabi polje zahtevka - label_copy_same_as_target: Enako kot cilj - label_copy_target: Cilj - notice_issue_done_ratios_updated: Razmerje opravljenega zahtevka posodobljeno. - error_workflow_copy_source: Prosimo izberite vir zahtevka ali vlogo - label_update_issue_done_ratios: Posodobi razmerje opravljenega zahtevka - setting_start_of_week: ZaÄni koledarje z - permission_view_issues: Poglej zahtevke - label_display_used_statuses_only: Prikaži samo stanja ki uporabljajo ta sledilnik - label_revision_id: Revizija %{value} - label_api_access_key: API dostopni kljuÄ - label_api_access_key_created_on: API dostopni kljuÄ ustvarjen pred %{value} - label_feeds_access_key: RSS dostopni kljuÄ - notice_api_access_key_reseted: VaÅ¡ API dostopni kljuÄ je bil ponastavljen. - setting_rest_api_enabled: OmogoÄi REST spletni servis - label_missing_api_access_key: ManjkajoÄ API dostopni kljuÄ - label_missing_feeds_access_key: ManjkajoÄ RSS dostopni kljuÄ - button_show: Prikaži - text_line_separated: Dovoljenih veÄ vrednosti (ena vrstica za vsako vrednost). - setting_mail_handler_body_delimiters: Odreži e-poÅ¡to po eni od teh vrstic - permission_add_subprojects: Ustvari podprojekte - label_subproject_new: Nov podprojekt - text_own_membership_delete_confirmation: |- - Odstranili boste nekatere ali vse od dovoljenj zaradi Äesar morda ne boste mogli veÄ urejati tega projekta. - Ali ste prepriÄani, da želite nadaljevati? - label_close_versions: Zapri dokonÄane verzije - label_board_sticky: Lepljivo - label_board_locked: Zaklenjeno - permission_export_wiki_pages: Izvozi wiki strani - setting_cache_formatted_text: Predpomni oblikovano besedilo - permission_manage_project_activities: Uredi aktivnosti projekta - error_unable_delete_issue_status: Stanja zahtevka ni bilo možno spremeniti - label_profile: Profil - permission_manage_subtasks: Uredi podnaloge - field_parent_issue: Nadrejena naloga - label_subtask_plural: Podnaloge - label_project_copy_notifications: Med kopiranjem projekta poÅ¡lji e-poÅ¡tno sporoÄilo - error_can_not_delete_custom_field: Polja po meri ni mogoÄe izbrisati - error_unable_to_connect: Povezava ni mogoÄa (%{value}) - error_can_not_remove_role: Ta vloga je v uporabi in je ni mogoÄe izbrisati. - error_can_not_delete_tracker: Ta sledilnik vsebuje zahtevke in se ga ne more izbrisati. - field_principal: Upravnik varnosti - label_my_page_block: Moj gradnik strani - notice_failed_to_save_members: "Shranjevanje uporabnika(ov) ni uspelo: %{errors}." - text_zoom_out: Približaj - text_zoom_in: Oddalji - notice_unable_delete_time_entry: Brisanje dnevnika porabljenaga Äasa ni mogoÄe. - label_overall_spent_time: Skupni porabljeni Äas - field_time_entries: Beleži porabljeni Äas - project_module_gantt: Gantogram - project_module_calendar: Koledear - button_edit_associated_wikipage: "Uredi povezano Wiki stran: %{page_title}" - text_are_you_sure_with_children: IzbriÅ¡i zahtevek in vse podazahtevke? - field_text: Besedilno polje - label_user_mail_option_only_owner: Samo za stvari katerih lastnik sem - setting_default_notification_option: Privzeta možnost obveÅ¡Äanja - label_user_mail_option_only_my_events: Samo za stvari, ki jih opazujem ali sem v njih vpleten - label_user_mail_option_only_assigned: Samo za stvari, ki smo mi dodeljene - label_user_mail_option_none: Noben dogodek - field_member_of_group: PooblaÅ¡ÄenÄeva skupina - field_assigned_to_role: PooblaÅ¡ÄenÄeva vloga - notice_not_authorized_archived_project: Projekt, do katerega poskuÅ¡ate dostopati, je bil arhiviran. - label_principal_search: "PoiÅ¡Äi uporabnika ali skupino:" - label_user_search: "PoiÅ¡Äi uporabnikia:" - field_visible: Viden - setting_emails_header: Glava e-poÅ¡te - setting_commit_logtime_activity_id: Aktivnost zabeleženega Äasa - text_time_logged_by_changeset: Uporabljeno v spremembi %{value}. - setting_commit_logtime_enabled: OmogoÄi beleženje Äasa - notice_gantt_chart_truncated: Graf je bil odrezan, ker je prekoraÄil najveÄje dovoljeno Å¡tevilo elementov, ki se jih lahko prikaže (%{max}) - setting_gantt_items_limit: NajveÄje Å¡tevilo elementov prikazano na gantogramu - field_warn_on_leaving_unsaved: Opozori me, kadar zapuÅ¡Äam stran z neshranjenim besedilom - text_warn_on_leaving_unsaved: Trenutna stran vsebuje neshranjeno besedilo ki bo izgubljeno, Äe zapustite to stran. - label_my_queries: Moje poizvedbe po meri - text_journal_changed_no_detail: "%{label} posodobljen" - label_news_comment_added: Komentar dodan novici - button_expand_all: RazÅ¡iri vse - button_collapse_all: SkrÄi vse - label_additional_workflow_transitions_for_assignee: Dovoljeni dodatni prehodi kadar je uporabnik pooblaÅ¡Äenec - label_additional_workflow_transitions_for_author: Dovoljeni dodatni prehodi kadar je uporabnik avtor - label_bulk_edit_selected_time_entries: Skupinsko urejanje izbranih Äasovnih zapisov - text_time_entries_destroy_confirmation: Ali ste prepriÄani, da želite izbristai izbran(e) Äasovn(i/e) zapis(e)? - label_role_anonymous: Anonimni - label_role_non_member: NeÄlan - label_issue_note_added: Dodan zaznamek - label_issue_status_updated: Status posodobljen - label_issue_priority_updated: Prioriteta posodobljena - label_issues_visibility_own: Zahtevek ustvarjen s strani uporabnika ali dodeljen uporabniku - field_issues_visibility: Vidljivost zahtevkov - label_issues_visibility_all: Vsi zahtevki - permission_set_own_issues_private: Nastavi lastne zahtevke kot javne ali zasebne - field_is_private: Zaseben - permission_set_issues_private: Nastavi zahtevke kot javne ali zasebne - label_issues_visibility_public: Vsi nezasebni zahtevki - text_issues_destroy_descendants_confirmation: To bo izbrisalo tudi %{count} podnalog(o). - field_commit_logs_encoding: Kodiranje sporoÄil ob predaji - field_scm_path_encoding: Pot do kodiranja - text_scm_path_encoding_note: "Privzeto: UTF-8" - field_path_to_repository: Pot do shrambe - field_root_directory: Korenska mapa - field_cvs_module: Modul - field_cvsroot: CVSROOT - text_mercurial_repository_note: Lokalna shramba (npr. /hgrepo, c:\hgrepo) - text_scm_command: Ukaz - text_scm_command_version: Verzija - label_git_report_last_commit: SporoÄi zadnje uveljavljanje datotek in map - text_scm_config: Svoje SCM ukaze lahko nastavite v datoteki config/configuration.yml. Po urejanju prosimo ponovno zaženite aplikacijo. - text_scm_command_not_available: SCM ukaz ni na voljo. Prosimo preverite nastavitve v upravljalskem podoknu. - - text_git_repository_note: Shramba je prazna in lokalna (npr. /gitrepo, c:\gitrepo) - - notice_issue_successful_create: Ustvarjen zahtevek %{id}. - label_between: med - setting_issue_group_assignment: Dovoli dodeljevanje zahtevka skupinam - label_diff: diff - - description_query_sort_criteria_direction: Sort direction - description_project_scope: Search scope - description_filter: Filter - description_user_mail_notification: Mail notification settings - description_date_from: Enter start date - description_message_content: Message content - description_available_columns: Available Columns - description_date_range_interval: Choose range by selecting start and end date - description_issue_category_reassign: Choose issue category - description_search: Searchfield - description_notes: Notes - description_date_range_list: Choose range from list - description_choose_project: Projects - description_date_to: Enter end date - description_query_sort_criteria_attribute: Sort attribute - description_wiki_subpages_reassign: Choose new parent page - description_selected_columns: Selected Columns - label_parent_revision: Parent - label_child_revision: Child - error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. - setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues - button_edit_section: Edit this section - setting_repositories_encodings: Attachments and repositories encodings - description_all_columns: All Columns - button_export: Export - label_export_options: "%{export_format} export options" - error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/47/47226d5a01dc8a69470963b9e6191a0eef3e2f87.svn-base --- a/.svn/pristine/47/47226d5a01dc8a69470963b9e6191a0eef3e2f87.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ -

<%=l(:label_watched_issues)%> (<%= Issue.visible.watched_by(user.id).count %>)

-<% watched_issues = Issue.visible.on_active_project.watched_by(user.id).recently_updated.with_limit(10) %> - -<%= render :partial => 'issues/list_simple', :locals => { :issues => watched_issues } %> -<% if watched_issues.length > 0 %> -

<%= link_to l(:label_issue_view_all), :controller => 'issues', - :action => 'index', - :set_filter => 1, - :watcher_id => 'me', - :sort => 'updated_on:desc' %>

-<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/47/479ac14fe82d486e34532603c24b409ad8800748.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/47/479ac14fe82d486e34532603c24b409ad8800748.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,405 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 IssueQuery < Query + + self.queried_class = Issue + + self.available_columns = [ + QueryColumn.new(:id, :sortable => "#{Issue.table_name}.id", :default_order => 'desc', :caption => '#', :frozen => true), + QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true), + QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true), + QueryColumn.new(:parent, :sortable => ["#{Issue.table_name}.root_id", "#{Issue.table_name}.lft ASC"], :default_order => 'desc', :caption => :field_parent_issue), + QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true), + QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true), + QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"), + QueryColumn.new(:author, :sortable => lambda {User.fields_for_order_statement("authors")}, :groupable => true), + QueryColumn.new(:assigned_to, :sortable => lambda {User.fields_for_order_statement}, :groupable => true), + QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'), + QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true), + QueryColumn.new(:fixed_version, :sortable => lambda {Version.fields_for_order_statement}, :groupable => true), + QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"), + QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"), + QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"), + QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true), + QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'), + QueryColumn.new(:closed_on, :sortable => "#{Issue.table_name}.closed_on", :default_order => 'desc'), + QueryColumn.new(:relations, :caption => :label_related_issues), + QueryColumn.new(:description, :inline => false) + ] + + scope :visible, lambda {|*args| + user = args.shift || User.current + base = Project.allowed_to_condition(user, :view_issues, *args) + user_id = user.logged? ? user.id : 0 + + includes(:project).where("(#{table_name}.project_id IS NULL OR (#{base})) AND (#{table_name}.is_public = ? OR #{table_name}.user_id = ?)", true, user_id) + } + + def initialize(attributes=nil, *args) + super attributes + self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} } + end + + # Returns true if the query is visible to +user+ or the current user. + def visible?(user=User.current) + (project.nil? || user.allowed_to?(:view_issues, project)) && (self.is_public? || self.user_id == user.id) + end + + def initialize_available_filters + principals = [] + subprojects = [] + versions = [] + categories = [] + issue_custom_fields = [] + + if project + principals += project.principals.sort + unless project.leaf? + subprojects = project.descendants.visible.all + principals += Principal.member_of(subprojects) + end + versions = project.shared_versions.all + categories = project.issue_categories.all + issue_custom_fields = project.all_issue_custom_fields + else + if all_projects.any? + principals += Principal.member_of(all_projects) + end + versions = Version.visible.find_all_by_sharing('system') + issue_custom_fields = IssueCustomField.where(:is_filter => true, :is_for_all => true).all + end + principals.uniq! + principals.sort! + users = principals.select {|p| p.is_a?(User)} + + + add_available_filter "status_id", + :type => :list_status, :values => IssueStatus.sorted.all.collect{|s| [s.name, s.id.to_s] } + + if project.nil? + project_values = [] + if User.current.logged? && User.current.memberships.any? + project_values << ["<< #{l(:label_my_projects).downcase} >>", "mine"] + end + project_values += all_projects_values + add_available_filter("project_id", + :type => :list, :values => project_values + ) unless project_values.empty? + end + + add_available_filter "tracker_id", + :type => :list, :values => trackers.collect{|s| [s.name, s.id.to_s] } + add_available_filter "priority_id", + :type => :list, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } + + author_values = [] + author_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged? + author_values += users.collect{|s| [s.name, s.id.to_s] } + add_available_filter("author_id", + :type => :list, :values => author_values + ) unless author_values.empty? + + assigned_to_values = [] + assigned_to_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged? + assigned_to_values += (Setting.issue_group_assignment? ? + principals : users).collect{|s| [s.name, s.id.to_s] } + add_available_filter("assigned_to_id", + :type => :list_optional, :values => assigned_to_values + ) unless assigned_to_values.empty? + + group_values = Group.all.collect {|g| [g.name, g.id.to_s] } + add_available_filter("member_of_group", + :type => :list_optional, :values => group_values + ) unless group_values.empty? + + role_values = Role.givable.collect {|r| [r.name, r.id.to_s] } + add_available_filter("assigned_to_role", + :type => :list_optional, :values => role_values + ) unless role_values.empty? + + if versions.any? + add_available_filter "fixed_version_id", + :type => :list_optional, + :values => versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } + end + + if categories.any? + add_available_filter "category_id", + :type => :list_optional, + :values => categories.collect{|s| [s.name, s.id.to_s] } + end + + add_available_filter "subject", :type => :text + add_available_filter "created_on", :type => :date_past + add_available_filter "updated_on", :type => :date_past + add_available_filter "closed_on", :type => :date_past + add_available_filter "start_date", :type => :date + add_available_filter "due_date", :type => :date + add_available_filter "estimated_hours", :type => :float + add_available_filter "done_ratio", :type => :integer + + if User.current.allowed_to?(:set_issues_private, nil, :global => true) || + User.current.allowed_to?(:set_own_issues_private, nil, :global => true) + add_available_filter "is_private", + :type => :list, + :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]] + end + + if User.current.logged? + add_available_filter "watcher_id", + :type => :list, :values => [["<< #{l(:label_me)} >>", "me"]] + end + + if subprojects.any? + add_available_filter "subproject_id", + :type => :list_subprojects, + :values => subprojects.collect{|s| [s.name, s.id.to_s] } + end + + add_custom_fields_filters(issue_custom_fields) + + add_associations_custom_fields_filters :project, :author, :assigned_to, :fixed_version + + IssueRelation::TYPES.each do |relation_type, options| + add_available_filter relation_type, :type => :relation, :label => options[:name] + end + + Tracker.disabled_core_fields(trackers).each {|field| + delete_available_filter field + } + end + + def available_columns + return @available_columns if @available_columns + @available_columns = self.class.available_columns.dup + @available_columns += (project ? + project.all_issue_custom_fields : + IssueCustomField.all + ).collect {|cf| QueryCustomFieldColumn.new(cf) } + + if User.current.allowed_to?(:view_time_entries, project, :global => true) + index = nil + @available_columns.each_with_index {|column, i| index = i if column.name == :estimated_hours} + index = (index ? index + 1 : -1) + # insert the column after estimated_hours or at the end + @available_columns.insert index, QueryColumn.new(:spent_hours, + :sortable => "COALESCE((SELECT SUM(hours) FROM #{TimeEntry.table_name} WHERE #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id), 0)", + :default_order => 'desc', + :caption => :label_spent_time + ) + end + + if User.current.allowed_to?(:set_issues_private, nil, :global => true) || + User.current.allowed_to?(:set_own_issues_private, nil, :global => true) + @available_columns << QueryColumn.new(:is_private, :sortable => "#{Issue.table_name}.is_private") + end + + disabled_fields = Tracker.disabled_core_fields(trackers).map {|field| field.sub(/_id$/, '')} + @available_columns.reject! {|column| + disabled_fields.include?(column.name.to_s) + } + + @available_columns + end + + def default_columns_names + @default_columns_names ||= begin + default_columns = Setting.issue_list_default_columns.map(&:to_sym) + + project.present? ? default_columns : [:project] | default_columns + end + end + + # Returns the issue count + def issue_count + Issue.visible.count(:include => [:status, :project], :conditions => statement) + rescue ::ActiveRecord::StatementInvalid => e + raise StatementInvalid.new(e.message) + end + + # Returns the issue count by group or nil if query is not grouped + def issue_count_by_group + r = nil + if grouped? + begin + # Rails3 will raise an (unexpected) RecordNotFound if there's only a nil group value + r = Issue.visible.count(:joins => joins_for_order_statement(group_by_statement), :group => group_by_statement, :include => [:status, :project], :conditions => statement) + rescue ActiveRecord::RecordNotFound + r = {nil => issue_count} + end + c = group_by_column + if c.is_a?(QueryCustomFieldColumn) + r = r.keys.inject({}) {|h, k| h[c.custom_field.cast_value(k)] = r[k]; h} + end + end + r + rescue ::ActiveRecord::StatementInvalid => e + raise StatementInvalid.new(e.message) + end + + # Returns the issues + # Valid options are :order, :offset, :limit, :include, :conditions + def issues(options={}) + order_option = [group_by_sort_order, options[:order]].flatten.reject(&:blank?) + + issues = Issue.visible.where(options[:conditions]).all( + :include => ([:status, :project] + (options[:include] || [])).uniq, + :conditions => statement, + :order => order_option, + :joins => joins_for_order_statement(order_option.join(',')), + :limit => options[:limit], + :offset => options[:offset] + ) + + if has_column?(:spent_hours) + Issue.load_visible_spent_hours(issues) + end + if has_column?(:relations) + Issue.load_visible_relations(issues) + end + issues + rescue ::ActiveRecord::StatementInvalid => e + raise StatementInvalid.new(e.message) + end + + # Returns the issues ids + def issue_ids(options={}) + order_option = [group_by_sort_order, options[:order]].flatten.reject(&:blank?) + + Issue.visible.scoped(:conditions => options[:conditions]).scoped(:include => ([:status, :project] + (options[:include] || [])).uniq, + :conditions => statement, + :order => order_option, + :joins => joins_for_order_statement(order_option.join(',')), + :limit => options[:limit], + :offset => options[:offset]).find_ids + rescue ::ActiveRecord::StatementInvalid => e + raise StatementInvalid.new(e.message) + end + + # Returns the journals + # Valid options are :order, :offset, :limit + def journals(options={}) + Journal.visible.all( + :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}], + :conditions => statement, + :order => options[:order], + :limit => options[:limit], + :offset => options[:offset] + ) + rescue ::ActiveRecord::StatementInvalid => e + raise StatementInvalid.new(e.message) + end + + # Returns the versions + # Valid options are :conditions + def versions(options={}) + Version.visible.where(options[:conditions]).all( + :include => :project, + :conditions => project_statement + ) + rescue ::ActiveRecord::StatementInvalid => e + raise StatementInvalid.new(e.message) + end + + def sql_for_watcher_id_field(field, operator, value) + db_table = Watcher.table_name + "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " + + sql_for_field(field, '=', value, db_table, 'user_id') + ')' + end + + def sql_for_member_of_group_field(field, operator, value) + if operator == '*' # Any group + groups = Group.all + operator = '=' # Override the operator since we want to find by assigned_to + elsif operator == "!*" + groups = Group.all + operator = '!' # Override the operator since we want to find by assigned_to + else + groups = Group.find_all_by_id(value) + end + groups ||= [] + + members_of_groups = groups.inject([]) {|user_ids, group| + user_ids + group.user_ids + [group.id] + }.uniq.compact.sort.collect(&:to_s) + + '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')' + end + + def sql_for_assigned_to_role_field(field, operator, value) + case operator + when "*", "!*" # Member / Not member + sw = operator == "!*" ? 'NOT' : '' + nl = operator == "!*" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : '' + "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}" + + " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id))" + when "=", "!" + role_cond = value.any? ? + "#{MemberRole.table_name}.role_id IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")" : + "1=0" + + sw = operator == "!" ? 'NOT' : '' + nl = operator == "!" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : '' + "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}, #{MemberRole.table_name}" + + " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id AND #{Member.table_name}.id = #{MemberRole.table_name}.member_id AND #{role_cond}))" + end + end + + def sql_for_is_private_field(field, operator, value) + op = (operator == "=" ? 'IN' : 'NOT IN') + va = value.map {|v| v == '0' ? connection.quoted_false : connection.quoted_true}.uniq.join(',') + + "#{Issue.table_name}.is_private #{op} (#{va})" + end + + def sql_for_relations(field, operator, value, options={}) + relation_options = IssueRelation::TYPES[field] + return relation_options unless relation_options + + relation_type = field + join_column, target_join_column = "issue_from_id", "issue_to_id" + if relation_options[:reverse] || options[:reverse] + relation_type = relation_options[:reverse] || relation_type + join_column, target_join_column = target_join_column, join_column + end + + sql = case operator + when "*", "!*" + op = (operator == "*" ? 'IN' : 'NOT IN') + "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}')" + when "=", "!" + op = (operator == "=" ? 'IN' : 'NOT IN') + "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = #{value.first.to_i})" + when "=p", "=!p", "!p" + op = (operator == "!p" ? 'NOT IN' : 'IN') + comp = (operator == "=!p" ? '<>' : '=') + "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name}, #{Issue.table_name} relissues WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = relissues.id AND relissues.project_id #{comp} #{value.first.to_i})" + end + + if relation_options[:sym] == field && !options[:reverse] + sqls = [sql, sql_for_relations(field, operator, value, :reverse => true)] + sqls.join(["!", "!*", "!p"].include?(operator) ? " AND " : " OR ") + else + sql + end + end + + IssueRelation::TYPES.keys.each do |relation_type| + alias_method "sql_for_#{relation_type}_field".to_sym, :sql_for_relations + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/47/47cd8b345d1020290ba00f3aec67bf352663d44c.svn-base --- a/.svn/pristine/47/47cd8b345d1020290ba00f3aec67bf352663d44c.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,72 +0,0 @@ -module CodeRay -module Encoders - - # = XML Encoder - # - # Uses REXML. Very slow. - class XML < Encoder - - register_for :xml - - FILE_EXTENSION = 'xml' - - autoload :REXML, 'rexml/document' - - DEFAULT_OPTIONS = { - :tab_width => 8, - :pretty => -1, - :transitive => false, - } - - protected - def setup options - super - - @doc = REXML::Document.new - @doc << REXML::XMLDecl.new - @tab_width = options[:tab_width] - @root = @node = @doc.add_element('coderay-tokens') - end - - def finish options - @doc.write @out, options[:pretty], options[:transitive], true - - super - end - - public - def text_token text, kind - if kind == :space - token = @node - else - token = @node.add_element kind.to_s - end - text.scan(/(\x20+)|(\t+)|(\n)|[^\x20\t\n]+/) do |space, tab, nl| - case - when space - token << REXML::Text.new(space, true) - when tab - token << REXML::Text.new(tab, true) - when nl - token << REXML::Text.new(nl, true) - else - token << REXML::Text.new($&) - end - end - end - - def begin_group kind - @node = @node.add_element kind.to_s - end - - def end_group kind - if @node == @root - raise 'no token to close!' - end - @node = @node.parent - end - - end - -end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/48/4829d2fe162c5f9c318c8ff9b05ff5fc25513144.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/48/4829d2fe162c5f9c318c8ff9b05ff5fc25513144.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,21 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +module WelcomeHelper +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/48/482dc54fc612e4f812361911050f6c949928488f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/48/482dc54fc612e4f812361911050f6c949928488f.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,50 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 CustomFieldValue + attr_accessor :custom_field, :customized, :value + + def custom_field_id + custom_field.id + end + + def true? + self.value == '1' + end + + def editable? + custom_field.editable? + end + + def visible? + custom_field.visible? + end + + def required? + custom_field.is_required? + end + + def to_s + value.to_s + end + + def validate_value + custom_field.validate_field_value(value).each do |message| + customized.errors.add(:base, custom_field.name + ' ' + message) + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/48/4852f1cf9dc40b28e85de8175f26734a857d7d04.svn-base --- a/.svn/pristine/48/4852f1cf9dc40b28e85de8175f26734a857d7d04.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -class SharedEngineController < ApplicationController - def an_action - render_class_and_action 'from alpha_engine' - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/48/4881b5ad6e4efa4165188246776dbe492c56e6df.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/48/4881b5ad6e4efa4165188246776dbe492c56e6df.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,112 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 Principal < ActiveRecord::Base + self.table_name = "#{table_name_prefix}users#{table_name_suffix}" + + # Account statuses + STATUS_ANONYMOUS = 0 + STATUS_ACTIVE = 1 + STATUS_REGISTERED = 2 + STATUS_LOCKED = 3 + + has_many :members, :foreign_key => 'user_id', :dependent => :destroy + has_many :memberships, :class_name => 'Member', :foreign_key => 'user_id', :include => [ :project, :roles ], :conditions => "#{Project.table_name}.status<>#{Project::STATUS_ARCHIVED}", :order => "#{Project.table_name}.name" + has_many :projects, :through => :memberships + has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify + + # Groups and active users + scope :active, lambda { where(:status => STATUS_ACTIVE) } + + scope :like, lambda {|q| + q = q.to_s + if q.blank? + where({}) + else + pattern = "%#{q}%" + sql = %w(login firstname lastname mail).map {|column| "LOWER(#{table_name}.#{column}) LIKE LOWER(:p)"}.join(" OR ") + params = {:p => pattern} + if q =~ /^(.+)\s+(.+)$/ + a, b = "#{$1}%", "#{$2}%" + sql << " OR (LOWER(#{table_name}.firstname) LIKE LOWER(:a) AND LOWER(#{table_name}.lastname) LIKE LOWER(:b))" + sql << " OR (LOWER(#{table_name}.firstname) LIKE LOWER(:b) AND LOWER(#{table_name}.lastname) LIKE LOWER(:a))" + params.merge!(:a => a, :b => b) + end + where(sql, params) + end + } + + # Principals that are members of a collection of projects + scope :member_of, lambda {|projects| + projects = [projects] unless projects.is_a?(Array) + if projects.empty? + where("1=0") + else + ids = projects.map(&:id) + active.uniq.joins(:members).where("#{Member.table_name}.project_id IN (?)", ids) + end + } + # Principals that are not members of projects + scope :not_member_of, lambda {|projects| + projects = [projects] unless projects.is_a?(Array) + if projects.empty? + where("1=0") + else + ids = projects.map(&:id) + where("#{Principal.table_name}.id NOT IN (SELECT DISTINCT user_id FROM #{Member.table_name} WHERE project_id IN (?))", ids) + end + } + scope :sorted, lambda { order(*Principal.fields_for_order_statement)} + + before_create :set_default_empty_values + + def name(formatter = nil) + to_s + end + + def <=>(principal) + if principal.nil? + -1 + elsif self.class.name == principal.class.name + self.to_s.downcase <=> principal.to_s.downcase + else + # groups after users + principal.class.name <=> self.class.name + end + end + + # Returns an array of fields names than can be used to make an order statement for principals. + # Users are sorted before Groups. + # Examples: + def self.fields_for_order_statement(table=nil) + table ||= table_name + columns = ['type DESC'] + (User.name_formatter[:order] - ['id']) + ['lastname', 'id'] + columns.uniq.map {|field| "#{table}.#{field}"} + end + + protected + + # Make sure we don't try to insert NULL values (see #4632) + def set_default_empty_values + self.login ||= '' + self.hashed_password ||= '' + self.firstname ||= '' + self.lastname ||= '' + self.mail ||= '' + true + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/48/48978cef54e6b0d716c9ba5b5c6c5a3e24326336.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/48/48978cef54e6b0d716c9ba5b5c6c5a3e24326336.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,154 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +module TimelogHelper + include ApplicationHelper + + def render_timelog_breadcrumb + links = [] + links << link_to(l(:label_project_all), {:project_id => nil, :issue_id => nil}) + links << link_to(h(@project), {:project_id => @project, :issue_id => nil}) if @project + if @issue + if @issue.visible? + links << link_to_issue(@issue, :subject => false) + else + links << "##{@issue.id}" + end + end + breadcrumb links + end + + # Returns a collection of activities for a select field. time_entry + # is optional and will be used to check if the selected TimeEntryActivity + # is active. + def activity_collection_for_select_options(time_entry=nil, project=nil) + project ||= @project + if project.nil? + activities = TimeEntryActivity.shared.active + else + activities = project.activities + end + + collection = [] + if time_entry && time_entry.activity && !time_entry.activity.active? + collection << [ "--- #{l(:actionview_instancetag_blank_option)} ---", '' ] + else + collection << [ "--- #{l(:actionview_instancetag_blank_option)} ---", '' ] unless activities.detect(&:is_default) + end + activities.each { |a| collection << [a.name, a.id] } + collection + end + + def select_hours(data, criteria, value) + if value.to_s.empty? + data.select {|row| row[criteria].blank? } + else + data.select {|row| row[criteria].to_s == value.to_s} + end + end + + def sum_hours(data) + sum = 0 + data.each do |row| + sum += row['hours'].to_f + end + sum + end + + def options_for_period_select(value) + options_for_select([[l(:label_all_time), 'all'], + [l(:label_today), 'today'], + [l(:label_yesterday), 'yesterday'], + [l(:label_this_week), 'current_week'], + [l(:label_last_week), 'last_week'], + [l(:label_last_n_weeks, 2), 'last_2_weeks'], + [l(:label_last_n_days, 7), '7_days'], + [l(:label_this_month), 'current_month'], + [l(:label_last_month), 'last_month'], + [l(:label_last_n_days, 30), '30_days'], + [l(:label_this_year), 'current_year']], + value) + end + + def format_criteria_value(criteria_options, value) + if value.blank? + "[#{l(:label_none)}]" + elsif k = criteria_options[:klass] + obj = k.find_by_id(value.to_i) + if obj.is_a?(Issue) + obj.visible? ? "#{obj.tracker} ##{obj.id}: #{obj.subject}" : "##{obj.id}" + else + obj + end + else + format_value(value, criteria_options[:format]) + end + end + + def report_to_csv(report) + decimal_separator = l(:general_csv_decimal_separator) + export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv| + # Column headers + headers = report.criteria.collect {|criteria| l(report.available_criteria[criteria][:label]) } + headers += report.periods + headers << l(:label_total_time) + csv << headers.collect {|c| Redmine::CodesetUtil.from_utf8( + c.to_s, + l(:general_csv_encoding) ) } + # Content + report_criteria_to_csv(csv, report.available_criteria, report.columns, report.criteria, report.periods, report.hours) + # Total row + str_total = Redmine::CodesetUtil.from_utf8(l(:label_total_time), l(:general_csv_encoding)) + row = [ str_total ] + [''] * (report.criteria.size - 1) + total = 0 + report.periods.each do |period| + sum = sum_hours(select_hours(report.hours, report.columns, period.to_s)) + total += sum + row << (sum > 0 ? ("%.2f" % sum).gsub('.',decimal_separator) : '') + end + row << ("%.2f" % total).gsub('.',decimal_separator) + csv << row + end + export + end + + def report_criteria_to_csv(csv, available_criteria, columns, criteria, periods, hours, level=0) + decimal_separator = l(:general_csv_decimal_separator) + hours.collect {|h| h[criteria[level]].to_s}.uniq.each do |value| + hours_for_value = select_hours(hours, criteria[level], value) + next if hours_for_value.empty? + row = [''] * level + row << Redmine::CodesetUtil.from_utf8( + format_criteria_value(available_criteria[criteria[level]], value).to_s, + l(:general_csv_encoding) ) + row += [''] * (criteria.length - level - 1) + total = 0 + periods.each do |period| + sum = sum_hours(select_hours(hours_for_value, columns, period.to_s)) + total += sum + row << (sum > 0 ? ("%.2f" % sum).gsub('.',decimal_separator) : '') + end + row << ("%.2f" % total).gsub('.',decimal_separator) + csv << row + if criteria.length > level + 1 + report_criteria_to_csv(csv, available_criteria, columns, criteria, periods, hours_for_value, level + 1) + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/48/48b2840db60b271c593d21c38917c44a5158b68b.svn-base --- a/.svn/pristine/48/48b2840db60b271c593d21c38917c44a5158b68b.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,29 +0,0 @@ -# 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 -r 0a574315af3e -r 4f746d8966dd .svn/pristine/48/48b4396c87b793f88c5ab4bc586ea45f81977244.svn-base --- a/.svn/pristine/48/48b4396c87b793f88c5ab4bc586ea45f81977244.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,28 +0,0 @@ -<%= error_messages_for 'message' %> -<% replying ||= false %> - -
- -


-<%= f.text_field :subject, :size => 120, :id => "message_subject" %> - -<% if !replying && User.current.allowed_to?(:edit_messages, @project) %> - - -<% end %> -

- -<% if !replying && !@message.new_record? && User.current.allowed_to?(:edit_messages, @project) %> -


- <%= f.select :board_id, @project.boards.collect {|b| [b.name, b.id]} %>

-<% end %> - -

-<%= label_tag "message_content", l(:description_message_content), :class => "hidden-for-sighted" %> -<%= f.text_area :content, :cols => 80, :rows => 15, :class => 'wiki-edit', :id => 'message_content' %>

-<%= wikitoolbar_for 'message_content' %> - - -

<%= l(:label_attachment_plural) %>
-<%= render :partial => 'attachments/form' %>

-
diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/49/493a8cd26b7fa714fe78dd4274ecdc966ff8a00e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/49/493a8cd26b7fa714fe78dd4274ecdc966ff8a00e.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,47 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 SubclassFactory + def self.included(base) + base.extend ClassMethods + end + + module ClassMethods + def get_subclass(class_name) + klass = nil + begin + klass = class_name.to_s.classify.constantize + rescue + # invalid class name + end + unless subclasses.include? klass + klass = nil + end + klass + end + + # Returns an instance of the given subclass name + def new_subclass_instance(class_name, *args) + klass = get_subclass(class_name) + if klass + klass.new(*args) + end + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/49/49a09daa149b6d9350cac1817bdfb249c5164fda.svn-base --- a/.svn/pristine/49/49a09daa149b6d9350cac1817bdfb249c5164fda.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -

<%= link_to l(:label_tracker_plural), trackers_path %> » <%=h @tracker %>

- -<% form_for @tracker, :builder => TabularFormBuilder do |f| %> -<%= render :partial => 'form', :locals => { :f => f } %> -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/49/49af1b38eba0e08163a526b80740e126cc5034c1.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/49/49af1b38eba0e08163a526b80740e126cc5034c1.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,1148 @@ +# Lithuanian translations for Ruby on Rails +# by Laurynas Butkus (laurynas.butkus@gmail.com) +# Redmine translation by Gediminas Muižis gediminas.muizis@gmail.com +# and Sergej Jegorov sergej.jegorov@gmail.com +# and Gytis Gurklys gytis.gurklys@gmail.com +# and Andrius KriuÄkovas andrius.kriuckovas@gmail.com + +lt: + direction: ltr + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%Y-%m-%d" + short: "%b %d" + long: "%B %d, %Y" + + day_names: [sekmadienis, pirmadienis, antradienis, treÄiadienis, ketvirtadienis, penktadienis, Å¡eÅ¡tadienis] +# standalone_day_names: [Sekmadienis, Pirmadienis, Antradienis, TreÄiadienis, Ketvirtadienis, Penktadienis, Å eÅ¡tadienis] + abbr_day_names: [Sek, Pir, Ant, Tre, Ket, Pen, Å eÅ¡] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, sausio, vasario, kovo, balandžio, gegužės, birželio, liepos, rugpjÅ«Äio, rugsÄ—jo, spalio, lapkriÄio, gruodžio] + abbr_month_names: [~, Sau, Vas, Kov, Bal, Geg, Bir, Lie, Rgp, Rgs, Spa, Lap, Grd] + # Used in date_select and datime_select. + order: + - :year + - :month + - :day + + time: + formats: + default: "%a, %d %b %Y %H:%M:%S %z" + time: "%H:%M" + short: "%d %b %H:%M" + long: "%B %d, %Y %H:%M" + am: "ryto" + pm: "vakaro" + + datetime: + distance_in_words: + half_a_minute: "pusÄ— minutÄ—s" + less_than_x_seconds: + one: "mažiau nei %{count} sekundÄ™" + few: "mažiau nei %{count} sekundes" + many: "mažiau nei %{count} sekundžių" + other: "mažiau nei %{count} sekundžių" + x_seconds: + one: "%{count} sekundÄ—" + few: "%{count} sekundÄ—s" + many: "%{count} sekundžių" + other: "%{count} sekundžių" + less_than_x_minutes: + one: "mažiau nei minutÄ™" + other: "mažiau nei %{count} minutes(Äių)" + x_minutes: + one: "1 minutÄ™" + other: "%{count} minutes(Äių)" + about_x_hours: + one: "apie 1 valandÄ…" + other: "apie %{count} valandas(ų)" + x_hours: + one: "1 valandÄ…" + other: "%{count} valandas(ų)" + x_days: + one: "1 dienÄ…" + other: "%{count} dienas(ų)" + about_x_months: + one: "apie 1 mÄ—nuo" + other: "apie %{count} mÄ—n." + x_months: + one: "1 mÄ—nuo" + other: "%{count} mÄ—n." + about_x_years: + one: "apie 1 metus" + other: "apie %{count} metų" + over_x_years: + one: "virÅ¡ 1 metų" + other: "virÅ¡ %{count} metų" + almost_x_years: + one: "beveik 1 metus" + other: "beveik %{count} metai(us)" + prompts: + year: "Metai" + month: "MÄ—nuo" + day: "Diena" + hour: "Valanda" + minute: "MinutÄ—" + second: "SekundÄ—s" + + number: + format: + separator: "," + delimiter: " " + precision: 3 + + currency: + format: + format: "%n %u" + unit: "Lt" + separator: "," + delimiter: " " + precision: 2 + + percentage: + format: + delimiter: "" + + precision: + format: + delimiter: "" + + human: + format: + delimiter: "" + precision: 3 + storage_units: + # Storage units output formatting. + # %u is the storage unit, %n is the number (default: 2 MB) + format: "%n %u" + units: + byte: + one: "baitas" + few: "baitų(ai)" + many: "baitų(ai)" + other: "baitų(ai)" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + +# Used in array.to_sentence. + support: + array: + # Rails 2.2 + sentence_connector: "ir" + skip_last_comma: true + # Rails 2.3 + words_connector: ", " + two_words_connector: " ir " + last_word_connector: " ir " + + activerecord: + errors: + template: + header: + one: "IÅ¡saugant objektÄ… %{model} rasta %{count} klaida" + few: "IÅ¡saugant objektÄ… %{model} rasta %{count} klaidų" + many: "IÅ¡saugant objektÄ… %{model} rastos %{count} klaidos" + other: "IÅ¡saugant objektÄ… %{model} rastos %{count} klaidos" + body: "Å iuose laukuose yra klaidų:" + + messages: + inclusion: "nenumatyta reikÅ¡mÄ—" + exclusion: "užimtas" + invalid: "neteisingas" + confirmation: "neteisingai pakartotas" + accepted: "turi bÅ«ti patvirtintas" + empty: "negali bÅ«ti tuÅ¡Äias" + blank: "negali bÅ«ti tuÅ¡Äias" + too_long: + one: "per ilgas (daugiausiai %{count} simbolius)" + few: "per ilgas (daugiausiai %{count} simboliu)" + many: "per ilgas (daugiausiai %{count} simboliu)" + other: "per ilgas (daugiausiai %{count} simboliai)" + too_short: + one: "per trumpas (mažiausiai %{count} simbolius)" + few: "per trumpas (mažiausiai %{count} simboliu)" + many: "per trumpas (mažiausiai %{count} simboliu)" + other: "per trumpas (mažiausiai %{count} simboliai)" + wrong_length: + one: "neteisingo ilgio (turi bÅ«ti lygiai %{count} simbolius)" + few: "neteisingo ilgio (turi bÅ«ti lygiai %{count} simboliu)" + many: "neteisingo ilgio (turi bÅ«ti lygiai %{count} simboliu)" + other: "neteisingo ilgio (turi bÅ«ti lygiai %{count} simboliai)" + taken: "jau užimtas" + not_a_number: "ne skaiÄius" + not_a_date: "is not a valid date" + greater_than: "turi bÅ«ti didesnis už %{count}" + greater_than_or_equal_to: "turi bÅ«ti didesnis arba lygus %{count}" + equal_to: "turi bÅ«ti lygus %{count}" + less_than: "turi bÅ«ti mažesnis už %{count}" + less_than_or_equal_to: "turi bÅ«ti mažesnis arba lygus %{count}" + odd: "turi bÅ«ti nelyginis" + even: "turi bÅ«ti lyginis" + greater_than_start_date: "turi bÅ«ti didesnÄ— negu pradžios data" + not_same_project: "nepriklauso tam paÄiam projektui" + circular_dependency: "Å is ryÅ¡ys sukurtų ciklinÄ™ priklausomybÄ™" + cant_link_an_issue_with_a_descendant: "Darbas negali bÅ«ti susietas su viena iÅ¡ savo darbo dalių" + + actionview_instancetag_blank_option: PraÅ¡om parinkti + + 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_decimal_separator: '.' + general_csv_encoding: UTF-8 + general_pdf_encoding: UTF-8 + general_first_day_of_week: '1' + + 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 nuorodÄ…, 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Å¡tÄ… 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 yra paÅ¡alintas. + notice_locking_conflict: Duomenys atnaujinti kito vartotojo. + notice_not_authorized: JÅ«s neturite teisių gauti prieigÄ… prie Å¡io puslapio. + notice_not_authorized_archived_project: Projektas, kurį bandote atidaryti, buvo suarchyvuotas. + notice_email_sent: "LaiÅ¡kas iÅ¡siųstas %{value}" + notice_email_error: "LaiÅ¡ko siuntimo metu įvyko klaida (%{value})" + notice_feeds_access_key_reseted: JÅ«sų RSS raktas buvo atnaujintas. + notice_api_access_key_reseted: JÅ«sų API prieigos raktas buvo atnaujintas. + notice_failed_to_save_issues: "Nepavyko iÅ¡saugoti %{count} problemos(ų) iÅ¡ %{total} pasirinkto: %{ids}." + notice_failed_to_save_members: "Nepavyko iÅ¡saugoti nario(ių): %{errors}." + notice_no_issue_selected: "Nepasirinkta nÄ— viena problema! PraÅ¡om pažymÄ—ti problemÄ…, kuriÄ… norite redaguoti." + notice_account_pending: "JÅ«sų paskyra buvo sukurta ir dabar laukiama administratoriaus patvirtinimo." + notice_default_data_loaded: Numatytoji konfiguracija sÄ—kmingai užkrauta. + notice_unable_delete_version: Neįmanoma panaikinti versijÄ…. + notice_unable_delete_time_entry: Neįmano iÅ¡trinti laiko žurnalo įrašą. + notice_issue_done_ratios_updated: Problemos baigtumo rodikliai atnaujinti. + notice_gantt_chart_truncated: Grafikas buvo sutrumpintas, kadangi jis virÅ¡ija maksimalų (%{max}) leistinų atvaizduoti elementų kiekį + notice_issue_successful_create: Darbas %{id} sukurtas. + + error_can_t_load_default_data: "Numatytoji konfiguracija negali bÅ«ti užkrauta: %{value}" + error_scm_not_found: "Duomenys ir/ar pakeitimai saugykloje(repozitorojoje) neegzistuoja." + error_scm_command_failed: "Ä®vyko klaida jungiantis prie saugyklos: %{value}" + error_scm_annotate: "Ä®raÅ¡as neegzistuoja arba negalima jo atvaizduoti." + error_scm_annotate_big_text_file: "Ä®raÅ¡o negalima atvaizduoti, nes jis virÅ¡ija maksimalų tekstinio failo dydį." + error_issue_not_found_in_project: 'Darbas nerastas arba nesuriÅ¡tas su Å¡iuo projektu' + error_no_tracker_in_project: 'Joks pÄ—dsekys nesusietas su Å¡iuo projektu. PraÅ¡om patikrinti Projekto nustatymus.' + error_no_default_issue_status: Nenustatyta numatytoji darbų bÅ«sena. PraÅ¡ome patikrinti konfigÅ«ravimÄ… ("Administravimas -> Darbų bÅ«senos"). + error_can_not_delete_custom_field: Negalima iÅ¡trinti kliento lauko + error_can_not_delete_tracker: "Å is pÄ—dsekys turi įraÅ¡us ir todÄ—l negali bÅ«ti iÅ¡trintas." + error_can_not_remove_role: "Å i rolÄ— yra naudojama ir negali bÅ«ti iÅ¡trinta." + error_can_not_reopen_issue_on_closed_version: Uždarytai versijai priskirtas darbas negali bÅ«ti atnaujintas. + error_can_not_archive_project: Å io projekto negalima suarchyvuoti + error_issue_done_ratios_not_updated: "Ä®raÅ¡o baigtumo rodikliai nebuvo atnaujinti. " + error_workflow_copy_source: 'PraÅ¡ome pasirinkti pirminį Å¡altinio seklį arba rolÄ™' + error_workflow_copy_target: 'PraÅ¡ome pasirinkti galutinį paskirties seklį(-ius) arba rolÄ™(-s)' + error_unable_delete_issue_status: 'Negalima iÅ¡trinti darbo statuso' + error_unable_to_connect: Negalima prisijungti (%{value}) + error_attachment_too_big: "Å i byla negali bÅ«ti įkelta, nes virÅ¡ija maksimaliÄ… (%{max_size}) leistinÄ… bylos apimtį" + warning_attachments_not_saved: "%{count} byla(ų) negali bÅ«ti iÅ¡saugota." + + mail_subject_lost_password: "JÅ«sų %{value} slaptažodis" + mail_body_lost_password: 'NorÄ—dami pakeisti slaptažodį, spauskite nuorodÄ…:' + mail_subject_register: "JÅ«sų %{value} paskyros aktyvavimas" + mail_body_register: 'NorÄ—dami aktyvuoti paskyrÄ…, spauskite nuorodÄ…:' + mail_body_account_information_external: "JÅ«s galite naudoti JÅ«sų %{value} paskyrÄ…, norÄ—dami prisijungti." + mail_body_account_information: Informacija apie JÅ«sų paskyrÄ… + mail_subject_account_activation_request: "%{value} paskyros aktyvavimo praÅ¡ymas" + mail_body_account_activation_request: "Užsiregistravo naujas vartotojas (%{value}). Jo paskyra laukia jÅ«sų patvirtinimo:" + mail_subject_reminder: "%{count} jums priskirti darbai per artimiausias %{days} dienų(as)" + mail_body_reminder: "%{count} darbas(ai), kurie yra jums priskirti, baigiasi per artimiausias %{days} dienų(as):" + mail_subject_wiki_content_added: "'%{id}' pridÄ—tas wiki puslapis" + mail_body_wiki_content_added: "'%{id}' wiki puslapį pridÄ—jo %{author}." + mail_subject_wiki_content_updated: "'%{id}' atnaujintas wiki puslapis" + mail_body_wiki_content_updated: "'%{id}' wiki puslapį atnaujino %{author}." + + + field_name: Pavadinimas + field_description: ApraÅ¡as + field_summary: Santrauka + field_is_required: Reikalaujama + field_firstname: Vardas + field_lastname: PavardÄ— + field_mail: El. paÅ¡tas + field_filename: Failas + field_filesize: Dydis + field_downloads: Atsiuntimai + field_author: Autorius + field_created_on: Sukurta + field_updated_on: Atnaujintas(a) + field_field_format: Formatas + 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: TikslinÄ— versija + field_user: Vartotojas + field_principal: Vardas + field_role: Vaidmuo + field_homepage: Pagrindinis puslapis + field_is_public: VieÅ¡as + field_parent: Priklauso projektui + 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 prisijungimas + field_language: Kalba + field_effective_date: Data + field_password: Slaptažodis + field_new_password: Naujas slaptažodis + field_password_confirmation: Patvirtinimas + field_version: Versija + field_type: Tipas + field_host: Pagrindinis kompiuteris + field_port: Prievadas + field_account: Paskyra + field_base_dn: Bazinis skiriamasis vardas (base DN) + field_attr_login: Registracijos vardo požymis (login) + field_attr_firstname: Vardo požymis + field_attr_lastname: PavardÄ—s požymis + 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: SlÄ—pti mano elektroninio paÅ¡to adresÄ… + field_comments: Komentaras + 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_issue_to: 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_entries: Praleistas laikas + 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 + field_editable: Redaguojamas + field_watcher: StebÄ—tojas + field_identity_url: OpenID URL + field_content: Turinys + field_group_by: Sugrupuoti pagal + field_sharing: Dalijimasis + field_parent_issue: PagrindinÄ— užduotis + field_member_of_group: "Priskirtojo grupÄ—" + field_assigned_to_role: "Priskirtojo rolÄ—" + field_text: Teksto laukas + field_visible: Matomas + field_warn_on_leaving_unsaved: "Ä®spÄ—ti mane, kai paliekamas puslapis su neiÅ¡saugotu tekstu" + field_issues_visibility: Darbų matomumas + field_is_private: Privatus + field_commit_logs_encoding: Commit praneÅ¡imų koduotÄ— + field_scm_path_encoding: Kelio koduotÄ— + field_path_to_repository: Saugyklos kelias + field_root_directory: Root directorija + field_cvsroot: CVSROOT + field_cvs_module: Modulis + + 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: IÅ¡leidimo elektroninio paÅ¡to adresas + setting_bcc_recipients: Akli tikslios kopijos gavÄ—jai (bcc) + setting_plain_text_mail: Tik tekstas (be HTML) + setting_host_name: Pagrindinio kompiuterio vardas + setting_text_formatting: Teksto formatavimas + setting_wiki_compression: Wiki istorijos suspaudimas + setting_feeds_limit: Perdavimo turinio maksimali riba + setting_default_projects_public: Nauji projektai vieÅ¡i pagal nutylÄ—jimÄ… + setting_autofetch_changesets: Automatinis pakeitimų siuntimas + setting_sys_api_enabled: Ä®galinti WS sandÄ—lio valdymui + setting_commit_ref_keywords: Nurodymo reikÅ¡miniai žodžiai + setting_commit_fix_keywords: Fiksavimo reikÅ¡miniai žodžiai + setting_autologin: Automatinis prisijungimas + 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: PridÄ—tų failų ir saugyklų Å¡ifravimas + setting_emails_header: LaiÅ¡ko antraÅ¡tÄ— + setting_emails_footer: LaiÅ¡ko poraÅ¡tÄ— + setting_protocol: Protokolas + setting_per_page_options: Ä®rašų puslapyje nustatymas + 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: Ä®galinti SCM + setting_mail_handler_body_delimiters: "Trumpinti laiÅ¡kus po vienos iÅ¡ Å¡ių eiluÄių" + setting_mail_handler_api_enabled: Ä®galinti WS įeinantiems laiÅ¡kams + setting_mail_handler_api_key: API raktas + setting_sequential_project_identifiers: Generuoti nuoseklius projekto identifikatorius + setting_gravatar_enabled: Naudoti Gravatar vartotojo paveiksliukus + setting_gravatar_default: Gravatar paveiksliukas pagal nutylÄ—jimÄ… + setting_diff_max_lines_displayed: Maksimalus rodomas pakeitimų eiluÄių skaiÄius + setting_file_max_size_displayed: Maksimalus testinių failų dydis rodomas vienoje eilutÄ—je + setting_repository_log_display_limit: Maksimalus revizijų skaiÄius rodomas failo loge + setting_openid: Leisti OpenID prisijungimÄ… ir registracijÄ… + setting_password_min_length: Minimalus slaptažodžio ilgis + setting_new_project_user_role_id: Vartotojo vaidmuo, suteikiamas ne administratoriui, kuris sukuria projektÄ… + setting_default_projects_modules: Pagal nutylÄ—jimÄ… naujam projektui priskirti moduliai + setting_issue_done_ratio: DArbo įvykdymo progresÄ… skaiÄiuoti su + setting_issue_done_ratio_issue_field: Naudoti darbo laukÄ… + setting_issue_done_ratio_issue_status: Naudoti darbo statusÄ… + setting_start_of_week: SavaitÄ—s pradžios diena + setting_rest_api_enabled: Ä®jungti REST web service + setting_cache_formatted_text: PaslÄ—pti formatuotÄ… tekstÄ… + setting_default_notification_option: Numatytosios praneÅ¡imų nuostatos + setting_commit_logtime_enabled: Ä®jungti laiko registravimÄ… + setting_commit_logtime_activity_id: Laiko įrašų veikla + setting_gantt_items_limit: Maksimalus rodmenų skaiÄius rodomas Gantt'o grafike + setting_issue_group_assignment: Leisti darbo priskirimÄ… grupÄ—ms + setting_default_issue_start_date_to_creation_date: Naudoti dabartinÄ™ datÄ… kaip naujų darbų pradžios datÄ… + + permission_add_project: Sukurti projektÄ… + permission_add_subprojects: Kurti subprojektus + permission_edit_project: Taisyti projektÄ… + permission_select_project_modules: Parinkti projekto modulius + permission_manage_members: Valdyti narius + permission_manage_project_activities: Valdyti projekto veiklas + permission_manage_versions: Valdyti versijas + permission_manage_categories: Valdyti darbų kategorijas + permission_view_issues: UžduoÄių peržiÅ«ra + permission_add_issues: Sukurti darbus + permission_edit_issues: Redaguoti darbus + permission_manage_issue_relations: Valdyti darbų ryÅ¡ius + permission_set_issues_private: Nustatyti darbÄ… vieÅ¡u ar privaÄiu + permission_set_own_issues_private: Nustatyti savo darbus vieÅ¡ais ar privaÄiais + permission_add_issue_notes: Sukurti pastabas + permission_edit_issue_notes: Redaguoti pastabas + permission_edit_own_issue_notes: Redaguoti savo pastabas + permission_move_issues: Perkelti darbus + permission_delete_issues: PaÅ¡alinti darbus + permission_manage_public_queries: Valdyti vieÅ¡as užklausas + permission_save_queries: IÅ¡saugoti užklausas + permission_view_gantt: Matyti Gantt grafikÄ… + permission_view_calendar: Matyti kalendorių + permission_view_issue_watchers: Matyti stebÄ—tojų sÄ…rašą + permission_add_issue_watchers: PridÄ—ti stebÄ—tojus + permission_delete_issue_watchers: PaÅ¡alinti stebÄ—tojus + permission_log_time: Regsitruoti dirbtÄ… laikÄ… + permission_view_time_entries: Matyti dirbtÄ… laikÄ… + permission_edit_time_entries: Redaguoti laiko įraÅ¡us + permission_edit_own_time_entries: Redguoti savo laiko įraÅ¡us + permission_manage_news: Valdyti naujienas + permission_comment_news: Komentuoti naujienas + permission_view_documents: Matyti dokumentus + permission_manage_files: Valdyti failus + permission_view_files: Matyti failus + permission_manage_wiki: Valdyti wiki + permission_rename_wiki_pages: Pervadinti wiki puslapius + permission_delete_wiki_pages: PaÅ¡alinti wiki puslapius + permission_view_wiki_pages: Matyti wiki + permission_view_wiki_edits: Matyti wiki istorijÄ… + permission_edit_wiki_pages: Redaguoti wiki puslapius + permission_delete_wiki_pages_attachments: PaÅ¡alinti priedus + permission_protect_wiki_pages: Apsaugoti wiki puslapius + permission_manage_repository: Valdyti saugyklÄ… + permission_browse_repository: PeržiÅ«rÄ—ti saugyklÄ… + permission_view_changesets: Matyti pakeitimus + permission_commit_access: Prieiga prie pakeitimų + permission_manage_boards: Valdyti forumus + permission_view_messages: Matyti praneÅ¡imus + permission_add_messages: Skelbti praneÅ¡imus + permission_edit_messages: Redaguoti praneÅ¡imus + permission_edit_own_messages: Redaguoti savo praneÅ¡imus + permission_delete_messages: PaÅ¡alinti praneÅ¡imus + permission_delete_own_messages: PaÅ¡alinti savo praneÅ¡imus + permission_export_wiki_pages: Eksportuoti wiki puslapius + permission_manage_subtasks: Valdyti darbo dalis + + project_module_issue_tracking: Darbų pÄ—dsekys + project_module_time_tracking: Laiko pÄ—dsekys + project_module_news: Naujienos + project_module_documents: Dokumentai + project_module_files: Failai + project_module_wiki: Wiki + project_module_repository: Saugykla + project_module_boards: Forumai + project_module_calendar: Kalendorius + project_module_gantt: Gantt + + label_user: Vartotojas + label_user_plural: Vartotojai + label_user_new: Naujas vartotojas + label_user_anonymous: Anonimas + label_project: Projektas + label_project_new: Naujas projektas + label_project_plural: Projektai + label_x_projects: + zero: nÄ—ra projektų + one: 1 projektas + other: "%{count} projektų" + label_project_all: Visi Projektai + label_project_latest: Naujausi 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 %{value}" + label_issue_added: Darbas pridÄ—tas + label_issue_updated: Darbas atnaujintas + label_issue_note_added: Pastaba pridÄ—ta + label_issue_status_updated: Statusas atnaujintas + label_issue_priority_updated: Prioritetas atnaujintas + label_document: Dokumentas + label_document_new: Naujas dokumentas + 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_role_anonymous: Anonimas + label_role_non_member: NÄ—ra narys + 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 bÅ«sena + label_issue_status_plural: Darbų bÅ«senos + label_issue_status_new: Nauja bÅ«sena + 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_login_with_open_id_option: arba prisijunkite su OpenID + label_password_lost: Prarastas slaptažodis + label_home: Pagrindinis + label_my_page: Mano puslapis + label_my_account: Mano paskyra + label_my_projects: Mano projektai + label_my_page_block: Mano puslapio blokas + 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 prisijungimas + label_registered_on: Užregistruota + label_activity: Veikla + label_overall_activity: Visa veikla + label_user_activity: "%{value} 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_subproject_new: Naujas subprojektas + label_and_its_subprojects: "%{value} 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: Slankiojo kablelio skaiÄius + label_boolean: Loginis + label_string: Tekstas + label_text: Ilgas tekstas + label_attribute: Požymis + label_attribute_plural: Požymiai + label_no_data: NÄ—ra kÄ… atvaizduoti + label_change_status: Pakeitimo bÅ«sena + label_history: Istorija + label_attachment: Filas + label_attachment_new: Naujas failas + label_attachment_delete: PaÅ¡alinkite failÄ… + label_attachment_plural: Failai + label_file_added: Failas pridÄ—tas + label_report: Ataskaita + label_report_plural: Ataskaitos + label_news: Naujiena + label_news_new: PridÄ—ti naujienas + label_news_plural: Naujienos + label_news_latest: PaskutinÄ—s naujienos + label_news_view_all: PeržiÅ«rÄ—ti visas naujienas + label_news_added: Naujiena pridÄ—ta + label_news_comment_added: Prie naujienos pridÄ—tas komentaras + label_settings: Nustatymai + label_overview: Apžvalga + label_version: Versija + label_version_new: Nauja versija + label_version_plural: Versijos + label_close_versions: Uždaryti užbaigtas versijas + label_confirmation: Patvirtinimas + label_export_to: 'Eksportuoti į:' + label_read: Skaitykite... + label_public_projects: VieÅ¡i projektai + label_open_issues: atidaryta + label_open_issues_plural: atidaryti + label_closed_issues: uždaryta + label_closed_issues_plural: uždaryti + label_x_open_issues_abbr_on_total: + zero: 0 atvirų / %{total} + one: 1 atviras / %{total} + other: "%{count} atviri / %{total}" + label_x_open_issues_abbr: + zero: 0 atvirų + one: 1 atviras + other: "%{count} atviri" + label_x_closed_issues_abbr: + zero: 0 uždarytų + one: 1 uždarytas + other: "%{count} uždarytų" + label_total: IÅ¡ viso + label_permissions: Leidimai + label_current_status: DabartinÄ— bÅ«sena + label_new_statuses_allowed: Naujos bÅ«senos galimos + label_all: visi(os) + label_none: joks + 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: Puslapyje + label_calendar: Kalendorius + label_months_from: mÄ—nesiai nuo + label_gantt: Gantt + label_internal: Vidinis + label_last_changes: "paskutiniai %{count} pokyÄiai(ių)" + label_change_view_all: PeržiÅ«rÄ—ti visus pakeitimus + label_personalize_page: Suasmeninti šį puslapį + label_comment: Komentaras + label_comment_plural: Komentarai + label_x_comments: + zero: nÄ—ra komentarų + one: 1 komentaras + other: "%{count} komentarų" + label_comment_add: PridÄ—kite komentarÄ… + label_comment_added: Komentaras pridÄ—tas + label_comment_delete: PaÅ¡alinti komentarus + label_query: Užklausa + label_query_plural: Užklausos + label_query_new: Nauja užklausa + label_my_queries: Mano sukurtos užklausos + label_filter_add: PridÄ—ti filtrÄ… + label_filter_plural: Filtrai + label_equals: yra + label_not_equals: nÄ—ra + label_in_less_than: anksÄiau nei po + label_in_more_than: vÄ—liau nei po + label_greater_or_equal: '>=' + label_less_or_equal: '<=' + label_between: tarp + label_in: per + label_today: Å¡iandien + label_all_time: visas laikas + label_yesterday: vakar + label_this_week: Å¡iÄ… savaitÄ™ + label_last_week: praeita savaitÄ— + label_last_n_days: "paskutinių %{count} dienų" + label_this_month: Å¡is menuo + label_last_month: praeitas mÄ—nuo + label_this_year: Å¡iemet + label_date_range: Dienų diapazonas + label_less_than_ago: vÄ—liau nei prieÅ¡ + label_more_than_ago: anksÄiau nei prieÅ¡ + label_ago: prieÅ¡ + label_contains: turi + label_not_contains: neturi + label_day_plural: dienų(os) + label_repository: Saugykla + label_repository_plural: Saugyklos + label_browse: NarÅ¡yti + label_branch: Å aka + label_tag: Tag + label_revision: Revizija + label_revision_plural: Revizijos + label_revision_id: Revizija %{value} + 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_view_all_revisions: PežiÅ«rÄ—ti visas revizijas + label_max_size: Maksimalus dydis + 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 %{value}" + label_roadmap_overdue: "%{value} vÄ—luojama" + label_roadmap_no_issues: Å iai versijai nepriskirtas koks darbas + 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: Indeksuoti pagal pavadinimÄ… + label_index_by_date: Indeksuoti pagal datÄ… + label_current_version: Einamoji versija + label_preview: PeržiÅ«ra + label_feed_plural: Kanalai + label_changes_details: Visų pakeitimų detalÄ—s + label_issue_tracking: Darbų sekimas + label_spent_time: Dirbtas laikas + label_overall_spent_time: Visas dirbtas laikas + label_f_hour: "%{value} valanda" + label_f_hour_plural: "%{value} valandų(os)" + label_time_tracking: Laiko sekimas + label_change_plural: Pakeitimai + label_statistics: Statistika + label_commits_per_month: Ä®kÄ—limai per mÄ—nesį + label_commits_per_author: Ä®kÄ—limai pagal autorių + label_diff: skirt + 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 bÅ«sena + label_loading: Kraunama... + label_relation_new: Naujas ryÅ¡ys + label_relation_delete: PaÅ¡alinti ryšį + label_relates_to: susietas su + label_duplicates: dubliuoja + label_duplicated_by: dubliuojasi su + label_blocks: blokuoja + label_blocked_by: blokuojamas + label_precedes: ankstesnÄ—(is) + 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: Rodyti užbaigtas versijas + label_me: aÅ¡ + label_board: Forumas + label_board_new: Naujas forumas + label_board_plural: Forumai + label_board_locked: Užrakinta + label_board_sticky: Lipnus + label_topic_plural: Temos + label_message_plural: PraneÅ¡imai + label_message_last: Paskutinis praneÅ¡imas + label_message_new: Naujas praneÅ¡imas + 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 %{value}" + label_send_test_email: Nusiųsti bandomÄ…jį laiÅ¡kÄ… + label_feeds_access_key: RSS prieigos raktas + label_missing_feeds_access_key: TRÅ«ksta RSS prieigos rakto + label_feeds_access_key_created_on: "RSS prieigos raktas sukurtas prieÅ¡ %{value}" + label_module_plural: Moduliai + label_added_time_by: "PridÄ—jo %{author} prieÅ¡ %{age}" + label_updated_time_by: "Atnaujino %{author} prieÅ¡ %{age}" + label_updated_time: "Atnaujinta prieÅ¡ %{value}" + label_jump_to_a_project: Å uolis į projektÄ…... + label_file_plural: Failai + label_changeset_plural: Pakeitimų rinkiniai + label_default_columns: Numatytieji stulpeliai + label_no_change_option: (Jokio pakeitimo) + label_bulk_edit_selected_issues: MasiÅ¡kai readguoti pasirinktus darbus + label_bulk_edit_selected_time_entries: MasiÅ¡kai redaguotumÄ—te pasirinktus laiko įraÅ¡us + label_theme: Tema + label_default: Numatyta(as) + label_search_titles_only: IeÅ¡koti tiktai pavadinimų + 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: "NÄ—ra įvykių" + label_user_mail_option_only_my_events: "Tiktai įvikiai, kuriuos stebiu arba esu įtrauktas" + label_user_mail_option_only_assigned: "Tiktai įvykiai, kuriems esu priskirtas" + label_user_mail_option_only_owner: "Tiktai įvikiai, kurių Å¡eikininkas esu" + 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 + label_display_per_page: "%{value} įrašų puslapyje" + label_age: Amžius + label_change_properties: Pakeisti nustatymus + label_general: Bendri(as) + label_more: Daugiau + label_scm: SCM + label_plugins: Ä®skiepiai + label_ldap_authentication: LDAP autentifikacija + label_downloads_abbr: siunt. + label_optional_description: ApibÅ«dinimas (laisvai pasirenkamas) + label_add_another_file: PridÄ—ti kitÄ… failÄ… + 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: Pavyzdys + label_display: Demonstruoti + label_sort: Rūšiuoti + label_ascending: DidÄ—jantis + label_descending: Mažėjantis + label_date_from_to: Nuo %{start} iki %{end} + label_wiki_content_added: Wiki puslapis pridÄ—tas + label_wiki_content_updated: Wiki puslapis atnaujintas + label_group: GrupÄ— + label_group_plural: GrupÄ—s + label_group_new: Nauja grupÄ— + label_time_entry_plural: Sprendimo laikas + label_version_sharing_none: Nesidalinama + label_version_sharing_descendants: Su subprojektais + label_version_sharing_hierarchy: Su projekto hierarchija + label_version_sharing_tree: Su projekto medžiu + label_version_sharing_system: Su visais projektais + label_update_issue_done_ratios: Atnaujinti darbo atlikimo progresÄ… + label_copy_source: Å altinis + label_copy_target: Tikslas + label_copy_same_as_target: Toks pat kaip tikslas + label_display_used_statuses_only: Rodyti tik tuos statusus, kurie naudojami Å¡io pÄ—dsekio + label_api_access_key: API prieigos raktas + label_missing_api_access_key: TrÅ«ksta API prieigos rakto + label_api_access_key_created_on: "API prieigos raktas sukurtas prieÅ¡ %{value}" + label_profile: Profilis + label_subtask_plural: Darbo dalys + label_project_copy_notifications: Siųsti paÅ¡to praneÅ¡imus kopijuojant projektÄ… + label_principal_search: "IeÅ¡koti vartotojo arba grupÄ—s:" + label_user_search: "IeÅ¡koti vartotojo:" + label_issues_visibility_all: Visi darbai + label_issues_visibility_public: Visi vieÅ¡i darbai + label_issues_visibility_own: Darbai, sukurti vartotojo arba jam priskirti + label_git_report_last_commit: Nurodyti paskutinį failų ir katalogų pakeitimÄ… + label_parent_revision: PirminÄ— revizija + label_child_revision: Sekanti revizija + label_export_options: "%{export_format} eksportavimo nustatymai" + + button_login: Registruotis + button_submit: Pateikti + button_save: IÅ¡saugoti + button_check_all: ŽymÄ—ti visus + button_uncheck_all: AtžymÄ—ti visus + button_collapse_all: Sutraukti visus + button_expand_all: IÅ¡skleisti visus + button_delete: PaÅ¡alinti + button_create: Sukurti + button_create_and_continue: Sukurti ir tÄ™sti + button_test: Testas + button_edit: Redaguoti + button_edit_associated_wikipage: "Redaguoti susijusį Wiki puslapį: %{page_title}" + 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_move_and_follow: Perkelti ir sekti + button_back: Atgal + button_cancel: AtÅ¡aukti + button_activate: Aktyvinti + button_sort: Rūšiuoti + button_log_time: Registruoti laikÄ… + button_rollback: Grįžti į Å¡iÄ… versijÄ… + button_watch: StebÄ—ti + button_unwatch: NestebÄ—ti + button_reply: Atsakyti + button_archive: Archyvuoti + button_unarchive: IÅ¡pakuoti + button_reset: Atstatyti + button_rename: Pervadinti + button_change_password: Pakeisti slaptažodį + button_copy: Kopijuoti + button_copy_and_follow: Kopijuoti ir laikytis + button_annotate: RaÅ¡yti pastabÄ… + button_update: Atnaujinti + button_configure: KonfigÅ«ruoti + button_quote: Cituoti + button_duplicate: Dubliuoti + button_show: Rodyti + button_edit_section: Redaguoti šį skirsnį + button_export: Eksportuoti + + status_active: aktyvus + status_registered: užregistruotas + status_locked: užrakintas + + version_status_open: atidaryta + version_status_locked: užrakinta + version_status_closed: uždaryta + + field_active: Aktyvus + + text_select_mail_notifications: IÅ¡rinkite veiksmus, apie kuriuos bÅ«tų praneÅ¡ta elektroniniu paÅ¡tu. + 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 norite paÅ¡alinti šį projektÄ… ir visus susijusius duomenis? + text_subprojects_destroy_warning: "Å is(ie) subprojektas(ai): %{value} 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: "%{label} pakeistas(a) iÅ¡ %{old} į %{new}" + text_journal_changed_no_detail: "%{label} atnaujintas(a)" + text_journal_set_to: "%{label} nustatytas(a) į %{value}" + text_journal_deleted: "%{label} iÅ¡trintas(a) (%{old})" + text_journal_added: "%{label} pridÄ—tas(a) %{value}" + text_tip_issue_begin_day: užduotis, prasidedanti Å¡iÄ… dienÄ… + text_tip_issue_end_day: užduotis, pasibaigianti Å¡iÄ… dienÄ… + text_tip_issue_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.
IÅ¡saugojus, identifikatorius negali bÅ«ti keiÄiamas.' + text_caracters_maximum: "%{count} simbolių maksimumas." + text_caracters_minimum: "Turi bÅ«ti mažiausiai %{count} simbolių ilgio." + text_length_between: "Ilgis tarp %{min} ir %{max} 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_line_separated: Galimos kelios reikÅ¡mÄ—s (viena linija vienai vertei). + text_issues_ref_in_commit_messages: Darbų susiejimas ir fiksavimas pavedimų žinutÄ—se + text_issue_added: "Darbas %{id} buvo praneÅ¡tas (by %{author})." + text_issue_updated: "Darbas %{id} buvo atnaujintas (by %{author})." + text_wiki_destroy_confirmation: Ar esate įsitikinÄ™s, kad norite paÅ¡alinti šį wiki puslapį ir visÄ… jo turinį? + text_issue_category_destroy_question: "Kai kurie darbai (%{count}) 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 gausite tiktai praneÅ¡imus apie tuos įvykius, kuriuos jÅ«s stebite, arba į kuriuos esate įtrauktas (pvz. darbai, kurių autorius jÅ«s esate ar esate priskirtas)." + 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." + text_load_default_configuration: Užkrauti numatytÄ…j konfiguracijÄ… + text_status_changed_by_changeset: "Pakeista %{value} revizijoje." + text_time_logged_by_changeset: "Applied in changeset %{value}." + text_issues_destroy_confirmation: 'Ar jÅ«s tikrai norite sunaikinti pažymÄ—tÄ…(us) darbÄ…(us)?' + text_issues_destroy_descendants_confirmation: Taip pat bus iÅ¡trinta(os) %{count} darbo dalis(ys). + text_time_entries_destroy_confirmation: 'Ar jÅ«s tikrai norite iÅ¡trinti pasirinktÄ…(us) laiko įrašą(us)?' + text_select_project_modules: 'Parinkite modulius, kuriuos norite naudoti Å¡iame projekte:' + text_default_administrator_account_changed: Administratoriaus numatytoji paskyra pakeista + text_file_repository_writable: Ä® failų saugyklÄ… saugoti galima (RW) + text_plugin_assets_writable: Ä® įskiepių apraÅ¡o punktų katalogÄ… įraÅ¡yti galima + text_rmagick_available: RMagick pasiekiamas (pasirinktinai) + text_destroy_time_entries_question: "Naikinamam darbui priskirta %{hours} valandų. KÄ… norite su jomis daryti?" + text_destroy_time_entries: IÅ¡trinti įraÅ¡ytas valandas + text_assign_time_entries_to_project: Priskirti įraÅ¡ytas valandas prie projekto + text_reassign_time_entries: 'Priskirti įraÅ¡ytas valandas Å¡iam darbui:' + text_user_wrote: "%{value} parašė:" + text_enumeration_destroy_question: "%{count} objektai(ų) priskirti Å¡iai reikÅ¡mei." + text_enumeration_category_reassign_to: 'Priskirti juos Å¡iai reikÅ¡mei:' + text_email_delivery_not_configured: "El.paÅ¡to siuntimas nesukonfigÅ«ruotas, ir perspÄ—jimai neaktyvus.\nSukonfigÅ«ruokite savo SMTP serverį byloje config/configuration.yml ir perleiskite programÄ… norÄ—dami pritaikyti pakeitimus." + text_repository_usernames_mapping: "Parinkite ar atnaujinkite Redmine vartotojÄ…, kuris paminÄ—tas saugyklos log'e.\nVartotojai, turintys tÄ… patį Redmine ir saugyklos vardÄ… ar el.paÅ¡tÄ… yra automatiÅ¡kai suriÅ¡ti." + text_diff_truncated: "... Å is diff'as nukarpytas, nes jis virÅ¡ijo maksimalų rodomų eiluÄių skaiÄių." + text_custom_field_possible_values_info: 'Po vienÄ… eilutÄ™ kiekvienai reikÅ¡mei' + text_wiki_page_destroy_question: "Å is puslapis turi %{descendants} susijusių arba iÅ¡vestinių puslapių. KÄ… norÄ—tumÄ—te daryti?" + text_wiki_page_nullify_children: Laikyti child puslapius kaip pagrindinius puslapius + text_wiki_page_destroy_children: "PaÅ¡alinti child puslapius ir jų palikuonis" + text_wiki_page_reassign_children: "Priskirkite iÅ¡ naujo 'child' puslapius Å¡iam pagrindiniam puslapiui" + text_own_membership_delete_confirmation: "JÅ«s esate pasiruošęs panaikinti dalį arba visus leidimus ir po Å¡io pakeitimo galite prarasti Å¡io projekto redagavimo galimybÄ™. \n Ar jÅ«s esate įsitikinÄ™s ir tÄ™sti?" + text_zoom_in: Priartinti + text_zoom_out: Nutolinti + text_warn_on_leaving_unsaved: "Dabartinis puslapis turi neiÅ¡saugoto teksto, kuris bus prarastas, jeigu paliksite šį puslapį." + text_scm_path_encoding_note: "Numatytasis: UTF-8" + text_git_repository_note: Saugykla (repository) yra plika ir vietinÄ— (pvz. /gitrepo, c:\gitrepo) + text_mercurial_repository_note: VietinÄ— saugykla (e.g. /hgrepo, c:\hgrepo) + text_scm_command: Komanda + text_scm_command_version: Versija + + default_role_manager: Vadovas + default_role_developer: Projektuotojas + default_role_reporter: Pranešėjas + default_tracker_bug: Klaida + default_tracker_feature: YpatybÄ— + default_tracker_support: Palaikymas + default_issue_status_new: Naujas + default_issue_status_in_progress: Vykdomas + default_issue_status_resolved: IÅ¡sprÄ™stas + default_issue_status_feedback: Grįžtamasis ryÅ¡ys + default_issue_status_closed: Uždarytas + default_issue_status_rejected: Atmestas + default_doc_category_user: Vartotojo dokumentacija + default_doc_category_tech: TechninÄ— 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) + enumeration_system_activity: Sistemos veikla + + description_filter: Filtras + description_search: PaieÅ¡kos laukas + description_choose_project: Projektai + description_project_scope: PaieÅ¡kos sritis + description_notes: Pastabos + description_message_content: ŽinutÄ—s turinys + description_query_sort_criteria_attribute: Rūšiuoti atributÄ… + description_query_sort_criteria_direction: Rūšiuoti kryptį + description_user_mail_notification: PaÅ¡to praneÅ¡imų nustatymai + description_available_columns: Galimi Stulpeliai + description_selected_columns: Pasirinkti Stulpeliai + description_all_columns: Visi stulpeliai + description_issue_category_reassign: Pasirinkti darbo kategorijÄ… + description_wiki_subpages_reassign: Pasirinkti naujÄ… pagrindinį puslapį + description_date_range_list: PasirinkitÄ™ diapazonÄ… iÅ¡ sÄ…raÅ¡o + description_date_range_interval: Pasirinkite diapazonÄ… pasirinkdami pradžios ir pabaigos datas + description_date_from: Ä®vesti pradžios datÄ… + description_date_to: Ä®vesti pabaigos datÄ… + + label_additional_workflow_transitions_for_assignee: Papildomi darbų eigos variantai kai darbas paskirtas vartotojui + label_additional_workflow_transitions_for_author: Papildomi darbų eigos variantai kai vartotojas yra darbo autorius + notice_failed_to_save_time_entries: "Nepavyko iÅ¡saugoti %{count} laiko žurnalo įrašų iÅ¡ %{total} parinktų: %{ids}." + label_x_issues: + zero: 0 darbas + one: 1 darbas + other: "%{count} darbai(ų)" + label_repository_new: Nauja saugykla + field_repository_is_default: PagrindinÄ— saugykla + label_copy_attachments: Kopijuoti priedus + label_item_position: "%{position}/%{count}" + label_completed_versions: Užbaigtos versijos + text_project_identifier_info: Leidžiamos tik mažosios raidÄ—s (a-z), skaitmenys, brÅ«kÅ¡neliai ir pabraukimo simboliai.
KartÄ… iÅ¡saugojus pakeitimai negalimi + field_multiple: Keletas reikÅ¡mių + setting_commit_cross_project_ref: Leisti visų kitų projektų įraÅ¡us susieti nuorodomis ir sutaisyti + text_issue_conflict_resolution_add_notes: IÅ¡saugoti mano žinutÄ™ ir atmesti likusius mano pataisymus + text_issue_conflict_resolution_overwrite: IÅ¡saugoti mano pakeitimus (ankstesnių pakeitimų žinutÄ—s bus iÅ¡saugotos, taÄiau kai kurie pakeitimai bus perraÅ¡yti) + notice_issue_update_conflict: Darbas buvo pakoreguotas kito vartotojo kol jÅ«s atlikote pakeitimus. + text_issue_conflict_resolution_cancel: Atmesti visus mano pakeitimus ir iÅ¡ naujo rodyti %{link} + permission_manage_related_issues: Tvarkyti susietus darbus + field_auth_source_ldap_filter: LDAP filtras + label_search_for_watchers: IeÅ¡koti vartotojų kuriuos įtraukti kaip stebÄ—tojus + notice_account_deleted: JÅ«sų paskyra panaikinta. + setting_unsubscribe: Leisti vartotojams panaikinti savo paskyrÄ… + button_delete_my_account: Panaikinti savo paskyrÄ… + text_account_destroy_confirmation: |- + Ar tikrai norite tÄ™sti? + JÅ«sų paskyra bus panaikinta ir nebus galimybÄ—s jos atkurti. + error_session_expired: JÅ«sų sesija pasibaigÄ—. PraÅ¡ome prisijunti iÅ¡ naujo. + text_session_expiration_settings: "Ä®spÄ—jimas: atlikus Å¡iuos pakeitimus visos aktyvios sesijos gali nustoti galiojusios (įskaitant jÅ«sų sesijÄ…)." + setting_session_lifetime: Sesijos maksimalus galiojimas + setting_session_timeout: Sesijos neveiklumo laiko tarpas + label_session_expiration: BaigÄ—si sujungimo sesija + permission_close_project: Uždaryti / atnaujinti projektÄ… + label_show_closed_projects: Matyti uždarytus projektus + button_close: Uždaryti + button_reopen: Atnaujinti + project_status_active: aktyvus + project_status_closed: uždarytas + project_status_archived: archyvuotas + text_project_closed: Å is projektas yra uždarytas, prieinamas tik peržiÅ«rai. + notice_user_successful_create: Vartotojas %{id} sukurtas. + field_core_fields: Standartiniai laukai + field_timeout: Timeout (po sek.) + setting_thumbnails_enabled: Rodyti sumažintus priedų atvaizdus + setting_thumbnails_size: Sumažinto atvaizdo dydis (taÅ¡keliais) + label_status_transitions: Darbų eiga + label_fields_permissions: Leidimai + label_readonly: Tik peržiÅ«ra + label_required: Privaloma(s) + text_repository_identifier_info: Leidžiamos tik mažosios raidÄ—s (a-z), skaitmenys, brÅ«kÅ¡neliai ir pabraukimo simboliai.
KartÄ… iÅ¡saugojus pakeitimai negalimi + field_board_parent: Pagrindinis forumas + label_attribute_of_project: Projekto pavadinimas %{name} + label_attribute_of_author: Autorius %{name} + label_attribute_of_assigned_to: Paskirtas %{name} + label_attribute_of_fixed_version: Versijos %{name} + label_copy_subtasks: Kopijuoti darbo dalis + label_copied_to: kopijuota į + label_copied_from: kopijuota iÅ¡ + label_any_issues_in_project: bet kurie projekto darbai + label_any_issues_not_in_project: bet kurie ne Å¡io projekto darbai + field_private_notes: PrivaÄios žinutÄ—s + permission_view_private_notes: Matyti privaÄias žinutes + permission_set_notes_private: Pakeisti žinutÄ™ privaÄia + label_no_issues_in_project: projekte nÄ—ra darbų + label_any: visi + label_last_n_weeks: prieÅ¡ %{count} sav. + setting_cross_project_subtasks: Leisti susieti skirtingų projektų užduoÄių dalis + label_cross_project_descendants: Su subprojektais + label_cross_project_tree: Su projekto medžiu + label_cross_project_hierarchy: Su projekto hierarchija + label_cross_project_system: Su visais projektais + button_hide: SlÄ—pti + setting_non_working_week_days: Nedarbo dienos + label_in_the_next_days: per ateinanÄias + label_in_the_past_days: per paskutines + label_attribute_of_user: Vartotojo %{name} + text_turning_multiple_off: Jei jÅ«s iÅ¡jungsite kelių reikÅ¡mių pasirinkimÄ…, visos iÅ¡vardintos reikÅ¡mÄ—s bus paÅ¡alintos ir palikta tik viena reikÅ¡mÄ— kiekvienam laukui. + label_attribute_of_issue: Ä®raÅ¡ai %{name} + permission_add_documents: PridÄ—ti dokumentus + permission_edit_documents: Redaguoti dokumentus + permission_delete_documents: Trinti dokumentus + label_gantt_progress_line: Progreso linija + setting_jsonp_enabled: Ä®galinti JSONP palaikymÄ… + field_inherit_members: PaveldÄ—ti narius + field_closed_on: Uždarytas + setting_default_projects_tracker_ids: Sekliai pagal nutylÄ—jimÄ… naujiems projektams + label_total_time: IÅ¡ viso + text_scm_config: JÅ«s galite pakeisti SCM komandas byloje config/configuration.yml. PraÅ¡ome perkrauti programÄ… po redagavimo, idant įgalinti pakeitimus. + text_scm_command_not_available: SCM komanda nepasiekiama. Patikrinkite administravimo skydelio nustatymus. diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/49/49c759950bed453cc0da75c2904a201b6bf1a7c4.svn-base --- a/.svn/pristine/49/49c759950bed453cc0da75c2904a201b6bf1a7c4.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,83 +0,0 @@ -module CodeRay -module Encoders - - # A simple JSON Encoder. - # - # Example: - # CodeRay.scan('puts "Hello world!"', :ruby).json - # yields - # [ - # {"type"=>"text", "text"=>"puts", "kind"=>"ident"}, - # {"type"=>"text", "text"=>" ", "kind"=>"space"}, - # {"type"=>"block", "action"=>"open", "kind"=>"string"}, - # {"type"=>"text", "text"=>"\"", "kind"=>"delimiter"}, - # {"type"=>"text", "text"=>"Hello world!", "kind"=>"content"}, - # {"type"=>"text", "text"=>"\"", "kind"=>"delimiter"}, - # {"type"=>"block", "action"=>"close", "kind"=>"string"}, - # ] - class JSON < Encoder - - begin - require 'json' - rescue LoadError - begin - require 'rubygems' unless defined? Gem - gem 'json' - require 'json' - rescue LoadError - $stderr.puts "The JSON encoder needs the JSON library.\n" \ - "Please gem install json." - raise - end - end - - register_for :json - FILE_EXTENSION = 'json' - - protected - def setup options - super - - @first = true - @out << '[' - end - - def finish options - @out << ']' - end - - def append data - if @first - @first = false - else - @out << ',' - end - - @out << data.to_json - end - - public - def text_token text, kind - append :type => 'text', :text => text, :kind => kind - end - - def begin_group kind - append :type => 'block', :action => 'open', :kind => kind - end - - def end_group kind - append :type => 'block', :action => 'close', :kind => kind - end - - def begin_line kind - append :type => 'block', :action => 'begin_line', :kind => kind - end - - def end_line kind - append :type => 'block', :action => 'end_line', :kind => kind - end - - end - -end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/49/49ce90be0c149fe7cbdb78cf85bb09564b042b23.svn-base --- a/.svn/pristine/49/49ce90be0c149fe7cbdb78cf85bb09564b042b23.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +0,0 @@ -Return-Path: -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" -To: -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. - -Projet: onlinestore -Tracker: Feature request -catégorie: Stock management -priorité: Urgent diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/49/49e241388e4f3f4216ec7154a82eaad6c0c8e9f4.svn-base --- a/.svn/pristine/49/49e241388e4f3f4216ec7154a82eaad6c0c8e9f4.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class JournalDetail < ActiveRecord::Base - belongs_to :journal - before_save :normalize_values - - private - - def normalize_values - self.value = normalize(value) - self.old_value = normalize(old_value) - end - - def normalize(v) - if v == true - "1" - elsif v == false - "0" - else - v - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4a/4a0639ca12562188292e323bfd92d9487f71fda7.svn-base --- a/.svn/pristine/4a/4a0639ca12562188292e323bfd92d9487f71fda7.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,78 +0,0 @@ ---- -wiki_pages_001: - created_on: 2007-03-07 00:08:07 +01:00 - title: CookBook_documentation - id: 1 - wiki_id: 1 - protected: true - parent_id: -wiki_pages_002: - created_on: 2007-03-08 00:18:07 +01:00 - title: Another_page - id: 2 - wiki_id: 1 - protected: false - parent_id: -wiki_pages_003: - created_on: 2007-03-08 00:18:07 +01:00 - title: Start_page - id: 3 - wiki_id: 2 - protected: false - parent_id: -wiki_pages_004: - created_on: 2007-03-08 00:18:07 +01:00 - title: Page_with_an_inline_image - id: 4 - 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 -wiki_pages_007: - created_on: 2007-03-08 00:18:07 +01:00 - title: Child_page_1 - id: 7 - wiki_id: 2 - protected: false - parent_id: 8 -wiki_pages_008: - created_on: 2007-03-08 00:18:07 +01:00 - title: Parent_page - id: 8 - wiki_id: 2 - protected: false - parent_id: -wiki_pages_009: - created_on: 2007-03-08 00:18:07 +01:00 - title: Child_page_2 - id: 9 - wiki_id: 2 - protected: false - parent_id: 8 -wiki_pages_010: - created_on: 2007-03-08 00:18:07 +01:00 - title: Этика_менеджмента - id: 10 - wiki_id: 1 - protected: false - parent_id: -wiki_pages_011: - created_on: 2007-03-08 00:18:07 +01:00 - title: Page_with_sections - id: 11 - wiki_id: 1 - protected: false - parent_id: diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4a/4a23cff6d2e8f3f4391d3ef5634965a5324bf60d.svn-base --- a/.svn/pristine/4a/4a23cff6d2e8f3f4391d3ef5634965a5324bf60d.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ -module RFPDF - module TemplateHandlers - class Base < ::ActionView::TemplateHandlers::ERB - - def compile(template) - src = "_rfpdf_compile_setup;" + super - end - end - end -end - - diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4a/4a4cc530e16fc15272849a68536d70a8eaa8b683.svn-base --- a/.svn/pristine/4a/4a4cc530e16fc15272849a68536d70a8eaa8b683.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,56 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) - -class CommentTest < ActiveSupport::TestCase - fixtures :users, :news, :comments - - def setup - @jsmith = User.find(2) - @news = News.find(1) - end - - def test_create - comment = Comment.new(:commented => @news, :author => @jsmith, :comments => "my comment") - assert comment.save - @news.reload - assert_equal 2, @news.comments_count - end - - def test_create_should_send_notification - Setting.notified_events << 'news_comment_added' - Watcher.create!(:watchable => @news, :user => @jsmith) - - assert_difference 'ActionMailer::Base.deliveries.size' do - Comment.create!(:commented => @news, :author => @jsmith, :comments => "my comment") - end - end - - def test_validate - comment = Comment.new(:commented => @news) - assert !comment.save - assert_equal 2, comment.errors.length - end - - def test_destroy - comment = Comment.find(1) - assert comment.destroy - @news.reload - assert_equal 0, @news.comments_count - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4a/4a7aa8e524e7ffcb9d61a2f01e83b69a07ba112b.svn-base --- a/.svn/pristine/4a/4a7aa8e524e7ffcb9d61a2f01e83b69a07ba112b.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ -<% if version.completed? %> -

<%= format_date(version.effective_date) %>

-<% elsif version.effective_date %> -

<%= due_date_distance_in_words(version.effective_date) %> (<%= format_date(version.effective_date) %>)

-<% end %> - -

<%=h version.description %>

-
    - <% version.custom_values.each do |custom_value| %> - <% if !custom_value.value.blank? %> -
  • <%=h custom_value.custom_field.name %>: <%=h show_value(custom_value) %>
  • - <% end %> - <% end %> -
- -<% if version.fixed_issues.count > 0 %> - <%= progress_bar([version.closed_pourcent, version.completed_pourcent], :width => '40em', :legend => ('%0.0f%' % version.completed_pourcent)) %> -

- <%= link_to_if(version.closed_issues_count > 0, l(:label_x_closed_issues_abbr, :count => version.closed_issues_count), :controller => 'issues', :action => 'index', :project_id => version.project, :status_id => 'c', :fixed_version_id => version, :set_filter => 1) %> - (<%= '%0.0f' % (version.closed_issues_count.to_f / version.fixed_issues.count * 100) %>%) -   - <%= link_to_if(version.open_issues_count > 0, l(:label_x_open_issues_abbr, :count => version.open_issues_count), :controller => 'issues', :action => 'index', :project_id => version.project, :status_id => 'o', :fixed_version_id => version, :set_filter => 1) %> - (<%= '%0.0f' % (version.open_issues_count.to_f / version.fixed_issues.count * 100) %>%) -

-<% else %> -

<%= l(:label_roadmap_no_issues) %>

-<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4a/4a905b256f6edbbe772142f865f00ae63e6aa5e8.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/4a/4a905b256f6edbbe772142f865f00ae63e6aa5e8.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,31 @@ + +<% if defined?(container) && container && container.saved_attachments %> + <% container.saved_attachments.each_with_index do |attachment, i| %> + + <%= text_field_tag("attachments[p#{i}][filename]", attachment.filename, :class => 'filename') + + text_field_tag("attachments[p#{i}][description]", attachment.description, :maxlength => 255, :placeholder => l(:label_optional_description), :class => 'description') + + link_to(' '.html_safe, attachment_path(attachment, :attachment_id => "p#{i}", :format => 'js'), :method => 'delete', :remote => true, :class => 'remove-upload') %> + <%= hidden_field_tag "attachments[p#{i}][token]", "#{attachment.token}" %> + + <% end %> +<% end %> + + +<%= file_field_tag 'attachments[dummy][file]', + :id => nil, + :class => 'file_selector', + :multiple => true, + :onchange => 'addInputFiles(this);', + :data => { + :max_file_size => Setting.attachment_max_size.to_i.kilobytes, + :max_file_size_message => l(:error_attachment_too_big, :max_size => number_to_human_size(Setting.attachment_max_size.to_i.kilobytes)), + :max_concurrent_uploads => Redmine::Configuration['max_concurrent_ajax_uploads'].to_i, + :upload_path => uploads_path(:format => 'js'), + :description_placeholder => l(:label_optional_description) + } %> +(<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>) + + +<% content_for :header_tags do %> + <%= javascript_include_tag 'attachments' %> +<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4a/4ab02a244084d766659b315d2b404e1d4afb8313.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/4a/4ab02a244084d766659b315d2b404e1d4afb8313.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,89 @@ +<%= error_messages_for 'project' %> + +
+ +

<%= f.text_field :name, :required => true, :size => 60 %>

+ +

<%= f.text_area :description, :rows => 8, :class => 'wiki-edit' %>

+

<%= f.text_field :identifier, :required => true, :size => 60, :disabled => @project.identifier_frozen?, :maxlength => Project::IDENTIFIER_MAX_LENGTH %> +<% unless @project.identifier_frozen? %> + <%= l(:text_length_between, :min => 1, :max => Project::IDENTIFIER_MAX_LENGTH) %> <%= l(:text_project_identifier_info).html_safe %> +<% end %>

+

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

+

<%= f.check_box :is_public %>

+ +<% unless @project.allowed_parents.compact.empty? %> +

<%= label(:project, :parent_id, l(:field_parent)) %><%= parent_project_select_tag(@project) %>

+<% end %> + +<% if @project.safe_attribute? 'inherit_members' %> +

<%= f.check_box :inherit_members %>

+<% end %> + +<%= wikitoolbar_for 'project_description' %> + +<% @project.custom_field_values.each do |value| %> +

<%= custom_field_tag_with_label :project, value %>

+<% end %> +<%= call_hook(:view_projects_form, :project => @project, :form => f) %> +
+ +<% if @project.new_record? %> +
<%= l(:label_module_plural) %> +<% Redmine::AccessControl.available_project_modules.each do |m| %> + +<% end %> +<%= hidden_field_tag 'project[enabled_module_names][]', '' %> +<%= javascript_tag 'observeProjectModules()' %> +
+<% end %> + +<% if @project.new_record? || @project.module_enabled?('issue_tracking') %> +<% unless @trackers.empty? %> +
<%=l(:label_tracker_plural)%> +<% @trackers.each do |tracker| %> + +<% end %> +<%= hidden_field_tag 'project[tracker_ids][]', '' %> +
+<% end %> + +<% unless @issue_custom_fields.empty? %> +
<%=l(:label_custom_field_plural)%> +<% @issue_custom_fields.each do |custom_field| %> + +<% end %> +<%= hidden_field_tag 'project[issue_custom_field_ids][]', '' %> +
+<% end %> +<% end %> + + +<% unless @project.identifier_frozen? %> + <% content_for :header_tags do %> + <%= javascript_include_tag 'project_identifier' %> + <% end %> +<% end %> + +<% if !User.current.admin? && @project.inherit_members? && @project.parent && User.current.member_of?(@project.parent) %> + <%= javascript_tag do %> + $(document).ready(function() { + $("#project_inherit_members").change(function(){ + if (!$(this).is(':checked')) { + if (!confirm("<%= escape_javascript(l(:text_own_membership_delete_confirmation)) %>")) { + $("#project_inherit_members").attr("checked", true); + } + } + }); + }); + <% end %> +<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4a/4ab9278cce0785883057ff4ba259f3d5b17b6fd7.svn-base --- a/.svn/pristine/4a/4ab9278cce0785883057ff4ba259f3d5b17b6fd7.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) -require 'mail_handler_controller' - -# Re-raise errors caught by the controller. -class MailHandlerController; def rescue_action(e) raise e end; end - -class MailHandlerControllerTest < ActionController::TestCase - fixtures :users, :projects, :enabled_modules, :roles, :members, :member_roles, :issues, :issue_statuses, :trackers, :enumerations - - FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler' - - def setup - @controller = MailHandlerController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - User.current = nil - end - - def test_should_create_issue - # Enable API and set a key - Setting.mail_handler_api_enabled = 1 - Setting.mail_handler_api_key = 'secret' - - post :index, :key => 'secret', :email => IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml')) - assert_response 201 - end - - def test_should_not_allow - # Disable API - Setting.mail_handler_api_enabled = 0 - Setting.mail_handler_api_key = 'secret' - - post :index, :key => 'secret', :email => IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml')) - assert_response 403 - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4a/4af7d6561304237ee0f7ba68af0460300a5a3233.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/4a/4af7d6561304237ee0f7ba68af0460300a5a3233.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,1089 @@ +ar: + # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl) + direction: rtl + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%m/%d/%Y" + short: "%b %d" + long: "%B %d, %Y" + + day_names: [الاحد, الاثنين, الثلاثاء, الاربعاء, الخميس, الجمعة, السبت] + abbr_day_names: [أح, اث, Ø«, ار, Ø®, ج, س] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, كانون الثاني, شباط, آذار, نيسان, أيار, حزيران, تموز, آب, أيلول, تشرين الأول, تشرين الثاني, كانون الأول] + abbr_month_names: [~, كانون الثاني, شباط, آذار, نيسان, أيار, حزيران, تموز, آب, أيلول, تشرين الأول, تشرين الثاني, كانون الأول] + # Used in date_select and datime_select. + order: + - :السنة + - :الشهر + - :اليوم + + time: + formats: + default: "%m/%d/%Y %I:%M %p" + time: "%I:%M %p" + short: "%d %b %H:%M" + long: "%B %d, %Y %H:%M" + am: "صباحا" + pm: "مساءا" + + datetime: + distance_in_words: + half_a_minute: "نص٠دقيقة" + less_than_x_seconds: + one: "أقل من ثانية" + other: "ثواني %{count}أقل من " + x_seconds: + one: "ثانية" + other: "%{count}ثواني " + less_than_x_minutes: + one: "أقل من دقيقة" + other: "دقائق%{count}أقل من " + x_minutes: + one: "دقيقة" + other: "%{count} دقائق" + about_x_hours: + one: "حوالي ساعة" + other: "ساعات %{count}حوالي " + x_hours: + one: "%{count} ساعة" + other: "%{count} ساعات" + x_days: + one: "يوم" + other: "%{count} أيام" + about_x_months: + one: "حوالي شهر" + other: "أشهر %{count} حوالي" + x_months: + one: "شهر" + other: "%{count} أشهر" + about_x_years: + one: "حوالي سنة" + other: "سنوات %{count}حوالي " + over_x_years: + one: "اكثر من سنة" + other: "سنوات %{count}أكثر من " + almost_x_years: + one: "تقريبا سنة" + other: "سنوات %{count} نقريبا" + number: + format: + separator: "." + delimiter: "" + precision: 3 + + human: + format: + delimiter: "" + precision: 3 + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + +# Used in array.to_sentence. + support: + array: + sentence_connector: "Ùˆ" + skip_last_comma: خطأ + + activerecord: + errors: + template: + header: + one: " %{model} خطأ يمنع تخزين" + other: " %{model} يمنع تخزين%{count}خطأ رقم " + messages: + inclusion: "غير مدرجة على القائمة" + exclusion: "محجوز" + invalid: "غير صالح" + confirmation: "غير متطابق" + accepted: "مقبولة" + empty: "لا يمكن ان تكون ÙØ§Ø±ØºØ©" + blank: "لا يمكن ان تكون ÙØ§Ø±ØºØ©" + too_long: " %{count}طويلة جدا، الحد الاقصى هو )" + too_short: " %{count}قصيرة جدا، الحد الادنى هو)" + wrong_length: " %{count}خطأ ÙÙŠ الطول، يجب ان يكون )" + taken: "لقد اتخذت سابقا" + not_a_number: "ليس رقما" + not_a_date: "ليس تاريخا صالحا" + greater_than: "%{count}يجب ان تكون اكثر من " + greater_than_or_equal_to: "%{count}يجب ان تكون اكثر من او تساوي" + equal_to: "%{count}يجب ان تساوي" + less_than: " %{count}يجب ان تكون اقل من" + less_than_or_equal_to: " %{count}يجب ان تكون اقل من او تساوي" + odd: "must be odd" + even: "must be even" + greater_than_start_date: "يجب ان تكون اكثر من تاريخ البداية" + not_same_project: "لا ينتمي الى Ù†ÙØ³ المشروع" + circular_dependency: "هذه العلاقة سو٠تخلق علاقة تبعية دائرية" + cant_link_an_issue_with_a_descendant: "لا يمكن ان تكون المشكلة مرتبطة بواحدة من المهام Ø§Ù„ÙØ±Ø¹ÙŠØ©" + + actionview_instancetag_blank_option: الرجاء التحديد + + general_text_No: 'لا' + general_text_Yes: 'نعم' + general_text_no: 'لا' + general_text_yes: 'نعم' + general_lang_name: 'Arabic (عربي)' + general_csv_separator: ',' + general_csv_decimal_separator: '.' + general_csv_encoding: ISO-8859-1 + general_pdf_encoding: UTF-8 + general_first_day_of_week: '7' + + notice_account_updated: لقد تم تجديد الحساب بنجاح. + notice_account_invalid_creditentials: اسم المستخدم او كلمة المرور غير صحيحة + notice_account_password_updated: لقد تم تجديد كلمة المرور بنجاح. + notice_account_wrong_password: كلمة المرور غير صحيحة + notice_account_register_done: لقد تم انشاء حسابك بنجاح، الرجاء تأكيد الطلب من البريد الالكتروني + notice_account_unknown_email: مستخدم غير معروÙ. + notice_can_t_change_password: هذا الحساب يستخدم جهاز خارجي غير مصرح به لا يمكن تغير كلمة المرور + notice_account_lost_email_sent: لقد تم ارسال رسالة على بريدك بالتعليمات اللازمة لتغير كلمة المرور + notice_account_activated: لقد تم ØªÙØ¹ÙŠÙ„ حسابك، يمكنك الدخول الان + notice_successful_create: لقد تم الانشاء بنجاح + notice_successful_update: لقد تم التحديث بنجاح + notice_successful_delete: لقد تم الحذ٠بنجاح + notice_successful_connection: لقد تم الربط بنجاح + notice_file_not_found: Ø§Ù„ØµÙØ­Ø© التي تحاول الدخول اليها غير موجوده او تم حذÙها + notice_locking_conflict: تم تحديث البيانات عن طريق مستخدم آخر. + notice_not_authorized: غير مصرح لك الدخول الى هذه المنطقة. + notice_not_authorized_archived_project: المشروع الذي تحاول الدخول اليه تم Ø§Ø±Ø´ÙØªÙ‡ + notice_email_sent: "%{value}تم ارسال رسالة الى " + notice_email_error: " (%{value})لقد حدث خطأ ما اثناء ارسال الرسالة الى " + notice_feeds_access_key_reseted: كلمة الدخول RSSلقد تم تعديل . + notice_api_access_key_reseted: كلمة الدخولAPIلقد تم تعديل . + notice_failed_to_save_issues: "ÙØ´Ù„ ÙÙŠ Ø­ÙØ¸ الملÙ" + notice_failed_to_save_members: "ÙØ´Ù„ ÙÙŠ Ø­ÙØ¸ الاعضاء: %{errors}." + notice_no_issue_selected: "لم يتم تحديد شيء، الرجاء تحديد المسألة التي تريد" + notice_account_pending: "لقد تم انشاء حسابك، الرجاء الانتظار حتى تتم المواÙقة" + notice_default_data_loaded: تم تحميل التكوين Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠ Ø¨Ù†Ø¬Ø§Ø­ + notice_unable_delete_version: غير قادر على مسح النسخة. + notice_unable_delete_time_entry: غير قادر على مسح وقت الدخول. + notice_issue_done_ratios_updated: لقد تم تحديث النسب. + notice_gantt_chart_truncated: " (%{max})لقد تم اقتطاع الرسم البياني لانه تجاوز الاحد الاقصى لعدد العناصر المسموح عرضها " + notice_issue_successful_create: "%{id}لقد تم انشاء " + + + error_can_t_load_default_data: "لم يتم تحميل التكوين Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠ ÙƒØ§Ù…Ù„Ø§ %{value}" + error_scm_not_found: "لم يتم العثور على ادخال ÙÙŠ المستودع" + error_scm_command_failed: "حدث خطأ عند محاولة الوصول الى المستودع: %{value}" + error_scm_annotate: "الادخال غير موجود." + error_scm_annotate_big_text_file: "لا يمكن Ø­ÙØ¸ الادخال لانه تجاوز الحد الاقصى لحجم الملÙ." + error_issue_not_found_in_project: 'لم يتم العثور على المخرج او انه ينتمي الى مشروع اخر' + error_no_tracker_in_project: 'لا يوجد متتبع لهذا المشروع، الرجاء التحقق من إعدادات المشروع. ' + error_no_default_issue_status: 'لم يتم التعر٠على اي وضع Ø§ÙØªØ±Ø§Ø¶ÙŠØŒ الرجاء التحقق من التكوين الخاص بك (اذهب الى إدارة-إصدار الحالات)' + error_can_not_delete_custom_field: غير قادر على حذ٠الحقل المظلل + error_can_not_delete_tracker: "هذا المتتبع يحتوي على مسائل نشطة ولا يمكن حذÙÙ‡" + error_can_not_remove_role: "هذا الدور قيد الاستخدام، لا يمكن حذÙÙ‡" + error_can_not_reopen_issue_on_closed_version: 'لا يمكن إعادة ÙØªØ­ قضية معينه لاصدار مقÙÙ„' + error_can_not_archive_project: لا يمكن Ø§Ø±Ø´ÙØ© هذا المشروع + error_issue_done_ratios_not_updated: "لم يتم تحديث النسب" + error_workflow_copy_source: 'الرجاء اختيار المتتبع او الادوار' + error_workflow_copy_target: 'الرجاء اختيار هد٠المتتبع او هد٠الادوار' + error_unable_delete_issue_status: 'غير قادر على حذ٠حالة القضية' + error_unable_to_connect: "تعذر الاتصال(%{value})" + error_attachment_too_big: " (%{max_size})لا يمكن تحميل هذا Ø§Ù„Ù…Ù„ÙØŒ لقد تجاوز الحد الاقصى المسموح به " + warning_attachments_not_saved: "%{count}تعذر Ø­ÙØ¸ الملÙ" + + mail_subject_lost_password: " %{value}كلمة المرور الخاصة بك " + mail_body_lost_password: 'لتغير كلمة المرور، انقر على الروابط التالية:' + mail_subject_register: " %{value}ØªÙØ¹ÙŠÙ„ حسابك " + mail_body_register: 'Ù„ØªÙØ¹ÙŠÙ„ حسابك، انقر على الروابط التالية:' + mail_body_account_information_external: " %{value}اصبح بامكانك استخدام حسابك للدخول" + mail_body_account_information: معلومات حسابك + mail_subject_account_activation_request: "%{value}طلب ØªÙØ¹ÙŠÙ„ الحساب " + mail_body_account_activation_request: " (%{value})تم تسجيل حساب جديد، بانتظار المواÙقة:" + mail_subject_reminder: "%{count}تم تأجيل المهام التالية " + mail_body_reminder: "%{count}يجب ان تقوم بتسليم المهام التالية :" + mail_subject_wiki_content_added: "'%{id}' تم Ø§Ø¶Ø§ÙØ© ØµÙØ­Ø© ويكي" + mail_body_wiki_content_added: "The '%{id}' تم Ø§Ø¶Ø§ÙØ© ØµÙØ­Ø© ويكي من قبل %{author}." + mail_subject_wiki_content_updated: "'%{id}' تم تحديث ØµÙØ­Ø© ويكي" + mail_body_wiki_content_updated: "The '%{id}'تم تحديث ØµÙØ­Ø© ويكي من قبل %{author}." + + + 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: التعبير العادي + field_min_length: الحد الادنى للطول + field_max_length: الحد الاعلى للطول + field_value: القيمة + field_category: Ø§Ù„ÙØ¦Ø© + field_title: العنوان + field_project: المشروع + field_issue: القضية + field_status: الحالة + field_notes: ملاحظات + field_is_closed: القضية مغلقة + field_is_default: القيمة Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠØ© + field_tracker: المتتبع + field_subject: الموضوع + field_due_date: تاريخ الاستحقاق + field_assigned_to: المحال اليه + field_priority: الأولوية + field_fixed_version: الاصدار المستهد٠+ field_user: المستخدم + field_principal: الرئيسي + field_role: دور + field_homepage: Ø§Ù„ØµÙØ­Ø© الرئيسية + field_is_public: عام + field_parent: مشروع ÙØ±Ø¹ÙŠ Ù…Ù† + field_is_in_roadmap: القضايا المعروضة ÙÙŠ خارطة الطريق + field_login: تسجيل الدخول + field_mail_notification: ملاحظات على البريد الالكتروني + field_admin: المدير + field_last_login_on: اخر اتصال + field_language: لغة + field_effective_date: تاريخ + field_password: كلمة المرور + field_new_password: كلمة المرور الجديدة + field_password_confirmation: تأكيد + field_version: إصدار + field_type: نوع + field_host: المضي٠+ field_port: Ø§Ù„Ù…Ù†ÙØ° + field_account: الحساب + field_base_dn: DN قاعدة + field_attr_login: سمة الدخول + field_attr_firstname: سمة الاسم الاول + field_attr_lastname: سمة الاسم الاخير + field_attr_mail: سمة البريد الالكتروني + field_onthefly: إنشاء حساب مستخدم على تحرك + field_start_date: تاريخ البدية + field_done_ratio: "% تم" + field_auth_source: وضع المصادقة + field_hide_mail: Ø¥Ø®ÙØ§Ø¡ بريدي الإلكتروني + field_comments: تعليق + field_url: رابط + field_start_page: ØµÙØ­Ø© البداية + field_subproject: المشروع Ø§Ù„ÙØ±Ø¹ÙŠ + field_hours: ساعات + field_activity: النشاط + field_spent_on: تاريخ + field_identifier: المعر٠+ field_is_filter: استخدم كتصÙية + field_issue_to: القضايا المتصلة + field_delay: تأخير + field_assignable: يمكن ان تستند القضايا الى هذا الدور + field_redirect_existing_links: إعادة توجيه الروابط الموجودة + field_estimated_hours: الوقت المتوقع + field_column_names: أعمدة + field_time_entries: وقت الدخول + field_time_zone: المنطقة الزمنية + field_searchable: يمكن البحث Ùيه + field_default_value: القيمة Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠØ© + field_comments_sorting: اعرض التعليقات + field_parent_title: ØµÙØ­Ø© الوالدين + field_editable: يمكن اعادة تحريره + field_watcher: مراقب + field_identity_url: Ø§ÙØªØ­ الرابط الخاص بالهوية الشخصية + field_content: المحتويات + field_group_by: مجموعة النتائج عن طريق + field_sharing: مشاركة + field_parent_issue: مهمة الوالدين + field_member_of_group: "مجموعة المحال" + field_assigned_to_role: "دور المحال" + field_text: حقل نصي + field_visible: غير مرئي + field_warn_on_leaving_unsaved: "الرجاء التحذير عند مغادرة ØµÙØ­Ø© والنص غير محÙوظ" + field_issues_visibility: القضايا المرئية + field_is_private: خاص + field_commit_logs_encoding: رسائل الترميز + field_scm_path_encoding: ترميز المسار + field_path_to_repository: مسار المستودع + field_root_directory: دليل الجذر + field_cvsroot: CVSجذر + field_cvs_module: وحدة + + setting_app_title: عنوان التطبيق + setting_app_subtitle: العنوان Ø§Ù„ÙØ±Ø¹ÙŠ Ù„Ù„ØªØ·Ø¨ÙŠÙ‚ + setting_welcome_text: نص الترحيب + setting_default_language: اللغة Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠØ© + setting_login_required: مطلوب المصادقة + setting_self_registration: التسجيل الذاتي + setting_attachment_max_size: الحد الاقصى Ù„Ù„Ù…Ù„ÙØ§Øª المرÙقة + setting_issues_export_limit: الحد الاقصى لقضايا التصدير + setting_mail_from: انبعاثات عنوان بريدك + setting_bcc_recipients: مستلمين النسخ المخÙية (bcc) + setting_plain_text_mail: نص عادي (no HTML) + setting_host_name: اسم ومسار المستخدم + setting_text_formatting: تنسيق النص + setting_wiki_compression: ضغط تاريخ الويكي + setting_feeds_limit: Atom feeds الحد الاقصى لعدد البنود ÙÙŠ + setting_default_projects_public: المشاريع الجديده متاحة للجميع Ø§ÙØªØ±Ø§Ø¶ÙŠØ§ + setting_autofetch_changesets: الإحضار التلقائي + setting_sys_api_enabled: من ادارة المستودع WS تمكين + setting_commit_ref_keywords: مرجعية الكلمات Ø§Ù„Ù…ÙØªØ§Ø­ÙŠØ© + setting_commit_fix_keywords: تصحيح الكلمات Ø§Ù„Ù…ÙØªØ§Ø­ÙŠØ© + setting_autologin: الدخول التلقائي + setting_date_format: تنسيق التاريخ + setting_time_format: تنسيق الوقت + setting_cross_project_issue_relations: السماح بادارج القضايا ÙÙŠ هذا المشروع + setting_issue_list_default_columns: الاعمدة Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠØ© المعروضة ÙÙŠ قائمة القضية + setting_repositories_encodings: ترميز المرÙقات والمستودعات + setting_emails_header: رأس رسائل البريد الإلكتروني + setting_emails_footer: ذيل رسائل البريد الإلكتروني + setting_protocol: بروتوكول + setting_per_page_options: الكائنات لكل خيارات Ø§Ù„ØµÙØ­Ø© + setting_user_format: تنسيق عرض المستخدم + setting_activity_days_default: الايام المعروضة على نشاط المشروع + setting_display_subprojects_issues: عرض القضايا Ø§Ù„ÙØ±Ø¹ÙŠØ© للمشارع الرئيسية بشكل Ø§ÙØªØ±Ø§Ø¶ÙŠ + setting_enabled_scm: SCM تمكين + setting_mail_handler_body_delimiters: "اقتطاع رسائل البريد الإلكتروني بعد هذه الخطوط" + setting_mail_handler_api_enabled: للرسائل الواردةWS تمكين + setting_mail_handler_api_key: API Ù…ÙØªØ§Ø­ + setting_sequential_project_identifiers: انشاء Ù…Ø¹Ø±ÙØ§Øª المشروع المتسلسلة + setting_gravatar_enabled: كأيقونة مستخدمGravatar استخدام + setting_gravatar_default: Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠØ©Gravatar صورة + setting_diff_max_lines_displayed: الحد الاقصى لعدد الخطوط + setting_file_max_size_displayed: الحد الأقصى لحجم النص المعروض على Ø§Ù„Ù…Ù„ÙØ§Øª المرÙقة + setting_repository_log_display_limit: الحد الاقصى لعدد التنقيحات المعروضة على مل٠السجل + setting_openid: السماح بدخول اسم المستخدم Ø§Ù„Ù…ÙØªÙˆØ­ والتسجيل + setting_password_min_length: الحد الادني لطول كلمة المرور + setting_new_project_user_role_id: الدور المسند الى المستخدم غير المسؤول الذي يقوم بإنشاء المشروع + setting_default_projects_modules: تمكين الوحدات النمطية للمشاريع الجديدة بشكل Ø§ÙØªØ±Ø§Ø¶ÙŠ + setting_issue_done_ratio: حساب نسبة القضية المنتهية + setting_issue_done_ratio_issue_field: استخدم حقل القضية + setting_issue_done_ratio_issue_status: استخدم وضع القضية + setting_start_of_week: بدأ التقويم + setting_rest_api_enabled: تمكين باقي خدمات الويب + setting_cache_formatted_text: النص المسبق تنسيقه ÙÙŠ ذاكرة التخزين المؤقت + setting_default_notification_option: خيار الاعلام Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠ + setting_commit_logtime_enabled: تميكن وقت الدخول + setting_commit_logtime_activity_id: النشاط ÙÙŠ وقت الدخول + setting_gantt_items_limit: الحد الاقصى لعدد العناصر المعروضة على المخطط + setting_issue_group_assignment: السماح للإحالة الى المجموعات + setting_default_issue_start_date_to_creation_date: استخدام التاريخ الحالي كتاريخ بدأ للقضايا الجديدة + + permission_add_project: إنشاء مشروع + permission_add_subprojects: إنشاء مشاريع ÙØ±Ø¹ÙŠØ© + permission_edit_project: تعديل مشروع + permission_select_project_modules: تحديد شكل المشروع + permission_manage_members: إدارة الاعضاء + permission_manage_project_activities: ادارة اصدارات المشروع + permission_manage_versions: ادارة الاصدارات + permission_manage_categories: ادارة انواع القضايا + permission_view_issues: عرض القضايا + permission_add_issues: Ø§Ø¶Ø§ÙØ© القضايا + permission_edit_issues: تعديل القضايا + permission_manage_issue_relations: ادارة علاقات القضايا + permission_set_issues_private: تعين قضايا عامة او خاصة + permission_set_own_issues_private: تعين القضايا الخاصة بك كقضايا عامة او خاصة + 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_delete_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_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: حماية ØµÙØ­Ø§Øª ويكي + 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: حذ٠الرسائل الخاصة + permission_export_wiki_pages: تصدير ØµÙØ­Ø§Øª ويكي + permission_manage_subtasks: ادارة المهام Ø§Ù„ÙØ±Ø¹ÙŠØ© + + project_module_issue_tracking: تعقب القضايا + project_module_time_tracking: التعقب الزمني + project_module_news: الاخبار + project_module_documents: المستندات + project_module_files: Ø§Ù„Ù…Ù„ÙØ§Øª + project_module_wiki: ويكي + project_module_repository: المستودع + project_module_boards: المنتديات + project_module_calendar: التقويم + project_module_gantt: جانت + + label_user: المستخدم + label_user_plural: المستخدمين + label_user_new: مستخدم جديد + label_user_anonymous: مجهول الهوية + label_project: مشروع + label_project_new: مشروع جديد + label_project_plural: مشاريع + label_x_projects: + zero: لا يوجد مشاريع + one: مشروع واحد + other: "%{count} مشاريع" + label_project_all: كل المشاريع + label_project_latest: احدث المشاريع + label_issue: قضية + label_issue_new: قضية جديدة + label_issue_plural: قضايا + label_issue_view_all: عرض كل القضايا + label_issues_by: " %{value}القضية لصحابها" + label_issue_added: تم Ø§Ø¶Ø§ÙØ© القضية + label_issue_updated: تم تحديث القضية + label_issue_note_added: تم Ø§Ø¶Ø§ÙØ© الملاحظة + label_issue_status_updated: تم تحديث الحالة + label_issue_priority_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_role_anonymous: مجهول الهوية + label_role_non_member: ليس عضو + label_member: عضو + label_member_new: عضو جديد + label_member_plural: اعضاء + label_tracker: المتتبع + label_tracker_plural: المتتبعين + label_tracker_new: متتبع جديد + label_workflow: سير العمل + label_issue_status: وضع القضية + label_issue_status_plural: اوضاع القضية + label_issue_status_new: وضع جديد + label_issue_category: نوع القضية + label_issue_category_plural: انواع القضايا + label_issue_category_new: نوع جديد + label_custom_field: تخصيص حقل + label_custom_field_plural: تخصيص حقول + label_custom_field_new: حقل مخصص جديد + label_enumerations: التعدادات + label_enumeration_new: قيمة جديدة + label_information: معلومة + label_information_plural: معلومات + label_please_login: برجى تسجيل الدخول + label_register: تسجيل + label_login_with_open_id_option: او الدخول بهوية Ù…ÙØªÙˆØ­Ø© + label_password_lost: Ùقدت كلمة السر + label_home: Ø§Ù„ØµÙØ­Ø© الرئيسية + label_my_page: Ø§Ù„ØµÙØ­Ø© الخاصة بي + label_my_account: حسابي + label_my_projects: مشاريعي الخاصة + label_my_page_block: حجب ØµÙØ­ØªÙŠ Ø§Ù„Ø®Ø§ØµØ© + label_administration: الإدارة + label_login: تسجيل الدخول + label_logout: تسجيل الخروج + label_help: مساعدة + label_reported_issues: أبلغ القضايا + label_assigned_to_me_issues: المسائل المعنية إلى + label_last_login: آخر اتصال + label_registered_on: مسجل على + label_activity: النشاط + label_overall_activity: النشاط العام + label_user_activity: "قيمة النشاط" + label_new: جديدة + label_logged_as: تم تسجيل دخولك + label_environment: البيئة + label_authentication: المصادقة + label_auth_source: وضع المصادقة + label_auth_source_new: وضع مصادقة جديدة + label_auth_source_plural: أوضاع المصادقة + label_subproject_plural: مشاريع ÙØ±Ø¹ÙŠØ© + label_subproject_new: مشروع ÙØ±Ø¹ÙŠ Ø¬Ø¯ÙŠØ¯ + label_and_its_subprojects: "قيمةالمشاريع Ø§Ù„ÙØ±Ø¹ÙŠØ© الخاصة بك" + label_min_max_length: الحد الاقصى والادنى للطول + label_list: قائمة + label_date: تاريخ + label_integer: عدد صحيح + label_float: تعويم + label_boolean: منطقية + label_string: النص + label_text: نص طويل + label_attribute: سمة + label_attribute_plural: السمات + 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_news_comment_added: Ø¥Ø¶Ø§ÙØ© التعليقات على أخبار + label_settings: إعدادات + label_overview: لمحة عامة + label_version: الإصدار + label_version_new: الإصدار الجديد + label_version_plural: الإصدارات + label_close_versions: أكملت إغلاق الإصدارات + 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_x_open_issues_abbr_on_total: + zero: 0 Ù…ÙØªÙˆØ­ / %{total} + one: 1 Ù…ÙØªÙˆØ­ / %{total} + other: "%{count} Ù…ÙØªÙˆØ­ / %{total}" + label_x_open_issues_abbr: + zero: 0 Ù…ÙØªÙˆØ­ + one: 1 مقتوح + other: "%{count} Ù…ÙØªÙˆØ­" + label_x_closed_issues_abbr: + zero: 0 مغلق + one: 1 مغلق + other: "%{count} مغلق" + 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: جانت + label_internal: الداخلية + label_last_changes: "آخر التغييرات %{count}" + label_change_view_all: عرض ÙƒØ§ÙØ© التغييرات + label_personalize_page: تخصيص هذه Ø§Ù„ØµÙØ­Ø© + label_comment: تعليق + label_comment_plural: تعليقات + label_x_comments: + zero: لا يوجد تعليقات + one: تعليق واحد + other: "%{count} تعليقات" + label_comment_add: Ø¥Ø¶Ø§ÙØ© تعليق + label_comment_added: تم Ø¥Ø¶Ø§ÙØ© التعليق + label_comment_delete: حذ٠التعليقات + label_query: استعلام مخصص + label_query_plural: استعلامات مخصصة + label_query_new: استعلام جديد + label_my_queries: استعلاماتي المخصصة + label_filter_add: Ø¥Ø¶Ø§ÙØ© عامل تصÙية + label_filter_plural: عوامل التصÙية + label_equals: يساوي + label_not_equals: لا يساوي + label_in_less_than: ÙÙŠ أقل من + label_in_more_than: ÙÙŠ أكثر من + label_greater_or_equal: '>=' + label_less_or_equal: '< =' + label_between: بين + label_in: ÙÙŠ + label_today: اليوم + label_all_time: كل الوقت + label_yesterday: بالأمس + label_this_week: هذا الأسبوع + label_last_week: الأسبوع الماضي + label_last_n_days: "ايام %{count} اخر" + label_this_month: هذا الشهر + label_last_month: الشهر الماضي + label_this_year: هذا العام + label_date_range: نطاق التاريخ + label_less_than_ago: أقل من قبل أيام + label_more_than_ago: أكثر من قبل أيام + label_ago: منذ أيام + label_contains: يحتوي على + label_not_contains: لا يحتوي على + label_day_plural: أيام + label_repository: المستودع + label_repository_plural: المستودعات + label_browse: ØªØµÙØ­ + label_branch: ÙØ±Ø¹ + label_tag: ربط + label_revision: مراجعة + label_revision_plural: تنقيحات + label_revision_id: " %{value}مراجعة" + label_associated_revisions: التنقيحات المرتبطة + label_added: Ø¥Ø¶Ø§ÙØ© + label_modified: تعديل + label_copied: نسخ + label_renamed: إعادة تسمية + label_deleted: حذ٠+ label_latest_revision: آخر تنقيح + label_latest_revision_plural: أحدث المراجعات + label_view_revisions: عرض التنقيحات + label_view_all_revisions: عرض ÙƒØ§ÙØ© المراجعات + label_max_size: الحد الأقصى للحجم + label_sort_highest: التحرك إلى أعلى + label_sort_higher: تحريك لأعلى + label_sort_lower: تحريك لأسÙÙ„ + label_sort_lowest: الانتقال إلى أسÙÙ„ + label_roadmap: خارطة الطريق + label_roadmap_due_in: " %{value}تستحق ÙÙŠ " + label_roadmap_overdue: "%{value}تأخير" + 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: الÙهرس حسب العنوان + label_index_by_date: الÙهرس حسب التاريخ + label_current_version: الإصدار الحالي + label_preview: معاينة + label_feed_plural: موجز ويب + label_changes_details: ØªÙØ§ØµÙŠÙ„ جميع التغييرات + label_issue_tracking: تعقب القضايا + label_spent_time: أمضى بعض الوقت + label_overall_spent_time: الوقت الذي تم Ø§Ù†ÙØ§Ù‚Ù‡ كاملا + label_f_hour: "%{value} ساعة" + label_f_hour_plural: "%{value} ساعات" + label_time_tracking: تعقب الوقت + label_change_plural: التغييرات + label_statistics: إحصاءات + label_commits_per_month: يثبت ÙÙŠ الشهر + label_commits_per_author: يثبت لكل مؤل٠+ label_diff: Ø§Ù„Ø§Ø®ØªÙ„Ø§ÙØ§Øª + label_view_diff: عرض Ø§Ù„Ø§Ø®ØªÙ„Ø§ÙØ§Øª + label_diff_inline: مضمنة + label_diff_side_by_side: جنبا إلى جنب + label_options: خيارات + label_copy_workflow_from: نسخ سير العمل من + label_permissions_report: تقرير أذونات + label_watched_issues: شاهد القضايا + label_related_issues: القضايا ذات الصلة + label_applied_status: تطبيق مركز + label_loading: تحميل... + label_relation_new: علاقة جديدة + label_relation_delete: حذ٠العلاقة + label_relates_to: ذات الصلة إلى + label_duplicates: التكرارات + label_duplicated_by: ازدواج + label_blocks: حظر + label_blocked_by: حظر بواسطة + label_precedes: يسبق + label_follows: يتبع + label_end_to_start: نهاية لبدء + label_end_to_end: نهاية إلى نهاية + label_start_to_start: بدء إلى بدء + label_start_to_end: بداية لنهاية + label_stay_logged_in: تسجيل الدخول ÙÙŠ + label_disabled: تعطيل + label_show_completed_versions: أكملت إظهار إصدارات + label_me: لي + label_board: المنتدى + label_board_new: منتدى جديد + label_board_plural: المنتديات + label_board_locked: تأمين + label_board_sticky: لزجة + label_topic_plural: المواضيع + label_message_plural: رسائل + label_message_last: آخر رسالة + label_message_new: رسالة جديدة + label_message_posted: تم Ø§Ø¶Ø§ÙØ© الرسالة + label_reply_plural: الردود + label_send_information: إرسال معلومات الحساب للمستخدم + label_year: سنة + label_month: شهر + label_week: أسبوع + label_date_from: من + label_date_to: إلى + label_language_based: استناداً إلى لغة المستخدم + label_sort_by: " %{value}الترتيب حسب " + label_send_test_email: ارسل رسالة الكترونية كاختبار + label_feeds_access_key: RSS Ù…ÙØªØ§Ø­ دخول + label_missing_feeds_access_key: Ù…ÙقودRSS Ù…ÙØªØ§Ø­ دخول + label_feeds_access_key_created_on: "RSS تم انشاء Ù…ÙØªØ§Ø­ %{value} منذ" + label_module_plural: الوحدات النمطية + label_added_time_by: " تم Ø§Ø¶Ø§ÙØªÙ‡ من قبل%{author} %{age} منذ" + label_updated_time_by: " تم تحديثه من قبل%{author} %{age} منذ" + label_updated_time: "تم التحديث %{value} منذ" + label_jump_to_a_project: الانتقال إلى مشروع... + label_file_plural: Ø§Ù„Ù…Ù„ÙØ§Øª + label_changeset_plural: اعدادات التغير + label_default_columns: الاعمدة Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠØ© + label_no_change_option: (أي تغيير) + label_bulk_edit_selected_issues: تحرير القضايا المظللة + label_bulk_edit_selected_time_entries: تعديل كل الإدخالات ÙÙŠ كل الاوقات + 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_option_only_my_events: "السماح لي Ùقط بمشاهدة الاحداث الخاصة" + label_user_mail_option_only_assigned: "Ùقط الخيارات التي تم تعيينها" + label_user_mail_option_only_owner: "Ùقط للخيارات التي املكها" + label_user_mail_no_self_notified: "لا تريد اعلامك بالتغيرات التي تجريها Ø¨Ù†ÙØ³Ùƒ" + label_registration_activation_by_email: حساب التنشيط عبر البريد الإلكتروني + label_registration_manual_activation: تنشيط الحساب اليدوي + label_registration_automatic_activation: تنشيط الحساب التلقائي + label_display_per_page: "لكل ØµÙØ­Ø©: %{value}" + label_age: العمر + label_change_properties: تغيير الخصائص + label_general: عامة + label_more: أكثر + label_scm: scm + label_plugins: Ø§Ù„Ø¥Ø¶Ø§ÙØ§Øª + label_ldap_authentication: مصادقة LDAP + label_downloads_abbr: D/L + label_optional_description: وص٠اختياري + label_add_another_file: Ø¥Ø¶Ø§ÙØ© مل٠آخر + label_preferences: ØªÙØ¶ÙŠÙ„ات + label_chronological_order: ÙÙŠ ترتيب زمني + label_reverse_chronological_order: ÙÙŠ ترتيب زمني عكسي + label_planning: التخطيط + label_incoming_emails: رسائل البريد الإلكتروني الوارد + label_generate_key: إنشاء Ù…ÙØªØ§Ø­ + label_issue_watchers: المراقبون + label_example: مثال + label_display: العرض + label_sort: ÙØ±Ø² + label_ascending: تصاعدي + label_descending: تنازلي + label_date_from_to: من %{start} الى %{end} + label_wiki_content_added: Ø¥Ø¶Ø§ÙØ© ØµÙØ­Ø© ويكي + label_wiki_content_updated: تحديث ØµÙØ­Ø© ويكي + label_group: مجموعة + label_group_plural: المجموعات + label_group_new: مجموعة جديدة + label_time_entry_plural: أمضى بعض الوقت + label_version_sharing_none: لم يشارك + label_version_sharing_descendants: يشارك + label_version_sharing_hierarchy: مع التسلسل الهرمي للمشروع + label_version_sharing_tree: مع شجرة المشروع + label_version_sharing_system: مع جميع المشاريع + label_update_issue_done_ratios: تحديث قضيةالنسب + label_copy_source: مصدر + label_copy_target: الهد٠+ label_copy_same_as_target: Ù†ÙØ³ الهد٠+ label_display_used_statuses_only: عرض الحالات المستخدمة من قبل هذا "تعقب" Ùقط + label_api_access_key: Ù…ÙØªØ§Ø­ الوصول إلى API + label_missing_api_access_key: API لم يتم الحصول على Ù…ÙØªØ§Ø­ الوصول + label_api_access_key_created_on: " API إنشاء Ù…ÙØªØ§Ø­ الوصول إلى" + label_profile: المل٠الشخصي + label_subtask_plural: المهام Ø§Ù„ÙØ±Ø¹ÙŠØ© + label_project_copy_notifications: إرسال إشعار الى البريد الإلكتروني عند نسخ المشروع + label_principal_search: "البحث عن مستخدم أو مجموعة:" + label_user_search: "البحث عن المستخدم:" + label_additional_workflow_transitions_for_author: الانتقالات الإضاÙية المسموح بها عند المستخدم صاحب البلاغ + label_additional_workflow_transitions_for_assignee: الانتقالات الإضاÙية المسموح بها عند المستخدم المحال إليه + label_issues_visibility_all: جميع القضايا + label_issues_visibility_public: جميع القضايا الخاصة + label_issues_visibility_own: القضايا التي أنشأها المستخدم + label_git_report_last_commit: اعتماد التقرير الأخير Ù„Ù„Ù…Ù„ÙØ§Øª والدلائل + label_parent_revision: الوالدين + label_child_revision: الطÙÙ„ + label_export_options: "%{export_format} خيارات التصدير" + + button_login: دخول + button_submit: تثبيت + button_save: Ø­ÙØ¸ + button_check_all: نحديد الكل + button_uncheck_all: عدم تحديد الكل + button_collapse_all: تقليص الكل + button_expand_all: عرض الكل + button_delete: حذ٠+ button_create: انشاء + button_create_and_continue: انشاء واستمرار + button_test: اختبار + button_edit: تعديل + button_edit_associated_wikipage: "تغير ØµÙØ­Ø© ويكي: %{page_title}" + button_add: Ø§Ø¶Ø§ÙØ© + button_change: تغير + button_apply: تطبيق + button_clear: واضح + button_lock: Ù‚ÙÙ„ + button_unlock: الغاء القÙÙ„ + button_download: تنزيل + button_list: قائمة + button_view: عرض + button_move: تحرك + button_move_and_follow: تحرك واتبع + button_back: رجوع + button_cancel: إلغاء + button_activate: تنشيط + button_sort: ترتيب + button_log_time: وقت الدخول + button_rollback: الرجوع الى هذا الاصدار + button_watch: يشاهد + button_unwatch: إلغاء المشاهدة + button_reply: رد + button_archive: الارشي٠+ button_unarchive: إلغاء Ø§Ù„Ø§Ø±Ø´ÙØ© + button_reset: إعادة + button_rename: إعادة التسمية + button_change_password: تغير كلمة المرور + button_copy: نسخ + button_copy_and_follow: نسخ واتباع + button_annotate: تعليق + button_update: تحديث + button_configure: تكوين + button_quote: يقتبس + button_duplicate: يضاع٠+ button_show: يظهر + button_edit_section: يعدل هذا الجزء + button_export: يستورد + + status_active: نشيط + status_registered: مسجل + status_locked: مقÙÙ„ + + version_status_open: Ù…ÙØªÙˆØ­ + version_status_locked: مقÙÙ„ + version_status_closed: مغلق + + field_active: ÙØ¹Ø§Ù„ + + text_select_mail_notifications: حدد الامور التي يجب ابلاغك بها عن طريق البريد الالكتروني + text_regexp_info: مثال. ^[A-Z0-9]+$ + text_min_max_length_info: الحد الاقصى والادني لطول المعلومات + text_project_destroy_confirmation: هل أنت متأكد من أنك تريد حذ٠هذا المشروع والبيانات ذات الصلة؟ + text_subprojects_destroy_warning: "subproject(s): سيتم حذ٠أيضا." + text_workflow_edit: حدد دوراً وتعقب لتحرير سير العمل + text_are_you_sure: هل أنت متأكد؟ + text_journal_changed: "%{label} تغير %{old} الى %{new}" + text_journal_changed_no_detail: "%{label} تم التحديث" + text_journal_set_to: "%{label} تغير الى %{value}" + text_journal_deleted: "%{label} تم الحذ٠(%{old})" + text_journal_added: "%{label} %{value} تم Ø§Ù„Ø§Ø¶Ø§ÙØ©" + text_tip_issue_begin_day: قضية بدأت اليوم + text_tip_issue_end_day: قضية انتهت اليوم + text_tip_issue_begin_end_day: قضية بدأت وانتهت اليوم + text_caracters_maximum: "%{count} الحد الاقصى." + text_caracters_minimum: "الحد الادنى %{count}" + text_length_between: "الطول %{min} بين %{max} رمز" + text_tracker_no_workflow: لم يتم تحديد سير العمل لهذا المتتبع + text_unallowed_characters: رموز غير مسموحة + text_comma_separated: مسموح رموز متنوعة ÙŠÙØµÙ„ها ÙØ§ØµÙ„Ø© . + text_line_separated: مسموح رموز متنوعة ÙŠÙØµÙ„ها سطور + text_issues_ref_in_commit_messages: الرجوع واصلاح القضايا ÙÙŠ رسائل المشتكين + text_issue_added: "القضية %{id} تم ابلاغها عن طريق %{author}." + text_issue_updated: "القضية %{id} تم تحديثها عن طريق %{author}." + text_wiki_destroy_confirmation: هل انت متأكد من رغبتك ÙÙŠ حذ٠هذا الويكي ومحتوياته؟ + text_issue_category_destroy_question: "بعض القضايا (%{count}) مرتبطة بهذه Ø§Ù„ÙØ¦Ø©ØŒ ماذا تريد ان ØªÙØ¹Ù„ بها؟" + text_issue_category_destroy_assignments: Ø­Ø°Ù Ø§Ù„ÙØ¦Ø© + text_issue_category_reassign_to: اعادة تثبيت البنود ÙÙŠ Ø§Ù„ÙØ¦Ø© + text_user_mail_option: "بالنسبة للمشاريع غير المحددة، سو٠يتم ابلاغك عن المشاريع التي تشاهدها او تشارك بها Ùقط!" + text_no_configuration_data: "الادوار والمتتبع وحالات القضية ومخطط سير العمل لم يتم تحديد وضعها Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠ Ø¨Ø¹Ø¯. " + text_load_default_configuration: احمل الاعدادات Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠØ© + text_status_changed_by_changeset: " طبق التغيرات المعينة على %{value}." + text_time_logged_by_changeset: "تم تطبيق التغيرات المعينة على %{value}." + text_issues_destroy_confirmation: هل انت متأكد من حذ٠البنود المظللة؟' + text_issues_destroy_descendants_confirmation: "سو٠يؤدي هذا الى حذ٠%{count} المهام Ø§Ù„ÙØ±Ø¹ÙŠØ© ايضا." + text_time_entries_destroy_confirmation: "هل انت متأكد من رغبتك ÙÙŠ حذ٠الادخالات الزمنية المحددة؟" + text_select_project_modules: قم بتحديد الوضع المناسب لهذا المشروع:' + text_default_administrator_account_changed: تم تعديل الاعدادات Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠØ© لحساب المدير + text_file_repository_writable: المرÙقات قابلة للكتابة + text_plugin_assets_writable: الدليل المساعد قابل للكتابة + text_destroy_time_entries_question: " ساعة على القضية التي تود حذÙها، ماذا تريد ان ØªÙØ¹Ù„ØŸ %{hours} تم تثبيت" + text_destroy_time_entries: قم بحذ٠الساعات المسجلة + text_assign_time_entries_to_project: ثبت الساعات المسجلة على التقرير + text_reassign_time_entries: 'اعادة تثبيت الساعات المسجلة لهذه القضية:' + text_user_wrote: "%{value} كتب:" + text_enumeration_destroy_question: "%{count} الكائنات المعنية لهذه القيمة" + text_enumeration_category_reassign_to: اعادة تثبيت الكائنات التالية لهذه القيمة:' + text_email_delivery_not_configured: "لم يتم تسليم البريد الالكتروني" + text_diff_truncated: '... لقد تم اقتطلع هذا الجزء لانه تجاوز الحد الاقصى المسموح بعرضه' + text_custom_field_possible_values_info: 'سطر لكل قيمة' + text_wiki_page_nullify_children: "Ø§Ù„Ø§Ø­ØªÙØ§Ø¸ Ø¨ØµÙØ­Ø§Øª الطÙÙ„ ÙƒØµÙØ­Ø§Øª جذر" + text_wiki_page_destroy_children: "Ø­Ø°Ù ØµÙØ­Ø§Øª الطÙÙ„ وجميع أولادهم" + text_wiki_page_reassign_children: "إعادة تعيين ØµÙØ­Ø§Øª تابعة لهذه Ø§Ù„ØµÙØ­Ø© الأصلية" + text_own_membership_delete_confirmation: "انت على وشك إزالة بعض أو ÙƒØ§ÙØ© الأذونات الخاصة بك، لن تكون قادراً على تحرير هذا المشروع بعد ذلك. هل أنت متأكد من أنك تريد المتابعة؟" + text_zoom_in: تصغير + text_zoom_out: تكبير + text_warn_on_leaving_unsaved: "Ø§Ù„ØµÙØ­Ø© تحتوي على نص غير مخزن، سو٠يÙقد النص اذا تم الخروج من Ø§Ù„ØµÙØ­Ø©." + text_scm_path_encoding_note: "Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠ: UTF-8" + text_git_repository_note: مستودع ÙØ§Ø±Øº ومحلي + text_mercurial_repository_note: مستودع محلي + text_scm_command: امر + text_scm_command_version: اصدار + text_scm_config: الرجاء اعادة تشغيل التطبيق + text_scm_command_not_available: الامر غير Ù…ØªÙˆÙØ±ØŒ الرجاء التحقق من لوحة التحكم + + default_role_manager: مدير + default_role_developer: مطور + default_role_reporter: مراسل + default_tracker_bug: الشوائب + default_tracker_feature: خاصية + default_tracker_support: دعم + default_issue_status_new: جديد + default_issue_status_in_progress: جاري التحميل + default_issue_status_resolved: الحل + default_issue_status_feedback: التغذية الراجعة + default_issue_status_closed: مغلق + default_issue_status_rejected: مرÙوض + default_doc_category_user: مستندات المستخدم + default_doc_category_tech: المستندات التقنية + default_priority_low: قليل + default_priority_normal: عادي + default_priority_high: عالي + default_priority_urgent: طارئ + default_priority_immediate: مباشرة + default_activity_design: تصميم + default_activity_development: تطوير + + enumeration_issue_priorities: الاولويات + enumeration_doc_categories: تصني٠المستندات + enumeration_activities: الانشطة + enumeration_system_activity: نشاط النظام + description_filter: Ùلترة + description_search: حقل البحث + description_choose_project: مشاريع + description_project_scope: مجال البحث + description_notes: ملاحظات + description_message_content: محتويات الرسالة + description_query_sort_criteria_attribute: نوع الترتيب + description_query_sort_criteria_direction: اتجاه الترتيب + description_user_mail_notification: إعدادات البريد الالكتروني + description_available_columns: الاعمدة Ø§Ù„Ù…ØªÙˆÙØ±Ø© + description_selected_columns: الاعمدة المحددة + description_all_columns: كل الاعمدة + description_issue_category_reassign: اختر التصني٠+ description_wiki_subpages_reassign: اختر ØµÙØ­Ø© جديدة + description_date_range_list: اختر المجال من القائمة + description_date_range_interval: اختر المدة عن طريق اختيار تاريخ البداية والنهاية + description_date_from: ادخل تاريخ البداية + description_date_to: ادخل تاريخ الانتهاء + text_rmagick_available: RMagick available (optional) + text_wiki_page_destroy_question: This page has %{descendants} child page(s) and descendant(s). What do you want to do? + text_repository_usernames_mapping: |- + Select or update the Redmine user mapped to each username found in the repository log. + Users with the same Redmine and repository username or email are automatically mapped. + notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." + label_x_issues: + zero: 0 قضية + one: 1 قضية + other: "%{count} قضايا" + label_repository_new: New repository + field_repository_is_default: Main repository + label_copy_attachments: Copy attachments + label_item_position: "%{position}/%{count}" + label_completed_versions: Completed versions + text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
Once saved, the identifier cannot be changed. + field_multiple: Multiple values + setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed + text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes + text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) + notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. + text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} + permission_manage_related_issues: Manage related issues + field_auth_source_ldap_filter: LDAP filter + label_search_for_watchers: Search for watchers to add + notice_account_deleted: Your account has been permanently deleted. + setting_unsubscribe: Allow users to delete their own account + button_delete_my_account: Delete my account + text_account_destroy_confirmation: |- + Are you sure you want to proceed? + Your account will be permanently deleted, with no way to reactivate it. + error_session_expired: Your session has expired. Please login again. + text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." + setting_session_lifetime: Session maximum lifetime + setting_session_timeout: Session inactivity timeout + label_session_expiration: Session expiration + permission_close_project: Close / reopen the project + label_show_closed_projects: View closed projects + button_close: Close + button_reopen: Reopen + project_status_active: active + project_status_closed: closed + project_status_archived: archived + text_project_closed: This project is closed and read-only. + notice_user_successful_create: User %{id} created. + field_core_fields: Standard fields + field_timeout: Timeout (in seconds) + setting_thumbnails_enabled: Display attachment thumbnails + setting_thumbnails_size: Thumbnails size (in pixels) + label_status_transitions: Status transitions + label_fields_permissions: Fields permissions + label_readonly: Read-only + label_required: Required + text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
Once saved, the identifier cannot be changed. + field_board_parent: Parent forum + label_attribute_of_project: Project's %{name} + label_attribute_of_author: Author's %{name} + label_attribute_of_assigned_to: Assignee's %{name} + label_attribute_of_fixed_version: Target version's %{name} + label_copy_subtasks: Copy subtasks + label_copied_to: copied to + label_copied_from: copied from + label_any_issues_in_project: any issues in project + label_any_issues_not_in_project: any issues not in project + field_private_notes: Private notes + permission_view_private_notes: View private notes + permission_set_notes_private: Set notes as private + label_no_issues_in_project: no issues in project + label_any: جميع + label_last_n_weeks: last %{count} weeks + setting_cross_project_subtasks: Allow cross-project subtasks + label_cross_project_descendants: يشارك + label_cross_project_tree: مع شجرة المشروع + label_cross_project_hierarchy: مع التسلسل الهرمي للمشروع + label_cross_project_system: مع جميع المشاريع + button_hide: Hide + setting_non_working_week_days: Non-working days + label_in_the_next_days: in the next + label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: الإجمالي diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4b/4b8ff24938a4dd7deed4f9307f87afc0e6c1ff11.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/4b/4b8ff24938a4dd7deed4f9307f87afc0e6c1ff11.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,31 @@ +
+<%= link_to l(:label_auth_source_new), {:action => 'new'}, :class => 'icon icon-add' %> +
+ +

<%=l(:label_auth_source_plural)%>

+ + + + + + + + + + +<% for source in @auth_sources %> + "> + + + + + + +<% end %> + +
<%=l(:field_name)%><%=l(:field_type)%><%=l(:field_host)%><%=l(:label_user_plural)%>
<%= link_to(h(source.name), :action => 'edit', :id => source)%><%= h source.auth_method_name %><%= h source.host %><%= h source.users.count %> + <%= link_to l(:button_test), try_connection_auth_source_path(source), :class => 'icon icon-test' %> + <%= delete_link auth_source_path(source) %> +
+ +

<%= pagination_links_full @auth_source_pages %>

diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4b/4b9d105a6ddf9e2913feb748f35b6ced3a593557.svn-base --- a/.svn/pristine/4b/4b9d105a6ddf9e2913feb748f35b6ced3a593557.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1045 +0,0 @@ -# Spanish translations for Rails -# by Francisco Fernando García Nieto (ffgarcianieto@gmail.com) -# Redmine spanish translation: -# by J. Cayetano Delgado (Cayetano _dot_ Delgado _at_ ioko _dot_ com) - -es: - number: - # Used in number_with_delimiter() - # These are also the defaults for 'currency', 'percentage', 'precision', and 'human' - format: - # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5) - separator: "," - # Delimets thousands (e.g. 1,000,000 is a million) (always in groups of three) - delimiter: "." - # Number of decimals, behind the separator (1 with a precision of 2 gives: 1.00) - precision: 3 - - # Used in number_to_currency() - currency: - format: - # Where is the currency sign? %u is the currency unit, %n the number (default: $5.00) - format: "%n %u" - unit: "€" - # These three are to override number.format and are optional - separator: "," - delimiter: "." - precision: 2 - - # Used in number_to_percentage() - percentage: - format: - # These three are to override number.format and are optional - # separator: - delimiter: "" - # precision: - - # Used in number_to_precision() - precision: - format: - # These three are to override number.format and are optional - # separator: - delimiter: "" - # precision: - - # Used in number_to_human_size() - human: - format: - # These three are to override number.format and are optional - # separator: - delimiter: "" - precision: 1 - storage_units: - format: "%n %u" - units: - byte: - one: "Byte" - other: "Bytes" - kb: "KB" - mb: "MB" - gb: "GB" - tb: "TB" - - # Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words() - datetime: - distance_in_words: - half_a_minute: "medio minuto" - less_than_x_seconds: - one: "menos de 1 segundo" - other: "menos de %{count} segundos" - x_seconds: - one: "1 segundo" - other: "%{count} segundos" - less_than_x_minutes: - one: "menos de 1 minuto" - other: "menos de %{count} minutos" - x_minutes: - one: "1 minuto" - other: "%{count} minutos" - about_x_hours: - one: "alrededor de 1 hora" - other: "alrededor de %{count} horas" - x_days: - one: "1 día" - other: "%{count} días" - about_x_months: - one: "alrededor de 1 mes" - other: "alrededor de %{count} meses" - x_months: - one: "1 mes" - other: "%{count} meses" - about_x_years: - one: "alrededor de 1 año" - other: "alrededor de %{count} años" - over_x_years: - one: "más de 1 año" - other: "más de %{count} años" - almost_x_years: - one: "casi 1 año" - other: "casi %{count} años" - - activerecord: - errors: - template: - header: - one: "no se pudo guardar este %{model} porque se encontró 1 error" - other: "no se pudo guardar este %{model} porque se encontraron %{count} errores" - # The variable :count is also available - body: "Se encontraron problemas con los siguientes campos:" - - # The values :model, :attribute and :value are always available for interpolation - # The value :count is available when applicable. Can be used for pluralization. - messages: - inclusion: "no está incluido en la lista" - exclusion: "está reservado" - invalid: "no es válido" - confirmation: "no coincide con la confirmación" - accepted: "debe ser aceptado" - empty: "no puede estar vacío" - blank: "no puede estar en blanco" - too_long: "es demasiado largo (%{count} caracteres máximo)" - too_short: "es demasiado corto (%{count} caracteres mínimo)" - wrong_length: "no tiene la longitud correcta (%{count} caracteres exactos)" - taken: "ya está en uso" - not_a_number: "no es un número" - greater_than: "debe ser mayor que %{count}" - greater_than_or_equal_to: "debe ser mayor que o igual a %{count}" - equal_to: "debe ser igual a %{count}" - less_than: "debe ser menor que %{count}" - less_than_or_equal_to: "debe ser menor que o igual a %{count}" - odd: "debe ser impar" - even: "debe ser par" - greater_than_start_date: "debe ser posterior a la fecha de comienzo" - not_same_project: "no pertenece al mismo proyecto" - circular_dependency: "Esta relación podría crear una dependencia circular" - cant_link_an_issue_with_a_descendant: "Esta petición no puede ser ligada a una de estas tareas" - - # Append your own errors here or at the model/attributes scope. - - models: - # Overrides default messages - - attributes: - # Overrides model and default messages. - - direction: ltr - date: - formats: - # Use the strftime parameters for formats. - # When no format has been given, it uses default. - # You can provide other formats here if you like! - default: "%Y-%m-%d" - short: "%d de %b" - long: "%d de %B de %Y" - - day_names: [Domingo, Lunes, Martes, Miércoles, Jueves, Viernes, Sábado] - abbr_day_names: [Dom, Lun, Mar, Mie, Jue, Vie, Sab] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, Enero, Febrero, Marzo, Abril, Mayo, Junio, Julio, Agosto, Setiembre, Octubre, Noviembre, Diciembre] - abbr_month_names: [~, Ene, Feb, Mar, Abr, May, Jun, Jul, Ago, Set, Oct, Nov, Dic] - # Used in date_select and datime_select. - order: - - :year - - :month - - :day - - time: - formats: - default: "%A, %d de %B de %Y %H:%M:%S %z" - time: "%H:%M" - short: "%d de %b %H:%M" - long: "%d de %B de %Y %H:%M" - am: "am" - pm: "pm" - -# Used in array.to_sentence. - support: - array: - sentence_connector: "y" - - actionview_instancetag_blank_option: Por favor seleccione - - 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_in_progress: En curso - 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_developer: 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: %{value}" - 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_annotate_big_text_file: "La entrada no puede anotarse, al superar el tamaño máximo para ficheros de texto." - error_scm_command_failed: "Se produjo un error al acceder al repositorio: %{value}" - error_scm_not_found: "La entrada y/o la revisión no existe en el repositorio." - 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_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_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_is_filter: Usado como filtro - field_is_for_all: Para todos los proyectos - 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: 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_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_port: Puerto - 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: Asunto - field_subproject: Proyecto secundario - 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_first_day_of_week: '1' - general_lang_name: 'Español' - general_pdf_encoding: UTF-8 - 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: "%{count} 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 %{author} hace %{age}" - 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: "%{value} 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_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_x_open_issues_abbr_on_total: - zero: 0 abiertas / %{total} - one: 1 abierta / %{total} - other: "%{count} abiertas / %{total}" - label_x_open_issues_abbr: - zero: 0 abiertas - one: 1 abierta - other: "%{count} abiertas" - label_x_closed_issues_abbr: - zero: 0 cerradas - one: 1 cerrada - other: "%{count} 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_x_comments: - zero: sin comentarios - one: 1 comentario - other: "%{count} 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: %{value}" - label_document: Documento - label_document_added: Documento añadido - label_document_new: Nuevo documento - label_document_plural: Documentos - label_download: "%{count} Descarga" - label_download_plural: "%{count} 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: "%{value} hora" - label_f_hour_plural: "%{value} horas" - label_feed_plural: Feeds - label_feeds_access_key_created_on: "Clave de acceso por RSS creada hace %{value}" - 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_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 %{value}" - label_jump_to_a_project: Ir al proyecto... - label_language_based: Basado en el idioma - label_last_changes: "últimos %{count} cambios" - label_last_login: Última conexión - label_last_month: último mes - label_last_n_days: "últimos %{count} días" - 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_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_modification: "%{count} modificación" - label_modification_plural: "%{count} 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_view_all: Ver todas las noticias - label_next: Siguiente - label_no_change_option: (Sin cambios) - label_no_data: Ningún dato disponible - label_nobody: nadie - label_none: ninguno - label_not_contains: no contiene - label_not_equals: no igual - label_open_issues: abierta - label_open_issues_plural: abiertas - 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_permissions_report: Informe de permisos - label_personalize_page: Personalizar esta página - 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_x_projects: - zero: sin proyectos - one: 1 proyecto - other: "%{count} proyectos" - label_public_projects: Proyectos públicos - label_query: Consulta personalizada - label_query_new: Nueva consulta - 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_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_roadmap: Planificación - label_roadmap_due_in: "Finaliza en %{value}" - label_roadmap_no_issues: No hay peticiones para esta versión - label_roadmap_overdue: "%{value} 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_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 %{value}" - 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 %{value}" - label_updated_time_by: "Actualizado por %{author} hace %{age}" - label_used_by: Utilizado por - label_user: Usuario - label_user_activity: "Actividad de %{value}" - 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_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: Modificación Wiki - label_wiki_edit_plural: Modificaciones Wiki - label_wiki_page: Página Wiki - label_wiki_page_plural: Páginas Wiki - label_workflow: Flujo de trabajo - label_year: Año - label_yesterday: ayer - mail_body_account_activation_request: "Se ha inscrito un nuevo usuario (%{value}). La cuenta está pendiende de aprobación:" - mail_body_account_information: Información sobre su cuenta - mail_body_account_information_external: "Puede usar su cuenta %{value} 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: "%{count} peticion(es) asignadas a tí finalizan en los próximos %{days} días:" - mail_subject_account_activation_request: "Petición de activación de cuenta %{value}" - mail_subject_lost_password: "Tu contraseña del %{value}" - mail_subject_register: "Activación de la cuenta del %{value}" - mail_subject_reminder: "%{count} peticion(es) finalizan en los próximos %{days} 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. - notice_email_error: "Ha ocurrido un error mientras enviando el correo (%{value})" - notice_email_sent: "Se ha enviado un correo a %{value}" - notice_failed_to_save_issues: "Imposible grabar %{count} peticion(es) de %{total} seleccionada(s): %{ids}." - 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_documents: Documentos - project_module_files: Ficheros - project_module_issue_tracking: Peticiones - project_module_news: Noticias - project_module_repository: Repositorio - project_module_time_tracking: Control de tiempo - project_module_wiki: Wiki - setting_activity_days_default: Días a mostrar en la actividad de proyecto - 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_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 - 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_per_page_options: Objetos por página - setting_plain_text_mail: sólo texto plano (no HTML) - setting_protocol: Protocolo - 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: "%{count} caracteres como máximo." - text_caracters_minimum: "%{count} 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 %{hours} 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: "Las notificaciones están desactivadas porque el servidor de correo no está configurado.\nConfigure el servidor de SMTP en config/configuration.yml y reinicie la aplicación para activar los cambios." - text_enumeration_category_reassign_to: 'Reasignar al siguiente valor:' - text_enumeration_destroy_question: "%{count} objetos con este valor asignado." - text_file_repository_writable: Se puede escribir en el repositorio - text_issue_added: "Petición %{id} añadida por %{author}." - text_issue_category_destroy_assignments: Dejar las peticiones sin categoría - text_issue_category_destroy_question: "Algunas peticiones (%{count}) 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 %{id} ha sido actualizada por %{author}." - 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_length_between: "Longitud entre %{min} y %{max} 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.
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 %{value}" - text_subprojects_destroy_warning: "Los proyectos secundarios: %{value} también se eliminarán" - text_tip_issue_begin_day: tarea que comienza este día - text_tip_issue_begin_end_day: tarea que comienza y termina este día - text_tip_issue_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: "%{value} 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: Se puede escribir en el directorio público de las extensiones - warning_attachments_not_saved: "No se han podido grabar %{count} ficheros." - button_create_and_continue: Crear y continuar - text_custom_field_possible_values_info: 'Un valor en cada línea' - label_display: Mostrar - field_editable: Modificable - setting_repository_log_display_limit: Número máximo de revisiones mostradas en el fichero de trazas - setting_file_max_size_displayed: Tamaño máximo de los ficheros de texto mostrados - field_watcher: Seguidor - setting_openid: Permitir identificación y registro por OpenID - field_identity_url: URL de OpenID - label_login_with_open_id_option: o identifíquese con OpenID - field_content: Contenido - label_descending: Descendente - label_sort: Ordenar - label_ascending: Ascendente - label_date_from_to: Desde %{start} hasta %{end} - label_greater_or_equal: ">=" - label_less_or_equal: <= - text_wiki_page_destroy_question: Esta página tiene %{descendants} página(s) hija(s) y descendiente(s). ¿Qué desea hacer? - text_wiki_page_reassign_children: Reasignar páginas hijas a esta página - text_wiki_page_nullify_children: Dejar páginas hijas como páginas raíz - text_wiki_page_destroy_children: Eliminar páginas hijas y todos sus descendientes - setting_password_min_length: Longitud mínima de la contraseña - field_group_by: Agrupar resultados por - mail_subject_wiki_content_updated: "La página wiki '%{id}' ha sido actualizada" - label_wiki_content_added: Página wiki añadida - mail_subject_wiki_content_added: "Se ha añadido la página wiki '%{id}'." - mail_body_wiki_content_added: "%{author} ha añadido la página wiki '%{id}'." - label_wiki_content_updated: Página wiki actualizada - mail_body_wiki_content_updated: La página wiki '%{id}' ha sido actualizada por %{author}. - permission_add_project: Crear proyecto - setting_new_project_user_role_id: Permiso asignado a un usuario no-administrador para crear proyectos - label_view_all_revisions: Ver todas las revisiones - label_tag: Etiqueta - label_branch: Rama - error_no_tracker_in_project: Este proyecto no tiene asociados tipos de peticiones. Por favor, revise la configuración. - error_no_default_issue_status: No se ha definido un estado de petición por defecto. Por favor, revise la configuración (en "Administración" -> "Estados de las peticiones"). - text_journal_changed: "%{label} cambiado %{old} por %{new}" - text_journal_set_to: "%{label} establecido a %{value}" - text_journal_deleted: "%{label} eliminado (%{old})" - label_group_plural: Grupos - label_group: Grupo - label_group_new: Nuevo grupo - label_time_entry_plural: Tiempo dedicado - text_journal_added: "Añadido %{label} %{value}" - field_active: Activo - enumeration_system_activity: Actividad del sistema - permission_delete_issue_watchers: Borrar seguidores - version_status_closed: cerrado - version_status_locked: bloqueado - version_status_open: abierto - error_can_not_reopen_issue_on_closed_version: No se puede reabrir una petición asignada a una versión cerrada - - label_user_anonymous: Anónimo - button_move_and_follow: Mover y seguir - setting_default_projects_modules: Módulos activados por defecto en proyectos nuevos - setting_gravatar_default: Imagen Gravatar por defecto - field_sharing: Compartir - button_copy_and_follow: Copiar y seguir - label_version_sharing_hierarchy: Con la jerarquía del proyecto - label_version_sharing_tree: Con el árbol del proyecto - label_version_sharing_descendants: Con proyectos hijo - label_version_sharing_system: Con todos los proyectos - label_version_sharing_none: No compartir - button_duplicate: Duplicar - error_can_not_archive_project: Este proyecto no puede ser archivado - label_copy_source: Fuente - setting_issue_done_ratio: Calcular el ratio de tareas realizadas con - setting_issue_done_ratio_issue_status: Usar el estado de tareas - error_issue_done_ratios_not_updated: Ratios de tareas realizadas no actualizado. - error_workflow_copy_target: Por favor, elija categoría(s) y perfil(es) destino - setting_issue_done_ratio_issue_field: Utilizar el campo de petición - label_copy_same_as_target: El mismo que el destino - label_copy_target: Destino - notice_issue_done_ratios_updated: Ratios de tareas realizadas actualizados. - error_workflow_copy_source: Por favor, elija una categoría o rol de origen - label_update_issue_done_ratios: Actualizar ratios de tareas realizadas - setting_start_of_week: Comenzar las semanas en - permission_view_issues: Ver peticiones - label_display_used_statuses_only: Sólo mostrar los estados usados por este tipo de petición - label_revision_id: Revisión %{value} - label_api_access_key: Clave de acceso de la API - label_api_access_key_created_on: Clave de acceso de la API creada hace %{value} - label_feeds_access_key: Clave de acceso RSS - notice_api_access_key_reseted: Clave de acceso a la API regenerada. - setting_rest_api_enabled: Activar servicio web REST - label_missing_api_access_key: Clave de acceso a la API ausente - label_missing_feeds_access_key: Clave de accesso RSS ausente - button_show: Mostrar - text_line_separated: Múltiples valores permitidos (un valor en cada línea). - setting_mail_handler_body_delimiters: Truncar correos tras una de estas líneas - permission_add_subprojects: Crear subproyectos - label_subproject_new: Nuevo subproyecto - text_own_membership_delete_confirmation: |- - Está a punto de eliminar algún o todos sus permisos y podría perder la posibilidad de modificar este proyecto tras hacerlo. - ¿Está seguro de querer continuar? - label_close_versions: Cerrar versiones completadas - label_board_sticky: Pegajoso - label_board_locked: Bloqueado - permission_export_wiki_pages: Exportar páginas wiki - setting_cache_formatted_text: Cachear texto formateado - permission_manage_project_activities: Gestionar actividades del proyecto - error_unable_delete_issue_status: Fue imposible eliminar el estado de la petición - label_profile: Perfil - permission_manage_subtasks: Gestionar subtareas - field_parent_issue: Tarea padre - label_subtask_plural: Subtareas - label_project_copy_notifications: Enviar notificaciones por correo electrónico durante la copia del proyecto - error_can_not_delete_custom_field: Fue imposible eliminar el campo personalizado - error_unable_to_connect: Fue imposible conectar con (%{value}) - error_can_not_remove_role: Este rol está en uso y no puede ser eliminado. - error_can_not_delete_tracker: Este tipo contiene peticiones y no puede ser eliminado. - field_principal: Principal - label_my_page_block: Bloque Mi página - notice_failed_to_save_members: "Fallo al guardar miembro(s): %{errors}." - text_zoom_out: Alejar - text_zoom_in: Acercar - notice_unable_delete_time_entry: Fue imposible eliminar la entrada de tiempo dedicado. - label_overall_spent_time: Tiempo total dedicado - field_time_entries: Log time - project_module_gantt: Gantt - project_module_calendar: Calendario - button_edit_associated_wikipage: "Editar paginas Wiki asociadas: %{page_title}" - text_are_you_sure_with_children: ¿Borrar peticiones y todas sus peticiones hijas? - field_text: Campo de texto - label_user_mail_option_only_owner: Solo para objetos que soy propietario - setting_default_notification_option: Opcion de notificacion por defecto - label_user_mail_option_only_my_events: Solo para objetos que soy seguidor o estoy involucrado - label_user_mail_option_only_assigned: Solo para objetos que estoy asignado - label_user_mail_option_none: Sin eventos - field_member_of_group: Asignado al grupo - field_assigned_to_role: Asignado al perfil - notice_not_authorized_archived_project: El proyecto al que intenta acceder ha sido archivado. - label_principal_search: "Buscar por usuario o grupo:" - label_user_search: "Buscar por usuario:" - field_visible: Visible - setting_emails_header: Encabezado de Correos - - setting_commit_logtime_activity_id: Actividad de los tiempos registrados - text_time_logged_by_changeset: Aplicado en los cambios %{value}. - setting_commit_logtime_enabled: Habilitar registro de horas - notice_gantt_chart_truncated: Se recortó el diagrama porque excede el número máximo de elementos que pueden ser mostrados (%{max}) - setting_gantt_items_limit: Número máximo de elementos mostrados en el diagrama de Gantt - field_warn_on_leaving_unsaved: Avisarme cuando vaya a abandonar una página con texto no guardado - text_warn_on_leaving_unsaved: Esta página contiene texto no guardado y si la abandona sus cambios se perderán - label_my_queries: Mis consultas personalizadas - text_journal_changed_no_detail: "Se actualizó %{label}" - label_news_comment_added: Comentario añadido a noticia - button_expand_all: Expandir todo - button_collapse_all: Contraer todo - label_additional_workflow_transitions_for_assignee: Transiciones adicionales permitidas cuando la petición está asignada al usuario - label_additional_workflow_transitions_for_author: Transiciones adicionales permitidas cuando el usuario es autor de la petición - label_bulk_edit_selected_time_entries: Editar en bloque las horas seleccionadas - text_time_entries_destroy_confirmation: ¿Está seguro de querer eliminar (la hora seleccionada/las horas seleccionadas)? - label_role_anonymous: Anónimo - label_role_non_member: No miembro - label_issue_note_added: Nota añadida - label_issue_status_updated: Estado actualizado - label_issue_priority_updated: Prioridad actualizada - label_issues_visibility_own: Peticiones creadas por el usuario o asignadas a él - field_issues_visibility: Visibilidad de las peticiones - label_issues_visibility_all: Todas las peticiones - permission_set_own_issues_private: Poner las peticiones propias como públicas o privadas - field_is_private: Privada - permission_set_issues_private: Poner peticiones como públicas o privadas - label_issues_visibility_public: Todas las peticiones no privadas - text_issues_destroy_descendants_confirmation: Se procederá a borrar también %{count} subtarea(s). - field_commit_logs_encoding: Codificación de los mensajes de commit - field_scm_path_encoding: Codificación de las rutas - text_scm_path_encoding_note: "Por defecto: UTF-8" - field_path_to_repository: Ruta al repositorio - field_root_directory: Directorio raíz - field_cvs_module: Módulo - field_cvsroot: CVSROOT - text_mercurial_repository_note: Repositorio local (e.g. /hgrepo, c:\hgrepo) - text_scm_command: Orden - text_scm_command_version: Versión - label_git_report_last_commit: Informar del último commit para ficheros y directorios - text_scm_config: Puede configurar las órdenes de cada scm en configuration/configuration.yml. Por favor, reinicie la aplicación después de editarlo - text_scm_command_not_available: La orden para el Scm no está disponible. Por favor, compruebe la configuración en el panel de administración. - notice_issue_successful_create: Petición %{id} creada. - label_between: entre - setting_issue_group_assignment: Permitir asignar peticiones a grupos - label_diff: diferencias - text_git_repository_note: El repositorio es básico y local (p.e. /gitrepo, c:\gitrepo) - description_query_sort_criteria_direction: Dirección de ordenación - description_project_scope: Ãmbito de búsqueda - description_filter: Filtro - description_user_mail_notification: Configuración de notificaciones por correo - description_date_from: Introduzca la fecha de inicio - description_message_content: Contenido del mensaje - description_available_columns: Columnas disponibles - description_date_range_interval: Elija el rango seleccionando la fecha de inicio y fin - description_issue_category_reassign: Elija la categoría de la petición - description_search: Campo de búsqueda - description_notes: Notas - description_date_range_list: Elija el rango en la lista - description_choose_project: Proyectos - description_date_to: Introduzca la fecha fin - description_query_sort_criteria_attribute: Atributo de ordenación - description_wiki_subpages_reassign: Elija la nueva página padre - description_selected_columns: Columnas seleccionadas - label_parent_revision: Padre - label_child_revision: Hijo - setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues - button_edit_section: Edit this section - setting_repositories_encodings: Attachments and repositories encodings - description_all_columns: Todas las columnas - button_export: Exportar - label_export_options: "%{export_format} opciones de exportación" - error_attachment_too_big: Este fichero no se puede adjuntar porque excede el tamaño máximo de fichero (%{max_size}) diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4b/4bca034528beb58f74c7ed4f1bd974d891aaa55f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/4b/4bca034528beb58f74c7ed4f1bd974d891aaa55f.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,39 @@ +
+<% if @group.users.any? %> + + + + + + + <% @group.users.sort.each do |user| %> + + + + + <% end %> + +
<%= l(:label_user) %>
<%= link_to_user user %> + <%= delete_link group_user_path(@group, :user_id => user), :remote => true %> +
+<% else %> +

<%= l(:label_no_data) %>

+<% end %> +
+ +
+ <%= form_for(@group, :remote => true, :url => group_users_path(@group), + :html => {:method => :post}) do |f| %> +
<%=l(:label_user_new)%> + +

<%= label_tag "user_search", l(:label_user_search) %><%= text_field_tag 'user_search', nil %>

+ <%= javascript_tag "observeSearchfield('user_search', null, '#{ escape_javascript autocomplete_for_user_group_path(@group) }')" %> + +
+ <%= render_principals_for_new_group_users(@group) %> +
+ +

<%= submit_tag l(:button_add) %>

+
+ <% end %> +
diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4b/4bd5699fdfa1ef964b63852ff5e554bbdc600109.svn-base --- a/.svn/pristine/4b/4bd5699fdfa1ef964b63852ff5e554bbdc600109.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ -module Engines - class Plugin - class Loader < Rails::Plugin::Loader - protected - def register_plugin_as_loaded(plugin) - super plugin - Engines.plugins << plugin - end - end - end -end \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4c/4c2f7807c17edfaadfde41de28c64ed5f2677c01.svn-base --- a/.svn/pristine/4c/4c2f7807c17edfaadfde41de28c64ed5f2677c01.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -module Redmine - module Platform - class << self - def mswin? - (RUBY_PLATFORM =~ /(:?mswin|mingw)/) || - (RUBY_PLATFORM == 'java' && (ENV['OS'] || ENV['os']) =~ /windows/i) - end - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4c/4c336c258123fb4e9b45bc7fc7cd3f56f7293d30.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/4c/4c336c258123fb4e9b45bc7fc7cd3f56f7293d30.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,21 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +module IssueCategoriesHelper +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4c/4c46450a69eddda0d211771bcf055604ac13c9ff.svn-base --- a/.svn/pristine/4c/4c46450a69eddda0d211771bcf055604ac13c9ff.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -class Namespace::SharedPluginController < ApplicationController - def an_action - render_class_and_action 'from alpha_plugin' - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4c/4c59ba20a8f0cc2dfb2dedfa1e4641821d4c6a38.svn-base --- a/.svn/pristine/4c/4c59ba20a8f0cc2dfb2dedfa1e4641821d4c6a38.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,86 +0,0 @@ -// ** I18N - -// Calendar NO language (Norwegian/Norsk bokmÃ¥l) -// Author: Kai Olav Fredriksen - -// full day names -Calendar._DN = new Array -("Søndag", - "Mandag", - "Tirsdag", - "Onsdag", - "Torsdag", - "Fredag", - "Lørdag", - "Søndag"); - -Calendar._SDN_len = 3; // short day name length -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 = 1; - -// full month names -Calendar._MN = new Array -("Januar", - "Februar", - "Mars", - "April", - "Mai", - "Juni", - "Juli", - "August", - "September", - "Oktober", - "November", - "Desember"); - -// tooltips -Calendar._TT = {}; -Calendar._TT["INFO"] = "Om kalenderen"; - -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"] = "Forrige Ã¥r (hold for meny)"; -Calendar._TT["PREV_MONTH"] = "Forrige mÃ¥ned (hold for meny)"; -Calendar._TT["GO_TODAY"] = "GÃ¥ til idag"; -Calendar._TT["NEXT_MONTH"] = "Neste mÃ¥ned (hold for meny)"; -Calendar._TT["NEXT_YEAR"] = "Neste Ã¥r (hold for meny)"; -Calendar._TT["SEL_DATE"] = "Velg dato"; -Calendar._TT["DRAG_TO_MOVE"] = "Dra for Ã¥ flytte"; -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"] = "Vis %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"] = "Lukk"; -Calendar._TT["TODAY"] = "Idag"; -Calendar._TT["TIME_PART"] = "(Shift-)Klikk eller dra for Ã¥ endre verdi"; - -// date formats -Calendar._TT["DEF_DATE_FORMAT"] = "%%d.%m.%Y"; -Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; - -Calendar._TT["WK"] = "uke"; -Calendar._TT["TIME"] = "Tid:"; diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4c/4c87209f1f933df211a435d5f5082b69a9cf19f5.svn-base --- a/.svn/pristine/4c/4c87209f1f933df211a435d5f5082b69a9cf19f5.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,297 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../../test_helper', __FILE__) - -class ApiTest::ProjectsTest < ActionController::IntegrationTest - fixtures :projects, :versions, :users, :roles, :members, :member_roles, :issues, :journals, :journal_details, - :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages, - :attachments, :custom_fields, :custom_values, :time_entries, :issue_categories - - def setup - Setting.rest_api_enabled = '1' - set_tmp_attachments_directory - end - - context "GET /projects" do - context ".xml" do - should "return projects" do - get '/projects.xml' - assert_response :success - assert_equal 'application/xml', @response.content_type - - assert_tag :tag => 'projects', - :child => {:tag => 'project', :child => {:tag => 'id', :content => '1'}} - end - end - - context ".json" do - should "return projects" do - get '/projects.json' - assert_response :success - assert_equal 'application/json', @response.content_type - - json = ActiveSupport::JSON.decode(response.body) - assert_kind_of Hash, json - assert_kind_of Array, json['projects'] - assert_kind_of Hash, json['projects'].first - assert json['projects'].first.has_key?('id') - end - end - end - - context "GET /projects/:id" do - context ".xml" do - # TODO: A private project is needed because should_allow_api_authentication - # actually tests that authentication is *required*, not just allowed - should_allow_api_authentication(:get, "/projects/2.xml") - - should "return requested project" do - get '/projects/1.xml' - assert_response :success - assert_equal 'application/xml', @response.content_type - - assert_tag :tag => 'project', - :child => {:tag => 'id', :content => '1'} - assert_tag :tag => 'custom_field', - :attributes => {:name => 'Development status'}, :content => 'Stable' - - assert_no_tag 'trackers' - assert_no_tag 'issue_categories' - end - - context "with hidden custom fields" do - setup do - ProjectCustomField.find_by_name('Development status').update_attribute :visible, false - end - - should "not display hidden custom fields" do - get '/projects/1.xml' - assert_response :success - assert_equal 'application/xml', @response.content_type - - assert_no_tag 'custom_field', - :attributes => {:name => 'Development status'} - end - end - - should "return categories with include=issue_categories" do - get '/projects/1.xml?include=issue_categories' - assert_response :success - assert_equal 'application/xml', @response.content_type - - assert_tag 'issue_categories', - :attributes => {:type => 'array'}, - :child => { - :tag => 'issue_category', - :attributes => { - :id => '2', - :name => 'Recipes' - } - } - end - - should "return trackers with include=trackers" do - get '/projects/1.xml?include=trackers' - assert_response :success - assert_equal 'application/xml', @response.content_type - - assert_tag 'trackers', - :attributes => {:type => 'array'}, - :child => { - :tag => 'tracker', - :attributes => { - :id => '2', - :name => 'Feature request' - } - } - end - end - - context ".json" do - should_allow_api_authentication(:get, "/projects/2.json") - - should "return requested project" do - get '/projects/1.json' - - json = ActiveSupport::JSON.decode(response.body) - assert_kind_of Hash, json - assert_kind_of Hash, json['project'] - assert_equal 1, json['project']['id'] - end - end - end - - context "POST /projects" do - context "with valid parameters" do - setup do - Setting.default_projects_modules = ['issue_tracking', 'repository'] - @parameters = {:project => {:name => 'API test', :identifier => 'api-test'}} - end - - context ".xml" do - should_allow_api_authentication(:post, - '/projects.xml', - {:project => {:name => 'API test', :identifier => 'api-test'}}, - {:success_code => :created}) - - - should "create a project with the attributes" do - assert_difference('Project.count') do - post '/projects.xml', @parameters, :authorization => credentials('admin') - end - - project = Project.first(:order => 'id DESC') - assert_equal 'API test', project.name - assert_equal 'api-test', project.identifier - assert_equal ['issue_tracking', 'repository'], project.enabled_module_names.sort - assert_equal Tracker.all.size, project.trackers.size - - assert_response :created - assert_equal 'application/xml', @response.content_type - assert_tag 'project', :child => {:tag => 'id', :content => project.id.to_s} - end - - should "accept enabled_module_names attribute" do - @parameters[:project].merge!({:enabled_module_names => ['issue_tracking', 'news', 'time_tracking']}) - - assert_difference('Project.count') do - post '/projects.xml', @parameters, :authorization => credentials('admin') - end - - project = Project.first(:order => 'id DESC') - assert_equal ['issue_tracking', 'news', 'time_tracking'], project.enabled_module_names.sort - end - - should "accept tracker_ids attribute" do - @parameters[:project].merge!({:tracker_ids => [1, 3]}) - - assert_difference('Project.count') do - post '/projects.xml', @parameters, :authorization => credentials('admin') - end - - project = Project.first(:order => 'id DESC') - assert_equal [1, 3], project.trackers.map(&:id).sort - end - end - end - - context "with invalid parameters" do - setup do - @parameters = {:project => {:name => 'API test'}} - end - - context ".xml" do - should "return errors" do - assert_no_difference('Project.count') do - post '/projects.xml', @parameters, :authorization => credentials('admin') - end - - assert_response :unprocessable_entity - assert_equal 'application/xml', @response.content_type - assert_tag 'errors', :child => {:tag => 'error', :content => "Identifier can't be blank"} - end - end - end - end - - context "PUT /projects/:id" do - context "with valid parameters" do - setup do - @parameters = {:project => {:name => 'API update'}} - end - - context ".xml" do - should_allow_api_authentication(:put, - '/projects/2.xml', - {:project => {:name => 'API update'}}, - {:success_code => :ok}) - - should "update the project" do - assert_no_difference 'Project.count' do - put '/projects/2.xml', @parameters, :authorization => credentials('jsmith') - end - assert_response :ok - assert_equal 'application/xml', @response.content_type - project = Project.find(2) - assert_equal 'API update', project.name - end - - should "accept enabled_module_names attribute" do - @parameters[:project].merge!({:enabled_module_names => ['issue_tracking', 'news', 'time_tracking']}) - - assert_no_difference 'Project.count' do - put '/projects/2.xml', @parameters, :authorization => credentials('admin') - end - assert_response :ok - project = Project.find(2) - assert_equal ['issue_tracking', 'news', 'time_tracking'], project.enabled_module_names.sort - end - - should "accept tracker_ids attribute" do - @parameters[:project].merge!({:tracker_ids => [1, 3]}) - - assert_no_difference 'Project.count' do - put '/projects/2.xml', @parameters, :authorization => credentials('admin') - end - assert_response :ok - project = Project.find(2) - assert_equal [1, 3], project.trackers.map(&:id).sort - end - end - end - - context "with invalid parameters" do - setup do - @parameters = {:project => {:name => ''}} - end - - context ".xml" do - should "return errors" do - assert_no_difference('Project.count') do - put '/projects/2.xml', @parameters, :authorization => credentials('admin') - end - - assert_response :unprocessable_entity - assert_equal 'application/xml', @response.content_type - assert_tag 'errors', :child => {:tag => 'error', :content => "Name can't be blank"} - end - end - end - end - - context "DELETE /projects/:id" do - context ".xml" do - should_allow_api_authentication(:delete, - '/projects/2.xml', - {}, - {:success_code => :ok}) - - should "delete the project" do - assert_difference('Project.count',-1) do - delete '/projects/2.xml', {}, :authorization => credentials('admin') - end - assert_response :ok - assert_nil Project.find_by_id(2) - end - end - end - - def credentials(user, password=nil) - ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user) - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4c/4cde02772540ced7426b8c2828a1d2272fc2d8ad.svn-base --- a/.svn/pristine/4c/4cde02772540ced7426b8c2828a1d2272fc2d8ad.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,128 +0,0 @@ -// ** I18N - -// Calendar pt language -// Author: Adalberto Machado, -// Corrected by: Pedro Araújo -// 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 -("Domingo", - "Segunda", - "Terça", - "Quarta", - "Quinta", - "Sexta", - "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", - "Seg", - "Ter", - "Qua", - "Qui", - "Sex", - "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 -("Janeiro", - "Fevereiro", - "Março", - "Abril", - "Maio", - "Junho", - "Julho", - "Agosto", - "Setembro", - "Outubro", - "Novembro", - "Dezembro"); - -// short month names -Calendar._SMN = new Array -("Jan", - "Fev", - "Mar", - "Abr", - "Mai", - "Jun", - "Jul", - "Ago", - "Set", - "Out", - "Nov", - "Dez"); - -// tooltips -Calendar._TT = {}; -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 ;-) -"Ú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" + -"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" + -"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 selecção rápida."; - -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. mês (segure para menu)"; -Calendar._TT["NEXT_YEAR"] = "Prox. ano (segure para menu)"; -Calendar._TT["SEL_DATE"] = "Seleccione a data"; -Calendar._TT["DRAG_TO_MOVE"] = "Arraste para mover"; -Calendar._TT["PART_TODAY"] = " (hoje)"; - -// 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"] = "Mostre %s primeiro"; - -// 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"] = "Fechar"; -Calendar._TT["TODAY"] = "Hoje"; -Calendar._TT["TIME_PART"] = "(Shift-)Click ou arraste para mudar valor"; - -// date formats -Calendar._TT["DEF_DATE_FORMAT"] = "%d/%m/%Y"; -Calendar._TT["TT_DATE_FORMAT"] = "%a, %e %b"; - -Calendar._TT["WK"] = "sm"; -Calendar._TT["TIME"] = "Hora:"; diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4c/4ce30d3521f9c879b76d2c58d967727fd036b9f7.svn-base --- a/.svn/pristine/4c/4ce30d3521f9c879b76d2c58d967727fd036b9f7.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,104 +0,0 @@ ---- -changesets_001: - commit_date: 2007-04-11 - committed_on: 2007-04-11 15:14:44 +02:00 - revision: 1 - id: 100 - 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 - revision: 2 - id: 101 - 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 - revision: 3 - id: 102 - comments: |- - A commit with wrong issue ids - 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 - revision: 4 - id: 103 - comments: |- - A commit with an issue id of an other project - IssueID 4 2 - repository_id: 10 - committer: dlopper - user_id: 3 -changesets_005: - commit_date: "2007-09-10" - comments: Modified one file in the folder. - committed_on: 2007-09-10 19:01:08 - revision: "5" - id: 104 - scmid: - user_id: 3 - repository_id: 10 - committer: dlopper -changesets_006: - commit_date: "2007-09-10" - comments: Moved helloworld.rb from / to /folder. - committed_on: 2007-09-10 19:01:47 - revision: "6" - id: 105 - scmid: - user_id: 3 - repository_id: 10 - committer: dlopper -changesets_007: - commit_date: "2007-09-10" - comments: Removed one file. - committed_on: 2007-09-10 19:02:16 - revision: "7" - id: 106 - scmid: - user_id: 3 - repository_id: 10 - committer: dlopper -changesets_008: - commit_date: "2007-09-10" - comments: |- - This commits references an issue. - Refs #2 - committed_on: 2007-09-10 19:04:35 - revision: "8" - id: 107 - scmid: - user_id: 3 - repository_id: 10 - committer: dlopper -changesets_009: - commit_date: "2009-09-10" - comments: One file added. - committed_on: 2009-09-10 19:04:35 - revision: "9" - id: 108 - scmid: - user_id: 3 - repository_id: 10 - committer: dlopper -changesets_010: - commit_date: "2009-09-10" - comments: Same file modified. - committed_on: 2009-09-10 19:04:35 - revision: "10" - id: 109 - scmid: - user_id: 3 - repository_id: 10 - committer: dlopper - \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4c/4ce47e5153ddf18249e25aad233433aafed78ea8.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/4c/4ce47e5153ddf18249e25aad233433aafed78ea8.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,278 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class Changeset < ActiveRecord::Base + belongs_to :repository + belongs_to :user + has_many :filechanges, :class_name => 'Change', :dependent => :delete_all + has_and_belongs_to_many :issues + has_and_belongs_to_many :parents, + :class_name => "Changeset", + :join_table => "#{table_name_prefix}changeset_parents#{table_name_suffix}", + :association_foreign_key => 'parent_id', :foreign_key => 'changeset_id' + has_and_belongs_to_many :children, + :class_name => "Changeset", + :join_table => "#{table_name_prefix}changeset_parents#{table_name_suffix}", + :association_foreign_key => 'changeset_id', :foreign_key => 'parent_id' + + acts_as_event :title => Proc.new {|o| o.title}, + :description => :long_comments, + :datetime => :committed_on, + :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :repository_id => o.repository.identifier_param, :rev => o.identifier}} + + acts_as_searchable :columns => 'comments', + :include => {:repository => :project}, + :project_key => "#{Repository.table_name}.project_id", + :date_column => 'committed_on' + + acts_as_activity_provider :timestamp => "#{table_name}.committed_on", + :author_key => :user_id, + :find_options => {:include => [:user, {:repository => :project}]} + + validates_presence_of :repository_id, :revision, :committed_on, :commit_date + validates_uniqueness_of :revision, :scope => :repository_id + validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true + + scope :visible, lambda {|*args| + includes(:repository => :project).where(Project.allowed_to_condition(args.shift || User.current, :view_changesets, *args)) + } + + after_create :scan_for_issues + before_create :before_create_cs + + def revision=(r) + write_attribute :revision, (r.nil? ? nil : r.to_s) + end + + # Returns the identifier of this changeset; depending on repository backends + def identifier + if repository.class.respond_to? :changeset_identifier + repository.class.changeset_identifier self + else + revision.to_s + end + end + + def committed_on=(date) + self.commit_date = date + super + end + + # Returns the readable identifier + def format_identifier + if repository.class.respond_to? :format_changeset_identifier + repository.class.format_changeset_identifier self + else + identifier + end + end + + def project + repository.project + end + + def author + user || committer.to_s.split('<').first + end + + def before_create_cs + self.committer = self.class.to_utf8(self.committer, repository.repo_log_encoding) + self.comments = self.class.normalize_comments( + self.comments, repository.repo_log_encoding) + self.user = repository.find_committer_user(self.committer) + end + + def scan_for_issues + scan_comment_for_issue_ids + end + + TIMELOG_RE = / + ( + ((\d+)(h|hours?))((\d+)(m|min)?)? + | + ((\d+)(h|hours?|m|min)) + | + (\d+):(\d+) + | + (\d+([\.,]\d+)?)h? + ) + /x + + def scan_comment_for_issue_ids + return if comments.blank? + # keywords used to reference issues + ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip) + ref_keywords_any = ref_keywords.delete('*') + # keywords used to fix issues + fix_keywords = Setting.commit_fix_keywords.downcase.split(",").collect(&:strip) + + kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|") + + referenced_issues = [] + + comments.scan(/([\s\(\[,-]|^)((#{kw_regexp})[\s:]+)?(#\d+(\s+@#{TIMELOG_RE})?([\s,;&]+#\d+(\s+@#{TIMELOG_RE})?)*)(?=[[:punct:]]|\s|<|$)/i) do |match| + action, refs = match[2], match[3] + next unless action.present? || ref_keywords_any + + refs.scan(/#(\d+)(\s+@#{TIMELOG_RE})?/).each do |m| + issue, hours = find_referenced_issue_by_id(m[0].to_i), m[2] + if issue + referenced_issues << issue + fix_issue(issue) if fix_keywords.include?(action.to_s.downcase) + log_time(issue, hours) if hours && Setting.commit_logtime_enabled? + end + end + end + + referenced_issues.uniq! + self.issues = referenced_issues unless referenced_issues.empty? + end + + def short_comments + @short_comments || split_comments.first + end + + def long_comments + @long_comments || split_comments.last + end + + def text_tag(ref_project=nil) + tag = if scmid? + "commit:#{scmid}" + else + "r#{revision}" + end + if repository && repository.identifier.present? + tag = "#{repository.identifier}|#{tag}" + end + if ref_project && project && ref_project != project + tag = "#{project.identifier}:#{tag}" + end + tag + end + + # Returns the title used for the changeset in the activity/search results + def title + repo = (repository && repository.identifier.present?) ? " (#{repository.identifier})" : '' + comm = short_comments.blank? ? '' : (': ' + short_comments) + "#{l(:label_revision)} #{format_identifier}#{repo}#{comm}" + end + + # Returns the previous changeset + def previous + @previous ||= Changeset.where(["id < ? AND repository_id = ?", id, repository_id]).order('id DESC').first + end + + # Returns the next changeset + def next + @next ||= Changeset.where(["id > ? AND repository_id = ?", id, repository_id]).order('id ASC').first + end + + # Creates a new Change from it's common parameters + def create_change(change) + Change.create(:changeset => self, + :action => change[:action], + :path => change[:path], + :from_path => change[:from_path], + :from_revision => change[:from_revision]) + end + + # Finds an issue that can be referenced by the commit message + def find_referenced_issue_by_id(id) + return nil if id.blank? + issue = Issue.find_by_id(id.to_i, :include => :project) + if Setting.commit_cross_project_ref? + # all issues can be referenced/fixed + elsif issue + # issue that belong to the repository project, a subproject or a parent project only + unless issue.project && + (project == issue.project || project.is_ancestor_of?(issue.project) || + project.is_descendant_of?(issue.project)) + issue = nil + end + end + issue + end + + private + + def fix_issue(issue) + status = IssueStatus.find_by_id(Setting.commit_fix_status_id.to_i) + if status.nil? + logger.warn("No status matches commit_fix_status_id setting (#{Setting.commit_fix_status_id})") if logger + return issue + end + + # the issue may have been updated by the closure of another one (eg. duplicate) + issue.reload + # don't change the status is the issue is closed + return if issue.status && issue.status.is_closed? + + journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, text_tag(issue.project))) + issue.status = status + unless Setting.commit_fix_done_ratio.blank? + issue.done_ratio = Setting.commit_fix_done_ratio.to_i + end + Redmine::Hook.call_hook(:model_changeset_scan_commit_for_issue_ids_pre_issue_update, + { :changeset => self, :issue => issue }) + unless issue.save + logger.warn("Issue ##{issue.id} could not be saved by changeset #{id}: #{issue.errors.full_messages}") if logger + end + issue + end + + def log_time(issue, hours) + time_entry = TimeEntry.new( + :user => user, + :hours => hours, + :issue => issue, + :spent_on => commit_date, + :comments => l(:text_time_logged_by_changeset, :value => text_tag(issue.project), + :locale => Setting.default_language) + ) + time_entry.activity = log_time_activity unless log_time_activity.nil? + + unless time_entry.save + logger.warn("TimeEntry could not be created by changeset #{id}: #{time_entry.errors.full_messages}") if logger + end + time_entry + end + + def log_time_activity + if Setting.commit_logtime_activity_id.to_i > 0 + TimeEntryActivity.find_by_id(Setting.commit_logtime_activity_id.to_i) + end + end + + def split_comments + comments =~ /\A(.+?)\r?\n(.*)$/m + @short_comments = $1 || comments + @long_comments = $2.to_s.strip + return @short_comments, @long_comments + end + + public + + # Strips and reencodes a commit log before insertion into the database + def self.normalize_comments(str, encoding) + Changeset.to_utf8(str.to_s.strip, encoding) + end + + def self.to_utf8(str, encoding) + Redmine::CodesetUtil.to_utf8(str, encoding) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4c/4ce572a386a0b87f49f89a4bc090bd7724a6e4b0.svn-base --- a/.svn/pristine/4c/4ce572a386a0b87f49f89a4bc090bd7724a6e4b0.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +0,0 @@ -class SharedPluginModel < ActiveRecord::Base - def self.report_location; TestHelper::report_location(__FILE__); end -end \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4c/4cf321de05d6993523d4f53f4c3078dcaf49b46c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/4c/4cf321de05d6993523d4f53f4c3078dcaf49b46c.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,265 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class SearchControllerTest < ActionController::TestCase + fixtures :projects, :enabled_modules, :roles, :users, :members, :member_roles, + :issues, :trackers, :issue_statuses, :enumerations, + :custom_fields, :custom_values, + :repositories, :changesets + + def setup + User.current = nil + end + + def test_search_for_projects + get :index + assert_response :success + assert_template 'index' + + get :index, :q => "cook" + assert_response :success + assert_template 'index' + assert assigns(:results).include?(Project.find(1)) + end + + def test_search_all_projects + get :index, :q => 'recipe subproject commit', :all_words => '' + assert_response :success + assert_template 'index' + + assert assigns(:results).include?(Issue.find(2)) + assert assigns(:results).include?(Issue.find(5)) + assert assigns(:results).include?(Changeset.find(101)) + assert_tag :dt, :attributes => { :class => /issue/ }, + :child => { :tag => 'a', :content => /Add ingredients categories/ }, + :sibling => { :tag => 'dd', :content => /should be classified by categories/ } + + assert assigns(:results_by_type).is_a?(Hash) + assert_equal 5, assigns(:results_by_type)['changesets'] + assert_tag :a, :content => 'Changesets (5)' + end + + def test_search_issues + get :index, :q => 'issue', :issues => 1 + assert_response :success + assert_template 'index' + + assert_equal true, assigns(:all_words) + assert_equal false, assigns(:titles_only) + 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_issues_should_search_notes + Journal.create!(:journalized => Issue.find(2), :notes => 'Issue notes with searchkeyword') + + get :index, :q => 'searchkeyword', :issues => 1 + assert_response :success + assert_include Issue.find(2), assigns(:results) + end + + def test_search_issues_with_multiple_matches_in_journals_should_return_issue_once + Journal.create!(:journalized => Issue.find(2), :notes => 'Issue notes with searchkeyword') + Journal.create!(:journalized => Issue.find(2), :notes => 'Issue notes with searchkeyword') + + get :index, :q => 'searchkeyword', :issues => 1 + assert_response :success + assert_include Issue.find(2), assigns(:results) + assert_equal 1, assigns(:results).size + end + + def test_search_issues_should_search_private_notes_with_permission_only + Journal.create!(:journalized => Issue.find(2), :notes => 'Private notes with searchkeyword', :private_notes => true) + @request.session[:user_id] = 2 + + Role.find(1).add_permission! :view_private_notes + get :index, :q => 'searchkeyword', :issues => 1 + assert_response :success + assert_include Issue.find(2), assigns(:results) + + Role.find(1).remove_permission! :view_private_notes + get :index, :q => 'searchkeyword', :issues => 1 + assert_response :success + assert_not_include Issue.find(2), assigns(:results) + end + + def test_search_all_projects_with_scope_param + get :index, :q => 'issue', :scope => 'all' + assert_response :success + assert_template 'index' + assert assigns(:results).present? + end + + def test_search_my_projects + @request.session[:user_id] = 2 + get :index, :id => 1, :q => 'recipe subproject', :scope => 'my_projects', :all_words => '' + assert_response :success + assert_template 'index' + assert assigns(:results).include?(Issue.find(1)) + assert !assigns(:results).include?(Issue.find(5)) + end + + def test_search_my_projects_without_memberships + # anonymous user has no memberships + get :index, :id => 1, :q => 'recipe subproject', :scope => 'my_projects', :all_words => '' + assert_response :success + assert_template 'index' + assert assigns(:results).empty? + end + + def test_search_project_and_subprojects + get :index, :id => 1, :q => 'recipe subproject', :scope => 'subprojects', :all_words => '' + assert_response :success + assert_template 'index' + assert assigns(:results).include?(Issue.find(1)) + assert assigns(:results).include?(Issue.find(5)) + end + + def test_search_without_searchable_custom_fields + CustomField.update_all "searchable = #{ActiveRecord::Base.connection.quoted_false}" + + get :index, :id => 1 + assert_response :success + assert_template 'index' + assert_not_nil assigns(:project) + + get :index, :id => 1, :q => "can" + assert_response :success + assert_template 'index' + end + + def test_search_with_searchable_custom_fields + get :index, :id => 1, :q => "stringforcustomfield" + assert_response :success + results = assigns(:results) + assert_not_nil results + assert_equal 1, results.size + assert results.include?(Issue.find(7)) + end + + def test_search_all_words + # 'all words' is on by default + get :index, :id => 1, :q => 'recipe updating saving', :all_words => '1' + assert_equal true, assigns(:all_words) + results = assigns(:results) + assert_not_nil results + assert_equal 1, results.size + assert results.include?(Issue.find(3)) + end + + def test_search_one_of_the_words + get :index, :id => 1, :q => 'recipe updating saving', :all_words => '' + assert_equal false, assigns(:all_words) + results = assigns(:results) + assert_not_nil results + assert_equal 3, results.size + assert results.include?(Issue.find(3)) + end + + def test_search_titles_only_without_result + get :index, :id => 1, :q => 'recipe updating saving', :titles_only => '1' + results = assigns(:results) + assert_not_nil results + assert_equal 0, results.size + end + + def test_search_titles_only + get :index, :id => 1, :q => 'recipe', :titles_only => '1' + assert_equal true, assigns(:titles_only) + results = assigns(:results) + assert_not_nil results + assert_equal 2, results.size + end + + def test_search_content + Issue.update_all("description = 'This is a searchkeywordinthecontent'", "id=1") + + get :index, :id => 1, :q => 'searchkeywordinthecontent', :titles_only => '' + assert_equal false, assigns(:titles_only) + results = assigns(:results) + assert_not_nil results + assert_equal 1, results.size + end + + def test_search_with_offset + get :index, :q => 'coo', :offset => '20080806073000' + assert_response :success + results = assigns(:results) + assert results.any? + assert results.map(&:event_datetime).max < '20080806T073000'.to_time + end + + def test_search_previous_with_offset + get :index, :q => 'coo', :offset => '20080806073000', :previous => '1' + assert_response :success + results = assigns(:results) + assert results.any? + assert results.map(&:event_datetime).min >= '20080806T073000'.to_time + end + + def test_search_with_invalid_project_id + get :index, :id => 195, :q => 'recipe' + assert_response 404 + assert_nil assigns(:results) + end + + def test_quick_jump_to_issue + # issue of a public project + get :index, :q => "3" + assert_redirected_to '/issues/3' + + # issue of a private project + get :index, :q => "4" + assert_response :success + assert_template 'index' + end + + def test_large_integer + get :index, :q => '4615713488' + assert_response :success + assert_template 'index' + end + + def test_tokens_with_quotes + get :index, :id => 1, :q => '"good bye" hello "bye bye"' + assert_equal ["good bye", "hello", "bye bye"], assigns(:tokens) + end + + def test_results_should_be_escaped_once + assert Issue.find(1).update_attributes(:subject => ' escaped_once', :description => ' escaped_once') + get :index, :q => 'escaped_once' + assert_response :success + assert_select '#search-results' do + assert_select 'dt.issue a', :text => /<subject>/ + assert_select 'dd', :text => /<description>/ + end + end + + def test_keywords_should_be_highlighted + assert Issue.find(1).update_attributes(:subject => 'subject highlighted', :description => 'description highlighted') + get :index, :q => 'highlighted' + assert_response :success + assert_select '#search-results' do + assert_select 'dt.issue a span.highlight', :text => 'highlighted' + assert_select 'dd span.highlight', :text => 'highlighted' + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4d/4d32c1bf128521985a7cc66ecfe319a7797f8c8c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/4d/4d32c1bf128521985a7cc66ecfe319a7797f8c8c.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,119 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class TimeEntry < ActiveRecord::Base + include Redmine::SafeAttributes + # could have used polymorphic association + # project association here allows easy loading of time entries at project level with one database trip + belongs_to :project + belongs_to :issue + belongs_to :user + belongs_to :activity, :class_name => 'TimeEntryActivity', :foreign_key => 'activity_id' + + attr_protected :project_id, :user_id, :tyear, :tmonth, :tweek + + acts_as_customizable + acts_as_event :title => Proc.new {|o| "#{l_hours(o.hours)} (#{(o.issue || o.project).event_title})"}, + :url => Proc.new {|o| {:controller => 'timelog', :action => 'index', :project_id => o.project, :issue_id => o.issue}}, + :author => :user, + :group => :issue, + :description => :comments + + acts_as_activity_provider :timestamp => "#{table_name}.created_on", + :author_key => :user_id, + :find_options => {:include => :project} + + validates_presence_of :user_id, :activity_id, :project_id, :hours, :spent_on + validates_numericality_of :hours, :allow_nil => true, :message => :invalid + validates_length_of :comments, :maximum => 255, :allow_nil => true + validates :spent_on, :date => true + before_validation :set_project_if_nil + validate :validate_time_entry + + scope :visible, lambda {|*args| + includes(:project).where(Project.allowed_to_condition(args.shift || User.current, :view_time_entries, *args)) + } + scope :on_issue, lambda {|issue| + includes(:issue).where("#{Issue.table_name}.root_id = #{issue.root_id} AND #{Issue.table_name}.lft >= #{issue.lft} AND #{Issue.table_name}.rgt <= #{issue.rgt}") + } + scope :on_project, lambda {|project, include_subprojects| + includes(:project).where(project.project_condition(include_subprojects)) + } + scope :spent_between, lambda {|from, to| + if from && to + where("#{TimeEntry.table_name}.spent_on BETWEEN ? AND ?", from, to) + elsif from + where("#{TimeEntry.table_name}.spent_on >= ?", from) + elsif to + where("#{TimeEntry.table_name}.spent_on <= ?", to) + else + where(nil) + end + } + + safe_attributes 'hours', 'comments', 'issue_id', 'activity_id', 'spent_on', 'custom_field_values', 'custom_fields' + + def initialize(attributes=nil, *args) + super + if new_record? && self.activity.nil? + if default_activity = TimeEntryActivity.default + self.activity_id = default_activity.id + end + self.hours = nil if hours == 0 + end + end + + def set_project_if_nil + self.project = issue.project if issue && project.nil? + end + + def validate_time_entry + errors.add :hours, :invalid if hours && (hours < 0 || hours >= 1000) + errors.add :project_id, :invalid if project.nil? + errors.add :issue_id, :invalid if (issue_id && !issue) || (issue && project!=issue.project) + end + + def hours=(h) + write_attribute :hours, (h.is_a?(String) ? (h.to_hours || h) : h) + end + + def hours + h = read_attribute(:hours) + if h.is_a?(Float) + h.round(2) + else + h + end + end + + # tyear, tmonth, tweek assigned where setting spent_on attributes + # these attributes make time aggregations easier + def spent_on=(date) + super + if spent_on.is_a?(Time) + self.spent_on = spent_on.to_date + end + self.tyear = spent_on ? spent_on.year : nil + self.tmonth = spent_on ? spent_on.month : nil + self.tweek = spent_on ? Date.civil(spent_on.year, spent_on.month, spent_on.day).cweek : nil + end + + # Returns true if the time entry can be edited by usr, otherwise false + def editable_by?(usr) + (usr == user && usr.allowed_to?(:edit_own_time_entries, project)) || usr.allowed_to?(:edit_time_entries, project) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4d/4d462e78d204c3aed1b100b04b6f63c31c637a6f.svn-base --- a/.svn/pristine/4d/4d462e78d204c3aed1b100b04b6f63c31c637a6f.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,127 +0,0 @@ -// ** I18N - -// Calendar FA language -// Author: Behrang Noroozinia, behrangn at g mail -// 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 -("یک‌شنبه", - "دوشنبه", - "سه‌شنبه", - "چهارشنبه", - "پنج‌شنبه", - "آدینه", - "شنبه", - "یک‌شنبه"); - -// 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 = 0; - -// full month names -Calendar._MN = new Array -("ژانویه", - "Ùوریه", - "مارس", - "آوریل", - "مه", - "ژوئن", - "ژوئیه", - "اوت", - "سپتامبر", - "اکتبر", - "نوامبر", - "دسامبر"); - -// short month names -Calendar._SMN = new Array -("ژان", - "Ùور", - "مار", - "آور", - "مه", - "ژوئن", - "ژوئیه", - "اوت", - "سپت", - "اکت", - "نوا", - "دسا"); - -// tooltips -Calendar._TT = {}; -Calendar._TT["INFO"] = "درباره گاهشمار"; - -Calendar._TT["ABOUT"] = -"DHTML Date/Time Selector\n" + -"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) -"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + -"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + -"\n\n" + -"Date selection:\n" + -"- Use the \xab, \xbb buttons to select year\n" + -"- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" + -"- Hold mouse button on any of the above buttons for faster selection."; -Calendar._TT["ABOUT_TIME"] = "\n\n" + -"Time selection:\n" + -"- Click on any of the time parts to increase it\n" + -"- or Shift-click to decrease it\n" + -"- or click and drag for faster selection."; - -Calendar._TT["PREV_YEAR"] = "سال پیشین (برای Ùهرست Ù†Ú¯Ù‡ دارید)"; -Calendar._TT["PREV_MONTH"] = "ماه پیشین ( برای Ùهرست Ù†Ú¯Ù‡ دارید)"; -Calendar._TT["GO_TODAY"] = "برو به امروز"; -Calendar._TT["NEXT_MONTH"] = "ماه پسین (برای Ùهرست Ù†Ú¯Ù‡ دارید)"; -Calendar._TT["NEXT_YEAR"] = "سال پسین (برای Ùهرست Ù†Ú¯Ù‡ دارید)"; -Calendar._TT["SEL_DATE"] = "گزینش"; -Calendar._TT["DRAG_TO_MOVE"] = "برای جابجایی بکشید"; -Calendar._TT["PART_TODAY"] = " (امروز)"; - -// the following is to inform that "%s" is to be the first day of week -// %s will be replaced with the day name. -Calendar._TT["DAY_FIRST"] = "آغاز Ù‡ÙØªÙ‡ از %s"; - -// This may be locale-dependent. It specifies the week-end days, as an array -// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 -// means Monday, etc. -Calendar._TT["WEEKEND"] = "4,5"; - -Calendar._TT["CLOSE"] = "بسته"; -Calendar._TT["TODAY"] = "امروز"; -Calendar._TT["TIME_PART"] = "زدن (با Shift) یا کشیدن برای ویرایش"; - -// date formats -Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; -Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; - -Calendar._TT["WK"] = "Ù‡ÙØªÙ‡"; -Calendar._TT["TIME"] = "زمان:"; diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4d/4d4d388d3654f2fbefb4fdd7c3e99a9ffa1a3eeb.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/4d/4d4d388d3654f2fbefb4fdd7c3e99a9ffa1a3eeb.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,48 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 GanttsController < ApplicationController + menu_item :gantt + before_filter :find_optional_project + + rescue_from Query::StatementInvalid, :with => :query_statement_invalid + + helper :gantt + helper :issues + helper :projects + helper :queries + include QueriesHelper + helper :sort + include SortHelper + include Redmine::Export::PDF + + def show + @gantt = Redmine::Helpers::Gantt.new(params) + @gantt.project = @project + retrieve_query + @query.group_by = nil + @gantt.query = @query if @query.valid? + + basename = (@project ? "#{@project.identifier}-" : '') + 'gantt' + + respond_to do |format| + format.html { render :action => "show", :layout => !request.xhr? } + format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{basename}.png") } if @gantt.respond_to?('to_image') + format.pdf { send_data(@gantt.to_pdf, :type => 'application/pdf', :filename => "#{basename}.pdf") } + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4d/4d670f96a6b54a9bfc31fb5cbe3121575a7b05e8.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/4d/4d670f96a6b54a9bfc31fb5cbe3121575a7b05e8.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,434 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../../../../test_helper', __FILE__) +begin + require 'mocha' + + class MercurialAdapterTest < ActiveSupport::TestCase + HELPERS_DIR = Redmine::Scm::Adapters::MercurialAdapter::HELPERS_DIR + TEMPLATE_NAME = Redmine::Scm::Adapters::MercurialAdapter::TEMPLATE_NAME + TEMPLATE_EXTENSION = Redmine::Scm::Adapters::MercurialAdapter::TEMPLATE_EXTENSION + + REPOSITORY_PATH = Rails.root.join('tmp/test/mercurial_repository').to_s + CHAR_1_HEX = "\xc3\x9c" + + if File.directory?(REPOSITORY_PATH) + def setup + adapter_class = Redmine::Scm::Adapters::MercurialAdapter + assert adapter_class + assert adapter_class.client_command + assert_equal true, adapter_class.client_available + assert_equal true, adapter_class.client_version_above?([0, 9, 5]) + + @adapter = Redmine::Scm::Adapters::MercurialAdapter.new( + REPOSITORY_PATH, + nil, + nil, + nil, + 'ISO-8859-1') + @diff_c_support = true + @char_1 = CHAR_1_HEX.dup + @tag_char_1 = "tag-#{CHAR_1_HEX}-00" + @branch_char_0 = "branch-#{CHAR_1_HEX}-00" + @branch_char_1 = "branch-#{CHAR_1_HEX}-01" + if @tag_char_1.respond_to?(:force_encoding) + @char_1.force_encoding('UTF-8') + @tag_char_1.force_encoding('UTF-8') + @branch_char_0.force_encoding('UTF-8') + @branch_char_1.force_encoding('UTF-8') + end + end + + def test_hgversion + to_test = { "Mercurial Distributed SCM (version 0.9.5)\n" => [0,9,5], + "Mercurial Distributed SCM (1.0)\n" => [1,0], + "Mercurial Distributed SCM (1e4ddc9ac9f7+20080325)\n" => nil, + "Mercurial Distributed SCM (1.0.1+20080525)\n" => [1,0,1], + "Mercurial Distributed SCM (1916e629a29d)\n" => nil, + "Mercurial SCM Distribuito (versione 0.9.5)\n" => [0,9,5], + "(1.6)\n(1.7)\n(1.8)" => [1,6], + "(1.7.1)\r\n(1.8.1)\r\n(1.9.1)" => [1,7,1]} + + to_test.each do |s, v| + test_hgversion_for(s, v) + end + end + + def test_template_path + to_test = { + [1,2] => "1.0", + [] => "1.0", + [1,2,1] => "1.0", + [1,7] => "1.0", + [1,7,1] => "1.0", + [2,0] => "1.0", + } + to_test.each do |v, template| + test_template_path_for(v, template) + end + end + + def test_info + [REPOSITORY_PATH, REPOSITORY_PATH + "/", + REPOSITORY_PATH + "//"].each do |repo| + adp = Redmine::Scm::Adapters::MercurialAdapter.new(repo) + repo_path = adp.info.root_url.gsub(/\\/, "/") + assert_equal REPOSITORY_PATH, repo_path + assert_equal '31', adp.info.lastrev.revision + assert_equal '31eeee7395c8',adp.info.lastrev.scmid + end + end + + def test_revisions + revisions = @adapter.revisions(nil, 2, 4) + assert_equal 3, revisions.size + assert_equal '2', revisions[0].revision + assert_equal '400bb8672109', revisions[0].scmid + assert_equal '4', revisions[2].revision + assert_equal 'def6d2f1254a', revisions[2].scmid + + revisions = @adapter.revisions(nil, 2, 4, {:limit => 2}) + assert_equal 2, revisions.size + assert_equal '2', revisions[0].revision + assert_equal '400bb8672109', revisions[0].scmid + end + + def test_parents + revs1 = @adapter.revisions(nil, 0, 0) + assert_equal 1, revs1.size + assert_equal [], revs1[0].parents + revs2 = @adapter.revisions(nil, 1, 1) + assert_equal 1, revs2.size + assert_equal 1, revs2[0].parents.size + assert_equal "0885933ad4f6", revs2[0].parents[0] + revs3 = @adapter.revisions(nil, 30, 30) + assert_equal 1, revs3.size + assert_equal 2, revs3[0].parents.size + assert_equal "a94b0528f24f", revs3[0].parents[0] + assert_equal "3a330eb32958", revs3[0].parents[1] + end + + def test_diff + if @adapter.class.client_version_above?([1, 2]) + assert_nil @adapter.diff(nil, '100000') + end + assert_nil @adapter.diff(nil, '100000', '200000') + [2, '400bb8672109', '400', 400].each do |r1| + diff1 = @adapter.diff(nil, r1) + if @diff_c_support + assert_equal 28, diff1.size + buf = diff1[24].gsub(/\r\n|\r|\n/, "") + assert_equal "+ return true unless klass.respond_to?('watched_by')", buf + else + assert_equal 0, diff1.size + end + [4, 'def6d2f1254a'].each do |r2| + diff2 = @adapter.diff(nil, r1, r2) + assert_equal 49, diff2.size + buf = diff2[41].gsub(/\r\n|\r|\n/, "") + assert_equal "+class WelcomeController < ApplicationController", buf + diff3 = @adapter.diff('sources/watchers_controller.rb', r1, r2) + assert_equal 20, diff3.size + buf = diff3[12].gsub(/\r\n|\r|\n/, "") + assert_equal "+ @watched.remove_watcher(user)", buf + + diff4 = @adapter.diff(nil, r2, r1) + assert_equal 49, diff4.size + buf = diff4[41].gsub(/\r\n|\r|\n/, "") + assert_equal "-class WelcomeController < ApplicationController", buf + diff5 = @adapter.diff('sources/watchers_controller.rb', r2, r1) + assert_equal 20, diff5.size + buf = diff5[9].gsub(/\r\n|\r|\n/, "") + assert_equal "- @watched.remove_watcher(user)", buf + end + end + end + + def test_diff_made_by_revision + if @diff_c_support + [24, '24', '4cddb4e45f52'].each do |r1| + diff1 = @adapter.diff(nil, r1) + assert_equal 5, diff1.size + buf = diff1[4].gsub(/\r\n|\r|\n/, "") + assert_equal '+0885933ad4f68d77c2649cd11f8311276e7ef7ce tag-init-revision', buf + end + end + end + + def test_cat + [2, '400bb8672109', '400', 400].each do |r| + buf = @adapter.cat('sources/welcome_controller.rb', r) + assert buf + lines = buf.split("\r\n") + assert_equal 25, lines.length + assert_equal 'class WelcomeController < ApplicationController', lines[17] + end + assert_nil @adapter.cat('sources/welcome_controller.rb') + end + + def test_annotate + assert_equal [], @adapter.annotate("sources/welcome_controller.rb").lines + [2, '400bb8672109', '400', 400].each do |r| + ann = @adapter.annotate('sources/welcome_controller.rb', r) + assert ann + assert_equal '1', ann.revisions[17].revision + assert_equal '9d5b5b004199', ann.revisions[17].identifier + assert_equal 'jsmith', ann.revisions[0].author + assert_equal 25, ann.lines.length + assert_equal 'class WelcomeController < ApplicationController', ann.lines[17] + end + end + + def test_entries + assert_nil @adapter.entries(nil, '100000') + + assert_equal 1, @adapter.entries("sources", 3).size + assert_equal 1, @adapter.entries("sources", 'b3a615152df8').size + + [2, '400bb8672109', '400', 400].each do |r| + entries1 = @adapter.entries(nil, r) + assert entries1 + assert_equal 3, entries1.size + assert_equal 'sources', entries1[1].name + assert_equal 'sources', entries1[1].path + assert_equal 'dir', entries1[1].kind + readme = entries1[2] + assert_equal 'README', readme.name + assert_equal 'README', readme.path + assert_equal 'file', readme.kind + assert_equal 27, readme.size + assert_equal '1', readme.lastrev.revision + assert_equal '9d5b5b004199', readme.lastrev.identifier + # 2007-12-14 10:24:01 +0100 + assert_equal Time.gm(2007, 12, 14, 9, 24, 1), readme.lastrev.time + + entries2 = @adapter.entries('sources', r) + assert entries2 + assert_equal 2, entries2.size + assert_equal 'watchers_controller.rb', entries2[0].name + assert_equal 'sources/watchers_controller.rb', entries2[0].path + assert_equal 'file', entries2[0].kind + assert_equal 'welcome_controller.rb', entries2[1].name + assert_equal 'sources/welcome_controller.rb', entries2[1].path + assert_equal 'file', entries2[1].kind + end + end + + def test_entries_tag + entries1 = @adapter.entries(nil, 'tag_test.00') + assert entries1 + assert_equal 3, entries1.size + assert_equal 'sources', entries1[1].name + assert_equal 'sources', entries1[1].path + assert_equal 'dir', entries1[1].kind + readme = entries1[2] + assert_equal 'README', readme.name + assert_equal 'README', readme.path + assert_equal 'file', readme.kind + assert_equal 21, readme.size + assert_equal '0', readme.lastrev.revision + assert_equal '0885933ad4f6', readme.lastrev.identifier + # 2007-12-14 10:22:52 +0100 + assert_equal Time.gm(2007, 12, 14, 9, 22, 52), readme.lastrev.time + end + + def test_entries_branch + entries1 = @adapter.entries(nil, 'test-branch-00') + assert entries1 + assert_equal 5, entries1.size + assert_equal 'sql_escape', entries1[2].name + assert_equal 'sql_escape', entries1[2].path + assert_equal 'dir', entries1[2].kind + readme = entries1[4] + assert_equal 'README', readme.name + assert_equal 'README', readme.path + assert_equal 'file', readme.kind + assert_equal 365, readme.size + assert_equal '8', readme.lastrev.revision + assert_equal 'c51f5bb613cd', readme.lastrev.identifier + # 2001-02-01 00:00:00 -0900 + assert_equal Time.gm(2001, 2, 1, 9, 0, 0), readme.lastrev.time + end + + def test_locate_on_outdated_repository + assert_equal 1, @adapter.entries("images", 0).size + assert_equal 2, @adapter.entries("images").size + assert_equal 2, @adapter.entries("images", 2).size + end + + def test_access_by_nodeid + path = 'sources/welcome_controller.rb' + assert_equal @adapter.cat(path, 2), @adapter.cat(path, '400bb8672109') + end + + def test_access_by_fuzzy_nodeid + path = 'sources/welcome_controller.rb' + # falls back to nodeid + assert_equal @adapter.cat(path, 2), @adapter.cat(path, '400') + end + + def test_tags + assert_equal [@tag_char_1, 'tag_test.00', 'tag-init-revision'], @adapter.tags + end + + def test_tagmap + tm = { + @tag_char_1 => 'adf805632193', + 'tag_test.00' => '6987191f453a', + 'tag-init-revision' => '0885933ad4f6', + } + assert_equal tm, @adapter.tagmap + end + + def test_branches + brs = [] + @adapter.branches.each do |b| + brs << b + end + assert_equal 7, brs.length + assert_equal 'default', brs[0].to_s + assert_equal '31', brs[0].revision + assert_equal '31eeee7395c8', brs[0].scmid + assert_equal 'test-branch-01', brs[1].to_s + assert_equal '30', brs[1].revision + assert_equal 'ad4dc4f80284', brs[1].scmid + assert_equal @branch_char_1, brs[2].to_s + assert_equal '27', brs[2].revision + assert_equal '7bbf4c738e71', brs[2].scmid + assert_equal 'branch (1)[2]&,%.-3_4', brs[3].to_s + assert_equal '25', brs[3].revision + assert_equal 'afc61e85bde7', brs[3].scmid + assert_equal @branch_char_0, brs[4].to_s + assert_equal '23', brs[4].revision + assert_equal 'c8d3e4887474', brs[4].scmid + assert_equal 'test_branch.latin-1', brs[5].to_s + assert_equal '22', brs[5].revision + assert_equal 'c2ffe7da686a', brs[5].scmid + assert_equal 'test-branch-00', brs[6].to_s + assert_equal '13', brs[6].revision + assert_equal '3a330eb32958', brs[6].scmid + end + + def test_branchmap + bm = { + 'default' => '31eeee7395c8', + 'test_branch.latin-1' => 'c2ffe7da686a', + 'branch (1)[2]&,%.-3_4' => 'afc61e85bde7', + 'test-branch-00' => '3a330eb32958', + "test-branch-01" => 'ad4dc4f80284', + @branch_char_0 => 'c8d3e4887474', + @branch_char_1 => '7bbf4c738e71', + } + assert_equal bm, @adapter.branchmap + end + + def test_path_space + p = 'README (1)[2]&,%.-3_4' + [15, '933ca60293d7'].each do |r1| + assert @adapter.diff(p, r1) + assert @adapter.cat(p, r1) + assert_equal 1, @adapter.annotate(p, r1).lines.length + [25, 'afc61e85bde7'].each do |r2| + assert @adapter.diff(p, r1, r2) + end + end + end + + def test_tag_non_ascii + p = "latin-1-dir/test-#{@char_1}-1.txt" + assert @adapter.cat(p, @tag_char_1) + assert_equal 1, @adapter.annotate(p, @tag_char_1).lines.length + end + + def test_branch_non_ascii + p = "latin-1-dir/test-#{@char_1}-subdir/test-#{@char_1}-1.txt" + assert @adapter.cat(p, @branch_char_1) + assert_equal 1, @adapter.annotate(p, @branch_char_1).lines.length + end + + def test_nodes_in_branch + [ + 'default', + @branch_char_1, + 'branch (1)[2]&,%.-3_4', + @branch_char_0, + 'test_branch.latin-1', + 'test-branch-00', + ].each do |bra| + nib0 = @adapter.nodes_in_branch(bra) + assert nib0 + nib1 = @adapter.nodes_in_branch(bra, :limit => 1) + assert_equal 1, nib1.size + case bra + when 'branch (1)[2]&,%.-3_4' + if @adapter.class.client_version_above?([1, 6]) + assert_equal 3, nib0.size + assert_equal nib0[0], 'afc61e85bde7' + nib2 = @adapter.nodes_in_branch(bra, :limit => 2) + assert_equal 2, nib2.size + assert_equal nib2[1], '933ca60293d7' + end + when @branch_char_1 + if @adapter.class.client_version_above?([1, 6]) + assert_equal 2, nib0.size + assert_equal nib0[1], '08ff3227303e' + nib2 = @adapter.nodes_in_branch(bra, :limit => 1) + assert_equal 1, nib2.size + assert_equal nib2[0], '7bbf4c738e71' + end + end + end + end + + def test_path_encoding_default_utf8 + adpt1 = Redmine::Scm::Adapters::MercurialAdapter.new( + REPOSITORY_PATH + ) + assert_equal "UTF-8", adpt1.path_encoding + adpt2 = Redmine::Scm::Adapters::MercurialAdapter.new( + REPOSITORY_PATH, + nil, + nil, + nil, + "" + ) + assert_equal "UTF-8", adpt2.path_encoding + end + + private + + def test_hgversion_for(hgversion, version) + @adapter.class.expects(:hgversion_from_command_line).returns(hgversion) + assert_equal version, @adapter.class.hgversion + end + + def test_template_path_for(version, template) + assert_equal "#{HELPERS_DIR}/#{TEMPLATE_NAME}-#{template}.#{TEMPLATE_EXTENSION}", + @adapter.class.template_path_for(version) + assert File.exist?(@adapter.class.template_path_for(version)) + end + else + puts "Mercurial test repository NOT FOUND. Skipping unit tests !!!" + def test_fake; assert true end + end + end +rescue LoadError + class MercurialMochaFake < ActiveSupport::TestCase + def test_fake; assert(false, "Requires mocha to run those tests") end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4d/4db1f392c52d1c52ef2d37c9a27b017bd4d891ac.svn-base --- a/.svn/pristine/4d/4db1f392c52d1c52ef2d37c9a27b017bd4d891ac.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,278 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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 #:nodoc: - - class PluginNotFound < StandardError; end - class PluginRequirementError < StandardError; end - - # Base class for Redmine plugins. - # Plugins are registered using the register class method that acts as the public constructor. - # - # Redmine::Plugin.register :example do - # name 'Example plugin' - # author 'John Smith' - # description 'This is an example plugin for Redmine' - # version '0.0.1' - # settings :default => {'foo'=>'bar'}, :partial => 'settings/settings' - # end - # - # === Plugin attributes - # - # +settings+ is an optional attribute that let the plugin be configurable. - # It must be a hash with the following keys: - # * :default: default value for the plugin settings - # * :partial: path of the configuration partial view, relative to the plugin app/views directory - # Example: - # settings :default => {'foo'=>'bar'}, :partial => 'settings/settings' - # In this example, the settings partial will be found here in the plugin directory: app/views/settings/_settings.rhtml. - # - # When rendered, the plugin settings value is available as the local variable +settings+ - class Plugin - @registered_plugins = {} - class << self - attr_reader :registered_plugins - private :new - - def def_field(*names) - class_eval do - names.each do |name| - define_method(name) do |*args| - args.empty? ? instance_variable_get("@#{name}") : instance_variable_set("@#{name}", *args) - end - end - end - end - end - def_field :name, :description, :url, :author, :author_url, :version, :settings - attr_reader :id - - # Plugin constructor - def self.register(id, &block) - p = new(id) - p.instance_eval(&block) - # Set a default name if it was not provided during registration - p.name(id.to_s.humanize) if p.name.nil? - # Adds plugin locales if any - # YAML translation files should be found under /config/locales/ - ::I18n.load_path += Dir.glob(File.join(Rails.root, 'vendor', 'plugins', id.to_s, 'config', 'locales', '*.yml')) - registered_plugins[id] = p - end - - # Returns an array of 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 - - # Checks if a plugin is installed - # - # @param [String] id name of the plugin - def self.installed?(id) - registered_plugins[id.to_sym].present? - 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 - - # Sets a requirement on a Redmine plugin version - # Raises a PluginRequirementError exception if the requirement is not met - # - # Examples - # # Requires a plugin named :foo version 0.7.3 or higher - # requires_redmine_plugin :foo, :version_or_higher => '0.7.3' - # requires_redmine_plugin :foo, '0.7.3' - # - # # Requires a specific version of a Redmine plugin - # requires_redmine_plugin :foo, :version => '0.7.3' # 0.7.3 only - # requires_redmine_plugin :foo, :version => ['0.7.3', '0.8.0'] # 0.7.3 or 0.8.0 - def requires_redmine_plugin(plugin_name, arg) - arg = { :version_or_higher => arg } unless arg.is_a?(Hash) - arg.assert_valid_keys(:version, :version_or_higher) - - plugin = Plugin.find(plugin_name) - current = plugin.version.split('.').collect(&:to_i) - - 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 the #{plugin_name} plugin #{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 versions of #{plugin_name}: #{v.join(', ')} but current is #{current.join('.')}") - end - end - end - true - end - - # Adds an item to the given +menu+. - # The +id+ parameter (equals to the project id) is automatically added to the url. - # menu :project_menu, :plugin_example, { :controller => 'example', :action => 'say_hello' }, :caption => 'Sample' - # - # +name+ parameter can be: :top_menu, :account_menu, :application_menu or :project_menu - # - 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+. - # - # The +actions+ argument is a hash with controllers as keys and actions as values (a single value or an array): - # permission :destroy_contacts, { :contacts => :destroy } - # permission :view_contacts, { :contacts => [:index, :show] } - # - # The +options+ argument can be used to make the permission public (implicitly given to any user) - # or to restrict users the permission can be given to. - # - # Examples - # # A permission that is implicitly given to any user - # # This permission won't appear on the Roles & Permissions setup screen - # permission :say_hello, { :example => :say_hello }, :public => true - # - # # A permission that can be given to any user - # permission :say_hello, { :example => :say_hello } - # - # # A permission that can be given to registered users only - # permission :say_hello, { :example => :say_hello }, :require => :loggedin - # - # # A permission that can be given to project members only - # permission :say_hello, { :example => :say_hello }, :require => :member - def permission(name, actions, options = {}) - if @project_module - Redmine::AccessControl.map {|map| map.project_module(@project_module) {|map|map.permission(name, actions, options)}} - else - Redmine::AccessControl.map {|map| map.permission(name, actions, options)} - end - end - - # Defines a project module, that can be enabled/disabled for each project. - # Permissions defined inside +block+ will be bind to the module. - # - # project_module :things do - # permission :view_contacts, { :contacts => [:list, :show] }, :public => true - # permission :destroy_contacts, { :contacts => :destroy } - # end - def project_module(name, &block) - @project_module = name - self.instance_eval(&block) - @project_module = nil - end - - # Registers an activity provider. - # - # Options: - # * :class_name - one or more model(s) that provide these events (inferred from event_type by default) - # * :default - setting this option to false will make the events not displayed by default - # - # A model can provide several activity event types. - # - # Examples: - # register :news - # register :scrums, :class_name => 'Meeting' - # register :issues, :class_name => ['Issue', 'Journal'] - # - # Retrieving events: - # Associated model(s) must implement the find_events class method. - # ActiveRecord models can use acts_as_activity_provider as a way to implement this class method. - # - # The following call should return all the scrum events visible by current user that occured in the 5 last days: - # Meeting.find_events('scrums', User.current, 5.days.ago, Date.today) - # Meeting.find_events('scrums', User.current, 5.days.ago, Date.today, :project => foo) # events for project foo only - # - # Note that :view_scrums permission is required to view these events in the activity view. - def activity_provider(*args) - Redmine::Activity.register(*args) - end - - # 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? - settings && settings.is_a?(Hash) && !settings[:partial].blank? - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4d/4db92861e8516d6d862c43d4820c7fc671e920ed.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/4d/4db92861e8516d6d862c43d4820c7fc671e920ed.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,1110 @@ +# Finnish translations for Ruby on Rails +# by Marko Seppä (marko.seppa@gmail.com) + +fi: + direction: ltr + date: + formats: + default: "%e. %Bta %Y" + long: "%A%e. %Bta %Y" + short: "%e.%m.%Y" + + day_names: [Sunnuntai, Maanantai, Tiistai, Keskiviikko, Torstai, Perjantai, Lauantai] + abbr_day_names: [Su, Ma, Ti, Ke, To, Pe, La] + month_names: [~, Tammikuu, Helmikuu, Maaliskuu, Huhtikuu, Toukokuu, Kesäkuu, Heinäkuu, Elokuu, Syyskuu, Lokakuu, Marraskuu, Joulukuu] + abbr_month_names: [~, Tammi, Helmi, Maalis, Huhti, Touko, Kesä, Heinä, Elo, Syys, Loka, Marras, Joulu] + order: + - :day + - :month + - :year + + time: + formats: + default: "%a, %e. %b %Y %H:%M:%S %z" + time: "%H:%M" + short: "%e. %b %H:%M" + long: "%B %d, %Y %H:%M" + am: "aamupäivä" + pm: "iltapäivä" + + support: + array: + words_connector: ", " + two_words_connector: " ja " + last_word_connector: " ja " + + + + number: + format: + separator: "," + delimiter: "." + precision: 3 + + currency: + format: + format: "%n %u" + unit: "€" + separator: "," + delimiter: "." + precision: 2 + + percentage: + format: + # separator: + delimiter: "" + # precision: + + precision: + format: + # separator: + delimiter: "" + # precision: + + human: + format: + delimiter: "" + precision: 3 + storage_units: + format: "%n %u" + units: + byte: + one: "Tavua" + other: "Tavua" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + + datetime: + distance_in_words: + half_a_minute: "puoli minuuttia" + less_than_x_seconds: + one: "aiemmin kuin sekunti" + other: "aiemmin kuin %{count} sekuntia" + x_seconds: + one: "sekunti" + other: "%{count} sekuntia" + less_than_x_minutes: + one: "aiemmin kuin minuutti" + other: "aiemmin kuin %{count} minuuttia" + x_minutes: + one: "minuutti" + other: "%{count} minuuttia" + about_x_hours: + one: "noin tunti" + other: "noin %{count} tuntia" + x_hours: + one: "1 tunti" + other: "%{count} tuntia" + x_days: + one: "päivä" + other: "%{count} päivää" + about_x_months: + one: "noin kuukausi" + other: "noin %{count} kuukautta" + x_months: + one: "kuukausi" + other: "%{count} kuukautta" + about_x_years: + one: "vuosi" + other: "noin %{count} vuotta" + over_x_years: + one: "yli vuosi" + other: "yli %{count} vuotta" + almost_x_years: + one: "almost 1 year" + other: "almost %{count} years" + prompts: + year: "Vuosi" + month: "Kuukausi" + day: "Päivä" + hour: "Tunti" + minute: "Minuutti" + second: "Sekuntia" + + activerecord: + errors: + template: + header: + one: "1 virhe esti tämän %{model} mallinteen tallentamisen" + other: "%{count} virhettä esti tämän %{model} mallinteen tallentamisen" + body: "Seuraavat kentät aiheuttivat ongelmia:" + messages: + inclusion: "ei löydy listauksesta" + exclusion: "on jo varattu" + invalid: "on kelvoton" + confirmation: "ei vastaa varmennusta" + accepted: "täytyy olla hyväksytty" + empty: "ei voi olla tyhjä" + blank: "ei voi olla sisällötön" + too_long: "on liian pitkä (maksimi on %{count} merkkiä)" + too_short: "on liian lyhyt (minimi on %{count} merkkiä)" + wrong_length: "on väärän pituinen (täytyy olla täsmälleen %{count} merkkiä)" + taken: "on jo käytössä" + not_a_number: "ei ole numero" + greater_than: "täytyy olla suurempi kuin %{count}" + greater_than_or_equal_to: "täytyy olla suurempi tai yhtä suuri kuin%{count}" + equal_to: "täytyy olla yhtä suuri kuin %{count}" + less_than: "täytyy olla pienempi kuin %{count}" + less_than_or_equal_to: "täytyy olla pienempi tai yhtä suuri kuin %{count}" + odd: "täytyy olla pariton" + even: "täytyy olla parillinen" + greater_than_start_date: "tulee olla aloituspäivän jälkeinen" + not_same_project: "ei kuulu samaan projektiin" + circular_dependency: "Tämä suhde loisi kehän." + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" + + actionview_instancetag_blank_option: Valitse, ole hyvä + + general_text_No: 'Ei' + general_text_Yes: 'Kyllä' + general_text_no: 'ei' + general_text_yes: 'kyllä' + general_lang_name: 'Finnish (Suomi)' + general_csv_separator: ',' + general_csv_decimal_separator: '.' + general_csv_encoding: ISO-8859-15 + general_pdf_encoding: UTF-8 + general_first_day_of_week: '1' + + notice_account_updated: Tilin päivitys onnistui. + notice_account_invalid_creditentials: Virheellinen käyttäjätunnus tai salasana + notice_account_password_updated: Salasanan päivitys onnistui. + notice_account_wrong_password: Väärä salasana + notice_account_register_done: Tilin luonti onnistui. Aktivoidaksesi tilin seuraa linkkiä joka välitettiin sähköpostiisi. + notice_account_unknown_email: Tuntematon käyttäjä. + notice_can_t_change_password: Tämä tili käyttää ulkoista tunnistautumisjärjestelmää. Salasanaa ei voi muuttaa. + notice_account_lost_email_sent: Sinulle on lähetetty sähköposti jossa on ohje kuinka vaihdat salasanasi. + notice_account_activated: Tilisi on nyt aktivoitu, voit kirjautua sisälle. + notice_successful_create: Luonti onnistui. + notice_successful_update: Päivitys onnistui. + notice_successful_delete: Poisto onnistui. + notice_successful_connection: Yhteyden muodostus onnistui. + notice_file_not_found: Hakemaasi sivua ei löytynyt tai se on poistettu. + notice_locking_conflict: Toinen käyttäjä on päivittänyt tiedot. + notice_not_authorized: Sinulla ei ole oikeutta näyttää tätä sivua. + notice_email_sent: "Sähköposti on lähetty osoitteeseen %{value}" + notice_email_error: "Sähköpostilähetyksessä tapahtui virhe (%{value})" + notice_feeds_access_key_reseted: RSS salasana on nollaantunut. + notice_failed_to_save_issues: "%{count} Tapahtum(an/ien) tallennus epäonnistui %{total} valitut: %{ids}." + notice_no_issue_selected: "Tapahtumia ei ole valittu! Valitse tapahtumat joita haluat muokata." + notice_account_pending: "Tilisi on luotu ja odottaa ylläpitäjän hyväksyntää." + notice_default_data_loaded: Vakioasetusten palautus onnistui. + + error_can_t_load_default_data: "Vakioasetuksia ei voitu ladata: %{value}" + error_scm_not_found: "Syötettä ja/tai versiota ei löydy tietovarastosta." + error_scm_command_failed: "Tietovarastoon pääsyssä tapahtui virhe: %{value}" + + mail_subject_lost_password: "Sinun %{value} salasanasi" + mail_body_lost_password: 'Vaihtaaksesi salasanasi, napsauta seuraavaa linkkiä:' + mail_subject_register: "%{value} tilin aktivointi" + mail_body_register: 'Aktivoidaksesi tilisi, napsauta seuraavaa linkkiä:' + mail_body_account_information_external: "Voit nyt käyttää %{value} tiliäsi kirjautuaksesi järjestelmään." + mail_body_account_information: Sinun tilin tiedot + mail_subject_account_activation_request: "%{value} tilin aktivointi pyyntö" + mail_body_account_activation_request: "Uusi käyttäjä (%{value}) on rekisteröitynyt. Hänen tili odottaa hyväksyntääsi:" + + + field_name: Nimi + field_description: Kuvaus + field_summary: Yhteenveto + field_is_required: Vaaditaan + field_firstname: Etunimi + field_lastname: Sukunimi + field_mail: Sähköposti + field_filename: Tiedosto + field_filesize: Koko + field_downloads: Latausta + field_author: Tekijä + field_created_on: Luotu + field_updated_on: Päivitetty + field_field_format: Muoto + field_is_for_all: Kaikille projekteille + field_possible_values: Mahdolliset arvot + field_regexp: Säännöllinen lauseke (reg exp) + field_min_length: Minimipituus + field_max_length: Maksimipituus + field_value: Arvo + field_category: Luokka + field_title: Otsikko + field_project: Projekti + field_issue: Tapahtuma + field_status: Tila + field_notes: Muistiinpanot + field_is_closed: Tapahtuma suljettu + field_is_default: Vakioarvo + field_tracker: Tapahtuma + field_subject: Aihe + field_due_date: Määräaika + field_assigned_to: Nimetty + field_priority: Prioriteetti + field_fixed_version: Kohdeversio + field_user: Käyttäjä + field_role: Rooli + field_homepage: Kotisivu + field_is_public: Julkinen + field_parent: Aliprojekti + field_is_in_roadmap: Tapahtumat näytetään roadmap näkymässä + field_login: Kirjautuminen + field_mail_notification: Sähköposti muistutukset + field_admin: Ylläpitäjä + field_last_login_on: Viimeinen yhteys + field_language: Kieli + field_effective_date: Päivä + field_password: Salasana + field_new_password: Uusi salasana + field_password_confirmation: Vahvistus + field_version: Versio + field_type: Tyyppi + field_host: Verkko-osoite + field_port: Portti + field_account: Tili + field_base_dn: Base DN + field_attr_login: Kirjautumismääre + field_attr_firstname: Etuminenmääre + field_attr_lastname: Sukunimenmääre + field_attr_mail: Sähköpostinmääre + field_onthefly: Automaattinen käyttäjien luonti + field_start_date: Alku + field_done_ratio: "% Tehty" + field_auth_source: Varmennusmuoto + field_hide_mail: Piiloita sähköpostiosoitteeni + field_comments: Kommentti + field_url: URL + field_start_page: Aloitussivu + field_subproject: Aliprojekti + field_hours: Tuntia + field_activity: Historia + field_spent_on: Päivä + field_identifier: Tunniste + field_is_filter: Käytetään suodattimena + field_issue_to: Liittyvä tapahtuma + field_delay: Viive + field_assignable: Tapahtumia voidaan nimetä tälle roolille + field_redirect_existing_links: Uudelleenohjaa olemassa olevat linkit + field_estimated_hours: Arvioitu aika + field_column_names: Saraketta + field_time_zone: Aikavyöhyke + field_searchable: Haettava + field_default_value: Vakioarvo + + setting_app_title: Ohjelman otsikko + setting_app_subtitle: Ohjelman alaotsikko + setting_welcome_text: Tervehdysteksti + setting_default_language: Vakiokieli + setting_login_required: Pakollinen kirjautuminen + setting_self_registration: Itserekisteröinti + setting_attachment_max_size: Liitteen maksimikoko + setting_issues_export_limit: Tapahtumien vientirajoite + setting_mail_from: Lähettäjän sähköpostiosoite + setting_bcc_recipients: Vastaanottajat piilokopiona (bcc) + setting_host_name: Verkko-osoite + setting_text_formatting: Tekstin muotoilu + setting_wiki_compression: Wiki historian pakkaus + setting_feeds_limit: Syötteen sisällön raja + setting_autofetch_changesets: Automaattisten muutosjoukkojen haku + setting_sys_api_enabled: Salli WS tietovaraston hallintaan + setting_commit_ref_keywords: Viittaavat hakusanat + setting_commit_fix_keywords: Korjaavat hakusanat + setting_autologin: Automaatinen kirjautuminen + setting_date_format: Päivän muoto + setting_time_format: Ajan muoto + setting_cross_project_issue_relations: Salli projektien väliset tapahtuminen suhteet + setting_issue_list_default_columns: Vakiosarakkeiden näyttö tapahtumalistauksessa + setting_emails_footer: Sähköpostin alatunniste + setting_protocol: Protokolla + setting_per_page_options: Sivun objektien määrän asetukset + + label_user: Käyttäjä + label_user_plural: Käyttäjät + label_user_new: Uusi käyttäjä + label_project: Projekti + label_project_new: Uusi projekti + label_project_plural: Projektit + label_x_projects: + zero: no projects + one: 1 project + other: "%{count} projects" + label_project_all: Kaikki projektit + label_project_latest: Uusimmat projektit + label_issue: Tapahtuma + label_issue_new: Uusi tapahtuma + label_issue_plural: Tapahtumat + label_issue_view_all: Näytä kaikki tapahtumat + label_issues_by: "Tapahtumat %{value}" + label_document: Dokumentti + label_document_new: Uusi dokumentti + label_document_plural: Dokumentit + label_role: Rooli + label_role_plural: Roolit + label_role_new: Uusi rooli + label_role_and_permissions: Roolit ja oikeudet + label_member: Jäsen + label_member_new: Uusi jäsen + label_member_plural: Jäsenet + label_tracker: Tapahtuma + label_tracker_plural: Tapahtumat + label_tracker_new: Uusi tapahtuma + label_workflow: Työnkulku + label_issue_status: Tapahtuman tila + label_issue_status_plural: Tapahtumien tilat + label_issue_status_new: Uusi tila + label_issue_category: Tapahtumaluokka + label_issue_category_plural: Tapahtumaluokat + label_issue_category_new: Uusi luokka + label_custom_field: Räätälöity kenttä + label_custom_field_plural: Räätälöidyt kentät + label_custom_field_new: Uusi räätälöity kenttä + label_enumerations: Lista + label_enumeration_new: Uusi arvo + label_information: Tieto + label_information_plural: Tiedot + label_please_login: Kirjaudu ole hyvä + label_register: Rekisteröidy + label_password_lost: Hukattu salasana + label_home: Koti + label_my_page: Omasivu + label_my_account: Oma tili + label_my_projects: Omat projektit + label_administration: Ylläpito + label_login: Kirjaudu sisään + label_logout: Kirjaudu ulos + label_help: Ohjeet + label_reported_issues: Raportoidut tapahtumat + label_assigned_to_me_issues: Minulle nimetyt tapahtumat + label_last_login: Viimeinen yhteys + label_registered_on: Rekisteröity + label_activity: Historia + label_new: Uusi + label_logged_as: Kirjauduttu nimellä + label_environment: Ympäristö + label_authentication: Varmennus + label_auth_source: Varmennustapa + label_auth_source_new: Uusi varmennustapa + label_auth_source_plural: Varmennustavat + label_subproject_plural: Aliprojektit + label_min_max_length: Min - Max pituudet + label_list: Lista + label_date: Päivä + label_integer: Kokonaisluku + label_float: Liukuluku + label_boolean: Totuusarvomuuttuja + label_string: Merkkijono + label_text: Pitkä merkkijono + label_attribute: Määre + label_attribute_plural: Määreet + label_no_data: Ei tietoa näytettäväksi + label_change_status: Muutos tila + label_history: Historia + label_attachment: Tiedosto + label_attachment_new: Uusi tiedosto + label_attachment_delete: Poista tiedosto + label_attachment_plural: Tiedostot + label_report: Raportti + label_report_plural: Raportit + label_news: Uutinen + label_news_new: Lisää uutinen + label_news_plural: Uutiset + label_news_latest: Viimeisimmät uutiset + label_news_view_all: Näytä kaikki uutiset + label_settings: Asetukset + label_overview: Yleiskatsaus + label_version: Versio + label_version_new: Uusi versio + label_version_plural: Versiot + label_confirmation: Vahvistus + label_export_to: Vie + label_read: Lukee... + label_public_projects: Julkiset projektit + label_open_issues: avoin, yhteensä + label_open_issues_plural: avointa, yhteensä + label_closed_issues: suljettu + label_closed_issues_plural: suljettua + label_x_open_issues_abbr_on_total: + zero: 0 open / %{total} + one: 1 open / %{total} + other: "%{count} open / %{total}" + label_x_open_issues_abbr: + zero: 0 open + one: 1 open + other: "%{count} open" + label_x_closed_issues_abbr: + zero: 0 closed + one: 1 closed + other: "%{count} closed" + label_total: Yhteensä + label_permissions: Oikeudet + label_current_status: Nykyinen tila + label_new_statuses_allowed: Uudet tilat sallittu + label_all: kaikki + label_none: ei mitään + label_nobody: ei kukaan + label_next: Seuraava + label_previous: Edellinen + label_used_by: Käytetty + label_details: Yksityiskohdat + label_add_note: Lisää muistiinpano + label_per_page: Per sivu + label_calendar: Kalenteri + label_months_from: kuukauden päässä + label_gantt: Gantt + label_internal: Sisäinen + label_last_changes: "viimeiset %{count} muutokset" + label_change_view_all: Näytä kaikki muutokset + label_personalize_page: Personoi tämä sivu + label_comment: Kommentti + label_comment_plural: Kommentit + label_x_comments: + zero: no comments + one: 1 comment + other: "%{count} comments" + label_comment_add: Lisää kommentti + label_comment_added: Kommentti lisätty + label_comment_delete: Poista kommentti + label_query: Räätälöity haku + label_query_plural: Räätälöidyt haut + label_query_new: Uusi haku + label_filter_add: Lisää suodatin + label_filter_plural: Suodattimet + label_equals: sama kuin + label_not_equals: eri kuin + label_in_less_than: pienempi kuin + label_in_more_than: suurempi kuin + label_today: tänään + label_this_week: tällä viikolla + label_less_than_ago: vähemmän kuin päivää sitten + label_more_than_ago: enemän kuin päivää sitten + label_ago: päiviä sitten + label_contains: sisältää + label_not_contains: ei sisällä + label_day_plural: päivää + label_repository: Tietovarasto + label_repository_plural: Tietovarastot + label_browse: Selaus + label_revision: Versio + label_revision_plural: Versiot + label_added: lisätty + label_modified: muokattu + label_deleted: poistettu + label_latest_revision: Viimeisin versio + label_latest_revision_plural: Viimeisimmät versiot + label_view_revisions: Näytä versiot + label_max_size: Suurin koko + label_sort_highest: Siirrä ylimmäiseksi + label_sort_higher: Siirrä ylös + label_sort_lower: Siirrä alas + label_sort_lowest: Siirrä alimmaiseksi + label_roadmap: Roadmap + label_roadmap_due_in: "Määräaika %{value}" + label_roadmap_overdue: "%{value} myöhässä" + label_roadmap_no_issues: Ei tapahtumia tälle versiolle + label_search: Haku + label_result_plural: Tulokset + label_all_words: kaikki sanat + label_wiki: Wiki + label_wiki_edit: Wiki muokkaus + label_wiki_edit_plural: Wiki muokkaukset + label_wiki_page: Wiki sivu + label_wiki_page_plural: Wiki sivut + label_index_by_title: Hakemisto otsikoittain + label_index_by_date: Hakemisto päivittäin + label_current_version: Nykyinen versio + label_preview: Esikatselu + label_feed_plural: Syötteet + label_changes_details: Kaikkien muutosten yksityiskohdat + label_issue_tracking: Tapahtumien seuranta + label_spent_time: Käytetty aika + label_f_hour: "%{value} tunti" + label_f_hour_plural: "%{value} tuntia" + label_time_tracking: Ajan seuranta + label_change_plural: Muutokset + label_statistics: Tilastot + label_commits_per_month: Tapahtumaa per kuukausi + label_commits_per_author: Tapahtumaa per tekijä + label_view_diff: Näytä erot + label_diff_inline: sisällössä + label_diff_side_by_side: vierekkäin + label_options: Valinnat + label_copy_workflow_from: Kopioi työnkulku + label_permissions_report: Oikeuksien raportti + label_watched_issues: Seurattavat tapahtumat + label_related_issues: Liittyvät tapahtumat + label_applied_status: Lisätty tila + label_loading: Lataa... + label_relation_new: Uusi suhde + label_relation_delete: Poista suhde + label_relates_to: liittyy + label_duplicates: kopio + label_blocks: estää + label_blocked_by: estetty + label_precedes: edeltää + label_follows: seuraa + label_end_to_start: lopusta alkuun + label_end_to_end: lopusta loppuun + label_start_to_start: alusta alkuun + label_start_to_end: alusta loppuun + label_stay_logged_in: Pysy kirjautuneena + label_disabled: poistettu käytöstä + label_show_completed_versions: Näytä valmiit versiot + label_me: minä + label_board: Keskustelupalsta + label_board_new: Uusi keskustelupalsta + label_board_plural: Keskustelupalstat + label_topic_plural: Aiheet + label_message_plural: Viestit + label_message_last: Viimeisin viesti + label_message_new: Uusi viesti + label_reply_plural: Vastaukset + label_send_information: Lähetä tilin tiedot käyttäjälle + label_year: Vuosi + label_month: Kuukausi + label_week: Viikko + label_language_based: Pohjautuen käyttäjän kieleen + label_sort_by: "Lajittele %{value}" + label_send_test_email: Lähetä testi sähköposti + label_feeds_access_key_created_on: "RSS salasana luotiin %{value} sitten" + label_module_plural: Moduulit + label_added_time_by: "Lisännyt %{author} %{age} sitten" + label_updated_time: "Päivitetty %{value} sitten" + label_jump_to_a_project: Siirry projektiin... + label_file_plural: Tiedostot + label_changeset_plural: Muutosryhmät + label_default_columns: Vakiosarakkeet + label_no_change_option: (Ei muutosta) + label_bulk_edit_selected_issues: Perusmuotoile valitut tapahtumat + label_theme: Teema + label_default: Vakio + label_search_titles_only: Hae vain otsikot + label_user_mail_option_all: "Kaikista tapahtumista kaikissa projekteistani" + label_user_mail_option_selected: "Kaikista tapahtumista vain valitsemistani projekteista..." + label_user_mail_no_self_notified: "En halua muistutusta muutoksista joita itse teen" + label_registration_activation_by_email: tilin aktivointi sähköpostitse + label_registration_manual_activation: tilin aktivointi käsin + label_registration_automatic_activation: tilin aktivointi automaattisesti + label_display_per_page: "Per sivu: %{value}" + label_age: Ikä + label_change_properties: Vaihda asetuksia + label_general: Yleinen + + button_login: Kirjaudu + button_submit: Lähetä + button_save: Tallenna + button_check_all: Valitse kaikki + button_uncheck_all: Poista valinnat + button_delete: Poista + button_create: Luo + button_test: Testaa + button_edit: Muokkaa + button_add: Lisää + button_change: Muuta + button_apply: Ota käyttöön + button_clear: Tyhjää + button_lock: Lukitse + button_unlock: Vapauta + button_download: Lataa + button_list: Lista + button_view: Näytä + button_move: Siirrä + button_back: Takaisin + button_cancel: Peruuta + button_activate: Aktivoi + button_sort: Järjestä + button_log_time: Seuraa aikaa + button_rollback: Siirry takaisin tähän versioon + button_watch: Seuraa + button_unwatch: Älä seuraa + button_reply: Vastaa + button_archive: Arkistoi + button_unarchive: Palauta + button_reset: Nollaus + button_rename: Uudelleen nimeä + button_change_password: Vaihda salasana + button_copy: Kopioi + button_annotate: Lisää selitys + button_update: Päivitä + + status_active: aktiivinen + status_registered: rekisteröity + status_locked: lukittu + + text_select_mail_notifications: Valitse tapahtumat joista tulisi lähettää sähköpostimuistutus. + text_regexp_info: esim. ^[A-Z0-9]+$ + text_min_max_length_info: 0 tarkoittaa, ei rajoitusta + text_project_destroy_confirmation: Oletko varma että haluat poistaa tämän projektin ja kaikki siihen kuuluvat tiedot? + text_workflow_edit: Valitse rooli ja tapahtuma muokataksesi työnkulkua + text_are_you_sure: Oletko varma? + text_tip_issue_begin_day: tehtävä joka alkaa tänä päivänä + text_tip_issue_end_day: tehtävä joka loppuu tänä päivänä + text_tip_issue_begin_end_day: tehtävä joka alkaa ja loppuu tänä päivänä + text_caracters_maximum: "%{count} merkkiä enintään." + text_caracters_minimum: "Täytyy olla vähintään %{count} merkkiä pitkä." + text_length_between: "Pituus välillä %{min} ja %{max} merkkiä." + text_tracker_no_workflow: Työnkulkua ei määritelty tälle tapahtumalle + text_unallowed_characters: Kiellettyjä merkkejä + text_comma_separated: Useat arvot sallittu (pilkku eroteltuna). + text_issues_ref_in_commit_messages: Liitän ja korjaan ongelmia syötetyssä viestissä + text_issue_added: "Issue %{id} has been reported by %{author}." + text_issue_updated: "Issue %{id} has been updated by %{author}." + text_wiki_destroy_confirmation: Oletko varma että haluat poistaa tämän wiki:n ja kaikki sen sisältämän tiedon? + text_issue_category_destroy_question: "Jotkut tapahtumat (%{count}) ovat nimetty tälle luokalle. Mitä haluat tehdä?" + text_issue_category_destroy_assignments: Poista luokan tehtävät + text_issue_category_reassign_to: Vaihda tapahtuma tähän luokkaan + text_user_mail_option: "Valitsemattomille projekteille, saat vain muistutuksen asioista joita seuraat tai olet mukana (esim. tapahtumat joissa olet tekijä tai nimettynä)." + text_no_configuration_data: "Rooleja, tapahtumien tiloja ja työnkulkua ei vielä olla määritelty.\nOn erittäin suotavaa ladata vakioasetukset. Voit muuttaa sitä latauksen jälkeen." + text_load_default_configuration: Lataa vakioasetukset + + default_role_manager: Päälikkö + default_role_developer: Kehittäjä + default_role_reporter: Tarkastelija + default_tracker_bug: Ohjelmointivirhe + default_tracker_feature: Ominaisuus + default_tracker_support: Tuki + default_issue_status_new: Uusi + default_issue_status_in_progress: In Progress + default_issue_status_resolved: Hyväksytty + default_issue_status_feedback: Palaute + default_issue_status_closed: Suljettu + default_issue_status_rejected: Hylätty + default_doc_category_user: Käyttäjä dokumentaatio + default_doc_category_tech: Tekninen dokumentaatio + default_priority_low: Matala + default_priority_normal: Normaali + default_priority_high: Korkea + default_priority_urgent: Kiireellinen + default_priority_immediate: Valitön + default_activity_design: Suunnittelu + default_activity_development: Kehitys + + enumeration_issue_priorities: Tapahtuman tärkeysjärjestys + enumeration_doc_categories: Dokumentin luokat + enumeration_activities: Historia (ajan seuranta) + label_associated_revisions: Liittyvät versiot + setting_user_format: Käyttäjien esitysmuoto + text_status_changed_by_changeset: "Päivitetty muutosversioon %{value}." + text_issues_destroy_confirmation: 'Oletko varma että haluat poistaa valitut tapahtumat ?' + label_more: Lisää + label_issue_added: Tapahtuma lisätty + label_issue_updated: Tapahtuma päivitetty + label_document_added: Dokumentti lisätty + label_message_posted: Viesti lisätty + label_file_added: Tiedosto lisätty + label_scm: SCM + text_select_project_modules: 'Valitse modulit jotka haluat käyttöön tähän projektiin:' + label_news_added: Uutinen lisätty + project_module_boards: Keskustelupalsta + project_module_issue_tracking: Tapahtuman seuranta + project_module_wiki: Wiki + project_module_files: Tiedostot + project_module_documents: Dokumentit + project_module_repository: Tietovarasto + project_module_news: Uutiset + project_module_time_tracking: Ajan seuranta + text_file_repository_writable: Kirjoitettava tiedostovarasto + text_default_administrator_account_changed: Vakio hallinoijan tunnus muutettu + text_rmagick_available: RMagick saatavilla (valinnainen) + button_configure: Asetukset + label_plugins: Lisäosat + label_ldap_authentication: LDAP tunnistautuminen + label_downloads_abbr: D/L + label_add_another_file: Lisää uusi tiedosto + label_this_month: tässä kuussa + text_destroy_time_entries_question: "%{hours} tuntia on raportoitu tapahtumasta jonka aiot poistaa. Mitä haluat tehdä ?" + label_last_n_days: "viimeiset %{count} päivää" + label_all_time: koko ajalta + error_issue_not_found_in_project: 'Tapahtumaa ei löytynyt tai se ei kuulu tähän projektiin' + label_this_year: tänä vuonna + text_assign_time_entries_to_project: Määritä tunnit projektille + label_date_range: Aikaväli + label_last_week: viime viikolla + label_yesterday: eilen + label_optional_description: Lisäkuvaus + label_last_month: viime kuussa + text_destroy_time_entries: Poista raportoidut tunnit + text_reassign_time_entries: 'Siirrä raportoidut tunnit tälle tapahtumalle:' + label_chronological_order: Aikajärjestyksessä + label_date_to: '' + setting_activity_days_default: Päivien esittäminen projektien historiassa + label_date_from: '' + label_in: '' + setting_display_subprojects_issues: Näytä aliprojektien tapahtumat pääprojektissa oletusarvoisesti + field_comments_sorting: Näytä kommentit + label_reverse_chronological_order: Käänteisessä aikajärjestyksessä + label_preferences: Asetukset + setting_default_projects_public: Uudet projektit ovat oletuksena julkisia + label_overall_activity: Kokonaishistoria + error_scm_annotate: "Merkintää ei ole tai siihen ei voi lisätä selityksiä." + label_planning: Suunnittelu + text_subprojects_destroy_warning: "Tämän aliprojekti(t): %{value} tullaan myös poistamaan." + label_and_its_subprojects: "%{value} ja aliprojektit" + mail_body_reminder: "%{count} sinulle nimettyä tapahtuma(a) erääntyy %{days} päivä sisään:" + mail_subject_reminder: "%{count} tapahtuma(a) erääntyy %{days} lähipäivinä" + text_user_wrote: "%{value} kirjoitti:" + label_duplicated_by: kopioinut + setting_enabled_scm: Versionhallinta käytettävissä + text_enumeration_category_reassign_to: 'Siirrä täksi arvoksi:' + text_enumeration_destroy_question: "%{count} kohdetta on sijoitettu tälle arvolle." + label_incoming_emails: Saapuvat sähköpostiviestit + label_generate_key: Luo avain + setting_mail_handler_api_enabled: Ota käyttöön WS saapuville sähköposteille + 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/configuration.yml tiedostosta ja uudelleenkäynnistä sovellus jotta asetukset astuvat voimaan." + field_parent_title: Aloitussivu + label_issue_watchers: Tapahtuman seuraajat + button_quote: Vastaa + setting_sequential_project_identifiers: Luo peräkkäiset projektien tunnisteet + 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_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 %{value} historia" + label_updated_time_by: "Updated by %{author} %{age} 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: "%{count} 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 + setting_file_max_size_displayed: Max size of text files displayed inline + field_watcher: Watcher + setting_openid: Allow OpenID login and registration + field_identity_url: OpenID URL + label_login_with_open_id_option: or login with OpenID + field_content: Content + label_descending: Descending + label_sort: Sort + label_ascending: Ascending + label_date_from_to: From %{start} to %{end} + label_greater_or_equal: ">=" + label_less_or_equal: <= + text_wiki_page_destroy_question: This page has %{descendants} child page(s) and descendant(s). What do you want to do? + text_wiki_page_reassign_children: Reassign child pages to this parent page + text_wiki_page_nullify_children: Keep child pages as root pages + text_wiki_page_destroy_children: Delete child pages and all their descendants + setting_password_min_length: Minimum password length + field_group_by: Group results by + mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated" + label_wiki_content_added: Wiki page added + mail_subject_wiki_content_added: "'%{id}' wiki page has been added" + mail_body_wiki_content_added: The '%{id}' wiki page has been added by %{author}. + label_wiki_content_updated: Wiki page updated + mail_body_wiki_content_updated: The '%{id}' wiki page has been updated by %{author}. + permission_add_project: Create project + setting_new_project_user_role_id: Role given to a non-admin user who creates a project + label_view_all_revisions: View all revisions + label_tag: Tag + label_branch: Branch + error_no_tracker_in_project: No tracker is associated to this project. Please check the Project settings. + error_no_default_issue_status: No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses"). + text_journal_changed: "%{label} changed from %{old} to %{new}" + text_journal_set_to: "%{label} set to %{value}" + text_journal_deleted: "%{label} deleted (%{old})" + label_group_plural: Groups + label_group: Group + label_group_new: New group + label_time_entry_plural: Spent time + text_journal_added: "%{label} %{value} added" + field_active: Active + enumeration_system_activity: System Activity + permission_delete_issue_watchers: Delete watchers + version_status_closed: closed + version_status_locked: locked + version_status_open: open + error_can_not_reopen_issue_on_closed_version: An issue assigned to a closed version can not be reopened + label_user_anonymous: Anonymous + button_move_and_follow: Move and follow + setting_default_projects_modules: Default enabled modules for new projects + setting_gravatar_default: Default Gravatar image + field_sharing: Sharing + label_version_sharing_hierarchy: With project hierarchy + label_version_sharing_system: With all projects + label_version_sharing_descendants: With subprojects + label_version_sharing_tree: With project tree + label_version_sharing_none: Not shared + error_can_not_archive_project: This project can not be archived + button_duplicate: Duplicate + button_copy_and_follow: Copy and follow + label_copy_source: Source + setting_issue_done_ratio: Calculate the issue done ratio with + setting_issue_done_ratio_issue_status: Use the issue status + error_issue_done_ratios_not_updated: Issue done ratios not updated. + error_workflow_copy_target: Please select target tracker(s) and role(s) + setting_issue_done_ratio_issue_field: Use the issue field + label_copy_same_as_target: Same as target + label_copy_target: Target + notice_issue_done_ratios_updated: Issue done ratios updated. + error_workflow_copy_source: Please select a source tracker or role + label_update_issue_done_ratios: Update issue done ratios + setting_start_of_week: Start calendars on + permission_view_issues: View Issues + label_display_used_statuses_only: Only display statuses that are used by this tracker + label_revision_id: Revision %{value} + label_api_access_key: API access key + label_api_access_key_created_on: API access key created %{value} ago + label_feeds_access_key: RSS access key + notice_api_access_key_reseted: Your API access key was reset. + setting_rest_api_enabled: Enable REST web service + label_missing_api_access_key: Missing an API access key + label_missing_feeds_access_key: Missing a RSS access key + button_show: Show + text_line_separated: Multiple values allowed (one line for each value). + setting_mail_handler_body_delimiters: Truncate emails after one of these lines + permission_add_subprojects: Create subprojects + label_subproject_new: New subproject + text_own_membership_delete_confirmation: |- + You are about to remove some or all of your permissions and may no longer be able to edit this project after that. + Are you sure you want to continue? + label_close_versions: Close completed versions + label_board_sticky: Sticky + label_board_locked: Locked + permission_export_wiki_pages: Export wiki pages + setting_cache_formatted_text: Cache formatted text + permission_manage_project_activities: Manage project activities + error_unable_delete_issue_status: Unable to delete issue status + label_profile: Profile + permission_manage_subtasks: Manage subtasks + field_parent_issue: Parent task + label_subtask_plural: Subtasks + label_project_copy_notifications: Send email notifications during the project copy + error_can_not_delete_custom_field: Unable to delete custom field + error_unable_to_connect: Unable to connect (%{value}) + error_can_not_remove_role: This role is in use and can not be deleted. + error_can_not_delete_tracker: This tracker contains issues and can't be deleted. + field_principal: Principal + label_my_page_block: My page block + notice_failed_to_save_members: "Failed to save member(s): %{errors}." + text_zoom_out: Zoom out + text_zoom_in: Zoom in + notice_unable_delete_time_entry: Unable to delete time log entry. + label_overall_spent_time: Overall spent time + field_time_entries: Log time + project_module_gantt: Gantt + project_module_calendar: Calendar + button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" + field_text: Text field + label_user_mail_option_only_owner: Only for things I am the owner of + setting_default_notification_option: Default notification option + label_user_mail_option_only_my_events: Only for things I watch or I'm involved in + label_user_mail_option_only_assigned: Only for things I am assigned to + label_user_mail_option_none: No events + field_member_of_group: Assignee's group + field_assigned_to_role: Assignee's role + notice_not_authorized_archived_project: The project you're trying to access has been archived. + label_principal_search: "Search for user or group:" + label_user_search: "Search for user:" + field_visible: Visible + setting_commit_logtime_activity_id: Activity for logged time + text_time_logged_by_changeset: Applied in changeset %{value}. + setting_commit_logtime_enabled: Enable time logging + notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) + setting_gantt_items_limit: Maximum number of items displayed on the gantt chart + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. + label_my_queries: My custom queries + text_journal_changed_no_detail: "%{label} updated" + label_news_comment_added: Comment added to a news + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + label_bulk_edit_selected_time_entries: Bulk edit selected time entries + text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? + label_role_anonymous: Anonymous + label_role_non_member: Non member + label_issue_note_added: Note added + label_issue_status_updated: Status updated + label_issue_priority_updated: Priority updated + label_issues_visibility_own: Issues created by or assigned to the user + field_issues_visibility: Issues visibility + label_issues_visibility_all: All issues + permission_set_own_issues_private: Set own issues public or private + field_is_private: Private + permission_set_issues_private: Set issues public or private + label_issues_visibility_public: All non private issues + text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). + field_commit_logs_encoding: Tee viestien koodaus + field_scm_path_encoding: Path encoding + text_scm_path_encoding_note: "Default: UTF-8" + field_path_to_repository: Path to repository + field_root_directory: Root directory + field_cvs_module: Module + field_cvsroot: CVSROOT + text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) + text_scm_command: Command + text_scm_command_version: Version + label_git_report_last_commit: Report last commit for files and directories + notice_issue_successful_create: Issue %{id} created. + label_between: between + setting_issue_group_assignment: Allow issue assignment to groups + label_diff: diff + text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) + description_query_sort_criteria_direction: Sort direction + description_project_scope: Search scope + description_filter: Filter + description_user_mail_notification: Mail notification settings + description_date_from: Enter start date + description_message_content: Message content + description_available_columns: Available Columns + description_date_range_interval: Choose range by selecting start and end date + description_issue_category_reassign: Choose issue category + description_search: Searchfield + description_notes: Notes + description_date_range_list: Choose range from list + description_choose_project: Projects + description_date_to: Enter end date + description_query_sort_criteria_attribute: Sort attribute + description_wiki_subpages_reassign: Choose new parent page + description_selected_columns: Selected Columns + label_parent_revision: Parent + label_child_revision: Child + error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. + setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues + button_edit_section: Edit this section + setting_repositories_encodings: Attachments and repositories encodings + description_all_columns: All Columns + button_export: Export + label_export_options: "%{export_format} export options" + error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) + notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." + label_x_issues: + zero: 0 tapahtuma + one: 1 tapahtuma + other: "%{count} tapahtumat" + label_repository_new: New repository + field_repository_is_default: Main repository + label_copy_attachments: Copy attachments + label_item_position: "%{position}/%{count}" + label_completed_versions: Completed versions + text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
Once saved, the identifier cannot be changed. + field_multiple: Multiple values + setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed + text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes + text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) + notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. + text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} + permission_manage_related_issues: Manage related issues + field_auth_source_ldap_filter: LDAP filter + label_search_for_watchers: Search for watchers to add + notice_account_deleted: Your account has been permanently deleted. + setting_unsubscribe: Allow users to delete their own account + button_delete_my_account: Delete my account + text_account_destroy_confirmation: |- + Are you sure you want to proceed? + Your account will be permanently deleted, with no way to reactivate it. + error_session_expired: Your session has expired. Please login again. + text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." + setting_session_lifetime: Session maximum lifetime + setting_session_timeout: Session inactivity timeout + label_session_expiration: Session expiration + permission_close_project: Close / reopen the project + label_show_closed_projects: View closed projects + button_close: Close + button_reopen: Reopen + project_status_active: active + project_status_closed: closed + project_status_archived: archived + text_project_closed: This project is closed and read-only. + notice_user_successful_create: User %{id} created. + field_core_fields: Standard fields + field_timeout: Timeout (in seconds) + setting_thumbnails_enabled: Display attachment thumbnails + setting_thumbnails_size: Thumbnails size (in pixels) + label_status_transitions: Status transitions + label_fields_permissions: Fields permissions + label_readonly: Read-only + label_required: Required + text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
Once saved, the identifier cannot be changed. + field_board_parent: Parent forum + label_attribute_of_project: Project's %{name} + label_attribute_of_author: Author's %{name} + label_attribute_of_assigned_to: Assignee's %{name} + label_attribute_of_fixed_version: Target version's %{name} + label_copy_subtasks: Copy subtasks + label_copied_to: copied to + label_copied_from: copied from + label_any_issues_in_project: any issues in project + label_any_issues_not_in_project: any issues not in project + field_private_notes: Private notes + permission_view_private_notes: View private notes + permission_set_notes_private: Set notes as private + label_no_issues_in_project: no issues in project + label_any: kaikki + label_last_n_weeks: last %{count} weeks + setting_cross_project_subtasks: Allow cross-project subtasks + label_cross_project_descendants: With subprojects + label_cross_project_tree: With project tree + label_cross_project_hierarchy: With project hierarchy + label_cross_project_system: With all projects + button_hide: Hide + setting_non_working_week_days: Non-working days + label_in_the_next_days: in the next + label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Yhteensä + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4d/4de18371feb9c1f0e9402b1bd393643194d5f79d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/4d/4de18371feb9c1f0e9402b1bd393643194d5f79d.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,98 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class RoutingUsersTest < ActionController::IntegrationTest + def test_users + assert_routing( + { :method => 'get', :path => "/users" }, + { :controller => 'users', :action => 'index' } + ) + assert_routing( + { :method => 'get', :path => "/users.xml" }, + { :controller => 'users', :action => 'index', :format => 'xml' } + ) + assert_routing( + { :method => 'get', :path => "/users/44" }, + { :controller => 'users', :action => 'show', :id => '44' } + ) + assert_routing( + { :method => 'get', :path => "/users/44.xml" }, + { :controller => 'users', :action => 'show', :id => '44', + :format => 'xml' } + ) + assert_routing( + { :method => 'get', :path => "/users/current" }, + { :controller => 'users', :action => 'show', :id => 'current' } + ) + assert_routing( + { :method => 'get', :path => "/users/current.xml" }, + { :controller => 'users', :action => 'show', :id => 'current', + :format => 'xml' } + ) + assert_routing( + { :method => 'get', :path => "/users/new" }, + { :controller => 'users', :action => 'new' } + ) + assert_routing( + { :method => 'get', :path => "/users/444/edit" }, + { :controller => 'users', :action => 'edit', :id => '444' } + ) + assert_routing( + { :method => 'post', :path => "/users" }, + { :controller => 'users', :action => 'create' } + ) + assert_routing( + { :method => 'post', :path => "/users.xml" }, + { :controller => 'users', :action => 'create', :format => 'xml' } + ) + assert_routing( + { :method => 'put', :path => "/users/444" }, + { :controller => 'users', :action => 'update', :id => '444' } + ) + assert_routing( + { :method => 'put', :path => "/users/444.xml" }, + { :controller => 'users', :action => 'update', :id => '444', + :format => 'xml' } + ) + assert_routing( + { :method => 'delete', :path => "/users/44" }, + { :controller => 'users', :action => 'destroy', :id => '44' } + ) + assert_routing( + { :method => 'delete', :path => "/users/44.xml" }, + { :controller => 'users', :action => 'destroy', :id => '44', + :format => 'xml' } + ) + assert_routing( + { :method => 'post', :path => "/users/123/memberships" }, + { :controller => 'users', :action => 'edit_membership', + :id => '123' } + ) + assert_routing( + { :method => 'put', :path => "/users/123/memberships/55" }, + { :controller => 'users', :action => 'edit_membership', + :id => '123', :membership_id => '55' } + ) + assert_routing( + { :method => 'delete', :path => "/users/123/memberships/55" }, + { :controller => 'users', :action => 'destroy_membership', + :id => '123', :membership_id => '55' } + ) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4d/4dfa42e1015d78dacb86fd8ca51a98e09e60e632.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/4d/4dfa42e1015d78dacb86fd8ca51a98e09e60e632.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,7 @@ +--- a.txt 2013-04-05 14:19:39.000000000 +0900 ++++ b.txt 2013-04-05 14:19:51.000000000 +0900 +@@ -1,3 +1,3 @@ + aaaa +-日本 ++ã«ã£ã½ã‚“日本 + bbbb diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4e/4e2b9ddd1957e8e9b8adf4bab52aeff57edc2985.svn-base --- a/.svn/pristine/4e/4e2b9ddd1957e8e9b8adf4bab52aeff57edc2985.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,127 +0,0 @@ -// ** I18N - -// Calendar EN language -// Author: Mihai Bazon, -// 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 -("Diumenge", - "Dilluns", - "Dimarts", - "Dimecres", - "Dijous", - "Divendres", - "Dissabte", - "Diumenge"); - -// 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 -("dg", - "dl", - "dt", - "dc", - "dj", - "dv", - "ds", - "dg"); - -// 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 -("Gener", - "Febrer", - "Març", - "Abril", - "Maig", - "Juny", - "Juliol", - "Agost", - "Setembre", - "Octubre", - "Novembre", - "Desembre"); - -// short month names -Calendar._SMN = new Array -("Gen", - "Feb", - "Mar", - "Abr", - "Mai", - "Jun", - "Jul", - "Ago", - "Set", - "Oct", - "Nov", - "Des"); - -// tooltips -Calendar._TT = {}; -Calendar._TT["INFO"] = "Quant al calendari"; - -Calendar._TT["ABOUT"] = -"Selector DHTML de data/hora\n" + -"(c) dynarch.com 2002-2005 / Autor: Mihai Bazon\n" + // don't translate this this ;-) -"Per aconseguir l'última versió visiteu: http://www.dynarch.com/projects/calendar/\n" + -"Distribuït sota la llicència GNU LGPL. Vegeu http://gnu.org/licenses/lgpl.html per obtenir més detalls." + -"\n\n" + -"Selecció de la data:\n" + -"- Utilitzeu els botons \xab, \xbb per seleccionar l'any\n" + -"- Utilitzeu els botons " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " per seleccionar el mes\n" + -"- Mantingueu premut el botó del ratolí sobre qualsevol d'aquests botons per a una selecció més ràpida."; -Calendar._TT["ABOUT_TIME"] = "\n\n" + -"Selecció de l'hora:\n" + -"- Feu clic en qualsevol part de l'hora per incrementar-la\n" + -"- o premeu majúscules per disminuir-la\n" + -"- o feu clic i arrossegueu per a una selecció més ràpida."; - -Calendar._TT["PREV_YEAR"] = "Any anterior (mantenir per menú)"; -Calendar._TT["PREV_MONTH"] = "Mes anterior (mantenir per menú)"; -Calendar._TT["GO_TODAY"] = "Anar a avui"; -Calendar._TT["NEXT_MONTH"] = "Mes següent (mantenir per menú)"; -Calendar._TT["NEXT_YEAR"] = "Any següent (mantenir per menú)"; -Calendar._TT["SEL_DATE"] = "Sel·lecciona la data"; -Calendar._TT["DRAG_TO_MOVE"] = "Arrossega per moure"; -Calendar._TT["PART_TODAY"] = " (avui)"; - -// 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"] = "Primer mostra el %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"] = "Tanca"; -Calendar._TT["TODAY"] = "Avui"; -Calendar._TT["TIME_PART"] = "(Majúscules-)Feu clic o arrossegueu per canviar el 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"] = "set"; -Calendar._TT["TIME"] = "Hora:"; diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4e/4e463aec85dd46647cbb6a677bff53b3a10966f4.svn-base --- a/.svn/pristine/4e/4e463aec85dd46647cbb6a677bff53b3a10966f4.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,31 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../../../test_helper', __FILE__) - -class Redmine::NotifiableTest < ActiveSupport::TestCase - def setup - end - - def test_all - assert_equal 12, Redmine::Notifiable.all.length - - %w(issue_added issue_updated issue_note_added issue_status_updated issue_priority_updated news_added news_comment_added document_added file_added message_posted wiki_content_added wiki_content_updated).each do |notifiable| - assert Redmine::Notifiable.all.collect(&:name).include?(notifiable), "missing #{notifiable}" - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4e/4e62fffdedf392e04d93318885f3d58ff665b392.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/4e/4e62fffdedf392e04d93318885f3d58ff665b392.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,113 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class TokenTest < ActiveSupport::TestCase + fixtures :tokens + + def test_create + token = Token.new + token.save + assert_equal 40, token.value.length + assert !token.expired? + end + + def test_create_should_remove_existing_tokens + user = User.find(1) + t1 = Token.create(:user => user, :action => 'autologin') + t2 = Token.create(:user => user, :action => 'autologin') + assert_not_equal t1.value, t2.value + assert !Token.exists?(t1.id) + assert Token.exists?(t2.id) + end + + def test_destroy_expired_should_not_destroy_feeds_and_api_tokens + Token.delete_all + + Token.create!(:user_id => 1, :action => 'api', :created_on => 7.days.ago) + Token.create!(:user_id => 1, :action => 'feeds', :created_on => 7.days.ago) + + assert_no_difference 'Token.count' do + assert_equal 0, Token.destroy_expired + end + end + + def test_destroy_expired_should_destroy_expired_tokens + Token.delete_all + + Token.create!(:user_id => 1, :action => 'autologin', :created_on => 7.days.ago) + Token.create!(:user_id => 2, :action => 'autologin', :created_on => 3.days.ago) + Token.create!(:user_id => 3, :action => 'autologin', :created_on => 1.hour.ago) + + assert_difference 'Token.count', -2 do + assert_equal 2, Token.destroy_expired + end + end + + def test_find_active_user_should_return_user + token = Token.create!(:user_id => 1, :action => 'api') + assert_equal User.find(1), Token.find_active_user('api', token.value) + end + + def test_find_active_user_should_return_nil_for_locked_user + token = Token.create!(:user_id => 1, :action => 'api') + User.find(1).lock! + assert_nil Token.find_active_user('api', token.value) + end + + def test_find_user_should_return_user + token = Token.create!(:user_id => 1, :action => 'api') + assert_equal User.find(1), Token.find_user('api', token.value) + end + + def test_find_user_should_return_locked_user + token = Token.create!(:user_id => 1, :action => 'api') + User.find(1).lock! + assert_equal User.find(1), Token.find_user('api', token.value) + end + + def test_find_token_should_return_the_token + token = Token.create!(:user_id => 1, :action => 'api') + assert_equal token, Token.find_token('api', token.value) + end + + def test_find_token_should_return_the_token_with_validity + token = Token.create!(:user_id => 1, :action => 'api', :created_on => 1.hour.ago) + assert_equal token, Token.find_token('api', token.value, 1) + end + + def test_find_token_should_return_nil_with_wrong_action + token = Token.create!(:user_id => 1, :action => 'feeds') + assert_nil Token.find_token('api', token.value) + end + + def test_find_token_should_return_nil_with_wrong_action + token = Token.create!(:user_id => 1, :action => 'feeds') + assert_nil Token.find_token('api', Token.generate_token_value) + end + + def test_find_token_should_return_nil_without_user + token = Token.create!(:user_id => 999, :action => 'api') + assert_nil Token.find_token('api', token.value) + end + + def test_find_token_should_return_nil_with_validity_expired + token = Token.create!(:user_id => 999, :action => 'api', :created_on => 2.days.ago) + assert_nil Token.find_token('api', token.value, 1) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4e/4e64ceb850aaa04a7acc9aad3a69b5f58d199fa5.svn-base --- a/.svn/pristine/4e/4e64ceb850aaa04a7acc9aad3a69b5f58d199fa5.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,182 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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 'digest/md5' - -module Redmine - module WikiFormatting - module Textile - class Formatter < RedCloth3 - include ActionView::Helpers::TagHelper - - # 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] - - def initialize(*args) - super - self.hard_breaks=true - self.no_span_caps=true - self.filter_styles=true - end - - def to_html(*rules) - @toc = [] - super(*RULES).to_s - end - - def get_section(index) - section = extract_sections(index)[1] - hash = Digest::MD5.hexdigest(section) - return section, hash - end - - def update_section(index, update, hash=nil) - t = extract_sections(index) - if hash.present? && hash != Digest::MD5.hexdigest(t[1]) - raise Redmine::WikiFormatting::StaleSectionError - end - t[1] = update unless t[1].blank? - t.reject(&:blank?).join "\n\n" - end - - def extract_sections(index) - @pre_list = [] - text = self.dup - rip_offtags text, false, false - before = '' - s = '' - after = '' - i = 0 - l = 1 - started = false - ended = false - text.scan(/(((?:.*?)(\A|\r?\n\r?\n))(h(\d+)(#{A}#{C})\.(?::(\S+))? (.*?)$)|.*)/m).each do |all, content, lf, heading, level| - if heading.nil? - if ended - after << all - elsif started - s << all - else - before << all - end - break - end - i += 1 - if ended - after << all - elsif i == index - l = level.to_i - before << content - s << heading - started = true - elsif i > index - s << content - if level.to_i > l - s << heading - else - after << heading - ended = true - end - else - before << all - end - end - sections = [before.strip, s.strip, after.strip] - sections.each {|section| smooth_offtags_without_code_highlighting section} - sections - end - - private - - # Patch for RedCloth. Fixed in RedCloth r128 but _why hasn't released it yet. - # http://code.whytheluckystiff.net/redcloth/changeset/128 - def hard_break( text ) - text.gsub!( /(.)\n(?!\n|\Z| *([#*=]+(\s|$)|[{|]))/, "\\1
" ) if hard_breaks - end - - alias :smooth_offtags_without_code_highlighting :smooth_offtags - # Patch to add code highlighting support to RedCloth - def smooth_offtags( text ) - unless @pre_list.empty? - ## replace
 content
-            text.gsub!(//) do
-              content = @pre_list[$1.to_i]
-              if content.match(/\s?(.+)/m)
-                content = "" +
-                  Redmine::SyntaxHighlighting.highlight_by_language($2, $1)
-              end
-              content
-            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 =~ /=]?/
-              # 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
-              tag = content_tag('a', proto + url, :href => "#{proto=="www."?"http://www.":proto}#{url}", :class => 'external')
-              %(#{leading}#{tag}#{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(/]*>(.*)(#{Regexp.escape(mail)})(.*)<\/a>/)
-              mail
-            else
-              content_tag('a', mail, :href => "mailto:#{mail}", :class => "email")
-            end
-          end
-        end
-      end
-    end
-  end
-end
diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4e/4ead8da3bccd9129e3a3aa36afffc50dc032eb7b.svn-base
--- a/.svn/pristine/4e/4ead8da3bccd9129e3a3aa36afffc50dc032eb7b.svn-base	Fri Jun 14 09:07:32 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-

<%= l(:mail_body_wiki_content_updated, :id => link_to(h(@wiki_content.page.pretty_title), @wiki_content_url), - :author => h(@wiki_content.author)) %>
-<%=h @wiki_content.comments %>

- -

<%= l(:label_view_diff) %>:
-<%= link_to h(@wiki_diff_url), @wiki_diff_url %>

diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4f/4f4249c007fe05ae60a997c7b935c55203608b2e.svn-base --- a/.svn/pristine/4f/4f4249c007fe05ae60a997c7b935c55203608b2e.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -/* - * Raphael 1.5.2 - JavaScript Vector Library - * - * Copyright (c) 2010 Dmitry Baranovskiy (http://raphaeljs.com) - * Licensed under the MIT (http://raphaeljs.com/license.html) license. - */ -(function(){function cC(a,b,c,d,e,f){function o(a,b){var c,d,e,f,j,k;for(e=a,k=0;k<8;k++){f=m(e)-a;if(B(f)d)return d;while(cf?c=e:d=e,e=(d-c)/2+c}return e}function n(a,b){var c=o(a,b);return((l*c+k)*c+j)*c}function m(a){return((i*a+h)*a+g)*a}var g=3*b,h=3*(d-b)-g,i=1-g-h,j=3*c,k=3*(e-c)-j,l=1-j-k;return n(a,1/(200*f))}function cB(b){return function(c,d,e,f){var g={back:b};a.is(e,"function")?f=e:g.rot=e,c&&c.constructor==bN&&(c=c.attrs.path),c&&(g.along=c);return this.animate(g,d,f)}}function cp(){return this.x+q+this.y}function bm(a,b,c){function d(){var g=Array[e].slice.call(arguments,0),h=g[v]("â–º"),i=d.cache=d.cache||{},j=d.count=d.count||[];if(i[f](h))return c?c(i[h]):i[h];j[w]>=1e3&&delete i[j.shift()],j[L](h),i[h]=a[m](b,g);return c?c(i[h]):i[h]}return d}function bh(){var a=[],b=0;for(;b<32;b++)a[b]=(~~(y.random()*16))[H](16);a[12]=4,a[16]=(a[16]&3|8)[H](16);return"r-"+a[v]("")}function a(){if(a.is(arguments[0],G)){var b=arguments[0],d=bV[m](a,b.splice(0,3+a.is(b[0],E))),e=d.set();for(var g=0,h=b[w];g',bg=bf.firstChild,bg.style.behavior="url(#default#VML)";if(!bg||typeof bg.adj!="object")return a.type=null;bf=null}a.svg=!(a.vml=a.type=="VML"),j[e]=a[e],k=j[e],a._id=0,a._oid=0,a.fn={},a.is=function(a,b){b=x.call(b);if(b=="finite")return!O[f](+a);return b=="null"&&a===null||b==typeof a||b=="object"&&a===Object(a)||b=="array"&&Array.isArray&&Array.isArray(a)||J.call(a).slice(8,-1).toLowerCase()==b},a.angle=function(b,c,d,e,f,g){if(f==null){var h=b-d,i=c-e;if(!h&&!i)return 0;return((h<0)*180+y.atan(-i/-h)*180/D+360)%360}return a.angle(b,c,f,g)-a.angle(d,e,f,g)},a.rad=function(a){return a%360*D/180},a.deg=function(a){return a*180/D%360},a.snapTo=function(b,c,d){d=a.is(d,"finite")?d:10;if(a.is(b,G)){var e=b.length;while(e--)if(B(b[e]-c)<=d)return b[e]}else{b=+b;var f=c%b;if(fb-d)return c-f+b}return c},a.setWindow=function(a){h=a,g=h.document};var bi=function(b){if(a.vml){var c=/^\s+|\s+$/g,d;try{var e=new ActiveXObject("htmlfile");e.write(""),e.close(),d=e.body}catch(f){d=createPopup().document.body}var h=d.createTextRange();bi=bm(function(a){try{d.style.color=r(a)[Y](c,p);var b=h.queryCommandValue("ForeColor");b=(b&255)<<16|b&65280|(b&16711680)>>>16;return"#"+("000000"+b[H](16)).slice(-6)}catch(e){return"none"}})}else{var i=g.createElement("i");i.title="Raphaël Colour Picker",i.style.display="none",g.body[l](i),bi=bm(function(a){i.style.color=a;return g.defaultView.getComputedStyle(i,p).getPropertyValue("color")})}return bi(b)},bj=function(){return"hsb("+[this.h,this.s,this.b]+")"},bk=function(){return"hsl("+[this.h,this.s,this.l]+")"},bl=function(){return this.hex};a.hsb2rgb=function(b,c,d,e){a.is(b,"object")&&"h"in b&&"s"in b&&"b"in b&&(d=b.b,c=b.s,b=b.h,e=b.o);return a.hsl2rgb(b,c,d/2,e)},a.hsl2rgb=function(b,c,d,e){a.is(b,"object")&&"h"in b&&"s"in b&&"l"in b&&(d=b.l,c=b.s,b=b.h);if(b>1||c>1||d>1)b/=360,c/=100,d/=100;var f={},g=["r","g","b"],h,i,j,k,l,m;if(!c)f={r:d,g:d,b:d};else{d<.5?h=d*(1+c):h=d+c-d*c,i=2*d-h;for(var n=0;n<3;n++)j=b+1/3*-(n-1),j<0&&j++,j>1&&j--,j*6<1?f[g[n]]=i+(h-i)*6*j:j*2<1?f[g[n]]=h:j*3<2?f[g[n]]=i+(h-i)*(2/3-j)*6:f[g[n]]=i}f.r*=255,f.g*=255,f.b*=255,f.hex="#"+(16777216|f.b|f.g<<8|f.r<<16).toString(16).slice(1),a.is(e,"finite")&&(f.opacity=e),f.toString=bl;return f},a.rgb2hsb=function(b,c,d){c==null&&a.is(b,"object")&&"r"in b&&"g"in b&&"b"in b&&(d=b.b,c=b.g,b=b.r);if(c==null&&a.is(b,F)){var e=a.getRGB(b);b=e.r,c=e.g,d=e.b}if(b>1||c>1||d>1)b/=255,c/=255,d/=255;var f=z(b,c,d),g=A(b,c,d),h,i,j=f;if(g==f)return{h:0,s:0,b:f,toString:bj};var k=f-g;i=k/f,b==f?h=(c-d)/k:c==f?h=2+(d-b)/k:h=4+(b-c)/k,h/=6,h<0&&h++,h>1&&h--;return{h:h,s:i,b:j,toString:bj}},a.rgb2hsl=function(b,c,d){c==null&&a.is(b,"object")&&"r"in b&&"g"in b&&"b"in b&&(d=b.b,c=b.g,b=b.r);if(c==null&&a.is(b,F)){var e=a.getRGB(b);b=e.r,c=e.g,d=e.b}if(b>1||c>1||d>1)b/=255,c/=255,d/=255;var f=z(b,c,d),g=A(b,c,d),h,i,j=(f+g)/2,k;if(g==f)k={h:0,s:0,l:j};else{var l=f-g;i=j<.5?l/(f+g):l/(2-f-g),b==f?h=(c-d)/l:c==f?h=2+(d-b)/l:h=4+(b-c)/l,h/=6,h<0&&h++,h>1&&h--,k={h:h,s:i,l:j}}k.toString=bk;return k},a._path2string=function(){return this.join(",")[Y](ba,"$1")},a.getRGB=bm(function(b){if(!b||!!((b=r(b)).indexOf("-")+1))return{r:-1,g:-1,b:-1,hex:"none",error:1};if(b=="none")return{r:-1,g:-1,b:-1,hex:"none"};!_[f](b.toLowerCase().substring(0,2))&&b.charAt()!="#"&&(b=bi(b));var c,d,e,g,h,i,j,k=b.match(N);if(k){k[2]&&(g=T(k[2].substring(5),16),e=T(k[2].substring(3,5),16),d=T(k[2].substring(1,3),16)),k[3]&&(g=T((i=k[3].charAt(3))+i,16),e=T((i=k[3].charAt(2))+i,16),d=T((i=k[3].charAt(1))+i,16)),k[4]&&(j=k[4][s]($),d=S(j[0]),j[0].slice(-1)=="%"&&(d*=2.55),e=S(j[1]),j[1].slice(-1)=="%"&&(e*=2.55),g=S(j[2]),j[2].slice(-1)=="%"&&(g*=2.55),k[1].toLowerCase().slice(0,4)=="rgba"&&(h=S(j[3])),j[3]&&j[3].slice(-1)=="%"&&(h/=100));if(k[5]){j=k[5][s]($),d=S(j[0]),j[0].slice(-1)=="%"&&(d*=2.55),e=S(j[1]),j[1].slice(-1)=="%"&&(e*=2.55),g=S(j[2]),j[2].slice(-1)=="%"&&(g*=2.55),(j[0].slice(-3)=="deg"||j[0].slice(-1)=="°")&&(d/=360),k[1].toLowerCase().slice(0,4)=="hsba"&&(h=S(j[3])),j[3]&&j[3].slice(-1)=="%"&&(h/=100);return a.hsb2rgb(d,e,g,h)}if(k[6]){j=k[6][s]($),d=S(j[0]),j[0].slice(-1)=="%"&&(d*=2.55),e=S(j[1]),j[1].slice(-1)=="%"&&(e*=2.55),g=S(j[2]),j[2].slice(-1)=="%"&&(g*=2.55),(j[0].slice(-3)=="deg"||j[0].slice(-1)=="°")&&(d/=360),k[1].toLowerCase().slice(0,4)=="hsla"&&(h=S(j[3])),j[3]&&j[3].slice(-1)=="%"&&(h/=100);return a.hsl2rgb(d,e,g,h)}k={r:d,g:e,b:g},k.hex="#"+(16777216|g|e<<8|d<<16).toString(16).slice(1),a.is(h,"finite")&&(k.opacity=h);return k}return{r:-1,g:-1,b:-1,hex:"none",error:1}},a),a.getColor=function(a){var b=this.getColor.start=this.getColor.start||{h:0,s:1,b:a||.75},c=this.hsb2rgb(b.h,b.s,b.b);b.h+=.075,b.h>1&&(b.h=0,b.s-=.2,b.s<=0&&(this.getColor.start={h:0,s:1,b:b.b}));return c.hex},a.getColor.reset=function(){delete this.start},a.parsePathString=bm(function(b){if(!b)return null;var c={a:7,c:6,h:1,l:2,m:2,q:4,s:4,t:2,v:1,z:0},d=[];a.is(b,G)&&a.is(b[0],G)&&(d=bo(b)),d[w]||r(b)[Y](bb,function(a,b,e){var f=[],g=x.call(b);e[Y](bc,function(a,b){b&&f[L](+b)}),g=="m"&&f[w]>2&&(d[L]([b][n](f.splice(0,2))),g="l",b=b=="m"?"l":"L");while(f[w]>=c[g]){d[L]([b][n](f.splice(0,c[g])));if(!c[g])break}}),d[H]=a._path2string;return d}),a.findDotsAtSegment=function(a,b,c,d,e,f,g,h,i){var j=1-i,k=C(j,3)*a+C(j,2)*3*i*c+j*3*i*i*e+C(i,3)*g,l=C(j,3)*b+C(j,2)*3*i*d+j*3*i*i*f+C(i,3)*h,m=a+2*i*(c-a)+i*i*(e-2*c+a),n=b+2*i*(d-b)+i*i*(f-2*d+b),o=c+2*i*(e-c)+i*i*(g-2*e+c),p=d+2*i*(f-d)+i*i*(h-2*f+d),q=(1-i)*a+i*c,r=(1-i)*b+i*d,s=(1-i)*e+i*g,t=(1-i)*f+i*h,u=90-y.atan((m-o)/(n-p))*180/D;(m>o||n1&&(x=y.sqrt(x),c=x*c,d=x*d);var z=c*c,A=d*d,C=(f==g?-1:1)*y.sqrt(B((z*A-z*u*u-A*t*t)/(z*u*u+A*t*t))),E=C*c*u/d+(a+h)/2,F=C*-d*t/c+(b+i)/2,G=y.asin(((b-F)/d).toFixed(9)),H=y.asin(((i-F)/d).toFixed(9));G=aH&&(G=G-D*2),!g&&H>G&&(H=H-D*2)}else G=j[0],H=j[1],E=j[2],F=j[3];var I=H-G;if(B(I)>k){var J=H,K=h,L=i;H=G+k*(g&&H>G?1:-1),h=E+c*y.cos(H),i=F+d*y.sin(H),m=bt(h,i,c,d,e,0,g,K,L,[H,J,E,F])}I=H-G;var M=y.cos(G),N=y.sin(G),O=y.cos(H),P=y.sin(H),Q=y.tan(I/4),R=4/3*c*Q,S=4/3*d*Q,T=[a,b],U=[a+R*N,b-S*M],V=[h+R*P,i-S*O],W=[h,i];U[0]=2*T[0]-U[0],U[1]=2*T[1]-U[1];if(j)return[U,V,W][n](m);m=[U,V,W][n](m)[v]()[s](",");var X=[];for(var Y=0,Z=m[w];Y"1e12"&&(l=.5),B(n)>"1e12"&&(n=.5),l>0&&l<1&&(q=bu(a,b,c,d,e,f,g,h,l),p[L](q.x),o[L](q.y)),n>0&&n<1&&(q=bu(a,b,c,d,e,f,g,h,n),p[L](q.x),o[L](q.y)),i=f-2*d+b-(h-2*f+d),j=2*(d-b)-2*(f-d),k=b-d,l=(-j+y.sqrt(j*j-4*i*k))/2/i,n=(-j-y.sqrt(j*j-4*i*k))/2/i,B(l)>"1e12"&&(l=.5),B(n)>"1e12"&&(n=.5),l>0&&l<1&&(q=bu(a,b,c,d,e,f,g,h,l),p[L](q.x),o[L](q.y)),n>0&&n<1&&(q=bu(a,b,c,d,e,f,g,h,n),p[L](q.x),o[L](q.y));return{min:{x:A[m](0,p),y:A[m](0,o)},max:{x:z[m](0,p),y:z[m](0,o)}}}),bw=bm(function(a,b){var c=bq(a),d=b&&bq(b),e={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},f={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},g=function(a,b){var c,d;if(!a)return["C",b.x,b.y,b.x,b.y,b.x,b.y];!(a[0]in{T:1,Q:1})&&(b.qx=b.qy=null);switch(a[0]){case"M":b.X=a[1],b.Y=a[2];break;case"A":a=["C"][n](bt[m](0,[b.x,b.y][n](a.slice(1))));break;case"S":c=b.x+(b.x-(b.bx||b.x)),d=b.y+(b.y-(b.by||b.y)),a=["C",c,d][n](a.slice(1));break;case"T":b.qx=b.x+(b.x-(b.qx||b.x)),b.qy=b.y+(b.y-(b.qy||b.y)),a=["C"][n](bs(b.x,b.y,b.qx,b.qy,a[1],a[2]));break;case"Q":b.qx=a[1],b.qy=a[2],a=["C"][n](bs(b.x,b.y,a[1],a[2],a[3],a[4]));break;case"L":a=["C"][n](br(b.x,b.y,a[1],a[2]));break;case"H":a=["C"][n](br(b.x,b.y,a[1],b.y));break;case"V":a=["C"][n](br(b.x,b.y,b.x,a[1]));break;case"Z":a=["C"][n](br(b.x,b.y,b.X,b.Y))}return a},h=function(a,b){if(a[b][w]>7){a[b].shift();var e=a[b];while(e[w])a.splice(b++,0,["C"][n](e.splice(0,6)));a.splice(b,1),k=z(c[w],d&&d[w]||0)}},i=function(a,b,e,f,g){a&&b&&a[g][0]=="M"&&b[g][0]!="M"&&(b.splice(g,0,["M",f.x,f.y]),e.bx=0,e.by=0,e.x=a[g][1],e.y=a[g][2],k=z(c[w],d&&d[w]||0))};for(var j=0,k=z(c[w],d&&d[w]||0);j.5)*2-1;C(e-.5,2)+C(f-.5,2)>.25&&(f=y.sqrt(.25-C(e-.5,2))*g+.5)&&f!=.5&&(f=f.toFixed(5)-1e-5*g)}return p}),b=b[s](/\s*\-\s*/);if(d=="linear"){var i=b.shift();i=-S(i);if(isNaN(i))return null;var j=[0,0,y.cos(i*D/180),y.sin(i*D/180)],k=1/(z(B(j[2]),B(j[3]))||1);j[2]*=k,j[3]*=k,j[2]<0&&(j[0]=-j[2],j[2]=0),j[3]<0&&(j[1]=-j[3],j[3]=0)}var m=bx(b);if(!m)return null;var n=a.getAttribute(I);n=n.match(/^url\(#(.*)\)$/),n&&c.defs.removeChild(g.getElementById(n[1]));var o=bG(d+"Gradient");o.id=bh(),bG(o,d=="radial"?{fx:e,fy:f}:{x1:j[0],y1:j[1],x2:j[2],y2:j[3]}),c.defs[l](o);for(var q=0,t=m[w];q1?G.opacity/100:G.opacity});case"stroke":G=a.getRGB(o),h[R](n,G.hex),n=="stroke"&&G[f]("opacity")&&bG(h,{"stroke-opacity":G.opacity>1?G.opacity/100:G.opacity});break;case"gradient":(({circle:1,ellipse:1})[f](c.type)||r(o).charAt()!="r")&&bI(h,o,c.paper);break;case"opacity":i.gradient&&!i[f]("stroke-opacity")&&bG(h,{"stroke-opacity":o>1?o/100:o});case"fill-opacity":if(i.gradient){var H=g.getElementById(h.getAttribute(I)[Y](/^url\(#|\)$/g,p));if(H){var J=H.getElementsByTagName("stop");J[J[w]-1][R]("stop-opacity",o)}break};default:n=="font-size"&&(o=T(o,10)+"px");var K=n[Y](/(\-.)/g,function(a){return V.call(a.substring(1))});h.style[K]=o,h[R](n,o)}}bM(c,d),m?c.rotate(m.join(q)):S(j)&&c.rotate(j,!0)},bL=1.2,bM=function(b,c){if(b.type=="text"&&!!(c[f]("text")||c[f]("font")||c[f]("font-size")||c[f]("x")||c[f]("y"))){var d=b.attrs,e=b.node,h=e.firstChild?T(g.defaultView.getComputedStyle(e.firstChild,p).getPropertyValue("font-size"),10):10;if(c[f]("text")){d.text=c.text;while(e.firstChild)e.removeChild(e.firstChild);var i=r(c.text)[s]("\n");for(var j=0,k=i[w];jb.height&&(b.height=f.y+f.height-b.y),f.x+f.width-b.x>b.width&&(b.width=f.x+f.width-b.x)}}a&&this.hide();return b},bN[e].attr=function(b,c){if(this.removed)return this;if(b==null){var d={};for(var e in this.attrs)this.attrs[f](e)&&(d[e]=this.attrs[e]);this._.rt.deg&&(d.rotation=this.rotate()),(this._.sx!=1||this._.sy!=1)&&(d.scale=this.scale()),d.gradient&&d.fill=="none"&&(d.fill=d.gradient)&&delete d.gradient;return d}if(c==null&&a.is(b,F)){if(b=="translation")return cA.call(this);if(b=="rotation")return this.rotate();if(b=="scale")return this.scale();if(b==I&&this.attrs.fill=="none"&&this.attrs.gradient)return this.attrs.gradient;return this.attrs[b]}if(c==null&&a.is(b,G)){var g={};for(var h=0,i=b.length;h")),m.W=h.w=m.paper.span.offsetWidth,m.H=h.h=m.paper.span.offsetHeight,m.X=h.x,m.Y=h.y+Q(m.H/2);switch(h["text-anchor"]){case"start":m.node.style["v-text-align"]="left",m.bbx=Q(m.W/2);break;case"end":m.node.style["v-text-align"]="right",m.bbx=-Q(m.W/2);break;default:m.node.style["v-text-align"]="center"}}},bI=function(a,b){a.attrs=a.attrs||{};var c=a.attrs,d,e="linear",f=".5 .5";a.attrs.gradient=b,b=r(b)[Y](bd,function(a,b,c){e="radial",b&&c&&(b=S(b),c=S(c),C(b-.5,2)+C(c-.5,2)>.25&&(c=y.sqrt(.25-C(b-.5,2))*((c>.5)*2-1)+.5),f=b+q+c);return p}),b=b[s](/\s*\-\s*/);if(e=="linear"){var g=b.shift();g=-S(g);if(isNaN(g))return null}var h=bx(b);if(!h)return null;a=a.shape||a.node,d=a.getElementsByTagName(I)[0]||cd(I),!d.parentNode&&a.appendChild(d);if(h[w]){d.on=!0,d.method="none",d.color=h[0].color,d.color2=h[h[w]-1].color;var i=[];for(var j=0,k=h[w];j')}}catch(ce){cd=function(a){return g.createElement("<"+a+' xmlns="urn:schemas-microsoft.com:vml" class="rvml">')}}bV=function(){var b=by[m](0,arguments),c=b.container,d=b.height,e,f=b.width,h=b.x,i=b.y;if(!c)throw new Error("VML container not found.");var k=new j,n=k.canvas=g.createElement("div"),o=n.style;h=h||0,i=i||0,f=f||512,d=d||342,f==+f&&(f+="px"),d==+d&&(d+="px"),k.width=1e3,k.height=1e3,k.coordsize=b_*1e3+q+b_*1e3,k.coordorigin="0 0",k.span=g.createElement("span"),k.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;",n[l](k.span),o.cssText=a.format("top:0;left:0;width:{0};height:{1};display:inline-block;position:relative;clip:rect(0 {0} {1} 0);overflow:hidden",f,d),c==1?(g.body[l](n),o.left=h+"px",o.top=i+"px",o.position="absolute"):c.firstChild?c.insertBefore(n,c.firstChild):c[l](n),bz.call(k,k,a.fn);return k},k.clear=function(){this.canvas.innerHTML=p,this.span=g.createElement("span"),this.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;",this.canvas[l](this.span),this.bottom=this.top=null},k.remove=function(){this.canvas.parentNode.removeChild(this.canvas);for(var a in this)this[a]=bF(a);return!0}}var cf=navigator.userAgent.match(/Version\/(.*?)\s/);navigator.vendor=="Apple Computer, Inc."&&(cf&&cf[1]<4||navigator.platform.slice(0,2)=="iP")?k.safari=function(){var a=this.rect(-99,-99,this.width+99,this.height+99).attr({stroke:"none"});h.setTimeout(function(){a.remove()})}:k.safari=function(){};var cg=function(){this.returnValue=!1},ch=function(){return this.originalEvent.preventDefault()},ci=function(){this.cancelBubble=!0},cj=function(){return this.originalEvent.stopPropagation()},ck=function(){if(g.addEventListener)return function(a,b,c,d){var e=o&&u[b]?u[b]:b,g=function(e){if(o&&u[f](b))for(var g=0,h=e.targetTouches&&e.targetTouches.length;g1&&(a=Array[e].splice.call(arguments,0,arguments[w]));return new cD(a)},k.setSize=bU,k.top=k.bottom=null,k.raphael=a,bO.resetScale=function(){if(this.removed)return this;this._.sx=1,this._.sy=1,this.attrs.scale="1 1"},bO.scale=function(a,b,c,d){if(this.removed)return this;if(a==null&&b==null)return{x:this._.sx,y:this._.sy,toString:cp};b=b||a,!+b&&(b=a);var e,f,g,h,i=this.attrs;if(a!=0){var j=this.getBBox(),k=j.x+j.width/2,l=j.y+j.height/2,m=B(a/this._.sx),o=B(b/this._.sy);c=+c||c==0?c:k,d=+d||d==0?d:l;var r=this._.sx>0,s=this._.sy>0,t=~~(a/B(a)),u=~~(b/B(b)),x=m*t,y=o*u,z=this.node.style,A=c+B(k-c)*x*(k>c==r?1:-1),C=d+B(l-d)*y*(l>d==s?1:-1),D=a*t>b*u?o:m;switch(this.type){case"rect":case"image":var E=i.width*m,F=i.height*o;this.attr({height:F,r:i.r*D,width:E,x:A-E/2,y:C-F/2});break;case"circle":case"ellipse":this.attr({rx:i.rx*m,ry:i.ry*o,r:i.r*D,cx:A,cy:C});break;case"text":this.attr({x:A,y:C});break;case"path":var G=bp(i.path),H=!0,I=r?x:m,J=s?y:o;for(var K=0,L=G[w];Kr?p=n.data[r*l]:(p=a.findDotsAtSegment(b,c,d,e,f,g,h,i,r/l),n.data[r]=p),r&&(k+=C(C(o.x-p.x,2)+C(o.y-p.y,2),.5));if(j!=null&&k>=j)return p;o=p}if(j==null)return k},cs=function(b,c){return function(d,e,f){d=bw(d);var g,h,i,j,k="",l={},m,n=0;for(var o=0,p=d.length;oe){if(c&&!l.start){m=cr(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n),k+=["C",m.start.x,m.start.y,m.m.x,m.m.y,m.x,m.y];if(f)return k;l.start=k,k=["M",m.x,m.y+"C",m.n.x,m.n.y,m.end.x,m.end.y,i[5],i[6]][v](),n+=j,g=+i[5],h=+i[6];continue}if(!b&&!c){m=cr(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n);return{x:m.x,y:m.y,alpha:m.alpha}}}n+=j,g=+i[5],h=+i[6]}k+=i}l.end=k,m=b?n:c?l:a.findDotsAtSegment(g,h,i[1],i[2],i[3],i[4],i[5],i[6],1),m.alpha&&(m={x:m.x,y:m.y,alpha:m.alpha});return m}},ct=cs(1),cu=cs(),cv=cs(0,1);bO.getTotalLength=function(){if(this.type=="path"){if(this.node.getTotalLength)return this.node.getTotalLength();return ct(this.attrs.path)}},bO.getPointAtLength=function(a){if(this.type=="path")return cu(this.attrs.path,a)},bO.getSubpath=function(a,b){if(this.type=="path"){if(B(this.getTotalLength()-b)<"1e-6")return cv(this.attrs.path,a).end;var c=cv(this.attrs.path,b,1);return a?cv(c,a).end:c}},a.easing_formulas={linear:function(a){return a},"<":function(a){return C(a,3)},">":function(a){return C(a-1,3)+1},"<>":function(a){a=a*2;if(a<1)return C(a,3)/2;a-=2;return(C(a,3)+2)/2},backIn:function(a){var b=1.70158;return a*a*((b+1)*a-b)},backOut:function(a){a=a-1;var b=1.70158;return a*a*((b+1)*a+b)+1},elastic:function(a){if(a==0||a==1)return a;var b=.3,c=b/4;return C(2,-10*a)*y.sin((a-c)*2*D/b)+1},bounce:function(a){var b=7.5625,c=2.75,d;a<1/c?d=b*a*a:a<2/c?(a-=1.5/c,d=b*a*a+.75):a<2.5/c?(a-=2.25/c,d=b*a*a+.9375):(a-=2.625/c,d=b*a*a+.984375);return d}};var cw=[],cx=function(){var b=+(new Date);for(var c=0;c -# by Alberto Ferreira -pt: - support: - array: - sentence_connector: "e" - skip_last_comma: true - - direction: ltr - date: - formats: - default: "%d/%m/%Y" - short: "%d de %B" - long: "%d de %B de %Y" - only_day: "%d" - day_names: [Domingo, Segunda, Terça, Quarta, Quinta, Sexta, Sábado] - abbr_day_names: [Dom, Seg, Ter, Qua, Qui, Sex, Sáb] - month_names: [~, Janeiro, Fevereiro, Março, Abril, Maio, Junho, Julho, Agosto, Setembro, Outubro, Novembro, Dezembro] - abbr_month_names: [~, Jan, Fev, Mar, Abr, Mai, Jun, Jul, Ago, Set, Out, Nov, Dez] - order: - - :day - - :month - - :year - - time: - formats: - default: "%A, %d de %B de %Y, %H:%Mh" - time: "%H:%M" - short: "%d/%m, %H:%M hs" - long: "%A, %d de %B de %Y, %H:%Mh" - am: '' - pm: '' - - datetime: - distance_in_words: - half_a_minute: "meio minuto" - less_than_x_seconds: - one: "menos de 1 segundo" - other: "menos de %{count} segundos" - x_seconds: - one: "1 segundo" - other: "%{count} segundos" - less_than_x_minutes: - one: "menos de um minuto" - other: "menos de %{count} minutos" - x_minutes: - one: "1 minuto" - other: "%{count} minutos" - about_x_hours: - one: "aproximadamente 1 hora" - other: "aproximadamente %{count} horas" - x_days: - one: "1 dia" - other: "%{count} dias" - about_x_months: - one: "aproximadamente 1 mês" - other: "aproximadamente %{count} meses" - x_months: - one: "1 mês" - other: "%{count} meses" - about_x_years: - one: "aproximadamente 1 ano" - other: "aproximadamente %{count} anos" - over_x_years: - one: "mais de 1 ano" - other: "mais de %{count} anos" - almost_x_years: - one: "almost 1 year" - other: "almost %{count} years" - - number: - format: - precision: 3 - separator: ',' - delimiter: '.' - currency: - format: - unit: '€' - precision: 2 - format: "%u %n" - separator: ',' - delimiter: '.' - percentage: - format: - delimiter: '' - precision: - format: - delimiter: '' - human: - format: - precision: 1 - delimiter: '' - storage_units: - format: "%n %u" - units: - byte: - one: "Byte" - other: "Bytes" - kb: "KB" - mb: "MB" - gb: "GB" - tb: "TB" - - activerecord: - errors: - template: - header: - one: "Não foi possível guardar %{model}: 1 erro" - other: "Não foi possível guardar %{model}: %{count} erros" - body: "Por favor, verifique os seguintes campos:" - messages: - inclusion: "não está incluído na lista" - exclusion: "não está disponível" - invalid: "não é válido" - confirmation: "não está de acordo com a confirmação" - accepted: "precisa de ser aceite" - empty: "não pode estar em branco" - blank: "não pode estar em branco" - too_long: "tem demasiados caracteres (máximo: %{count} caracteres)" - too_short: "tem poucos caracteres (mínimo: %{count} caracteres)" - wrong_length: "não é do tamanho correcto (necessita de ter %{count} caracteres)" - taken: "não está disponível" - not_a_number: "não é um número" - greater_than: "tem de ser maior do que %{count}" - greater_than_or_equal_to: "tem de ser maior ou igual a %{count}" - equal_to: "tem de ser igual a %{count}" - less_than: "tem de ser menor do que %{count}" - less_than_or_equal_to: "tem de ser menor ou igual a %{count}" - odd: "tem de ser ímpar" - even: "tem de ser par" - greater_than_start_date: "deve ser maior que a data inicial" - not_same_project: "não pertence ao mesmo projecto" - circular_dependency: "Esta relação iria criar uma dependência circular" - cant_link_an_issue_with_a_descendant: "Não é possível ligar uma tarefa a uma sub-tarefa que lhe é pertencente" - - ## Translated by: Pedro Araújo - actionview_instancetag_blank_option: Seleccione - - general_text_No: 'Não' - 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-15 - general_pdf_encoding: UTF-8 - general_first_day_of_week: '1' - - notice_account_updated: A conta foi actualizada com sucesso. - notice_account_invalid_creditentials: Utilizador ou palavra-chave inválidos. - notice_account_password_updated: A palavra-chave foi alterada com sucesso. - notice_account_wrong_password: Palavra-chave errada. - notice_account_register_done: A conta foi criada com sucesso. - notice_account_unknown_email: Utilizador desconhecido. - 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. É agora possível 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 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 %{value}" - notice_email_error: "Ocorreu um erro ao enviar o e-mail (%{value})" - notice_feeds_access_key_reseted: A sua chave de RSS foi inicializada. - notice_failed_to_save_issues: "Não foi possível guardar %{count} tarefa(s) das %{total} seleccionadas: %{ids}." - 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_can_t_load_default_data: "Não foi possível carregar a configuração padrão: %{value}" - 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: %{value}" - 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 de %{value}" - mail_body_lost_password: 'Para mudar a sua palavra-chave, clique na ligação abaixo:' - mail_subject_register: "Activação de conta de %{value}" - mail_body_register: 'Para activar a sua conta, clique na ligação abaixo:' - mail_body_account_information_external: "Pode utilizar a conta %{value} para autenticar-se." - mail_body_account_information: Informação da sua conta - mail_subject_account_activation_request: "Pedido de activação da conta %{value}" - mail_body_account_activation_request: "Um novo utilizador (%{value}) registou-se. A sua conta está à espera de aprovação:" - mail_subject_reminder: "%{count} tarefa(s) para entregar nos próximos %{days} dias" - mail_body_reminder: "%{count} tarefa(s) que estão atribuídas a si estão agendadas para estarem completas nos próximos %{days} dias:" - - gui_validation_error: 1 erro - gui_validation_error_plural: "%{count} erros" - - field_name: Nome - field_description: Descrição - field_summary: Sumário - field_is_required: Obrigatório - field_firstname: Nome - field_lastname: Apelido - field_mail: E-mail - field_filename: Ficheiro - field_filesize: Tamanho - field_downloads: Downloads - field_author: Autor - field_created_on: Criado - field_updated_on: Alterado - field_field_format: Formato - field_is_for_all: Para todos os projectos - field_possible_values: Valores possíveis - field_regexp: Expressão regular - field_min_length: Tamanho mínimo - field_max_length: Tamanho máximo - field_value: Valor - field_category: Categoria - field_title: Título - field_project: Projecto - field_issue: Tarefa - field_status: Estado - field_notes: Notas - field_is_closed: Tarefa fechada - field_is_default: Valor por omissão - field_tracker: Tipo - field_subject: Assunto - field_due_date: Data fim - field_assigned_to: Atribuído a - field_priority: Prioridade - field_fixed_version: Versão - field_user: Utilizador - field_role: Função - field_homepage: Página - field_is_public: Público - field_parent: Sub-projecto de - field_is_in_roadmap: Tarefas mostradas no mapa de planificação - field_login: Nome de utilizador - field_mail_notification: Notificações por e-mail - field_admin: Administrador - field_last_login_on: Última visita - field_language: Língua - field_effective_date: Data - field_password: Palavra-chave - field_new_password: Nova palavra-chave - field_password_confirmation: Confirmação - field_version: Versão - field_type: Tipo - field_host: Servidor - field_port: Porta - field_account: Conta - field_base_dn: Base DN - field_attr_login: Atributo utilizador - field_attr_firstname: Atributo nome próprio - field_attr_lastname: Atributo último nome - field_attr_mail: Atributo e-mail - field_onthefly: Criação imediata de utilizadores - field_start_date: Data início - field_done_ratio: "% Completo" - field_auth_source: Modo de autenticação - field_hide_mail: Esconder endereço de e-mail - field_comments: Comentário - field_url: URL - field_start_page: Página inicial - field_subproject: Subprojecto - field_hours: Horas - field_activity: Actividade - field_spent_on: Data - field_identifier: Identificador - field_is_filter: Usado como filtro - field_issue_to: Tarefa relacionada - field_delay: Atraso - field_assignable: As tarefas podem ser associadas a esta função - field_redirect_existing_links: Redireccionar ligações 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: 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: 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_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 de fecho - setting_autologin: Login automático - setting_date_format: Formato da data - 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_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 - label_user_new: Novo utilizador - label_project: Projecto - label_project_new: Novo projecto - label_project_plural: Projectos - label_x_projects: - zero: no projects - one: 1 project - other: "%{count} projects" - label_project_all: Todos os projectos - label_project_latest: Últimos projectos - 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 %{value}" - label_issue_added: Tarefa adicionada - label_issue_updated: Tarefa actualizada - label_document: Documento - label_document_new: Novo documento - label_document_plural: Documentos - label_document_added: Documento adicionado - label_role: Função - label_role_plural: Funções - label_role_new: Nova função - label_role_and_permissions: Funções e permissões - label_member: Membro - label_member_new: Novo membro - label_member_plural: Membros - label_tracker: Tipo - label_tracker_plural: Tipos - label_tracker_new: Novo tipo - label_workflow: Fluxo de trabalho - label_issue_status: Estado da tarefa - label_issue_status_plural: Estados da tarefa - label_issue_status_new: Novo estado - 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ções - label_enumeration_new: Novo valor - label_information: Informação - label_information_plural: Informações - 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_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 criadas - label_assigned_to_me_issues: Tarefas atribuídas a mim - label_last_login: Último acesso - label_registered_on: Registado em - label_activity: Actividade - label_overall_activity: Actividade geral - label_new: Novo - label_logged_as: Ligado como - label_environment: Ambiente - label_authentication: Autenticação - label_auth_source: Modo de autenticação - label_auth_source_new: Novo modo de autenticação - label_auth_source_plural: Modos de autenticação - label_subproject_plural: Sub-projectos - label_and_its_subprojects: "%{value} 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 - label_attribute: Atributo - label_attribute_plural: Atributos - label_download: "%{count} Download" - label_download_plural: "%{count} Downloads" - label_no_data: Sem dados para mostrar - label_change_status: Mudar estado - label_history: Histórico - 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ó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_news_added: Notícia adicionada - 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: '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_x_open_issues_abbr_on_total: - zero: 0 open / %{total} - one: 1 open / %{total} - other: "%{count} open / %{total}" - label_x_open_issues_abbr: - zero: 0 open - one: 1 open - other: "%{count} open" - label_x_closed_issues_abbr: - zero: 0 closed - one: 1 closed - other: "%{count} closed" - label_total: Total - label_permissions: Permissões - label_current_status: Estado actual - 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 - label_details: Detalhes - label_add_note: Adicionar nota - label_per_page: Por página - label_calendar: Calendário - label_months_from: meses de - label_gantt: Gantt - label_internal: Interno - label_last_changes: "últimas %{count} 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_x_comments: - zero: no comments - one: 1 comment - other: "%{count} comments" - label_comment_add: Adicionar comentário - label_comment_added: Comentário adicionado - 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 é - 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_last_week: semana passada - label_last_n_days: "últimos %{count} 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_repository_plural: Repositórios - label_browse: Navegar - label_modification: "%{count} alteração" - label_modification_plural: "%{count} 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_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: Planificação - label_roadmap_due_in: "Termina em %{value}" - label_roadmap_overdue: "Atrasado %{value}" - 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: 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 - label_preview: Pré-visualizar - label_feed_plural: Feeds - label_changes_details: Detalhes de todas as mudanças - label_issue_tracking: Tarefas - label_spent_time: Tempo gasto - label_f_hour: "%{value} hora" - label_f_hour_plural: "%{value} horas" - label_time_tracking: Registo de tempo - label_change_plural: Mudanças - label_statistics: Estatísticas - label_commits_per_month: Commits por mês - label_commits_per_author: Commits por autor - label_view_diff: Ver diferenças - label_diff_inline: inline - label_diff_side_by_side: lado a lado - label_options: Opções - label_copy_workflow_from: Copiar fluxo de trabalho de - label_permissions_report: Relatório de permissões - label_watched_issues: Tarefas observadas - label_related_issues: Tarefas relacionadas - label_applied_status: Estado aplicado - label_loading: A carregar... - label_relation_new: Nova relação - label_relation_delete: Apagar relação - label_relates_to: relacionado a - label_duplicates: duplica - label_duplicated_by: duplicado por - label_blocks: bloqueia - label_blocked_by: bloqueado por - label_precedes: precede - label_follows: segue - 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: desactivado - label_show_completed_versions: Mostrar versões acabadas - label_me: eu - label_board: Forum - label_board_new: Novo forum - label_board_plural: Forums - label_topic_plural: Tópicos - label_message_plural: Mensagens - 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_week: Semana - label_date_from: De - label_date_to: Para - label_language_based: Baseado na língua do utilizador - label_sort_by: "Ordenar por %{value}" - label_send_test_email: enviar um e-mail de teste - label_feeds_access_key_created_on: "Chave RSS criada há %{value} atrás" - label_module_plural: Módulos - label_added_time_by: "Adicionado por %{author} há %{age} atrás" - label_updated_time: "Alterado há %{value} 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_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: %{value}" - 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: Submeter - button_save: Guardar - 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: Alterar - button_apply: Aplicar - button_clear: Limpar - button_lock: Bloquear - button_unlock: Desbloquear - button_download: Download - button_list: Listar - button_view: Ver - button_move: Mover - button_back: Voltar - button_cancel: Cancelar - button_activate: Activar - button_sort: Ordenar - button_log_time: Tempo de trabalho - button_rollback: Voltar para esta versão - button_watch: 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: registado - status_locked: bloqueado - - 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: Tem a certeza que deseja apagar o projecto e todos os dados relacionados? - text_subprojects_destroy_warning: "O(s) seu(s) sub-projecto(s): %{value} também será/serão apagado(s)." - text_workflow_edit: Seleccione uma função e um tipo de tarefa para editar o fluxo de trabalho - text_are_you_sure: Tem a certeza? - text_tip_issue_begin_day: tarefa a começar neste dia - text_tip_issue_end_day: tarefa a acabar neste dia - text_tip_issue_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.
Uma vez guardado, o identificador não poderá ser alterado.' - text_caracters_maximum: "máximo %{count} caracteres." - text_caracters_minimum: "Deve ter pelo menos %{count} caracteres." - text_length_between: "Deve ter entre %{min} e %{max} caracteres." - text_tracker_no_workflow: Sem fluxo de trabalho 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 fechando tarefas em mensagens de commit - text_issue_added: "Tarefa %{id} foi criada por %{author}." - text_issue_updated: "Tarefa %{id} foi actualizada por %{author}." - 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 (%{count}) 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: "Perfis, 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 %{value}." - 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: "%{hours} 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: "%{value} escreveu:" - text_enumeration_destroy_question: "%{count} 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/configuration.yml e reinicie a aplicação para activar estas funcionalidades." - - default_role_manager: Gestor - default_role_developer: Programador - default_role_reporter: Repórter - default_tracker_bug: Bug - default_tracker_feature: Funcionalidade - default_tracker_support: Suporte - default_issue_status_new: Novo - default_issue_status_in_progress: Em curso - default_issue_status_resolved: Resolvido - default_issue_status_feedback: Feedback - default_issue_status_closed: Fechado - default_issue_status_rejected: Rejeitado - default_doc_category_user: Documentação de utilizador - default_doc_category_tech: Documentação técnica - default_priority_low: Baixa - default_priority_normal: Normal - default_priority_high: Alta - default_priority_urgent: Urgente - default_priority_immediate: Imediata - default_activity_design: Planeamento - default_activity_development: Desenvolvimento - - 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 ícones 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 %{value}" - label_updated_time_by: "Actualizado por %{author} há %{age}" - 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: Escrita na pasta de activos dos módulos de extensão possível - warning_attachments_not_saved: "Não foi possível gravar %{count} ficheiro(s) ." - button_create_and_continue: Criar e continuar - text_custom_field_possible_values_info: 'Uma linha para cada valor' - label_display: Mostrar - field_editable: Editável - setting_repository_log_display_limit: Número máximo de revisões exibido no relatório de ficheiro - setting_file_max_size_displayed: Tamanho máximo dos ficheiros de texto exibidos inline - field_watcher: Observador - setting_openid: Permitir início de sessão e registo com OpenID - field_identity_url: URL do OpenID - label_login_with_open_id_option: ou início de sessão com OpenID - field_content: Conteúdo - label_descending: Descendente - label_sort: Ordenar - label_ascending: Ascendente - label_date_from_to: De %{start} a %{end} - label_greater_or_equal: ">=" - label_less_or_equal: <= - text_wiki_page_destroy_question: Esta página tem %{descendants} página(s) subordinada(s) e descendente(s). O que deseja fazer? - text_wiki_page_reassign_children: Reatribuir páginas subordinadas a esta página principal - text_wiki_page_nullify_children: Manter páginas subordinadas como páginas raíz - text_wiki_page_destroy_children: Apagar as páginas subordinadas e todos os seus descendentes - setting_password_min_length: Tamanho mínimo de palavra-chave - field_group_by: Agrupar resultados por - mail_subject_wiki_content_updated: "A página Wiki '%{id}' foi actualizada" - label_wiki_content_added: Página Wiki adicionada - mail_subject_wiki_content_added: "A página Wiki '%{id}' foi adicionada" - mail_body_wiki_content_added: A página Wiki '%{id}' foi adicionada por %{author}. - label_wiki_content_updated: Página Wiki actualizada - mail_body_wiki_content_updated: A página Wiki '%{id}' foi actualizada por %{author}. - permission_add_project: Criar projecto - setting_new_project_user_role_id: Função atribuída a um utilizador não-administrador que cria um projecto - label_view_all_revisions: Ver todas as revisões - label_tag: Etiqueta - label_branch: Ramo - error_no_tracker_in_project: Este projecto não tem associado nenhum tipo de tarefas. Verifique as definições do projecto. - error_no_default_issue_status: Não está definido um estado padrão para as tarefas. Verifique a sua configuração (dirija-se a "Administração -> Estados da tarefa"). - label_group_plural: Grupos - label_group: Grupo - label_group_new: Novo grupo - label_time_entry_plural: Tempo registado - text_journal_changed: "%{label} alterado de %{old} para %{new}" - text_journal_set_to: "%{label} configurado como %{value}" - text_journal_deleted: "%{label} apagou (%{old})" - text_journal_added: "%{label} %{value} adicionado" - field_active: Activo - enumeration_system_activity: Actividade de sistema - permission_delete_issue_watchers: Apagar observadores - version_status_closed: fechado - version_status_locked: protegido - version_status_open: aberto - error_can_not_reopen_issue_on_closed_version: Não é possível voltar a abrir uma tarefa atribuída a uma versão fechada - label_user_anonymous: Anónimo - button_move_and_follow: Mover e seguir - setting_default_projects_modules: Módulos activos por predefinição para novos projectos - setting_gravatar_default: Imagem Gravatar predefinida - field_sharing: Partilha - label_version_sharing_hierarchy: Com hierarquia do projecto - label_version_sharing_system: Com todos os projectos - label_version_sharing_descendants: Com os sub-projectos - label_version_sharing_tree: Com árvore do projecto - label_version_sharing_none: Não partilhado - error_can_not_archive_project: Não é possível arquivar este projecto - button_duplicate: Duplicar - button_copy_and_follow: Copiar e seguir - label_copy_source: Origem - setting_issue_done_ratio: Calcular a percentagem de progresso da tarefa - setting_issue_done_ratio_issue_status: Através do estado da tarefa - error_issue_done_ratios_not_updated: Percentagens de progresso da tarefa não foram actualizadas. - error_workflow_copy_target: Seleccione os tipos de tarefas e funções desejadas - setting_issue_done_ratio_issue_field: Através do campo da tarefa - label_copy_same_as_target: Mesmo que o alvo - label_copy_target: Alvo - notice_issue_done_ratios_updated: Percentagens de progresso da tarefa actualizadas. - error_workflow_copy_source: Seleccione um tipo de tarefa ou função de origem - label_update_issue_done_ratios: Actualizar percentagens de progresso da tarefa - setting_start_of_week: Iniciar calendários a - permission_view_issues: Ver tarefas - label_display_used_statuses_only: Só exibir estados empregues por este tipo de tarefa - label_revision_id: Revisão %{value} - label_api_access_key: Chave de acesso API - label_api_access_key_created_on: Chave de acesso API criada há %{value} - label_feeds_access_key: Chave de acesso RSS - notice_api_access_key_reseted: A sua chave de acesso API foi reinicializada. - setting_rest_api_enabled: Activar serviço Web REST - label_missing_api_access_key: Chave de acesso API em falta - label_missing_feeds_access_key: Chave de acesso RSS em falta - button_show: Mostrar - text_line_separated: Vários valores permitidos (uma linha para cada valor). - setting_mail_handler_body_delimiters: Truncar mensagens de correio electrónico após uma destas linhas - permission_add_subprojects: Criar sub-projectos - label_subproject_new: Novo sub-projecto - text_own_membership_delete_confirmation: |- - Está prestes a eliminar parcial ou totalmente as suas permissões. É possível que não possa editar o projecto após esta acção. - Tem a certeza de que deseja continuar? - label_close_versions: Fechar versões completas - label_board_sticky: Fixar mensagem - label_board_locked: Proteger - permission_export_wiki_pages: Exportar páginas Wiki - setting_cache_formatted_text: Colocar formatação do texto na memória cache - permission_manage_project_activities: Gerir actividades do projecto - error_unable_delete_issue_status: Não foi possível apagar o estado da tarefa - label_profile: Perfil - permission_manage_subtasks: Gerir sub-tarefas - field_parent_issue: Tarefa principal - label_subtask_plural: Sub-tarefa - label_project_copy_notifications: Enviar notificações por e-mail durante a cópia do projecto - error_can_not_delete_custom_field: Não foi possível apagar o campo personalizado - error_unable_to_connect: Não foi possível ligar (%{value}) - error_can_not_remove_role: Esta função está actualmente em uso e não pode ser apagada. - error_can_not_delete_tracker: Existem ainda tarefas nesta categoria. Não é possível apagar este tipo de tarefa. - field_principal: Principal - label_my_page_block: Bloco da minha página - notice_failed_to_save_members: "Erro ao guardar o(s) membro(s): %{errors}." - text_zoom_out: Ampliar - text_zoom_in: Reduzir - notice_unable_delete_time_entry: Não foi possível apagar a entrada de tempo registado. - label_overall_spent_time: Total de tempo registado - field_time_entries: Tempo registado - project_module_gantt: Gantt - project_module_calendar: Calendário - button_edit_associated_wikipage: "Editar página Wiki associada: %{page_title}" - text_are_you_sure_with_children: Apagar tarefa e todas as sub-tarefas? - field_text: Campo de texto - label_user_mail_option_only_owner: Apenas para tarefas das quais sou proprietário - setting_default_notification_option: Opção predefinida de notificação - label_user_mail_option_only_my_events: Apenas para tarefas que observo ou em que estou envolvido - label_user_mail_option_only_assigned: Apenas para tarefas que me foram atribuídas - label_user_mail_option_none: Sem eventos - field_member_of_group: Grupo do detentor de atribuição - field_assigned_to_role: Papel do detentor de atribuição - notice_not_authorized_archived_project: O projecto a que tentou aceder foi arquivado. - label_principal_search: "Procurar utilizador ou grupo:" - label_user_search: "Procurar utilizador:" - field_visible: Visível - setting_emails_header: Cabeçalho dos e-mails - setting_commit_logtime_activity_id: Actividade para tempo registado - text_time_logged_by_changeset: Aplicado no conjunto de alterações %{value}. - setting_commit_logtime_enabled: Activar registo de tempo - notice_gantt_chart_truncated: O gráfico foi truncado porque excede o número máximo de itens visível (%{máx.}) - setting_gantt_items_limit: Número máximo de itens exibidos no gráfico Gantt - field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text - text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. - label_my_queries: My custom queries - text_journal_changed_no_detail: "%{label} updated" - label_news_comment_added: Comment added to a news - button_expand_all: Expand all - button_collapse_all: Collapse all - label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee - label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author - label_bulk_edit_selected_time_entries: Bulk edit selected time entries - text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? - label_role_anonymous: Anonymous - label_role_non_member: Non member - label_issue_note_added: Note added - label_issue_status_updated: Status updated - label_issue_priority_updated: Priority updated - label_issues_visibility_own: Issues created by or assigned to the user - field_issues_visibility: Issues visibility - label_issues_visibility_all: All issues - permission_set_own_issues_private: Set own issues public or private - field_is_private: Private - permission_set_issues_private: Set issues public or private - label_issues_visibility_public: All non private issues - text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). - field_commit_logs_encoding: Encoding das mensagens de commit - field_scm_path_encoding: Path encoding - text_scm_path_encoding_note: "Default: UTF-8" - field_path_to_repository: Path to repository - field_root_directory: Root directory - field_cvs_module: Module - field_cvsroot: CVSROOT - text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) - text_scm_command: Command - text_scm_command_version: Version - label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. - notice_issue_successful_create: Issue %{id} created. - label_between: between - setting_issue_group_assignment: Allow issue assignment to groups - label_diff: diff - text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) - description_query_sort_criteria_direction: Sort direction - description_project_scope: Search scope - description_filter: Filter - description_user_mail_notification: Mail notification settings - description_date_from: Enter start date - description_message_content: Message content - description_available_columns: Available Columns - description_date_range_interval: Choose range by selecting start and end date - description_issue_category_reassign: Choose issue category - description_search: Searchfield - description_notes: Notes - description_date_range_list: Choose range from list - description_choose_project: Projects - description_date_to: Enter end date - description_query_sort_criteria_attribute: Sort attribute - description_wiki_subpages_reassign: Choose new parent page - description_selected_columns: Selected Columns - label_parent_revision: Parent - label_child_revision: Child - error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. - setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues - button_edit_section: Edit this section - setting_repositories_encodings: Attachments and repositories encodings - description_all_columns: All Columns - button_export: Export - label_export_options: "%{export_format} export options" - error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4f/4f68d76f852340754bcbaa389f8acdaba7e60be4.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/4f/4f68d76f852340754bcbaa389f8acdaba7e60be4.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,217 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class ProjectEnumerationsControllerTest < ActionController::TestCase + fixtures :projects, :trackers, :issue_statuses, :issues, + :enumerations, :users, :issue_categories, + :projects_trackers, + :roles, + :member_roles, + :members, + :enabled_modules, + :custom_fields, :custom_fields_projects, + :custom_fields_trackers, :custom_values, + :time_entries + + self.use_transactional_fixtures = false + + def setup + @request.session[:user_id] = nil + Setting.default_language = 'en' + end + + def test_update_to_override_system_activities + @request.session[:user_id] = 2 # manager + billable_field = TimeEntryActivityCustomField.find_by_name("Billable") + + put :update, :project_id => 1, :enumerations => { + "9"=> {"parent_id"=>"9", "custom_field_values"=>{"7" => "1"}, "active"=>"0"}, # Design, De-activate + "10"=> {"parent_id"=>"10", "custom_field_values"=>{"7"=>"0"}, "active"=>"1"}, # Development, Change custom value + "14"=>{"parent_id"=>"14", "custom_field_values"=>{"7"=>"1"}, "active"=>"1"}, # Inactive Activity, Activate with custom value + "11"=>{"parent_id"=>"11", "custom_field_values"=>{"7"=>"1"}, "active"=>"1"} # QA, no changes + } + + assert_response :redirect + assert_redirected_to '/projects/ecookbook/settings/activities' + + # Created project specific activities... + project = Project.find('ecookbook') + + # ... Design + design = project.time_entry_activities.find_by_name("Design") + assert design, "Project activity not found" + + assert_equal 9, design.parent_id # Relate to the system activity + assert_not_equal design.parent.id, design.id # Different records + assert_equal design.parent.name, design.name # Same name + assert !design.active? + + # ... Development + development = project.time_entry_activities.find_by_name("Development") + assert development, "Project activity not found" + + assert_equal 10, development.parent_id # Relate to the system activity + assert_not_equal development.parent.id, development.id # Different records + assert_equal development.parent.name, development.name # Same name + assert development.active? + assert_equal "0", development.custom_value_for(billable_field).value + + # ... Inactive Activity + previously_inactive = project.time_entry_activities.find_by_name("Inactive Activity") + assert previously_inactive, "Project activity not found" + + assert_equal 14, previously_inactive.parent_id # Relate to the system activity + assert_not_equal previously_inactive.parent.id, previously_inactive.id # Different records + assert_equal previously_inactive.parent.name, previously_inactive.name # Same name + assert previously_inactive.active? + assert_equal "1", previously_inactive.custom_value_for(billable_field).value + + # ... QA + assert_equal nil, project.time_entry_activities.find_by_name("QA"), "Custom QA activity created when it wasn't modified" + end + + def test_update_will_update_project_specific_activities + @request.session[:user_id] = 2 # manager + + project_activity = TimeEntryActivity.new({ + :name => 'Project Specific', + :parent => TimeEntryActivity.first, + :project => Project.find(1), + :active => true + }) + assert project_activity.save + project_activity_two = TimeEntryActivity.new({ + :name => 'Project Specific Two', + :parent => TimeEntryActivity.last, + :project => Project.find(1), + :active => true + }) + assert project_activity_two.save + + + put :update, :project_id => 1, :enumerations => { + project_activity.id => {"custom_field_values"=>{"7" => "1"}, "active"=>"0"}, # De-activate + project_activity_two.id => {"custom_field_values"=>{"7" => "1"}, "active"=>"0"} # De-activate + } + + assert_response :redirect + assert_redirected_to '/projects/ecookbook/settings/activities' + + # Created project specific activities... + project = Project.find('ecookbook') + assert_equal 2, project.time_entry_activities.count + + activity_one = project.time_entry_activities.find_by_name(project_activity.name) + assert activity_one, "Project activity not found" + assert_equal project_activity.id, activity_one.id + assert !activity_one.active? + + activity_two = project.time_entry_activities.find_by_name(project_activity_two.name) + assert activity_two, "Project activity not found" + assert_equal project_activity_two.id, activity_two.id + assert !activity_two.active? + end + + def test_update_when_creating_new_activities_will_convert_existing_data + assert_equal 3, TimeEntry.find_all_by_activity_id_and_project_id(9, 1).size + + @request.session[:user_id] = 2 # manager + put :update, :project_id => 1, :enumerations => { + "9"=> {"parent_id"=>"9", "custom_field_values"=>{"7" => "1"}, "active"=>"0"} # Design, De-activate + } + assert_response :redirect + + # No more TimeEntries using the system activity + assert_equal 0, TimeEntry.find_all_by_activity_id_and_project_id(9, 1).size, "Time Entries still assigned to system activities" + # All TimeEntries using project activity + project_specific_activity = TimeEntryActivity.find_by_parent_id_and_project_id(9, 1) + assert_equal 3, TimeEntry.find_all_by_activity_id_and_project_id(project_specific_activity.id, 1).size, "No Time Entries assigned to the project activity" + end + + def test_update_when_creating_new_activities_will_not_convert_existing_data_if_an_exception_is_raised + # TODO: Need to cause an exception on create but these tests + # aren't setup for mocking. Just create a record now so the + # second one is a dupicate + parent = TimeEntryActivity.find(9) + TimeEntryActivity.create!({:name => parent.name, :project_id => 1, :position => parent.position, :active => true}) + TimeEntry.create!({:project_id => 1, :hours => 1.0, :user => User.find(1), :issue_id => 3, :activity_id => 10, :spent_on => '2009-01-01'}) + + assert_equal 3, TimeEntry.find_all_by_activity_id_and_project_id(9, 1).size + assert_equal 1, TimeEntry.find_all_by_activity_id_and_project_id(10, 1).size + + @request.session[:user_id] = 2 # manager + put :update, :project_id => 1, :enumerations => { + "9"=> {"parent_id"=>"9", "custom_field_values"=>{"7" => "1"}, "active"=>"0"}, # Design + "10"=> {"parent_id"=>"10", "custom_field_values"=>{"7"=>"0"}, "active"=>"1"} # Development, Change custom value + } + assert_response :redirect + + # TimeEntries shouldn't have been reassigned on the failed record + assert_equal 3, TimeEntry.find_all_by_activity_id_and_project_id(9, 1).size, "Time Entries are not assigned to system activities" + # TimeEntries shouldn't have been reassigned on the saved record either + assert_equal 1, TimeEntry.find_all_by_activity_id_and_project_id(10, 1).size, "Time Entries are not assigned to system activities" + end + + def test_destroy + @request.session[:user_id] = 2 # manager + project_activity = TimeEntryActivity.new({ + :name => 'Project Specific', + :parent => TimeEntryActivity.first, + :project => Project.find(1), + :active => true + }) + assert project_activity.save + project_activity_two = TimeEntryActivity.new({ + :name => 'Project Specific Two', + :parent => TimeEntryActivity.last, + :project => Project.find(1), + :active => true + }) + assert project_activity_two.save + + delete :destroy, :project_id => 1 + assert_response :redirect + assert_redirected_to '/projects/ecookbook/settings/activities' + + assert_nil TimeEntryActivity.find_by_id(project_activity.id) + assert_nil TimeEntryActivity.find_by_id(project_activity_two.id) + end + + def test_destroy_should_reassign_time_entries_back_to_the_system_activity + @request.session[:user_id] = 2 # manager + project_activity = TimeEntryActivity.new({ + :name => 'Project Specific Design', + :parent => TimeEntryActivity.find(9), + :project => Project.find(1), + :active => true + }) + assert project_activity.save + assert TimeEntry.update_all("activity_id = '#{project_activity.id}'", ["project_id = ? AND activity_id = ?", 1, 9]) + assert_equal 3, TimeEntry.find_all_by_activity_id_and_project_id(project_activity.id, 1).size + + delete :destroy, :project_id => 1 + assert_response :redirect + assert_redirected_to '/projects/ecookbook/settings/activities' + + assert_nil TimeEntryActivity.find_by_id(project_activity.id) + assert_equal 0, TimeEntry.find_all_by_activity_id_and_project_id(project_activity.id, 1).size, "TimeEntries still assigned to project specific activity" + assert_equal 3, TimeEntry.find_all_by_activity_id_and_project_id(9, 1).size, "TimeEntries still assigned to project specific activity" + end + +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4f/4f7c43bee38a0086927663fbc7e435338678ebfe.svn-base --- a/.svn/pristine/4f/4f7c43bee38a0086927663fbc7e435338678ebfe.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -

<%=l(:label_version)%>

- -<% labelled_tabular_form_for @version do |f| %> -<%= render :partial => 'form', :locals => { :f => f } %> -<%= submit_tag l(:button_save) %> -<% end %> - diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4f/4f91e1e0bb038b11d88f8da5ac2ec5d27ca2031c.svn-base --- a/.svn/pristine/4f/4f91e1e0bb038b11d88f8da5ac2ec5d27ca2031c.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,23 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class UserCustomField < CustomField - def type_name - :label_user_plural - end -end - diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4f/4f9b6989936a59570ee254faff0b99a606637086.svn-base --- a/.svn/pristine/4f/4f9b6989936a59570ee254faff0b99a606637086.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,15 +0,0 @@ -class InsertBuiltinRoles < ActiveRecord::Migration - def self.up - nonmember = Role.new(:name => 'Non member', :position => 0) - nonmember.builtin = Role::BUILTIN_NON_MEMBER - nonmember.save - - anonymous = Role.new(:name => 'Anonymous', :position => 0) - anonymous.builtin = Role::BUILTIN_ANONYMOUS - anonymous.save - end - - def self.down - Role.destroy_all 'builtin <> 0' - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4f/4fb5582b9423d32809fbaa513dda130c44491b13.svn-base --- a/.svn/pristine/4f/4fb5582b9423d32809fbaa513dda130c44491b13.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,34 +0,0 @@ -desc 'Generates a configuration file for cookie store sessions.' - -file 'config/initializers/session_store.rb' do - path = File.join(Rails.root, 'config', 'initializers', 'session_store.rb') - secret = ActiveSupport::SecureRandom.hex(40) - File.open(path, 'w') do |f| - f.write <<"EOF" -# This file was generated by 'rake config/initializers/session_store.rb', -# and should not be made visible to public. -# If you have a load-balancing Redmine cluster, you will need to use the -# same version of this file on each machine. And be sure to restart your -# server when you modify this file. - -# Your secret key for verifying cookie session data integrity. If you -# change this key, all old sessions will become invalid! Make sure the -# secret is at least 30 characters and all random, no regular words or -# you'll be exposed to dictionary attacks. -ActionController::Base.session = { - :key => '_redmine_session', - # - # Uncomment and edit the :session_path below if are hosting your Redmine - # at a suburi and don't want the top level path to access the cookies - # - # See: http://www.redmine.org/issues/3968 - # - # :session_path => '/url_path_to/your/redmine/', - :secret => '#{secret}' -} -EOF - end -end - -desc 'Generates a configuration file for cookie store sessions.' -task :generate_session_store => ['config/initializers/session_store.rb'] diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4f/4fb8c835126f8a63271f8bd86689461f1834a855.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/4f/4fb8c835126f8a63271f8bd86689461f1834a855.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,202 @@ +--- +roles_001: + name: Manager + id: 1 + builtin: 0 + issues_visibility: all + permissions: | + --- + - :add_project + - :edit_project + - :close_project + - :select_project_modules + - :manage_members + - :manage_versions + - :manage_categories + - :view_issues + - :add_issues + - :edit_issues + - :manage_issue_relations + - :manage_subtasks + - :add_issue_notes + - :move_issues + - :delete_issues + - :view_issue_watchers + - :add_issue_watchers + - :set_issues_private + - :set_notes_private + - :view_private_notes + - :delete_issue_watchers + - :manage_public_queries + - :save_queries + - :view_gantt + - :view_calendar + - :log_time + - :view_time_entries + - :edit_time_entries + - :delete_time_entries + - :manage_news + - :comment_news + - :view_documents + - :add_documents + - :edit_documents + - :delete_documents + - :view_wiki_pages + - :export_wiki_pages + - :view_wiki_edits + - :edit_wiki_pages + - :delete_wiki_pages_attachments + - :protect_wiki_pages + - :delete_wiki_pages + - :rename_wiki_pages + - :add_messages + - :edit_messages + - :delete_messages + - :manage_boards + - :view_files + - :manage_files + - :browse_repository + - :manage_repository + - :view_changesets + - :manage_related_issues + - :manage_project_activities + + position: 1 +roles_002: + name: Developer + id: 2 + builtin: 0 + issues_visibility: default + permissions: | + --- + - :edit_project + - :manage_members + - :manage_versions + - :manage_categories + - :view_issues + - :add_issues + - :edit_issues + - :manage_issue_relations + - :manage_subtasks + - :add_issue_notes + - :move_issues + - :delete_issues + - :view_issue_watchers + - :save_queries + - :view_gantt + - :view_calendar + - :log_time + - :view_time_entries + - :edit_own_time_entries + - :manage_news + - :comment_news + - :view_documents + - :add_documents + - :edit_documents + - :delete_documents + - :view_wiki_pages + - :view_wiki_edits + - :edit_wiki_pages + - :protect_wiki_pages + - :delete_wiki_pages + - :add_messages + - :edit_own_messages + - :delete_own_messages + - :manage_boards + - :view_files + - :manage_files + - :browse_repository + - :view_changesets + + position: 2 +roles_003: + name: Reporter + id: 3 + builtin: 0 + issues_visibility: default + permissions: | + --- + - :edit_project + - :manage_members + - :manage_versions + - :manage_categories + - :view_issues + - :add_issues + - :edit_issues + - :manage_issue_relations + - :add_issue_notes + - :move_issues + - :view_issue_watchers + - :save_queries + - :view_gantt + - :view_calendar + - :log_time + - :view_time_entries + - :manage_news + - :comment_news + - :view_documents + - :add_documents + - :edit_documents + - :delete_documents + - :view_wiki_pages + - :view_wiki_edits + - :edit_wiki_pages + - :delete_wiki_pages + - :add_messages + - :manage_boards + - :view_files + - :manage_files + - :browse_repository + - :view_changesets + + position: 3 +roles_004: + name: Non member + id: 4 + builtin: 1 + issues_visibility: default + permissions: | + --- + - :view_issues + - :add_issues + - :edit_issues + - :manage_issue_relations + - :add_issue_notes + - :save_queries + - :view_gantt + - :view_calendar + - :log_time + - :view_time_entries + - :comment_news + - :view_documents + - :view_wiki_pages + - :view_wiki_edits + - :edit_wiki_pages + - :add_messages + - :view_files + - :manage_files + - :browse_repository + - :view_changesets + + position: 4 +roles_005: + name: Anonymous + id: 5 + builtin: 2 + issues_visibility: default + permissions: | + --- + - :view_issues + - :add_issue_notes + - :view_gantt + - :view_calendar + - :view_time_entries + - :view_documents + - :view_wiki_pages + - :view_wiki_edits + - :view_files + - :browse_repository + - :view_changesets + + position: 5 + diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4f/4fc2ff472415472e5bc5a895e07b22fbca85faf1.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/4f/4fc2ff472415472e5bc5a895e07b22fbca85faf1.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,40 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class MailHandlerController < ActionController::Base + before_filter :check_credential + + # Submits an incoming email to MailHandler + def index + options = params.dup + email = options.delete(:email) + if MailHandler.receive(email, options) + render :nothing => true, :status => :created + else + render :nothing => true, :status => :unprocessable_entity + end + end + + private + + def check_credential + User.current = nil + unless Setting.mail_handler_api_enabled? && params[:key].to_s == Setting.mail_handler_api_key + render :text => 'Access denied. Incoming emails WS is disabled or key is invalid.', :status => 403 + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/4f/4fc60931645e3d4832cac83cc1c9d2c25c5df098.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/4f/4fc60931645e3d4832cac83cc1c9d2c25c5df098.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,130 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class EnumerationTest < ActiveSupport::TestCase + fixtures :enumerations, :issues, :custom_fields, :custom_values + + def test_objects_count + # low priority + assert_equal 6, Enumeration.find(4).objects_count + # urgent + assert_equal 0, Enumeration.find(7).objects_count + end + + def test_in_use + # low priority + assert Enumeration.find(4).in_use? + # urgent + assert !Enumeration.find(7).in_use? + end + + def test_default + e = Enumeration.default + assert e.is_a?(Enumeration) + assert e.is_default? + assert e.active? + assert_equal 'Default Enumeration', e.name + end + + def test_default_non_active + e = Enumeration.find(12) + assert e.is_a?(Enumeration) + assert e.is_default? + assert e.active? + e.update_attributes(:active => false) + assert e.is_default? + assert !e.active? + end + + def test_create + e = Enumeration.new(:name => 'Not default', :is_default => false) + e.type = 'Enumeration' + assert e.save + assert_equal 'Default Enumeration', Enumeration.default.name + end + + def test_create_as_default + e = Enumeration.new(:name => 'Very urgent', :is_default => true) + e.type = 'Enumeration' + assert e.save + assert_equal e, Enumeration.default + end + + def test_update_default + e = Enumeration.default + e.update_attributes(:name => 'Changed', :is_default => true) + assert_equal e, Enumeration.default + end + + def test_update_default_to_non_default + e = Enumeration.default + e.update_attributes(:name => 'Changed', :is_default => false) + assert_nil Enumeration.default + end + + def test_change_default + e = Enumeration.find_by_name('Default Enumeration') + e.update_attributes(:name => 'Changed Enumeration', :is_default => true) + assert_equal e, Enumeration.default + end + + def test_destroy_with_reassign + Enumeration.find(4).destroy(Enumeration.find(6)) + assert_nil Issue.where(:priority_id => 4).first + assert_equal 6, Enumeration.find(6).objects_count + end + + def test_should_be_customizable + assert Enumeration.included_modules.include?(Redmine::Acts::Customizable::InstanceMethods) + end + + def test_should_belong_to_a_project + association = Enumeration.reflect_on_association(:project) + assert association, "No Project association found" + assert_equal :belongs_to, association.macro + end + + def test_should_act_as_tree + enumeration = Enumeration.find(4) + + assert enumeration.respond_to?(:parent) + assert enumeration.respond_to?(:children) + end + + def test_is_override + # Defaults to off + enumeration = Enumeration.find(4) + assert !enumeration.is_override? + + # Setup as an override + enumeration.parent = Enumeration.find(5) + assert enumeration.is_override? + end + + def test_get_subclasses + classes = Enumeration.get_subclasses + assert_include IssuePriority, classes + assert_include DocumentCategory, classes + assert_include TimeEntryActivity, classes + + classes.each do |klass| + assert_equal Enumeration, klass.superclass + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/50/504a75e28140e0b003d557f020f846d8f93e9c79.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/50/504a75e28140e0b003d557f020f846d8f93e9c79.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,73 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class SettingsController < ApplicationController + layout 'admin' + menu_item :plugins, :only => :plugin + + helper :queries + + before_filter :require_admin + + def index + edit + render :action => 'edit' + end + + def edit + @notifiables = Redmine::Notifiable.all + if request.post? && params[:settings] && params[:settings].is_a?(Hash) + settings = (params[:settings] || {}).dup.symbolize_keys + settings.each do |name, value| + # remove blank values in array settings + value.delete_if {|v| v.blank? } if value.is_a?(Array) + Setting[name] = value + end + flash[:notice] = l(:notice_successful_update) + redirect_to settings_path(:tab => params[:tab]) + else + @options = {} + user_format = User::USER_FORMATS.collect{|key, value| [key, value[:setting_order]]}.sort{|a, b| a[1] <=> b[1]} + @options[:user_format] = user_format.collect{|f| [User.current.name(f[0]), f[0].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? + + Redmine::Themes.rescan + end + end + + def plugin + @plugin = Redmine::Plugin.find(params[:id]) + unless @plugin.configurable? + render_404 + return + end + + if request.post? + Setting.send "plugin_#{@plugin.id}=", params[:settings] + flash[:notice] = l(:notice_successful_update) + redirect_to plugin_settings_path(@plugin) + else + @partial = @plugin.settings[:partial] + @settings = Setting.send "plugin_#{@plugin.id}" + end + rescue Redmine::PluginNotFound + render_404 + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/50/50652fe8225e67d65adfd17ea2e35e3021fb4461.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/50/50652fe8225e67d65adfd17ea2e35e3021fb4461.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,102 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../../test_helper', __FILE__) + +class Redmine::SafeAttributesTest < ActiveSupport::TestCase + fixtures :users + + class Base + def attributes=(attrs) + attrs.each do |key, value| + send("#{key}=", value) + end + end + end + + class Person < Base + attr_accessor :firstname, :lastname, :login + include Redmine::SafeAttributes + safe_attributes :firstname, :lastname + safe_attributes :login, :if => lambda {|person, user| user.admin?} + end + + class Book < Base + attr_accessor :title + include Redmine::SafeAttributes + safe_attributes :title + end + + def test_safe_attribute_names + p = Person.new + user = User.anonymous + assert_equal ['firstname', 'lastname'], p.safe_attribute_names(user) + assert p.safe_attribute?('firstname', user) + assert !p.safe_attribute?('login', user) + + p = Person.new + user = User.find(1) + assert_equal ['firstname', 'lastname', 'login'], p.safe_attribute_names(user) + assert p.safe_attribute?('firstname', user) + assert p.safe_attribute?('login', user) + end + + def test_safe_attribute_names_without_user + p = Person.new + User.current = nil + assert_equal ['firstname', 'lastname'], p.safe_attribute_names + assert p.safe_attribute?('firstname') + assert !p.safe_attribute?('login') + + p = Person.new + User.current = User.find(1) + assert_equal ['firstname', 'lastname', 'login'], p.safe_attribute_names + assert p.safe_attribute?('firstname') + assert p.safe_attribute?('login') + end + + def test_set_safe_attributes + p = Person.new + p.send('safe_attributes=', {'firstname' => 'John', 'lastname' => 'Smith', 'login' => 'jsmith'}, User.anonymous) + assert_equal 'John', p.firstname + assert_equal 'Smith', p.lastname + assert_nil p.login + + p = Person.new + User.current = User.find(1) + p.send('safe_attributes=', {'firstname' => 'John', 'lastname' => 'Smith', 'login' => 'jsmith'}, User.find(1)) + assert_equal 'John', p.firstname + assert_equal 'Smith', p.lastname + assert_equal 'jsmith', p.login + end + + def test_set_safe_attributes_without_user + p = Person.new + User.current = nil + p.safe_attributes = {'firstname' => 'John', 'lastname' => 'Smith', 'login' => 'jsmith'} + assert_equal 'John', p.firstname + assert_equal 'Smith', p.lastname + assert_nil p.login + + p = Person.new + User.current = User.find(1) + p.safe_attributes = {'firstname' => 'John', 'lastname' => 'Smith', 'login' => 'jsmith'} + assert_equal 'John', p.firstname + assert_equal 'Smith', p.lastname + assert_equal 'jsmith', p.login + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/50/509ee267bffe97965ae0a631af5009f5ad9e1775.svn-base --- a/.svn/pristine/50/509ee267bffe97965ae0a631af5009f5ad9e1775.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ -
-<% form_tag({:action => 'revision', :id => @project}) do %> -<%= l(:label_revision) %>: <%= text_field_tag 'rev', @rev, :size => 8 %> -<%= submit_tag 'OK' %> -<% end %> -
- -

<%= l(:label_revision_plural) %>

- -<%= render :partial => 'revisions', - :locals => {:project => @project, - :path => '', - :revisions => @changesets, - :entry => nil } %> - -

<%= pagination_links_full @changeset_pages,@changeset_count %>

- -<% content_for :header_tags do %> -<%= stylesheet_link_tag "scm" %> -<%= auto_discovery_link_tag( - :atom, - params.merge( - {:format => 'atom', :page => nil, :key => User.current.rss_key})) %> -<% end %> - -<% other_formats_links do |f| %> - <%= f.link_to 'Atom', :url => {:key => User.current.rss_key} %> -<% end %> - -<% html_title(l(:label_revision_plural)) -%> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/50/50da835a6f15cfea65885171e3501d71716f5bea.svn-base --- a/.svn/pristine/50/50da835a6f15cfea65885171e3501d71716f5bea.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) - -class EnabledModuleTest < ActiveSupport::TestCase - fixtures :projects, :wikis - - def test_enabling_wiki_should_create_a_wiki - CustomField.delete_all - project = Project.create!(:name => 'Project with wiki', :identifier => 'wikiproject') - assert_nil project.wiki - project.enabled_module_names = ['wiki'] - project.reload - assert_not_nil project.wiki - assert_equal 'Wiki', project.wiki.start_page - end - - def test_reenabling_wiki_should_not_create_another_wiki - project = Project.find(1) - assert_not_nil project.wiki - project.enabled_module_names = [] - project.reload - assert_no_difference 'Wiki.count' do - project.enabled_module_names = ['wiki'] - end - assert_not_nil project.wiki - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/50/50fe0fccbefe1bf6410e617ed3aed18dfc4dd61e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/50/50fe0fccbefe1bf6410e617ed3aed18dfc4dd61e.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,178 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class JournalTest < ActiveSupport::TestCase + fixtures :projects, :issues, :issue_statuses, :journals, :journal_details, + :users, :members, :member_roles, :roles, :enabled_modules, + :projects_trackers, :trackers + + def setup + @journal = Journal.find 1 + end + + def test_journalized_is_an_issue + issue = @journal.issue + assert_kind_of Issue, issue + assert_equal 1, issue.id + end + + def test_new_status + status = @journal.new_status + assert_not_nil status + assert_kind_of IssueStatus, status + assert_equal 2, status.id + end + + def test_create_should_send_email_notification + ActionMailer::Base.deliveries.clear + issue = Issue.first + user = User.first + journal = issue.init_journal(user, issue) + + assert journal.save + assert_equal 1, ActionMailer::Base.deliveries.size + end + + def test_should_not_save_journal_with_blank_notes_and_no_details + journal = Journal.new(:journalized => Issue.first, :user => User.first) + + assert_no_difference 'Journal.count' do + assert_equal false, journal.save + end + end + + def test_create_should_not_split_non_private_notes + assert_difference 'Journal.count' do + assert_no_difference 'JournalDetail.count' do + journal = Journal.generate!(:notes => 'Notes') + end + end + + assert_difference 'Journal.count' do + assert_difference 'JournalDetail.count' do + journal = Journal.generate!(:notes => 'Notes', :details => [JournalDetail.new]) + end + end + + assert_difference 'Journal.count' do + assert_difference 'JournalDetail.count' do + journal = Journal.generate!(:notes => '', :details => [JournalDetail.new]) + end + end + end + + def test_create_should_split_private_notes + assert_difference 'Journal.count' do + assert_no_difference 'JournalDetail.count' do + journal = Journal.generate!(:notes => 'Notes', :private_notes => true) + journal.reload + assert_equal true, journal.private_notes + assert_equal 'Notes', journal.notes + end + end + + assert_difference 'Journal.count', 2 do + assert_difference 'JournalDetail.count' do + journal = Journal.generate!(:notes => 'Notes', :private_notes => true, :details => [JournalDetail.new]) + journal.reload + assert_equal true, journal.private_notes + assert_equal 'Notes', journal.notes + assert_equal 0, journal.details.size + + journal_with_changes = Journal.order('id DESC').offset(1).first + assert_equal false, journal_with_changes.private_notes + assert_nil journal_with_changes.notes + assert_equal 1, journal_with_changes.details.size + assert_equal journal.created_on, journal_with_changes.created_on + end + end + + assert_difference 'Journal.count' do + assert_difference 'JournalDetail.count' do + journal = Journal.generate!(:notes => '', :private_notes => true, :details => [JournalDetail.new]) + journal.reload + assert_equal false, journal.private_notes + assert_equal '', journal.notes + assert_equal 1, journal.details.size + end + end + end + + def test_visible_scope_for_anonymous + # Anonymous user should see issues of public projects only + journals = Journal.visible(User.anonymous).all + assert journals.any? + assert_nil journals.detect {|journal| !journal.issue.project.is_public?} + # Anonymous user should not see issues without permission + Role.anonymous.remove_permission!(:view_issues) + journals = Journal.visible(User.anonymous).all + assert journals.empty? + end + + def test_visible_scope_for_user + user = User.find(9) + assert user.projects.empty? + # Non member user should see issues of public projects only + journals = Journal.visible(user).all + assert journals.any? + assert_nil journals.detect {|journal| !journal.issue.project.is_public?} + # Non member user should not see issues without permission + Role.non_member.remove_permission!(:view_issues) + user.reload + journals = Journal.visible(user).all + assert journals.empty? + # User should see issues of projects for which he has view_issues permissions only + Member.create!(:principal => user, :project_id => 1, :role_ids => [1]) + user.reload + journals = Journal.visible(user).all + assert journals.any? + assert_nil journals.detect {|journal| journal.issue.project_id != 1} + end + + def test_visible_scope_for_admin + user = User.find(1) + user.members.each(&:destroy) + assert user.projects.empty? + journals = Journal.visible(user).all + assert journals.any? + # Admin should see issues on private projects that he does not belong to + assert journals.detect {|journal| !journal.issue.project.is_public?} + end + + def test_details_should_normalize_dates + j = JournalDetail.create!(:old_value => Date.parse('2012-11-03'), :value => Date.parse('2013-01-02')) + j.reload + assert_equal '2012-11-03', j.old_value + assert_equal '2013-01-02', j.value + end + + def test_details_should_normalize_true_values + j = JournalDetail.create!(:old_value => true, :value => true) + j.reload + assert_equal '1', j.old_value + assert_equal '1', j.value + end + + def test_details_should_normalize_false_values + j = JournalDetail.create!(:old_value => false, :value => false) + j.reload + assert_equal '0', j.old_value + assert_equal '0', j.value + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/51/511d1a9b54cc17e8e6be5b0543b0c534e10de28e.svn-base --- a/.svn/pristine/51/511d1a9b54cc17e8e6be5b0543b0c534e10de28e.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -require File.join(File.dirname(__FILE__), *%w[.. .. test_helper]) - -class OverrideTest < ActiveSupport::TestCase - def test_overrides_from_the_application_should_work - assert true, "overriding plugin tests from the application should work" - end -end \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/51/514f8aeb54e3381fa2badbe487c0d7fd6bad2fd0.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/51/514f8aeb54e3381fa2badbe487c0d7fd6bad2fd0.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,145 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class IssueCategoriesControllerTest < ActionController::TestCase + fixtures :projects, :users, :members, :member_roles, :roles, :enabled_modules, :issue_categories, + :issues + + def setup + User.current = nil + @request.session[:user_id] = 2 + end + + def test_new + @request.session[:user_id] = 2 # manager + get :new, :project_id => '1' + assert_response :success + assert_template 'new' + assert_select 'input[name=?]', 'issue_category[name]' + end + + def test_new_from_issue_form + @request.session[:user_id] = 2 # manager + xhr :get, :new, :project_id => '1' + + assert_response :success + assert_template 'new' + assert_equal 'text/javascript', response.content_type + end + + def test_create + @request.session[:user_id] = 2 # manager + assert_difference 'IssueCategory.count' do + post :create, :project_id => '1', :issue_category => {:name => 'New category'} + end + assert_redirected_to '/projects/ecookbook/settings/categories' + category = IssueCategory.find_by_name('New category') + assert_not_nil category + assert_equal 1, category.project_id + end + + def test_create_failure + @request.session[:user_id] = 2 + post :create, :project_id => '1', :issue_category => {:name => ''} + assert_response :success + assert_template 'new' + end + + def test_create_from_issue_form + @request.session[:user_id] = 2 # manager + assert_difference 'IssueCategory.count' do + xhr :post, :create, :project_id => '1', :issue_category => {:name => 'New category'} + end + category = IssueCategory.first(:order => 'id DESC') + assert_equal 'New category', category.name + + assert_response :success + assert_template 'create' + assert_equal 'text/javascript', response.content_type + end + + def test_create_from_issue_form_with_failure + @request.session[:user_id] = 2 # manager + assert_no_difference 'IssueCategory.count' do + xhr :post, :create, :project_id => '1', :issue_category => {:name => ''} + end + + assert_response :success + assert_template 'new' + assert_equal 'text/javascript', response.content_type + end + + def test_edit + @request.session[:user_id] = 2 + get :edit, :id => 2 + assert_response :success + assert_template 'edit' + assert_select 'input[name=?][value=?]', 'issue_category[name]', 'Recipes' + end + + def test_update + assert_no_difference 'IssueCategory.count' do + put :update, :id => 2, :issue_category => { :name => 'Testing' } + end + assert_redirected_to '/projects/ecookbook/settings/categories' + assert_equal 'Testing', IssueCategory.find(2).name + end + + def test_update_failure + put :update, :id => 2, :issue_category => { :name => '' } + assert_response :success + assert_template 'edit' + end + + def test_update_not_found + put :update, :id => 97, :issue_category => { :name => 'Testing' } + assert_response 404 + end + + def test_destroy_category_not_in_use + delete :destroy, :id => 2 + assert_redirected_to '/projects/ecookbook/settings/categories' + assert_nil IssueCategory.find_by_id(2) + end + + def test_destroy_category_in_use + delete :destroy, :id => 1 + assert_response :success + assert_template 'destroy' + assert_not_nil IssueCategory.find_by_id(1) + end + + def test_destroy_category_in_use_with_reassignment + issue = Issue.where(:category_id => 1).first + delete :destroy, :id => 1, :todo => 'reassign', :reassign_to_id => 2 + assert_redirected_to '/projects/ecookbook/settings/categories' + assert_nil IssueCategory.find_by_id(1) + # check that the issue was reassign + assert_equal 2, issue.reload.category_id + end + + def test_destroy_category_in_use_without_reassignment + issue = Issue.where(:category_id => 1).first + delete :destroy, :id => 1, :todo => 'nullify' + assert_redirected_to '/projects/ecookbook/settings/categories' + assert_nil IssueCategory.find_by_id(1) + # check that the issue category was nullified + assert_nil issue.reload.category_id + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/51/51931955320a8df763ed8cb90bc828e4398d3b92.svn-base --- a/.svn/pristine/51/51931955320a8df763ed8cb90bc828e4398d3b92.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ -COPYING -ChangeLog -History.txt -Manifest.txt -README -Rakefile -TODO -lib/tree.rb -lib/tree/binarytree.rb -setup.rb -test/test_binarytree.rb -test/test_tree.rb diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/51/5194b9835666462a25b29cefc4dc53bf3bde050c.svn-base --- a/.svn/pristine/51/5194b9835666462a25b29cefc4dc53bf3bde050c.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1008 +0,0 @@ -mk: - # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl) - direction: ltr - date: - formats: - # Use the strftime parameters for formats. - # When no format has been given, it uses default. - # You can provide other formats here if you like! - default: "%d/%m/%Y" - short: "%d %b" - long: "%d %B, %Y" - - day_names: [недела, понеделник, вторник, Ñреда, четврток, петок, Ñабота] - abbr_day_names: [нед, пон, вто, Ñре, чет, пет, Ñаб] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, јануари, февруари, март, април, мај, јуни, јули, авгуÑÑ‚, Ñептември, октомври, ноември, декември] - abbr_month_names: [~, јан, фев, мар, апр, мај, јун, јул, авг, Ñеп, окт, ное, дек] - # Used in date_select and datime_select. - order: - - :day - - :month - - :year - - time: - formats: - default: "%d/%m/%Y %H:%M" - time: "%H:%M" - short: "%d %b %H:%M" - long: "%d %B, %Y %H:%M" - am: "предпладне" - pm: "попладне" - - datetime: - distance_in_words: - half_a_minute: "пола минута" - less_than_x_seconds: - one: "помалку од 1 Ñекунда" - other: "помалку од %{count} Ñекунди" - x_seconds: - one: "1 Ñекунда" - other: "%{count} Ñекунди" - less_than_x_minutes: - one: "помалку од 1 минута" - other: "помалку од %{count} минути" - x_minutes: - one: "1 минута" - other: "%{count} минути" - about_x_hours: - one: "околу 1 чаÑ" - other: "околу %{count} чаÑа" - x_days: - one: "1 ден" - other: "%{count} дена" - about_x_months: - one: "околу 1 меÑец" - other: "околу %{count} меÑеци" - x_months: - one: "1 меÑец" - other: "%{count} меÑеци" - about_x_years: - one: "околу 1 година" - other: "околу %{count} години" - over_x_years: - one: "преку 1 година" - other: "преку %{count} години" - almost_x_years: - one: "Ñкоро 1 година" - other: "Ñкоро %{count} години" - - number: - # Default format for numbers - format: - separator: "." - delimiter: "" - precision: 3 - human: - format: - delimiter: "" - precision: 1 - storage_units: - format: "%n %u" - units: - byte: - one: "Byte" - other: "Bytes" - kb: "KB" - mb: "MB" - gb: "GB" - tb: "TB" - - -# Used in array.to_sentence. - support: - array: - sentence_connector: "и" - skip_last_comma: false - - activerecord: - errors: - template: - header: - one: "1 error prohibited this %{model} from being saved" - other: "%{count} errors prohibited this %{model} from being saved" - messages: - inclusion: "не е вклучено во лиÑтата" - exclusion: "е резервирано" - invalid: "е невалидно" - confirmation: "не Ñе Ñовпаѓа Ñо потврдата" - accepted: "мора да е прифатено" - empty: "неможе да е празно" - blank: "неможе да е празно" - too_long: "е предолго (макÑ. %{count} знаци)" - too_short: "е прекратко (мин. %{count} знаци)" - wrong_length: "е погрешна должина (треба да е %{count} знаци)" - taken: "е веќе зафатено" - not_a_number: "не е број" - not_a_date: "не е валидна дата" - greater_than: "мора да е поголемо од %{count}" - greater_than_or_equal_to: "мора да е поголемо или еднакво на %{count}" - equal_to: "мора да е еднакво на %{count}" - less_than: "мора да е помало од %{count}" - less_than_or_equal_to: "мора да е помало или еднакво на %{count}" - odd: "мора да е непарно" - even: "мора да е парно" - greater_than_start_date: "мора да е поголема од почетната дата" - not_same_project: "не припаѓа на иÑтиот проект" - circular_dependency: "Оваа врÑка ќе креира кружна завиÑноÑÑ‚" - cant_link_an_issue_with_a_descendant: "Задача неможе да Ñе поврзе Ñо една од нејзините подзадачи" - - actionview_instancetag_blank_option: Изберете - - general_text_No: 'Ðе' - general_text_Yes: 'Да' - general_text_no: 'не' - general_text_yes: 'да' - general_lang_name: 'Macedonian (МакедонÑки)' - general_csv_separator: ',' - general_csv_decimal_separator: '.' - general_csv_encoding: UTF-8 - general_pdf_encoding: UTF-8 - 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: This account uses an external authentication source. Impossible to change the password. - notice_account_lost_email_sent: An email with instructions to choose a new password has been sent to you. - notice_account_activated: Your account has been activated. You can now log in. - notice_successful_create: УÑпешно креирање. - notice_successful_update: УÑпешно ажурирање. - notice_successful_delete: УÑпешно бришење. - notice_successful_connection: УÑпешна конекција. - notice_file_not_found: The page you were trying to access doesn't exist or has been removed. - notice_locking_conflict: Data has been updated by another user. - notice_not_authorized: You are not authorized to access this page. - notice_email_sent: "Е-порака е пратена на %{value}" - notice_email_error: "Се Ñлучи грешка при праќање на е-пораката (%{value})" - notice_feeds_access_key_reseted: Вашиот RSS клуч за приÑтап е reset. - notice_api_access_key_reseted: Вашиот API клуч за приÑтап е reset. - notice_failed_to_save_issues: "Failed to save %{count} issue(s) on %{total} selected: %{ids}." - notice_failed_to_save_members: "Failed to save member(s): %{errors}." - notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit." - notice_account_pending: "Your account was created and is now pending administrator approval." - notice_default_data_loaded: Default configuration successfully loaded. - notice_unable_delete_version: Unable to delete version. - notice_unable_delete_time_entry: Unable to delete time log entry. - notice_issue_done_ratios_updated: Issue done ratios updated. - - error_can_t_load_default_data: "Default configuration could not be loaded: %{value}" - 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: %{value}" - 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' - error_no_tracker_in_project: 'No tracker is associated to this project. Please check the Project settings.' - error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").' - error_can_not_delete_custom_field: Unable to delete custom field - error_can_not_delete_tracker: "This tracker contains issues and can't be deleted." - error_can_not_remove_role: "This role is in use and can not be deleted." - error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version can not be reopened' - error_can_not_archive_project: This project can not be archived - error_issue_done_ratios_not_updated: "Issue done ratios not updated." - error_workflow_copy_source: 'Please select a source tracker or role' - error_workflow_copy_target: 'Please select target tracker(s) and role(s)' - error_unable_delete_issue_status: 'Unable to delete issue status' - error_unable_to_connect: "Unable to connect (%{value})" - warning_attachments_not_saved: "%{count} file(s) could not be saved." - - mail_subject_lost_password: "Вашата %{value} лозинка" - mail_body_lost_password: 'To change your password, click on the following link:' - mail_subject_register: "Your %{value} account activation" - mail_body_register: 'To activate your account, click on the following link:' - mail_body_account_information_external: "You can use your %{value} account to log in." - mail_body_account_information: Your account information - mail_subject_account_activation_request: "%{value} account activation request" - mail_body_account_activation_request: "Ðов кориÑник (%{value}) е региÑтриран. The account is pending your approval:" - mail_subject_reminder: "%{count} issue(s) due in the next %{days} days" - mail_body_reminder: "%{count} issue(s) that are assigned to you are due in the next %{days} days:" - mail_subject_wiki_content_added: "'%{id}' wiki page has been added" - mail_body_wiki_content_added: "The '%{id}' wiki page has been added by %{author}." - mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated" - mail_body_wiki_content_updated: "The '%{id}' wiki page has been updated by %{author}." - - gui_validation_error: 1 грешка - gui_validation_error_plural: "%{count} грешки" - - 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: Default value - field_tracker: Tracker - field_subject: ÐаÑлов - field_due_date: Краен рок - field_assigned_to: Доделена на - field_priority: Приоритет - field_fixed_version: Target version - field_user: КориÑник - field_principal: Principal - field_role: Улога - field_homepage: Веб Ñтрана - field_is_public: Јавен - field_parent: Подпроект на - field_is_in_roadmap: Issues displayed in roadmap - field_login: КориÑник - field_mail_notification: ИзвеÑтувања по e-пошта - 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) креирање на кориÑници - field_start_date: Почеток - field_done_ratio: "% Завршено" - field_auth_source: Режим на автентикација - field_hide_mail: Криј ја мојата адреÑа на е-пошта - field_comments: Коментар - field_url: URL - field_start_page: Почетна Ñтрана - field_subproject: Подпроект - field_hours: ЧаÑови - field_activity: ÐктивноÑÑ‚ - field_spent_on: Дата - field_identifier: Идентификатор - field_is_filter: КориÑти како филтер - field_issue_to: Поврзана задача - field_delay: Доцнење - field_assignable: Ðа оваа улога може да Ñе доделуваат задачи - field_redirect_existing_links: ПренаÑочи ги поÑтоечките врÑки - field_estimated_hours: Проценето време - field_column_names: Колони - field_time_entries: Бележи време - field_time_zone: ВременÑка зона - field_searchable: Може да Ñе пребарува - field_default_value: Default value - field_comments_sorting: Прикажувај коментари - field_parent_title: Parent page - field_editable: Може да Ñе уредува - field_watcher: Watcher - field_identity_url: OpenID URL - field_content: Содржина - field_group_by: Групирај ги резултатите Ñпоред - field_sharing: Споделување - field_parent_issue: Parent task - - setting_app_title: ÐаÑлов на апликацијата - setting_app_subtitle: ПоднаÑлов на апликацијата - setting_welcome_text: ТекÑÑ‚ за добредојде - setting_default_language: Default јазик - setting_login_required: Задолжителна автентикација - setting_self_registration: Само-региÑтрација - setting_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_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: Дозволи релации на задачи меѓу проекти - setting_issue_list_default_columns: Default columns displayed on the issue list - setting_emails_footer: Emails footer - setting_protocol: Протокол - setting_per_page_options: Objects per page options - setting_user_format: Приказ на кориÑниците - setting_activity_days_default: Денови прикажана во активноÑта на проектот - setting_display_subprojects_issues: Прикажи ги задачите на подпроектите во главните проекти - setting_enabled_scm: Овозможи SCM - setting_mail_handler_body_delimiters: "Truncate emails after one of these lines" - 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_gravatar_default: Default Gravatar image - setting_diff_max_lines_displayed: Max number of diff lines displayed - setting_file_max_size_displayed: Max size of text files displayed inline - setting_repository_log_display_limit: Maximum number of revisions displayed on file log - setting_openid: Дозволи OpenID најава и региÑтрација - setting_password_min_length: Мин. должина на лозинка - setting_new_project_user_role_id: Улога доделена на неадминиÑтраторÑки кориÑник кој креира проект - setting_default_projects_modules: Default enabled modules for new projects - setting_issue_done_ratio: Calculate the issue done ratio with - setting_issue_done_ratio_issue_field: Use the issue field - setting_issue_done_ratio_issue_status: Use the issue status - setting_start_of_week: Start calendars on - setting_rest_api_enabled: Enable REST web service - setting_cache_formatted_text: Cache formatted text - - permission_add_project: Креирај проекти - permission_add_subprojects: Креирај подпроекти - permission_edit_project: Уреди проект - permission_select_project_modules: Изберете модули за проект - permission_manage_members: Manage members - permission_manage_project_activities: Manage project activities - permission_manage_versions: Manage versions - permission_manage_categories: Manage issue categories - permission_view_issues: Прегледај задачи - 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: View calendar - permission_view_issue_watchers: View watchers list - permission_add_issue_watchers: Add watchers - permission_delete_issue_watchers: Delete watchers - permission_log_time: Бележи потрошено време - permission_view_time_entries: Прегледај потрошено време - permission_edit_time_entries: Уредувај белешки за потрошено време - permission_edit_own_time_entries: Уредувај ÑопÑтвени белешки за потрошено време - permission_manage_news: Manage news - permission_comment_news: Коментирај на веÑти - permission_manage_documents: Manage documents - permission_view_documents: Прегледувај документи - permission_manage_files: Manage files - permission_view_files: Прегледувај датотеки - permission_manage_wiki: 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: Заштитувај вики Ñтраници - 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: Уредувај пораки - permission_edit_own_messages: Уредувај ÑопÑтвени пораки - permission_delete_messages: Бриши пораки - permission_delete_own_messages: Бриши ÑопÑтвени пораки - permission_export_wiki_pages: Export wiki pages - permission_manage_subtasks: Manage subtasks - - 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: Форуми - project_module_calendar: Календар - project_module_gantt: Gantt - - label_user: КориÑник - label_user_plural: КориÑници - label_user_new: Ðов кориÑник - label_user_anonymous: Ðнонимен - label_project: Проект - label_project_new: Ðов проект - label_project_plural: Проекти - label_x_projects: - zero: нема проекти - one: 1 проект - other: "%{count} проекти" - label_project_all: Сите проекти - label_project_latest: ПоÑледните проекти - label_issue: Задача - label_issue_new: Ðова задача - label_issue_plural: Задачи - label_issue_view_all: Прегледај ги Ñите задачи - label_issues_by: "Задачи по %{value}" - 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: Прилагодено поле - label_custom_field_plural: Прилагодени полиња - label_custom_field_new: Ðово прилагодено поле - label_enumerations: Enumerations - label_enumeration_new: Ðова вредноÑÑ‚ - label_information: Информација - label_information_plural: Информации - label_please_login: Ðајави Ñе - label_register: РегиÑтрирај Ñе - label_login_with_open_id_option: или најави Ñе Ñо OpenID - label_password_lost: Изгубена лозинка - label_home: Почетна - label_my_page: Мојата Ñтрана - label_my_account: Мојот профил - label_my_projects: Мои проекти - label_my_page_block: Блок елемент - label_administration: ÐдминиÑтрација - label_login: Ðајави Ñе - label_logout: Одјави Ñе - label_help: Помош - label_reported_issues: Пријавени задачи - label_assigned_to_me_issues: Задачи доделени на мене - label_last_login: ПоÑледна најава - label_registered_on: РегиÑтриран на - label_activity: ÐктивноÑÑ‚ - label_overall_activity: Севкупна активноÑÑ‚ - label_user_activity: "ÐктивноÑÑ‚ на %{value}" - label_new: Ðова - label_logged_as: Ðајавени Ñте како - label_environment: Опкружување - label_authentication: Ðвтентикација - label_auth_source: Режим на автентикација - label_auth_source_new: Ðов режим на автентикација - label_auth_source_plural: Режими на автентикација - label_subproject_plural: Подпроекти - label_subproject_new: Ðов подпроект - label_and_its_subprojects: "%{value} и неговите подпроекти" - label_min_max_length: Мин. - МакÑ. должина - label_list: ЛиÑта - label_date: Дата - label_integer: Integer - label_float: Float - label_boolean: Boolean - label_string: ТекÑÑ‚ - label_text: Долг текÑÑ‚ - label_attribute: Ðтрибут - label_attribute_plural: Ðтрибути - label_download: "%{count} превземање" - label_download_plural: "%{count} превземања" - 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_settings: Settings - label_overview: Преглед - label_version: Верзија - label_version_new: Ðова верзија - label_version_plural: Верзии - label_close_versions: Затвори ги завршените врзии - 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_x_open_issues_abbr_on_total: - zero: 0 отворени / %{total} - one: 1 отворена / %{total} - other: "%{count} отворени / %{total}" - label_x_open_issues_abbr: - zero: 0 отворени - one: 1 отворена - other: "%{count} отворени" - label_x_closed_issues_abbr: - zero: 0 затворени - one: 1 затворена - other: "%{count} затворени" - 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: Internal - label_last_changes: "поÑледни %{count} промени" - label_change_view_all: Прегледај ги Ñите промени - label_personalize_page: Прилагоди ја Ñтранава - label_comment: Коментар - label_comment_plural: Коментари - label_x_comments: - zero: нема коментари - one: 1 коментар - other: "%{count} коментари" - 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_greater_or_equal: '>=' - label_less_or_equal: '<=' - label_in: во - label_today: Ð´ÐµÐ½ÐµÑ - label_all_time: цело време - label_yesterday: вчера - label_this_week: оваа недела - label_last_week: минатата недела - label_last_n_days: "поÑледните %{count} дена" - 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: Складиште - label_repository_plural: Складишта - label_browse: ПрелиÑтувај - label_modification: "%{count} промени" - label_modification_plural: "%{count} промени" - label_branch: Гранка - label_tag: Tag - label_revision: Ревизија - label_revision_plural: Ревизии - label_revision_id: "Ревизија %{value}" - label_associated_revisions: Associated revisions - label_added: added - label_modified: modified - label_copied: copied - label_renamed: renamed - label_deleted: deleted - label_latest_revision: ПоÑледна ревизија - label_latest_revision_plural: ПоÑледни ревизии - label_view_revisions: Прегледај ги ревизиите - label_view_all_revisions: Прегледај ги Ñите ревизии - label_max_size: МакÑ. големина - label_sort_highest: ПремеÑти најгоре - label_sort_higher: ПремеÑти нагоре - label_sort_lower: ПремеÑти надоле - label_sort_lowest: ПремеÑти најдоле - label_roadmap: Roadmap - label_roadmap_due_in: "Due in %{value}" - label_roadmap_overdue: "КаÑни %{value}" - 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: Ð˜Ð½Ð´ÐµÐºÑ Ð¿Ð¾ наÑлов - label_index_by_date: Ð˜Ð½Ð´ÐµÐºÑ Ð¿Ð¾ дата - label_current_version: Current version - label_preview: Preview - label_feed_plural: Feeds - label_changes_details: Детали за Ñите промени - label_issue_tracking: Следење на задачи - label_spent_time: Потрошено време - label_overall_spent_time: Вкупно потрошено време - label_f_hour: "%{value} чаÑ" - label_f_hour_plural: "%{value} чаÑа" - 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: View differences - label_diff_inline: inline - label_diff_side_by_side: side by side - label_options: Опции - label_copy_workflow_from: Copy workflow from - label_permissions_report: Permissions report - label_watched_issues: Watched issues - label_related_issues: Поврзани задачи - label_applied_status: Applied status - label_loading: Loading... - label_relation_new: Ðова релација - label_relation_delete: Избриши релација - label_relates_to: related to - label_duplicates: дупликати - label_duplicated_by: duplicated by - label_blocks: blocks - label_blocked_by: блокирано од - label_precedes: претходи - label_follows: Ñледи - label_end_to_start: крај до почеток - label_end_to_end: крај до крај - label_start_to_start: почеток до почеток - label_start_to_end: почеток до крај - label_stay_logged_in: ОÑтанете најавени - label_disabled: disabled - label_show_completed_versions: Show completed versions - label_me: Ñ˜Ð°Ñ - label_board: Форум - label_board_new: Ðов форум - label_board_plural: Форуми - label_board_locked: Заклучен - label_board_sticky: Sticky - label_topic_plural: Теми - label_message_plural: Пораки - label_message_last: ПоÑледна порака - label_message_new: Ðова порака - label_message_posted: Поракате е додадена - label_reply_plural: Одговори - label_send_information: ИÑпрати ги информациите за профилот на кориÑникот - label_year: Година - label_month: МеÑец - label_week: Ðедела - label_date_from: Од - label_date_to: До - label_language_based: Според јазикот на кориÑникот - label_sort_by: "Подреди Ñпоред %{value}" - label_send_test_email: ИÑпрати теÑÑ‚ е-порака - label_feeds_access_key: RSS клуч за приÑтап - label_missing_feeds_access_key: ÐедоÑтика RSS клуч за приÑтап - label_feeds_access_key_created_on: "RSS клучот за приÑтап креиран пред %{value}" - label_module_plural: Модули - label_added_time_by: "Додадено од %{author} пред %{age}" - label_updated_time_by: "Ðжурирано од %{author} пред %{age}" - label_updated_time: "Ðжурирано пред %{value}" - 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: Default - label_search_titles_only: Пребарувај Ñамо наÑлови - label_user_mail_option_all: "За било кој наÑтан во Ñите мои проекти" - label_user_mail_option_selected: "За било кој наÑтан Ñамо во избраните проекти..." - label_user_mail_no_self_notified: "Ðе ме извеÑтувај за промените што Ñ˜Ð°Ñ Ð³Ð¸ правам" - label_registration_activation_by_email: активација на профил преку е-пошта - label_registration_manual_activation: мануелна активација на профил - label_registration_automatic_activation: автоматÑка активација на профил - label_display_per_page: "По Ñтрана: %{value}" - label_age: Age - label_change_properties: 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: Preferences - label_chronological_order: Во хронолошки ред - label_reverse_chronological_order: In reverse chronological order - label_planning: Планирање - label_incoming_emails: Дојдовни е-пораки - label_generate_key: Генерирај клуч - label_issue_watchers: Watchers - label_example: Пример - label_display: Прикажи - label_sort: Подреди - label_ascending: РаÑтечки - label_descending: Опаѓачки - label_date_from_to: Од %{start} до %{end} - label_wiki_content_added: Вики Ñтраница додадена - label_wiki_content_updated: Вики Ñтраница ажурирана - label_group: Група - label_group_plural: Групи - label_group_new: Ðова група - label_time_entry_plural: Потрошено време - label_version_sharing_none: Ðе Ñподелено - label_version_sharing_descendants: Со Ñите подпроекти - label_version_sharing_hierarchy: Со хиерархијата на проектот - label_version_sharing_tree: Со дрвото на проектот - label_version_sharing_system: Со Ñите проекти - label_update_issue_done_ratios: Update issue done ratios - label_copy_source: Извор - label_copy_target: ДеÑтинација - label_copy_same_as_target: ИÑто како деÑтинацијата - label_display_used_statuses_only: Only display statuses that are used by this tracker - label_api_access_key: API клуч за приÑтап - label_missing_api_access_key: ÐедоÑтига API клуч за приÑтап - label_api_access_key_created_on: "API клучот за приÑтап е креиран пред %{value}" - label_profile: Профил - label_subtask_plural: Подзадачи - label_project_copy_notifications: Праќај извеÑтувања по е-пошта при копирање на проект - - button_login: Ðајави Ñе - button_submit: ИÑпрати - button_save: Зачувај - button_check_all: Штиклирај ги Ñите - button_uncheck_all: Одштиклирај ги Ñите - button_delete: Избриши - button_create: Креирај - button_create_and_continue: Креирај и продолжи - button_test: ТеÑÑ‚ - button_edit: Уреди - button_add: Додади - button_change: Промени - button_apply: Примени - button_clear: Избриши - button_lock: Заклучи - button_unlock: Отклучи - button_download: Превземи - button_list: List - button_view: Прегледај - button_move: ПремеÑти - button_move_and_follow: ПремеÑти и Ñледи - button_back: Back - button_cancel: Откажи - button_activate: Ðктивирај - button_sort: Подреди - button_log_time: Бележи време - button_rollback: Rollback to this version - button_watch: Следи - button_unwatch: Ðе Ñледи - button_reply: Одговори - button_archive: Ðрхивирај - button_unarchive: Одархивирај - button_reset: Reset - button_rename: Преименувај - button_change_password: Промени лозинка - button_copy: Копирај - button_copy_and_follow: Копирај и Ñледи - button_annotate: Annotate - button_update: Ðжурирај - button_configure: Конфигурирај - button_quote: Цитирај - button_duplicate: Копирај - button_show: Show - - status_active: активни - status_registered: региÑтрирани - status_locked: заклучени - - version_status_open: отворени - version_status_locked: заклучени - version_status_closed: затворени - - field_active: Active - - text_select_mail_notifications: Изберете за кои наÑтани да Ñе праќаат извеÑтувања по е-пошта да Ñе праќаат. - text_regexp_info: eg. ^[A-Z0-9]+$ - text_min_max_length_info: 0 значи без ограничување - text_project_destroy_confirmation: Дали Ñте Ñигурни дека Ñакате да го избришете проектот и Ñите поврзани податоци? - text_subprojects_destroy_warning: "Ðеговите подпроекти: %{value} иÑто така ќе бидат избришани." - text_workflow_edit: Select a role and a tracker to edit the workflow - text_are_you_sure: Дали Ñте Ñигурни? - text_journal_changed: "%{label} променето од %{old} во %{new}" - text_journal_set_to: "%{label} set to %{value}" - text_journal_deleted: "%{label} избришан (%{old})" - text_journal_added: "%{label} %{value} додаден" - text_tip_issue_begin_day: задачи што почнуваат овој ден - text_tip_issue_end_day: задачи што завршуваат овој ден - text_tip_issue_begin_end_day: задачи што почнуваат и завршуваат овој ден - text_project_identifier_info: 'Само мали букви (a-z), бројки и dashes Ñе дозволени
По зачувувањето, идентификаторот неможе да Ñе Ñмени.' - text_caracters_maximum: "%{count} знаци макÑимум." - text_caracters_minimum: "Мора да е најмалку %{count} знаци долго." - text_length_between: "Должина помеѓу %{min} и %{max} знаци." - text_tracker_no_workflow: No workflow defined for this tracker - text_unallowed_characters: Ðедозволени знаци - text_comma_separated: Дозволени Ñе повеќе вредноÑти (разделени Ñо запирка). - text_line_separated: Дозволени Ñе повеќе вредноÑти (една линија за Ñекоја вредноÑÑ‚). - text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages - text_issue_added: "Задачата %{id} е пријавена од %{author}." - text_issue_updated: "Задачата %{id} е ажурирана од %{author}." - text_wiki_destroy_confirmation: Дали Ñте Ñигурни дека Ñакате да го избришете ова вики и целата негова Ñодржина? - text_issue_category_destroy_question: "Ðекои задачи (%{count}) Ñе доделени на оваа категорија. Што Ñакате да правите?" - text_issue_category_destroy_assignments: Remove category assignments - text_issue_category_reassign_to: Додели ги задачите на оваа категорија - text_user_mail_option: "For unselected projects, you will only receive notifications about things you watch or you're involved in (eg. issues you're the author or assignee)." - text_no_configuration_data: "Roles, trackers, issue statuses and workflow have not been configured yet.\nIt is highly recommended to load the default configuration. You will be able to modify it once loaded." - text_load_default_configuration: Load the default configuration - text_status_changed_by_changeset: "Applied in changeset %{value}." - text_issues_destroy_confirmation: 'Дали Ñте Ñигурни дека Ñакате да ги избришете избраните задачи?' - text_select_project_modules: 'Изберете модули за овој проект:' - text_default_administrator_account_changed: Default administrator account changed - text_file_repository_writable: Во папката за прилози може да Ñе запишува - text_plugin_assets_writable: Во папката за додатоци може да Ñе запишува - text_rmagick_available: RMagick available (незадолжително) - text_destroy_time_entries_question: "%{hours} 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: Додели ги пријавените чаÑови на проектот - text_reassign_time_entries: 'Reassign reported hours to this issue:' - text_user_wrote: "%{value} напиша:" - text_enumeration_destroy_question: "%{count} objects are assigned to this value." - text_enumeration_category_reassign_to: 'Reassign them to this value:' - text_email_delivery_not_configured: "ДоÑтавата по е-пошта не е конфигурирана, и извеÑтувањата Ñе оневозможени.\nКонфигурирајте го Вашиот SMTP Ñервер во config/configuration.yml и реÑтартирајте ја апликацијата." - 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' - text_wiki_page_destroy_question: "This page has %{descendants} child page(s) and descendant(s). What do you want to do?" - text_wiki_page_nullify_children: "Keep child pages as root pages" - text_wiki_page_destroy_children: "Delete child pages and all their descendants" - text_wiki_page_reassign_children: "Reassign child pages to this parent page" - text_own_membership_delete_confirmation: "You are about to remove some or all of your permissions and may no longer be able to edit this project after that.\nAre you sure you want to continue?" - text_zoom_in: Zoom in - text_zoom_out: Zoom out - - default_role_manager: Менаџер - default_role_developer: Developer - default_role_reporter: Reporter - default_tracker_bug: Грешка - default_tracker_feature: ФункционалноÑÑ‚ - default_tracker_support: Поддршка - default_issue_status_new: Ðова - default_issue_status_in_progress: Во Ð¿Ñ€Ð¾Ð³Ñ€ÐµÑ - 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: ÐктивноÑти (Ñледење на време) - enumeration_system_activity: СиÑтемÑка активноÑÑ‚ - - button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" - text_are_you_sure_with_children: Delete issue and all child issues? - field_text: Text field - label_user_mail_option_only_owner: Only for things I am the owner of - setting_default_notification_option: Default notification option - label_user_mail_option_only_my_events: Only for things I watch or I'm involved in - label_user_mail_option_only_assigned: Only for things I am assigned to - label_user_mail_option_none: No events - field_member_of_group: Assignee's group - field_assigned_to_role: Assignee's role - notice_not_authorized_archived_project: The project you're trying to access has been archived. - label_principal_search: "Search for user or group:" - label_user_search: "Search for user:" - field_visible: Visible - setting_emails_header: Emails header - setting_commit_logtime_activity_id: Activity for logged time - text_time_logged_by_changeset: Applied in changeset %{value}. - setting_commit_logtime_enabled: Enable time logging - notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) - setting_gantt_items_limit: Maximum number of items displayed on the gantt chart - field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text - text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. - label_my_queries: My custom queries - text_journal_changed_no_detail: "%{label} updated" - label_news_comment_added: Comment added to a news - button_expand_all: Expand all - button_collapse_all: Collapse all - label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee - label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author - label_bulk_edit_selected_time_entries: Bulk edit selected time entries - text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? - label_role_anonymous: Anonymous - label_role_non_member: Non member - label_issue_note_added: Note added - label_issue_status_updated: Status updated - label_issue_priority_updated: Priority updated - label_issues_visibility_own: Issues created by or assigned to the user - field_issues_visibility: Issues visibility - label_issues_visibility_all: All issues - permission_set_own_issues_private: Set own issues public or private - field_is_private: Private - permission_set_issues_private: Set issues public or private - label_issues_visibility_public: All non private issues - text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). - field_commit_logs_encoding: Commit messages encoding - field_scm_path_encoding: Path encoding - text_scm_path_encoding_note: "Default: UTF-8" - field_path_to_repository: Path to repository - field_root_directory: Root directory - field_cvs_module: Module - field_cvsroot: CVSROOT - text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) - text_scm_command: Command - text_scm_command_version: Version - label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. - notice_issue_successful_create: Issue %{id} created. - label_between: between - setting_issue_group_assignment: Allow issue assignment to groups - label_diff: diff - text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) - description_query_sort_criteria_direction: Sort direction - description_project_scope: Search scope - description_filter: Filter - description_user_mail_notification: Mail notification settings - description_date_from: Enter start date - description_message_content: Message content - description_available_columns: Available Columns - description_date_range_interval: Choose range by selecting start and end date - description_issue_category_reassign: Choose issue category - description_search: Searchfield - description_notes: Notes - description_date_range_list: Choose range from list - description_choose_project: Projects - description_date_to: Enter end date - description_query_sort_criteria_attribute: Sort attribute - description_wiki_subpages_reassign: Choose new parent page - description_selected_columns: Selected Columns - label_parent_revision: Parent - label_child_revision: Child - error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. - setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues - button_edit_section: Edit this section - setting_repositories_encodings: Attachments and repositories encodings - description_all_columns: All Columns - button_export: Export - label_export_options: "%{export_format} export options" - error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/51/519546b54485a90980ee98464e417cc62979df94.svn-base --- a/.svn/pristine/51/519546b54485a90980ee98464e417cc62979df94.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,26 +0,0 @@ -module CodeRay - module Scanners - - # Scanner for plain text. - # - # Yields just one token of the kind :plain. - # - # Alias: +plaintext+, +plain+ - class Text < Scanner - - register_for :text - title 'Plain text' - - KINDS_NOT_LOC = [:plain] # :nodoc: - - protected - - def scan_tokens encoder, options - encoder.text_token string, :plain - encoder - end - - end - - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/51/519b223dadd478a188b887acb238e809d1e44602.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/51/519b223dadd478a188b887acb238e809d1e44602.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,1 @@ +$('#attachments_<%= j params[:attachment_id] %>').remove(); diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/51/51b00c88d6dde33f6379ec80045abcc086071077.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/51/51b00c88d6dde33f6379ec80045abcc086071077.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,1086 @@ +sk: + direction: ltr + date: + formats: + default: "%Y-%m-%d" + short: "%b %d" + long: "%B %d, %Y" + + day_names: [Nedeľa, Pondelok, Utorok, Streda, Å tvrtok, Piatok, Sobota] + abbr_day_names: [Ne, Po, Ut, St, Å t, Pi, So] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, Január, Február, Marec, Apríl, Máj, Jún, Júl, August, September, Október, November, December] + abbr_month_names: [~, Jan, Feb, Mar, Apr, Máj, Jún, Júl, Aug, Sep, Okt, Nov, Dec] + # Used in date_select and datime_select. + order: + - :year + - :month + - :day + + time: + formats: + default: "%a, %d %b %Y %H:%M:%S %z" + time: "%H:%M" + short: "%d %b %H:%M" + long: "%B %d, %Y %H:%M" + am: "am" + pm: "pm" + + datetime: + distance_in_words: + half_a_minute: "pol minúty" + less_than_x_seconds: + one: "menej ako 1 sekunda" + other: "menej ako %{count} sekúnd" + x_seconds: + one: "1 sekunda" + other: "%{count} sekúnd" + less_than_x_minutes: + one: "menej ako minúta" + other: "menej ako %{count} minút" + x_minutes: + one: "1 minuta" + other: "%{count} minút" + about_x_hours: + one: "okolo 1 hodiny" + other: "okolo %{count} hodín" + x_hours: + one: "1 hodina" + other: "%{count} hodín" + x_days: + one: "1 deň" + other: "%{count} dní" + about_x_months: + one: "okolo 1 mesiaca" + other: "okolo %{count} mesiace/ov" + x_months: + one: "1 mesiac" + other: "%{count} mesiace/ov" + about_x_years: + one: "okolo 1 roka" + other: "okolo %{count} roky/ov" + over_x_years: + one: "cez 1 rok" + other: "cez %{count} roky/ov" + almost_x_years: + one: "almost 1 year" + other: "almost %{count} years" + + number: + format: + separator: "." + delimiter: "" + precision: 3 + + human: + format: + precision: 3 + delimiter: "" + storage_units: + format: "%n %u" + units: + kb: KB + tb: TB + gb: GB + byte: + one: Byte + other: Bytes + mb: MB + +# Used in array.to_sentence. + support: + array: + sentence_connector: "a" + skip_last_comma: false + + activerecord: + errors: + template: + header: + one: "1 error prohibited this %{model} from being saved" + other: "%{count} errors prohibited this %{model} from being saved" + messages: + inclusion: "nieje zahrnuté v zozname" + exclusion: "je rezervované" + invalid: "je neplatné" + confirmation: "sa nezhoduje s potvrdením" + accepted: "musí byÅ¥ akceptované" + empty: "nemôže byÅ¥ prázdne" + blank: "nemôže byÅ¥ prázdne" + too_long: "je príliÅ¡ dlhé" + too_short: "je príliÅ¡ krátke" + wrong_length: "má chybnú dĺžku" + taken: "je už použité" + not_a_number: "nieje Äíslo" + not_a_date: "nieje platný dátum" + greater_than: "musí byÅ¥ väÄšíe ako %{count}" + greater_than_or_equal_to: "musí byÅ¥ väÄÅ¡ie alebo rovné %{count}" + equal_to: "musí byÅ¥ rovné %{count}" + less_than: "musí byÅ¥ menej ako %{count}" + less_than_or_equal_to: "musí byÅ¥ menej alebo rovné %{count}" + odd: "musí byÅ¥ nepárne" + even: "musí byÅ¥ párne" + greater_than_start_date: "musí byÅ¥ neskôr ako poÄiatoÄný dátum" + not_same_project: "nepatrí rovnakému projektu" + circular_dependency: "Tento vzÅ¥ah by vytvoril cyklickú závislosÅ¥" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" + + # SK translation by Stanislav Pach | stano.pach@seznam.cz + + actionview_instancetag_blank_option: Prosím vyberte + + general_text_No: 'Nie' + general_text_Yes: 'Ãno' + general_text_no: 'nie' + general_text_yes: 'áno' + general_lang_name: 'SlovenÄina' + general_csv_separator: ',' + general_csv_decimal_separator: '.' + general_csv_encoding: UTF-8 + general_pdf_encoding: UTF-8 + 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žívateľ. + 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 zmazaná. + notice_locking_conflict: Údaje boli zmenené iným užívateľom. + notice_scm_error: Položka a/alebo revízia neexistuje v repozitári. + notice_not_authorized: Nemáte dostatoÄné práva pre zobrazenie tejto stránky. + notice_email_sent: "Na adresu %{value} bol odeslaný email" + notice_email_error: "Pri odosielaní emailu nastala chyba (%{value})" + notice_feeds_access_key_reseted: Váš klÃºÄ pre prístup k Atomu bol resetovaný. + notice_failed_to_save_issues: "Nastala chyba pri ukládaní %{count} úloh na %{total} zvolený: %{ids}." + notice_no_issue_selected: "Nebola zvolená žiadná úloha. Prosím, zvoľte úlohy, ktoré chcete upraviÅ¥" + 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á: %{value}" + error_scm_not_found: "Položka a/alebo revízia neexistuje v repozitári." + error_scm_command_failed: "Pri pokuse o prístup k repozitári doÅ¡lo k chybe: %{value}" + error_issue_not_found_in_project: 'Úloha nebola nájdená alebo nepatrí k tomuto projektu' + + mail_subject_lost_password: "VaÅ¡e heslo (%{value})" + mail_body_lost_password: 'Pre zmenu vaÅ¡eho hesla kliknite na následujúci odkaz:' + mail_subject_register: "Aktivácia úÄtu (%{value})" + mail_body_register: 'Pre aktiváciu vaÅ¡eho úÄtu kliknite na následujúci odkaz:' + mail_body_account_information_external: "Pomocou vaÅ¡eho úÄtu %{value} se môžete prihlásiÅ¥." + mail_body_account_information: Informácie o vaÅ¡om úÄte + mail_subject_account_activation_request: "Aktivácia %{value} úÄtu" + mail_body_account_activation_request: "Bol zaregistrovaný nový uživateľ %{value}. Aktivácia jeho úÄtu závisí na vaÅ¡om potvrdení." + + + 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_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žiÅ¥ ako filter + field_issue_to: 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 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íjemcovia 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 Webovú Službu (WS) pre správu repozitára + 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_ itories_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: Repozitár + 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_x_projects: + zero: žiadne projekty + one: 1 projekt + other: "%{count} projekty/ov" + 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 %{value}" + 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_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_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_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_x_open_issues_abbr_on_total: + zero: 0 otvorených z celkovo %{total} + one: 1 otvorený z celkovo %{total} + other: "%{count} otvorené/ých z celkovo %{total}" + label_x_open_issues_abbr: + zero: 0 otvorených + one: 1 otvorený + other: "%{count} otvorené/ých" + label_x_closed_issues_abbr: + zero: 0 zavretých + one: 1 zavretý + other: "%{count} zavreté/ých" + 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 %{count} 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_x_comments: + zero: žiaden komentár + one: 1 komentár + other: "%{count} komentáre/ov" + 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 %{count} 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: Repozitár + label_repository_plural: Repozitáre + label_browse: PrechádzaÅ¥ + 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_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 %{value}" + label_roadmap_overdue: "%{value} neskoro" + label_roadmap_no_issues: Pre túto verziu niesú ž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: "%{value} hodina" + label_f_hour_plural: "%{value} 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 %{value}" + label_send_test_email: PoslaÅ¥ testovací email + label_feeds_access_key_created_on: "Prístupový klÃºÄ pre RSS bol vytvorený pred %{value}" + label_module_plural: Moduly + label_added_time_by: "Pridané užívateľom %{author} pred %{age}" + label_updated_time: "Aktualizované pred %{value}" + 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_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: "%{value} 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Å¥ Äaľší 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 úprave workflow + text_are_you_sure: Ste si istý? + text_tip_issue_begin_day: úloha zaÄína v tento deň + text_tip_issue_end_day: úloha konÄí v tento deň + text_tip_issue_begin_end_day: úloha zaÄína a konÄí v tento deň + text_caracters_maximum: "%{count} znakov maximálne." + text_caracters_minimum: "Musí byÅ¥ aspoň %{count} znaky/ov dlhé." + text_length_between: "Dĺžka medzi %{min} až %{max} 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 %{id} bola vytvorená užívateľom %{author}." + text_issue_updated: "Úloha %{id} byla aktualizovaná užívateľom %{author}." + text_wiki_destroy_confirmation: Naozaj si prajete odstrániÅ¥ túto Wiki a celý jej obsah? + text_issue_category_destroy_question: "Niektoré úlohy (%{count}) 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 %{value}." + 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 repozitára + 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_developer: 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_in_progress: In Progress + 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): %{value} budú takisto vymazané." + label_and_its_subprojects: "%{value} a jeho podprojekty" + mail_body_reminder: "%{count} úloha(y), ktorá(é) je(sú) vám priradený(é), ma(jú) byÅ¥ hotova(é) za %{days} dní:" + mail_subject_reminder: "%{count} úloha(y) ma(jú) byÅ¥ hotova(é) za pár %{days} dní" + text_user_wrote: "%{value} 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: "%{count} 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úť Webovú Službu (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/configuration.yml a reÅ¡tartnite aplikáciu pre aktiváciu funkcie." + field_parent_title: Nadradená stránka + label_issue_watchers: Pozorovatelia + 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: Úprava úloh + permission_edit_own_time_entries: Úprava vlastných zaznamov o strávenom Äase + permission_manage_public_queries: Správa verejných otáziek + permission_add_issues: Pridanie úlohy + permission_log_time: Zaznamenávanie 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_edit_wiki_pages: Úprava 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 medzi úlohami + 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 úprav + permission_add_messages: Pridanie správ + permission_view_messages: Zobrazenie správ + permission_manage_files: Správa súborov + permission_edit_issue_notes: Úprava poznámok úlohy + permission_manage_news: Správa noviniek + permission_view_calendar: Zobrazenie kalendára + permission_manage_members: Správa Älenov + permission_edit_messages: Úprava správ + permission_delete_issues: Mazanie správ + permission_view_issue_watchers: Zobrazenie zoznamu pozorovateľov + permission_manage_repository: Správa repozitára + permission_commit_access: PovoliÅ¥ prístup + permission_browse_repository: Prechádzanie repozitára + permission_view_documents: Zobrazenie dokumentov + permission_edit_project: Úprava projektu + permission_add_issue_notes: Pridanie poznámky úlohy + permission_save_queries: Uloženie otáziek + permission_view_wiki_pages: Zobrazenie Wiki strániek + permission_rename_wiki_pages: Premenovanie Wiki strániek + permission_edit_time_entries: Úprava záznamov o strávenom Äase + permission_edit_own_issue_notes: Úprava 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 upravte mapovanie medzi užívateľmi systému Redmine a užívateľskými menami nájdenými v logu repozitára.\nUžívatelia s rovnakým prihlasovacím menom alebo emailom v systéme Redmine a repozitára sú mapovaní automaticky." + label_example: Príklad + label_user_activity: "Aktivita užívateľa %{value}" + label_updated_time_by: "Aktualizované užívateľom %{author} pred %{age}" + 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: "%{count} súbor(y) nemohol(li) byÅ¥ uložené." + field_editable: Editovateľné + label_display: Zobrazenie + button_create_and_continue: VytvoriÅ¥ a pokraÄovaÅ¥ + text_custom_field_possible_values_info: 'Jeden riadok pre každú hodnotu' + setting_repository_log_display_limit: Maximálne množstvo revizií zobrazené v logu + setting_file_max_size_displayed: Maximálna veľkosÅ¥ textových súborov zobrazených priamo na stránke + field_watcher: Pozorovateľ + setting_openid: PovoliÅ¥ OpenID prihlasovanie a registráciu + field_identity_url: OpenID URL + label_login_with_open_id_option: alebo sa prihlásiÅ¥ pomocou OpenID + field_content: Obsah + label_descending: Zostupné + label_sort: Zoradenie + label_ascending: Rastúce + label_date_from_to: Od %{start} do %{end} + label_greater_or_equal: ">=" + label_less_or_equal: <= + text_wiki_page_destroy_question: Táto stránka má %{descendants} podstránku/y a potomka/ov. ÄŒo chcete vykonaÅ¥? + text_wiki_page_reassign_children: PreradiÅ¥ podstránky k tejto hlavnej stránke + text_wiki_page_nullify_children: ZachovaÅ¥ podstránky ako hlavné stránky + text_wiki_page_destroy_children: VymazaÅ¥ podstránky a vÅ¡etkých ich potomkov + setting_password_min_length: Minimálna dĺžka hesla + field_group_by: Skupinové výsledky podľa + mail_subject_wiki_content_updated: "'%{id}' Wiki stránka bola aktualizovaná" + label_wiki_content_added: Wiki stránka pridaná + mail_subject_wiki_content_added: "'%{id}' Wiki stránka bola pridaná" + mail_body_wiki_content_added: The '%{id}' Wiki stránka bola pridaná užívateľom %{author}. + permission_add_project: Vytvorenie projektu + label_wiki_content_updated: Wiki stránka aktualizovaná + mail_body_wiki_content_updated: Wiki stránka '%{id}' bola aktualizovaná užívateľom %{author}. + setting_new_project_user_role_id: Rola dána non-admin užívateľovi, ktorý vytvorí projekt + label_view_all_revisions: ZobraziÅ¥ vÅ¡etkz revízie + label_tag: Tag + label_branch: Vetva + error_no_tracker_in_project: K tomuto projektu nieje priradená žiadna fronta. Prosím skontrolujte nastavenie projektu. + error_no_default_issue_status: Nieje definovaný východzí stav úlohy. Prosím skontrolujte vase nastavenie (ChoÄte na "Administrácia -> Stavz úloh"). + text_journal_changed: "%{label} zmenené z %{old} na %{new}" + text_journal_set_to: "%{label} nastavené na %{value}" + text_journal_deleted: "%{label} zmazané (%{old})" + label_group_plural: Skupiny + label_group: Skupina + label_group_new: Nová skupina + label_time_entry_plural: Strávený Äas + text_journal_added: "%{label} %{value} pridané" + field_active: Aktívne + enumeration_system_activity: Aktivita systému + permission_delete_issue_watchers: OdstrániÅ¥ pozorovateľov + version_status_closed: zavreté + version_status_locked: uzavreté + version_status_open: otvorené + error_can_not_reopen_issue_on_closed_version: Úloha priradená uzavretej verzií nemôže byÅ¥ znovu-otvorená + label_user_anonymous: Anonym + button_move_and_follow: Presunúť a následovaÅ¥ + setting_default_projects_modules: Prednastavené aktívne moduly pre nové projekty + setting_gravatar_default: Východzí Gravatar obrázok + field_sharing: Zdieľanie + label_version_sharing_hierarchy: S hierarchiou projektu + label_version_sharing_system: So vÅ¡etkými projektami + label_version_sharing_descendants: S podprojektami + label_version_sharing_tree: S projektovým stromom + label_version_sharing_none: Nezdielané + error_can_not_archive_project: Tento projekt nemôže byÅ¥ archivovaný + button_duplicate: DuplikovaÅ¥ + button_copy_and_follow: KopírovaÅ¥ a následovaÅ¥ + label_copy_source: Zdroj + setting_issue_done_ratio: VyrátaÅ¥ pomer vypracovania úlohy s + setting_issue_done_ratio_issue_status: PoužiÅ¥ stav úlohy + error_issue_done_ratios_not_updated: Stav vypracovania úlohy neaktualizovaný. + error_workflow_copy_target: Prosím zvoľte cieľovú frontu(y) a rolu(e) + setting_issue_done_ratio_issue_field: PoužiÅ¥ pole úlohy + label_copy_same_as_target: Rovnaké ako cieľ + label_copy_target: Cieľ + notice_issue_done_ratios_updated: Stav vypracovania úlohy aktualizovaný. + error_workflow_copy_source: Prosím zvoľte zdrojovú frontu alebo rolu + label_update_issue_done_ratios: Aktualizácia stavu úloh + setting_start_of_week: Å tart pracovného týždňa v + permission_view_issues: ZobraziÅ¥ úlohy + label_display_used_statuses_only: ZobraziÅ¥ len stavy, ktoré sú priradené k tejto fronte + label_revision_id: Revízia %{value} + label_api_access_key: API prístupový kÄ¾ÃºÄ + label_api_access_key_created_on: API prístupový kÄ¾ÃºÄ vytvorený pred %{value} + label_feeds_access_key: RSS prístupový kÄ¾ÃºÄ + notice_api_access_key_reseted: Váš API prístupový kÄ¾ÃºÄ bol resetovaný. + setting_rest_api_enabled: Zapnúť REST web službu + label_missing_api_access_key: API prístupový kľuÄ nenájdený + label_missing_feeds_access_key: RSS prístupový kÄ¾ÃºÄ nenájdený + button_show: ZobraziÅ¥ + text_line_separated: MožnosÅ¥ viacerých hodnôt (jeden riadok pre každú hodnotu). + setting_mail_handler_body_delimiters: OrezaÅ¥ emaily po následujúcich riadkoch + permission_add_subprojects: Vytváranie podprojektov + label_subproject_new: Nový podprojekt + text_own_membership_delete_confirmation: |- + Práve sa pokúšate o odstránenie niektorých alebo vÅ¡etkých prístupových práv a možno nebudete maÅ¥ možnost naÄalej upravovaÅ¥ tento projekt. + Ste si istý(á), že chcete pokraÄovat? + label_close_versions: UzavrieÅ¥ ukonÄené verzie + label_board_sticky: Sticky + label_board_locked: Uzamknuté + permission_export_wiki_pages: ExportovaÅ¥ WiKi stránky + setting_cache_formatted_text: Cache formatted text + permission_manage_project_activities: NastavovaÅ¥ aktivity projektu + error_unable_delete_issue_status: Nieje možné zmeniÅ¥ stav úlohy + label_profile: Profil + permission_manage_subtasks: NastavovaÅ¥ podúlohy + field_parent_issue: Nadradená úloha + label_subtask_plural: Podúlohy + label_project_copy_notifications: ZaslaÅ¥ emailové upozornenie behom kopírovania projektu + error_can_not_delete_custom_field: Nieje možné vymazaÅ¥ užívateľské pole + error_unable_to_connect: Nieje možné vymazaÅ¥ (%{value}) + error_can_not_remove_role: Táto roľa sa používa a nemôže byÅ¥ vymazaná. + error_can_not_delete_tracker: Táto fronta obsahuje úlohy a nemôže byÅ¥ vymazaná. + field_principal: Principal + label_my_page_block: My page block + notice_failed_to_save_members: "Failed to save member(s): %{errors}." + text_zoom_out: Zoom out + text_zoom_in: Zoom in + notice_unable_delete_time_entry: Unable to delete time log entry. + label_overall_spent_time: Overall spent time + field_time_entries: Log time + project_module_gantt: Gantt + project_module_calendar: Calendar + button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" + field_text: Text field + label_user_mail_option_only_owner: Only for things I am the owner of + setting_default_notification_option: Default notification option + label_user_mail_option_only_my_events: Only for things I watch or I'm involved in + label_user_mail_option_only_assigned: Only for things I am assigned to + label_user_mail_option_none: No events + field_member_of_group: Assignee's group + field_assigned_to_role: Assignee's role + notice_not_authorized_archived_project: The project you're trying to access has been archived. + label_principal_search: "Search for user or group:" + label_user_search: "Search for user:" + field_visible: Visible + setting_commit_logtime_activity_id: Activity for logged time + text_time_logged_by_changeset: Applied in changeset %{value}. + setting_commit_logtime_enabled: Enable time logging + notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) + setting_gantt_items_limit: Maximum number of items displayed on the gantt chart + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. + label_my_queries: My custom queries + text_journal_changed_no_detail: "%{label} updated" + label_news_comment_added: Comment added to a news + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + label_bulk_edit_selected_time_entries: Bulk edit selected time entries + text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? + label_role_anonymous: Anonymous + label_role_non_member: Non member + label_issue_note_added: Note added + label_issue_status_updated: Status updated + label_issue_priority_updated: Priority updated + label_issues_visibility_own: Issues created by or assigned to the user + field_issues_visibility: Issues visibility + label_issues_visibility_all: All issues + permission_set_own_issues_private: Set own issues public or private + field_is_private: Private + permission_set_issues_private: Set issues public or private + label_issues_visibility_public: All non private issues + text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). + field_commit_logs_encoding: Kódovanie prenášaných správ + field_scm_path_encoding: Path encoding + text_scm_path_encoding_note: "Default: UTF-8" + field_path_to_repository: Path to repository + field_root_directory: Root directory + field_cvs_module: Module + field_cvsroot: CVSROOT + text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) + text_scm_command: Command + text_scm_command_version: Version + label_git_report_last_commit: Report last commit for files and directories + notice_issue_successful_create: Issue %{id} created. + label_between: between + setting_issue_group_assignment: Allow issue assignment to groups + label_diff: diff + text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) + description_query_sort_criteria_direction: Sort direction + description_project_scope: Search scope + description_filter: Filter + description_user_mail_notification: Mail notification settings + description_date_from: Enter start date + description_message_content: Message content + description_available_columns: Available Columns + description_date_range_interval: Choose range by selecting start and end date + description_issue_category_reassign: Choose issue category + description_search: Searchfield + description_notes: Notes + description_date_range_list: Choose range from list + description_choose_project: Projects + description_date_to: Enter end date + description_query_sort_criteria_attribute: Sort attribute + description_wiki_subpages_reassign: Choose new parent page + description_selected_columns: Selected Columns + label_parent_revision: Parent + label_child_revision: Child + error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. + setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues + button_edit_section: Edit this section + setting_repositories_encodings: Attachments and repositories encodings + description_all_columns: All Columns + button_export: Export + label_export_options: "%{export_format} export options" + error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) + notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." + label_x_issues: + zero: 0 Úloha + one: 1 Úloha + other: "%{count} Úlohy" + label_repository_new: New repository + field_repository_is_default: Main repository + label_copy_attachments: Copy attachments + label_item_position: "%{position}/%{count}" + label_completed_versions: Completed versions + text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
Once saved, the identifier cannot be changed. + field_multiple: Multiple values + setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed + text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes + text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) + notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. + text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} + permission_manage_related_issues: Manage related issues + field_auth_source_ldap_filter: LDAP filter + label_search_for_watchers: Search for watchers to add + notice_account_deleted: Your account has been permanently deleted. + setting_unsubscribe: Allow users to delete their own account + button_delete_my_account: Delete my account + text_account_destroy_confirmation: |- + Are you sure you want to proceed? + Your account will be permanently deleted, with no way to reactivate it. + error_session_expired: Your session has expired. Please login again. + text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." + setting_session_lifetime: Session maximum lifetime + setting_session_timeout: Session inactivity timeout + label_session_expiration: Session expiration + permission_close_project: Close / reopen the project + label_show_closed_projects: View closed projects + button_close: Close + button_reopen: Reopen + project_status_active: active + project_status_closed: closed + project_status_archived: archived + text_project_closed: This project is closed and read-only. + notice_user_successful_create: User %{id} created. + field_core_fields: Standard fields + field_timeout: Timeout (in seconds) + setting_thumbnails_enabled: Display attachment thumbnails + setting_thumbnails_size: Thumbnails size (in pixels) + label_status_transitions: Status transitions + label_fields_permissions: Fields permissions + label_readonly: Read-only + label_required: Required + text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
Once saved, the identifier cannot be changed. + field_board_parent: Parent forum + label_attribute_of_project: Project's %{name} + label_attribute_of_author: Author's %{name} + label_attribute_of_assigned_to: Assignee's %{name} + label_attribute_of_fixed_version: Target version's %{name} + label_copy_subtasks: Copy subtasks + label_copied_to: copied to + label_copied_from: copied from + label_any_issues_in_project: any issues in project + label_any_issues_not_in_project: any issues not in project + field_private_notes: Private notes + permission_view_private_notes: View private notes + permission_set_notes_private: Set notes as private + label_no_issues_in_project: no issues in project + label_any: všetko + label_last_n_weeks: last %{count} weeks + setting_cross_project_subtasks: Allow cross-project subtasks + label_cross_project_descendants: S podprojektami + label_cross_project_tree: S projektovým stromom + label_cross_project_hierarchy: S hierarchiou projektu + label_cross_project_system: So všetkými projektami + button_hide: Hide + setting_non_working_week_days: Non-working days + label_in_the_next_days: in the next + label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Celkovo + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/51/51b862a2f02fe838775410e5f39462124421d172.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/51/51b862a2f02fe838775410e5f39462124421d172.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,65 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class WorkflowTest < ActiveSupport::TestCase + fixtures :roles, :trackers, :issue_statuses + + def test_copy + WorkflowTransition.delete_all + WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 1, :new_status_id => 2) + WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 1, :new_status_id => 3, :assignee => true) + WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 1, :new_status_id => 4, :author => true) + + assert_difference 'WorkflowTransition.count', 3 do + WorkflowTransition.copy(Tracker.find(2), Role.find(1), Tracker.find(3), Role.find(2)) + end + + assert WorkflowTransition.first(:conditions => {:role_id => 2, :tracker_id => 3, :old_status_id => 1, :new_status_id => 2, :author => false, :assignee => false}) + assert WorkflowTransition.first(:conditions => {:role_id => 2, :tracker_id => 3, :old_status_id => 1, :new_status_id => 3, :author => false, :assignee => true}) + assert WorkflowTransition.first(:conditions => {:role_id => 2, :tracker_id => 3, :old_status_id => 1, :new_status_id => 4, :author => true, :assignee => false}) + end + + def test_workflow_permission_should_validate_rule + wp = WorkflowPermission.new(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :field_name => 'due_date') + assert !wp.save + + wp.rule = 'foo' + assert !wp.save + + wp.rule = 'required' + assert wp.save + + wp.rule = 'readonly' + assert wp.save + end + + def test_workflow_permission_should_validate_field_name + wp = WorkflowPermission.new(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :rule => 'required') + assert !wp.save + + wp.field_name = 'foo' + assert !wp.save + + wp.field_name = 'due_date' + assert wp.save + + wp.field_name = '1' + assert wp.save + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/51/51b919386182e47c3ef0ca6e2a6f8f116db68244.svn-base --- a/.svn/pristine/51/51b919386182e47c3ef0ca6e2a6f8f116db68244.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +0,0 @@ -class Thing - def self.from_plugin; TestHelper::report_location(__FILE__); end -end \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/51/51be1ed9a6fd0f544bf7334bc87d5f8e3ed5bf74.svn-base --- a/.svn/pristine/51/51be1ed9a6fd0f544bf7334bc87d5f8e3ed5bf74.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,56 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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 CalendarsController < ApplicationController - menu_item :calendar - before_filter :find_optional_project - - rescue_from Query::StatementInvalid, :with => :query_statement_invalid - - helper :issues - helper :projects - helper :queries - include QueriesHelper - helper :sort - include SortHelper - - def show - if params[:year] and params[:year].to_i > 1900 - @year = params[:year].to_i - if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13 - @month = params[:month].to_i - end - end - @year ||= Date.today.year - @month ||= Date.today.month - - @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month) - retrieve_query - @query.group_by = nil - if @query.valid? - events = [] - events += @query.issues(:include => [:tracker, :assigned_to, :priority], - :conditions => ["((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?))", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt] - ) - events += @query.versions(:conditions => ["effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt]) - - @calendar.events = events - end - - render :action => 'show', :layout => false if request.xhr? - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/51/51f64479905fa6ebe754a8ef36329d97b292d2ad.svn-base --- a/.svn/pristine/51/51f64479905fa6ebe754a8ef36329d97b292d2ad.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,793 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class QueryColumn - attr_accessor :name, :sortable, :groupable, :default_order - include Redmine::I18n - - def initialize(name, options={}) - self.name = name - self.sortable = options[:sortable] - self.groupable = options[:groupable] || false - if groupable == true - self.groupable = name.to_s - end - self.default_order = options[:default_order] - @caption_key = options[:caption] || "field_#{name}" - end - - def caption - l(@caption_key) - end - - # Returns true if the column is sortable, otherwise false - def sortable? - !@sortable.nil? - end - - def sortable - @sortable.is_a?(Proc) ? @sortable.call : @sortable - end - - def value(issue) - issue.send name - end - - def css_classes - name - end -end - -class QueryCustomFieldColumn < QueryColumn - - def initialize(custom_field) - self.name = "cf_#{custom_field.id}".to_sym - self.sortable = custom_field.order_statement || false - if %w(list date bool int).include?(custom_field.field_format) - self.groupable = custom_field.order_statement - end - self.groupable ||= false - @cf = custom_field - end - - def caption - @cf.name - end - - def custom_field - @cf - end - - def value(issue) - cv = issue.custom_values.detect {|v| v.custom_field_id == @cf.id} - cv && @cf.cast_value(cv.value) - end - - def css_classes - @css_classes ||= "#{name} #{@cf.field_format}" - end -end - -class Query < ActiveRecord::Base - class StatementInvalid < ::ActiveRecord::StatementInvalid - end - - belongs_to :project - belongs_to :user - serialize :filters - serialize :column_names - serialize :sort_criteria, Array - - attr_protected :project_id, :user_id - - validates_presence_of :name, :on => :save - validates_length_of :name, :maximum => 255 - validate :validate_query_filters - - @@operators = { "=" => :label_equals, - "!" => :label_not_equals, - "o" => :label_open_issues, - "c" => :label_closed_issues, - "!*" => :label_none, - "*" => :label_all, - ">=" => :label_greater_or_equal, - "<=" => :label_less_or_equal, - "><" => :label_between, - " :label_in_less_than, - ">t+" => :label_in_more_than, - "t+" => :label_in, - "t" => :label_today, - "w" => :label_this_week, - ">t-" => :label_less_than_ago, - " :label_more_than_ago, - "t-" => :label_ago, - "~" => :label_contains, - "!~" => :label_not_contains } - - cattr_reader :operators - - @@operators_by_filter_type = { :list => [ "=", "!" ], - :list_status => [ "o", "=", "!", "c", "*" ], - :list_optional => [ "=", "!", "!*", "*" ], - :list_subprojects => [ "*", "!*", "=" ], - :date => [ "=", ">=", "<=", "><", "t+", "t+", "t", "w", ">t-", " [ "=", ">=", "<=", "><", ">t-", " [ "=", "~", "!", "!~" ], - :text => [ "~", "!~" ], - :integer => [ "=", ">=", "<=", "><", "!*", "*" ], - :float => [ "=", ">=", "<=", "><", "!*", "*" ] } - - cattr_reader :operators_by_filter_type - - @@available_columns = [ - QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true), - QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true), - QueryColumn.new(:parent, :sortable => ["#{Issue.table_name}.root_id", "#{Issue.table_name}.lft ASC"], :default_order => 'desc', :caption => :field_parent_issue), - QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true), - QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true), - QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"), - QueryColumn.new(:author, :sortable => lambda {User.fields_for_order_statement("authors")}, :groupable => true), - QueryColumn.new(:assigned_to, :sortable => lambda {User.fields_for_order_statement}, :groupable => true), - QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'), - QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true), - QueryColumn.new(:fixed_version, :sortable => ["#{Version.table_name}.effective_date", "#{Version.table_name}.name"], :default_order => 'desc', :groupable => true), - QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"), - QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"), - QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"), - QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true), - QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'), - ] - cattr_reader :available_columns - - named_scope :visible, lambda {|*args| - user = args.shift || User.current - base = Project.allowed_to_condition(user, :view_issues, *args) - user_id = user.logged? ? user.id : 0 - { - :conditions => ["(#{table_name}.project_id IS NULL OR (#{base})) AND (#{table_name}.is_public = ? OR #{table_name}.user_id = ?)", true, user_id], - :include => :project - } - } - - def initialize(attributes = nil) - super attributes - self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} } - end - - def after_initialize - # Store the fact that project is nil (used in #editable_by?) - @is_for_all = project.nil? - end - - def validate_query_filters - filters.each_key do |field| - if values_for(field) - case type_for(field) - when :integer - errors.add(label_for(field), :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^\d+$/) } - when :float - errors.add(label_for(field), :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^\d+(\.\d*)?$/) } - when :date, :date_past - case operator_for(field) - when "=", ">=", "<=", "><" - errors.add(label_for(field), :invalid) if values_for(field).detect {|v| v.present? && (!v.match(/^\d{4}-\d{2}-\d{2}$/) || (Date.parse(v) rescue nil).nil?) } - when ">t-", " 'position') : project.rolled_up_trackers - - @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } }, - "tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } }, - "priority_id" => { :type => :list, :order => 3, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } }, - "subject" => { :type => :text, :order => 8 }, - "created_on" => { :type => :date_past, :order => 9 }, - "updated_on" => { :type => :date_past, :order => 10 }, - "start_date" => { :type => :date, :order => 11 }, - "due_date" => { :type => :date, :order => 12 }, - "estimated_hours" => { :type => :float, :order => 13 }, - "done_ratio" => { :type => :integer, :order => 14 }} - - principals = [] - if project - principals += project.principals.sort - else - all_projects = Project.visible.all - if all_projects.any? - # members of visible projects - principals += Principal.active.find(:all, :conditions => ["#{User.table_name}.id IN (SELECT DISTINCT user_id FROM members WHERE project_id IN (?))", all_projects.collect(&:id)]).sort - - # project filter - project_values = [] - Project.project_tree(all_projects) do |p, level| - prefix = (level > 0 ? ('--' * level + ' ') : '') - project_values << ["#{prefix}#{p.name}", p.id.to_s] - end - @available_filters["project_id"] = { :type => :list, :order => 1, :values => project_values} unless project_values.empty? - end - end - users = principals.select {|p| p.is_a?(User)} - - assigned_to_values = [] - assigned_to_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged? - assigned_to_values += (Setting.issue_group_assignment? ? principals : users).collect{|s| [s.name, s.id.to_s] } - @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => assigned_to_values } unless assigned_to_values.empty? - - author_values = [] - author_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged? - author_values += users.collect{|s| [s.name, s.id.to_s] } - @available_filters["author_id"] = { :type => :list, :order => 5, :values => author_values } unless author_values.empty? - - group_values = Group.all.collect {|g| [g.name, g.id.to_s] } - @available_filters["member_of_group"] = { :type => :list_optional, :order => 6, :values => group_values } unless group_values.empty? - - role_values = Role.givable.collect {|r| [r.name, r.id.to_s] } - @available_filters["assigned_to_role"] = { :type => :list_optional, :order => 7, :values => role_values } unless role_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 - categories = project.issue_categories.all - unless categories.empty? - @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => categories.collect{|s| [s.name, s.id.to_s] } } - end - versions = project.shared_versions.all - unless versions.empty? - @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } } - end - unless project.leaf? - subprojects = project.descendants.visible.all - unless subprojects.empty? - @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => subprojects.collect{|s| [s.name, s.id.to_s] } } - end - end - add_custom_fields_filters(project.all_issue_custom_fields) - else - # global filters for cross project issue list - system_shared_versions = Version.visible.find_all_by_sharing('system') - unless system_shared_versions.empty? - @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => system_shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } } - end - add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true})) - end - @available_filters - end - - def add_filter(field, operator, values) - # values must be an array - return unless values.nil? || values.is_a?(Array) - # check if field is defined as an available filter - if available_filters.has_key? field - filter_options = available_filters[field] - # check if operator is allowed for that filter - #if @@operators_by_filter_type[filter_options[:type]].include? operator - # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]}) - # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator - #end - filters[field] = {:operator => operator, :values => (values || [''])} - end - end - - def add_short_filter(field, expression) - return unless expression && available_filters.has_key?(field) - field_type = available_filters[field][:type] - @@operators_by_filter_type[field_type].sort.reverse.detect do |operator| - next unless expression =~ /^#{Regexp.escape(operator)}(.*)$/ - add_filter field, operator, $1.present? ? $1.split('|') : [''] - end || add_filter(field, '=', expression.split('|')) - end - - # Add multiple filters using +add_filter+ - def add_filters(fields, operators, values) - if fields.is_a?(Array) && operators.is_a?(Hash) && (values.nil? || values.is_a?(Hash)) - fields.each do |field| - add_filter(field, operators[field], values && values[field]) - end - end - end - - def has_filter?(field) - filters and filters[field] - end - - def type_for(field) - available_filters[field][:type] if available_filters.has_key?(field) - end - - def operator_for(field) - has_filter?(field) ? filters[field][:operator] : nil - end - - def values_for(field) - has_filter?(field) ? filters[field][:values] : nil - end - - def value_for(field, index=0) - (values_for(field) || [])[index] - end - - def label_for(field) - label = available_filters[field][:name] if available_filters.has_key?(field) - label ||= field.gsub(/\_id$/, "") - end - - def available_columns - return @available_columns if @available_columns - @available_columns = ::Query.available_columns - @available_columns += (project ? - project.all_issue_custom_fields : - IssueCustomField.find(:all) - ).collect {|cf| QueryCustomFieldColumn.new(cf) } - end - - def self.available_columns=(v) - self.available_columns = (v) - end - - def self.add_available_column(column) - self.available_columns << (column) if column.is_a?(QueryColumn) - end - - # Returns an array of columns that can be used to group the results - def groupable_columns - available_columns.select {|c| c.groupable} - end - - # Returns a Hash of columns and the key for sorting - def sortable_columns - {'id' => "#{Issue.table_name}.id"}.merge(available_columns.inject({}) {|h, column| - h[column.name.to_s] = column.sortable - h - }) - end - - def columns - # preserve the column_names order - (has_default_columns? ? default_columns_names : column_names).collect do |name| - available_columns.find { |col| col.name == name } - end.compact - end - - def default_columns_names - @default_columns_names ||= begin - default_columns = Setting.issue_list_default_columns.map(&:to_sym) - - project.present? ? default_columns : [:project] | default_columns - end - end - - def column_names=(names) - if names - names = names.select {|n| n.is_a?(Symbol) || !n.blank? } - names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym } - # Set column_names to nil if default columns - if names == default_columns_names - names = nil - end - end - write_attribute(:column_names, names) - end - - def has_column?(column) - column_names && column_names.include?(column.name) - end - - def has_default_columns? - column_names.nil? || column_names.empty? - end - - def sort_criteria=(arg) - c = [] - if arg.is_a?(Hash) - arg = arg.keys.sort.collect {|k| arg[k]} - end - c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, o == 'desc' ? o : 'asc']} - write_attribute(:sort_criteria, c) - end - - def sort_criteria - read_attribute(:sort_criteria) || [] - end - - def sort_criteria_key(arg) - sort_criteria && sort_criteria[arg] && sort_criteria[arg].first - end - - def sort_criteria_order(arg) - sort_criteria && sort_criteria[arg] && sort_criteria[arg].last - end - - # Returns the SQL sort order that should be prepended for grouping - def group_by_sort_order - if grouped? && (column = group_by_column) - column.sortable.is_a?(Array) ? - column.sortable.collect {|s| "#{s} #{column.default_order}"}.join(',') : - "#{column.sortable} #{column.default_order}" - end - end - - # Returns true if the query is a grouped query - def grouped? - !group_by_column.nil? - end - - def group_by_column - groupable_columns.detect {|c| c.groupable && c.name.to_s == group_by} - end - - def group_by_statement - group_by_column.try(:groupable) - end - - def project_statement - project_clauses = [] - if project && !project.descendants.active.empty? - ids = [project.id] - if has_filter?("subproject_id") - case operator_for("subproject_id") - when '=' - # include the selected subprojects - ids += values_for("subproject_id").each(&:to_i) - when '!*' - # main project only - else - # all subprojects - ids += project.descendants.collect(&:id) - end - elsif Setting.display_subprojects_issues? - 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.any? ? project_clauses.join(' AND ') : nil - end - - def statement - # filters clauses - filters_clauses = [] - filters.each_key do |field| - next if field == "subproject_id" - v = values_for(field).clone - next unless v and !v.empty? - operator = operator_for(field) - - # "me" value subsitution - if %w(assigned_to_id author_id watcher_id).include?(field) - if v.delete("me") - if User.current.logged? - v.push(User.current.id.to_s) - v += User.current.group_ids.map(&:to_s) if field == 'assigned_to_id' - else - v.push("0") - end - end - end - - if field =~ /^cf_(\d+)$/ - # custom field - filters_clauses << sql_for_custom_field(field, operator, v, $1) - elsif respond_to?("sql_for_#{field}_field") - # specific statement - filters_clauses << send("sql_for_#{field}_field", field, operator, v) - else - # regular field - filters_clauses << '(' + sql_for_field(field, operator, v, Issue.table_name, field) + ')' - end - end if filters and valid? - - filters_clauses << project_statement - filters_clauses.reject!(&:blank?) - - filters_clauses.any? ? filters_clauses.join(' AND ') : nil - end - - # Returns the issue count - def issue_count - Issue.visible.count(:include => [:status, :project], :conditions => statement) - rescue ::ActiveRecord::StatementInvalid => e - raise StatementInvalid.new(e.message) - end - - # Returns the issue count by group or nil if query is not grouped - def issue_count_by_group - r = nil - if grouped? - begin - # Rails will raise an (unexpected) RecordNotFound if there's only a nil group value - r = Issue.visible.count(:group => group_by_statement, :include => [:status, :project], :conditions => statement) - rescue ActiveRecord::RecordNotFound - r = {nil => issue_count} - end - c = group_by_column - if c.is_a?(QueryCustomFieldColumn) - r = r.keys.inject({}) {|h, k| h[c.custom_field.cast_value(k)] = r[k]; h} - end - end - r - rescue ::ActiveRecord::StatementInvalid => e - raise StatementInvalid.new(e.message) - end - - # Returns the issues - # Valid options are :order, :offset, :limit, :include, :conditions - def issues(options={}) - order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',') - order_option = nil if order_option.blank? - - joins = (order_option && order_option.include?('authors')) ? "LEFT OUTER JOIN users authors ON authors.id = #{Issue.table_name}.author_id" : nil - - Issue.visible.scoped(:conditions => options[:conditions]).find :all, :include => ([:status, :project] + (options[:include] || [])).uniq, - :conditions => statement, - :order => order_option, - :joins => joins, - :limit => options[:limit], - :offset => options[:offset] - rescue ::ActiveRecord::StatementInvalid => e - raise StatementInvalid.new(e.message) - end - - # Returns the journals - # Valid options are :order, :offset, :limit - def journals(options={}) - Journal.visible.find :all, :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}], - :conditions => statement, - :order => options[:order], - :limit => options[:limit], - :offset => options[:offset] - rescue ::ActiveRecord::StatementInvalid => e - raise StatementInvalid.new(e.message) - end - - # Returns the versions - # Valid options are :conditions - def versions(options={}) - Version.visible.scoped(:conditions => options[:conditions]).find :all, :include => :project, :conditions => project_statement - rescue ::ActiveRecord::StatementInvalid => e - raise StatementInvalid.new(e.message) - end - - def sql_for_watcher_id_field(field, operator, value) - db_table = Watcher.table_name - "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " + - sql_for_field(field, '=', value, db_table, 'user_id') + ')' - end - - def sql_for_member_of_group_field(field, operator, value) - if operator == '*' # Any group - groups = Group.all - operator = '=' # Override the operator since we want to find by assigned_to - elsif operator == "!*" - groups = Group.all - operator = '!' # Override the operator since we want to find by assigned_to - else - groups = Group.find_all_by_id(value) - end - groups ||= [] - - members_of_groups = groups.inject([]) {|user_ids, group| - if group && group.user_ids.present? - user_ids << group.user_ids - end - user_ids.flatten.uniq.compact - }.sort.collect(&:to_s) - - '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')' - end - - def sql_for_assigned_to_role_field(field, operator, value) - case operator - when "*", "!*" # Member / Not member - sw = operator == "!*" ? 'NOT' : '' - nl = operator == "!*" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : '' - "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}" + - " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id))" - when "=", "!" - role_cond = value.any? ? - "#{MemberRole.table_name}.role_id IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")" : - "1=0" - - sw = operator == "!" ? 'NOT' : '' - nl = operator == "!" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : '' - "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}, #{MemberRole.table_name}" + - " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id AND #{Member.table_name}.id = #{MemberRole.table_name}.member_id AND #{role_cond}))" - end - end - - private - - def sql_for_custom_field(field, operator, value, custom_field_id) - db_table = CustomValue.table_name - db_field = 'value' - "#{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=#{custom_field_id} WHERE " + - sql_for_field(field, operator, value, db_table, db_field, true) + ')' - end - - # 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 "=" - if value.any? - case type_for(field) - when :date, :date_past - sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), (Date.parse(value.first) rescue nil)) - when :integer - sql = "#{db_table}.#{db_field} = #{value.first.to_i}" - when :float - sql = "#{db_table}.#{db_field} BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5}" - else - sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")" - end - else - # IN an empty set - sql = "1=0" - end - when "!" - if value.any? - sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))" - else - # NOT IN an empty set - sql = "1=1" - end - 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 ">=" - if [:date, :date_past].include?(type_for(field)) - sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), nil) - else - if is_custom_filter - sql = "CAST(#{db_table}.#{db_field} AS decimal(60,3)) >= #{value.first.to_f}" - else - sql = "#{db_table}.#{db_field} >= #{value.first.to_f}" - end - end - when "<=" - if [:date, :date_past].include?(type_for(field)) - sql = date_clause(db_table, db_field, nil, (Date.parse(value.first) rescue nil)) - else - if is_custom_filter - sql = "CAST(#{db_table}.#{db_field} AS decimal(60,3)) <= #{value.first.to_f}" - else - sql = "#{db_table}.#{db_field} <= #{value.first.to_f}" - end - end - when "><" - if [:date, :date_past].include?(type_for(field)) - sql = date_clause(db_table, db_field, (Date.parse(value[0]) rescue nil), (Date.parse(value[1]) rescue nil)) - else - if is_custom_filter - sql = "CAST(#{db_table}.#{db_field} AS decimal(60,3)) BETWEEN #{value[0].to_f} AND #{value[1].to_f}" - else - sql = "#{db_table}.#{db_field} BETWEEN #{value[0].to_f} AND #{value[1].to_f}" - end - end - 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 = relative_date_clause(db_table, db_field, - value.first.to_i, 0) - when "t+" - sql = relative_date_clause(db_table, db_field, value.first.to_i, nil) - when "= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week) - sql = relative_date_clause(db_table, db_field, - days_ago, - days_ago + 6) - when "~" - sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'" - when "!~" - sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'" - else - raise "Unknown query operator #{operator}" - end - - return sql - end - - def add_custom_fields_filters(custom_fields) - @available_filters ||= {} - - custom_fields.select(&:is_filter?).each do |field| - case field.field_format - when "text" - options = { :type => :text, :order => 20 } - when "list" - options = { :type => :list_optional, :values => field.possible_values, :order => 20} - when "date" - options = { :type => :date, :order => 20 } - when "bool" - options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 } - when "int" - options = { :type => :integer, :order => 20 } - when "float" - options = { :type => :float, :order => 20 } - when "user", "version" - next unless project - options = { :type => :list_optional, :values => field.possible_values_options(project), :order => 20} - else - options = { :type => :string, :order => 20 } - end - @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name }) - end - end - - # Returns a SQL clause for a date or datetime field. - def date_clause(table, field, from, to) - s = [] - if from - from_yesterday = from - 1 - from_yesterday_utc = Time.gm(from_yesterday.year, from_yesterday.month, from_yesterday.day) - s << ("#{table}.#{field} > '%s'" % [connection.quoted_date(from_yesterday_utc.end_of_day)]) - end - if to - to_utc = Time.gm(to.year, to.month, to.day) - s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date(to_utc.end_of_day)]) - end - s.join(' AND ') - end - - # Returns a SQL clause for a date or datetime field using relative dates. - def relative_date_clause(table, field, days_from, days_to) - date_clause(table, field, (days_from ? Date.today + days_from : nil), (days_to ? Date.today + days_to : nil)) - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/52/52004253c47464e971de8985c2ff7d4abac9aadd.svn-base --- a/.svn/pristine/52/52004253c47464e971de8985c2ff7d4abac9aadd.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,60 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -require 'net/imap' - -module Redmine - module IMAP - class << self - def check(imap_options={}, options={}) - host = imap_options[:host] || '127.0.0.1' - port = imap_options[:port] || '143' - ssl = !imap_options[:ssl].nil? - folder = imap_options[:folder] || 'INBOX' - - imap = Net::IMAP.new(host, port, ssl) - imap.login(imap_options[:username], imap_options[:password]) unless imap_options[:username].nil? - imap.select(folder) - imap.search(['NOT', 'SEEN']).each do |message_id| - msg = imap.fetch(message_id,'RFC822')[0].attr['RFC822'] - logger.debug "Receiving message #{message_id}" if logger && logger.debug? - if MailHandler.receive(msg, options) - 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 - end - - private - - def logger - RAILS_DEFAULT_LOGGER - end - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/52/5207c75436c85ec591b380afe15450272eeedb94.svn-base --- a/.svn/pristine/52/5207c75436c85ec591b380afe15450272eeedb94.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,81 +0,0 @@ -module CodeRay - - # = Duo - # - # A Duo is a convenient way to use CodeRay. You just create a Duo, - # giving it a lang (language of the input code) and a format (desired - # output format), and call Duo#highlight with the code. - # - # Duo makes it easy to re-use both scanner and encoder for a repetitive - # task. It also provides a very easy interface syntax: - # - # require 'coderay' - # CodeRay::Duo[:python, :div].highlight 'import this' - # - # Until you want to do uncommon things with CodeRay, I recommend to use - # this method, since it takes care of everything. - class Duo - - attr_accessor :lang, :format, :options - - # Create a new Duo, holding a lang and a format to highlight code. - # - # simple: - # CodeRay::Duo[:ruby, :html].highlight 'bla 42' - # - # with options: - # CodeRay::Duo[:ruby, :html, :hint => :debug].highlight '????::??' - # - # alternative syntax without options: - # CodeRay::Duo[:ruby => :statistic].encode 'class << self; end' - # - # alternative syntax with options: - # CodeRay::Duo[{ :ruby => :statistic }, :do => :something].encode 'abc' - # - # The options are forwarded to scanner and encoder - # (see CodeRay.get_scanner_options). - def initialize lang = nil, format = nil, options = {} - if format.nil? && lang.is_a?(Hash) && lang.size == 1 - @lang = lang.keys.first - @format = lang[@lang] - else - @lang = lang - @format = format - end - @options = options - end - - class << self - # To allow calls like Duo[:ruby, :html].highlight. - alias [] new - end - - # The scanner of the duo. Only created once. - def scanner - @scanner ||= CodeRay.scanner @lang, CodeRay.get_scanner_options(@options) - end - - # The encoder of the duo. Only created once. - def encoder - @encoder ||= CodeRay.encoder @format, @options - end - - # Tokenize and highlight the code using +scanner+ and +encoder+. - def encode code, options = {} - options = @options.merge options - encoder.encode(code, @lang, options) - end - alias highlight encode - - # Allows to use Duo like a proc object: - # - # CodeRay::Duo[:python => :yaml].call(code) - # - # or, in Ruby 1.9 and later: - # - # CodeRay::Duo[:python => :yaml].(code) - alias call encode - - end - -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/52/520b379788e40ebe77f4044b9785418c4b20c19c.svn-base --- a/.svn/pristine/52/520b379788e40ebe77f4044b9785418c4b20c19c.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,128 +0,0 @@ -// ** I18N - -// Calendar EN language -// Author: Mihai Bazon, -// Encoding: any -// Translater: Mads N. Vestergaard -// 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 -("Søndag", - "Mandag", - "Tirsdag", - "Onsdag", - "Torsdag", - "Fredag", - "Lørdag", - "Søndag"); - -// 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 -("Søn", - "Man", - "Tir", - "Ons", - "Tor", - "Fre", - "Lør", - "Søn"); - -// 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 -("Januar", - "Februar", - "Marts", - "April", - "Maj", - "Juni", - "Juli", - "August", - "September", - "Oktober", - "November", - "December"); - -// short month names -Calendar._SMN = new Array -("Jan", - "Feb", - "Mar", - "Apr", - "Maj", - "Jun", - "Jul", - "Aug", - "Sep", - "Okt", - "Nov", - "Dec"); - -// tooltips -Calendar._TT = {}; -Calendar._TT["INFO"] = "Om denne kalender"; - -Calendar._TT["ABOUT"] = -"DHTML Date/Time Selector\n" + -"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) -"For seneste version, besøg: http://www.dynarch.com/projects/calendar/\n" + -"Distribueret under GNU LGPL. Se http://gnu.org/licenses/lgpl.html for detaljer." + -"\n\n" + -"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 musetasten inde på punkterne for at vælge hurtigere."; -Calendar._TT["ABOUT_TIME"] = "\n\n" + -"Tids valg:\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 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"] = " (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. -Calendar._TT["DAY_FIRST"] = "Vis %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"] = "6,7"; - -Calendar._TT["CLOSE"] = "Luk"; -Calendar._TT["TODAY"] = "I dag"; -Calendar._TT["TIME_PART"] = "(Shift-)Klik eller træk for at ændre værdi"; - -// date formats -Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; -Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; - -Calendar._TT["WK"] = "uge"; -Calendar._TT["TIME"] = "Tid:"; diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/52/5239875e2f66506ef4227a2d0ff1c43dcfbd22ad.svn-base --- a/.svn/pristine/52/5239875e2f66506ef4227a2d0ff1c43dcfbd22ad.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,121 +0,0 @@ -
    - <%= call_hook(:view_issues_context_menu_start, {:issues => @issues, :can => @can, :back => @back }) %> - -<% if !@issue.nil? -%> -
  • <%= context_menu_link l(:button_edit), {:controller => 'issues', :action => 'edit', :id => @issue}, - :class => 'icon-edit', :disabled => !@can[:edit] %>
  • -<% else %> -
  • <%= context_menu_link l(:button_edit), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id)}, - :class => 'icon-edit', :disabled => !@can[:edit] %>
  • -<% end %> - - <% if @allowed_statuses.present? %> -
  • - <%= l(:field_status) %> -
      - <% @statuses.each do |s| -%> -
    • <%= context_menu_link h(s.name), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {:status_id => s}, :back_url => @back}, :method => :post, - :selected => (@issue && s == @issue.status), :disabled => !(@can[:update] && @allowed_statuses.include?(s)) %>
    • - <% end -%> -
    -
  • - <% end %> - - <% unless @trackers.nil? %> -
  • - <%= l(:field_tracker) %> -
      - <% @trackers.each do |t| -%> -
    • <%= context_menu_link h(t.name), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'tracker_id' => t}, :back_url => @back}, :method => :post, - :selected => (@issue && t == @issue.tracker), :disabled => !@can[:edit] %>
    • - <% end -%> -
    -
  • - <% end %> - -
  • - <%= l(:field_priority) %> -
      - <% @priorities.each do |p| -%> -
    • <%= context_menu_link h(p.name), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'priority_id' => p}, :back_url => @back}, :method => :post, - :selected => (@issue && p == @issue.priority), :disabled => (!@can[:edit] || @issues.detect {|i| !i.leaf?}) %>
    • - <% end -%> -
    -
  • - - <% #TODO: allow editing versions when multiple projects %> - <% unless @project.nil? || @project.shared_versions.open.empty? -%> -
  • - <%= l(:field_fixed_version) %> -
      - <% @project.shared_versions.open.sort.each do |v| -%> -
    • <%= context_menu_link format_version_name(v), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'fixed_version_id' => v}, :back_url => @back}, :method => :post, - :selected => (@issue && v == @issue.fixed_version), :disabled => !@can[:update] %>
    • - <% end -%> -
    • <%= context_menu_link l(:label_none), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'fixed_version_id' => 'none'}, :back_url => @back}, :method => :post, - :selected => (@issue && @issue.fixed_version.nil?), :disabled => !@can[:update] %>
    • -
    -
  • - <% end %> - <% if @assignables.present? -%> -
  • - <%= l(:field_assigned_to) %> -
      - <% @assignables.each do |u| -%> -
    • <%= context_menu_link h(u.name), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'assigned_to_id' => u}, :back_url => @back}, :method => :post, - :selected => (@issue && u == @issue.assigned_to), :disabled => !@can[:update] %>
    • - <% end -%> -
    • <%= context_menu_link l(:label_nobody), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'assigned_to_id' => 'none'}, :back_url => @back}, :method => :post, - :selected => (@issue && @issue.assigned_to.nil?), :disabled => !@can[:update] %>
    • -
    -
  • - <% end %> - <% unless @project.nil? || @project.issue_categories.empty? -%> -
  • - <%= l(:field_category) %> -
      - <% @project.issue_categories.each do |u| -%> -
    • <%= context_menu_link h(u.name), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'category_id' => u}, :back_url => @back}, :method => :post, - :selected => (@issue && u == @issue.category), :disabled => !@can[:update] %>
    • - <% end -%> -
    • <%= context_menu_link l(:label_none), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'category_id' => 'none'}, :back_url => @back}, :method => :post, - :selected => (@issue && @issue.category.nil?), :disabled => !@can[:update] %>
    • -
    -
  • - <% end -%> - - <% if Issue.use_field_for_done_ratio? %> -
  • - <%= l(:field_done_ratio) %> -
      - <% (0..10).map{|x|x*10}.each do |p| -%> -
    • <%= context_menu_link "#{p}%", {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'done_ratio' => p}, :back_url => @back}, :method => :post, - :selected => (@issue && p == @issue.done_ratio), :disabled => (!@can[:edit] || @issues.detect {|i| !i.leaf?}) %>
    • - <% end -%> -
    -
  • - <% end %> - -<% if !@issue.nil? %> - <% if @can[:log_time] -%> -
  • <%= context_menu_link l(:button_log_time), {:controller => 'timelog', :action => 'new', :issue_id => @issue}, - :class => 'icon-time-add' %>
  • - <% end %> - <% if User.current.logged? %> -
  • <%= watcher_link(@issue, User.current) %>
  • - <% end %> -<% end %> - -<% if @issue.present? %> -
  • <%= context_menu_link l(:button_duplicate), {:controller => 'issues', :action => 'new', :project_id => @project, :copy_from => @issue}, - :class => 'icon-duplicate', :disabled => !@can[:copy] %>
  • -<% end %> -
  • <%= context_menu_link l(:button_copy), new_issue_move_path(:ids => @issues.collect(&:id), :copy_options => {:copy => 't'}), - :class => 'icon-copy', :disabled => !@can[:move] %>
  • -
  • <%= context_menu_link l(:button_move), new_issue_move_path(:ids => @issues.collect(&:id)), - :class => 'icon-move', :disabled => !@can[:move] %>
  • -
  • <%= context_menu_link l(:button_delete), {:controller => 'issues', :action => 'destroy', :ids => @issues.collect(&:id), :back_url => @back}, - :method => :post, :confirm => issues_destroy_confirmation_message(@issues), :class => 'icon-del', :disabled => !@can[:delete] %>
  • - - <%= call_hook(:view_issues_context_menu_end, {:issues => @issues, :can => @can, :back => @back }) %> -
diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/52/523c9c2d9c7b015aa1113d76af95f9049ad0813d.svn-base --- a/.svn/pristine/52/523c9c2d9c7b015aa1113d76af95f9049ad0813d.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -# Only call Engines.init once, in the after_initialize block so that Rails -# plugin reloading works when turned on -config.after_initialize do - Engines.init(initializer) if defined? :Engines -end \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/52/524c64f89f9b5171077500c8f6e0240696dfd698.svn-base --- a/.svn/pristine/52/524c64f89f9b5171077500c8f6e0240696dfd698.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +0,0 @@ -#!/usr/bin/env ruby -require File.expand_path('../../config/boot', __FILE__) -require 'commands/generate' diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/52/52835e85663e2540d36b9bff7e6cf6a1403991c9.svn-base --- a/.svn/pristine/52/52835e85663e2540d36b9bff7e6cf6a1403991c9.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,115 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../../../test_helper', __FILE__) - -class Redmine::CodesetUtilTest < ActiveSupport::TestCase - - def test_to_utf8_by_setting_from_latin1 - with_settings :repositories_encodings => 'UTF-8,ISO-8859-1' do - s1 = "Texte encod\xc3\xa9" - s2 = "Texte encod\xe9" - s3 = s2.dup - if s1.respond_to?(:force_encoding) - s1.force_encoding("UTF-8") - s2.force_encoding("ASCII-8BIT") - s3.force_encoding("UTF-8") - end - assert_equal s1, Redmine::CodesetUtil.to_utf8_by_setting(s2) - assert_equal s1, Redmine::CodesetUtil.to_utf8_by_setting(s3) - end - end - - def test_to_utf8_by_setting_from_euc_jp - with_settings :repositories_encodings => 'UTF-8,EUC-JP' do - s1 = "\xe3\x83\xac\xe3\x83\x83\xe3\x83\x89\xe3\x83\x9e\xe3\x82\xa4\xe3\x83\xb3" - s2 = "\xa5\xec\xa5\xc3\xa5\xc9\xa5\xde\xa5\xa4\xa5\xf3" - s3 = s2.dup - if s1.respond_to?(:force_encoding) - s1.force_encoding("UTF-8") - s2.force_encoding("ASCII-8BIT") - s3.force_encoding("UTF-8") - end - assert_equal s1, Redmine::CodesetUtil.to_utf8_by_setting(s2) - assert_equal s1, Redmine::CodesetUtil.to_utf8_by_setting(s3) - end - end - - def test_to_utf8_by_setting_should_be_converted_all_latin1 - with_settings :repositories_encodings => 'ISO-8859-1' do - s1 = "\xc3\x82\xc2\x80" - s2 = "\xC2\x80" - s3 = s2.dup - if s1.respond_to?(:force_encoding) - s1.force_encoding("UTF-8") - s2.force_encoding("ASCII-8BIT") - s3.force_encoding("UTF-8") - end - assert_equal s1, Redmine::CodesetUtil.to_utf8_by_setting(s2) - assert_equal s1, Redmine::CodesetUtil.to_utf8_by_setting(s3) - end - end - - def test_to_utf8_by_setting_blank_string - assert_equal "", Redmine::CodesetUtil.to_utf8_by_setting("") - assert_equal nil, Redmine::CodesetUtil.to_utf8_by_setting(nil) - end - - def test_to_utf8_by_setting_returns_ascii_as_utf8 - s1 = "ASCII" - s2 = s1.dup - if s1.respond_to?(:force_encoding) - s1.force_encoding("UTF-8") - s2.force_encoding("ISO-8859-1") - end - str1 = Redmine::CodesetUtil.to_utf8_by_setting(s1) - str2 = Redmine::CodesetUtil.to_utf8_by_setting(s2) - assert_equal s1, str1 - assert_equal s1, str2 - if s1.respond_to?(:force_encoding) - assert_equal "UTF-8", str1.encoding.to_s - assert_equal "UTF-8", str2.encoding.to_s - end - end - - def test_to_utf8_by_setting_invalid_utf8_sequences_should_be_stripped - with_settings :repositories_encodings => '' do - # s1 = File.read("#{RAILS_ROOT}/test/fixtures/encoding/iso-8859-1.txt") - s1 = "Texte encod\xe9 en ISO-8859-1." - s1.force_encoding("ASCII-8BIT") if s1.respond_to?(:force_encoding) - str = Redmine::CodesetUtil.to_utf8_by_setting(s1) - if str.respond_to?(:force_encoding) - assert str.valid_encoding? - assert_equal "UTF-8", str.encoding.to_s - end - assert_equal "Texte encod? en ISO-8859-1.", str - end - end - - def test_to_utf8_by_setting_invalid_utf8_sequences_should_be_stripped_ja_jis - with_settings :repositories_encodings => 'ISO-2022-JP' do - s1 = "test\xb5\xfetest\xb5\xfe" - s1.force_encoding("ASCII-8BIT") if s1.respond_to?(:force_encoding) - str = Redmine::CodesetUtil.to_utf8_by_setting(s1) - if str.respond_to?(:force_encoding) - assert str.valid_encoding? - assert_equal "UTF-8", str.encoding.to_s - end - assert_equal "test??test??", str - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/52/52861ceb2f3a417977bfbcbe6c0ba816774849bd.svn-base --- a/.svn/pristine/52/52861ceb2f3a417977bfbcbe6c0ba816774849bd.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,101 +0,0 @@ ---- -enabled_modules_001: - name: issue_tracking - project_id: 1 - id: 1 -enabled_modules_002: - name: time_tracking - project_id: 1 - id: 2 -enabled_modules_003: - name: news - project_id: 1 - id: 3 -enabled_modules_004: - name: documents - project_id: 1 - id: 4 -enabled_modules_005: - name: files - project_id: 1 - id: 5 -enabled_modules_006: - name: wiki - project_id: 1 - id: 6 -enabled_modules_007: - name: repository - project_id: 1 - id: 7 -enabled_modules_008: - name: boards - project_id: 1 - id: 8 -enabled_modules_009: - name: repository - project_id: 3 - id: 9 -enabled_modules_010: - name: wiki - project_id: 3 - id: 10 -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 -enabled_modules_015: - name: wiki - project_id: 2 - id: 15 -enabled_modules_016: - name: boards - project_id: 2 - id: 16 -enabled_modules_017: - name: calendar - project_id: 1 - id: 17 -enabled_modules_018: - name: gantt - project_id: 1 - id: 18 -enabled_modules_019: - name: calendar - project_id: 2 - id: 19 -enabled_modules_020: - name: gantt - project_id: 2 - id: 20 -enabled_modules_021: - name: calendar - project_id: 3 - id: 21 -enabled_modules_022: - name: gantt - project_id: 3 - id: 22 -enabled_modules_023: - name: calendar - project_id: 5 - id: 23 -enabled_modules_024: - name: gantt - project_id: 5 - id: 24 -enabled_modules_025: - name: news - project_id: 2 - id: 25 diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/52/52e141046a10068211408d55ec6484cccb3d452a.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/52/52e141046a10068211408d55ec6484cccb3d452a.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,18 @@ +class CreateEnabledModules < ActiveRecord::Migration + def self.up + create_table :enabled_modules do |t| + t.column :project_id, :integer + t.column :name, :string, :null => false + end + add_index :enabled_modules, [:project_id], :name => :enabled_modules_project_id + + # Enable all modules for existing projects + Project.all.each do |project| + project.enabled_module_names = Redmine::AccessControl.available_project_modules + end + end + + def self.down + drop_table :enabled_modules + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/52/52f0bf1c239cf648c203a8def6f59245b007d594.svn-base --- a/.svn/pristine/52/52f0bf1c239cf648c203a8def6f59245b007d594.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +0,0 @@ -#!/usr/bin/env ruby -require File.expand_path('../../config/boot', __FILE__) -require 'commands/console' diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/52/52fb1ca187bdbea92b2ca21fb1a15fadb8ae488e.svn-base --- a/.svn/pristine/52/52fb1ca187bdbea92b2ca21fb1a15fadb8ae488e.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,51 +0,0 @@ -# Tests in this file ensure that: -# -# * plugin controller actions are found -# * actions defined in application controllers take precedence over those in plugins -# * actions in controllers in subsequently loaded plugins take precendence over those in previously loaded plugins -# * this works for actions in namespaced controllers accordingly - -require File.dirname(__FILE__) + '/../test_helper' - -class ControllerLoadingTest < ActionController::TestCase - def setup - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - end - - # plugin controller actions should be found - - def test_WITH_an_action_defined_only_in_a_plugin_IT_should_use_this_action - get_action_on_controller :an_action, :alpha_plugin - assert_response_body 'rendered in AlphaPluginController#an_action' - end - - def test_WITH_an_action_defined_only_in_a_namespaced_plugin_controller_IT_should_use_this_action - get_action_on_controller :an_action, :alpha_plugin, :namespace - assert_response_body 'rendered in Namespace::AlphaPluginController#an_action' - end - - # app takes precedence over plugins - - def test_WITH_an_action_defined_in_both_app_and_plugin_IT_should_use_the_one_in_app - get_action_on_controller :an_action, :app_and_plugin - assert_response_body 'rendered in AppAndPluginController#an_action (from app)' - end - - def test_WITH_an_action_defined_in_namespaced_controllers_in_both_app_and_plugin_IT_should_use_the_one_in_app - get_action_on_controller :an_action, :app_and_plugin, :namespace - assert_response_body 'rendered in Namespace::AppAndPluginController#an_action (from app)' - end - - # subsequently loaded plugins take precendence over previously loaded plugins - - def test_WITH_an_action_defined_in_two_plugin_controllers_IT_should_use_the_latter_of_both - get_action_on_controller :an_action, :shared_plugin - assert_response_body 'rendered in SharedPluginController#an_action (from beta_plugin)' - end - - def test_WITH_an_action_defined_in_two_namespaced_plugin_controllers_IT_should_use_the_latter_of_both - get_action_on_controller :an_action, :shared_plugin, :namespace - assert_response_body 'rendered in Namespace::SharedPluginController#an_action (from beta_plugin)' - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/53/5348a576b6e7f11a14f641fe1fc6002b23e71005.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/53/5348a576b6e7f11a14f641fe1fc6002b23e71005.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,6 @@ +

<%=l(:label_auth_source)%> (<%= h(@auth_source.auth_method_name) %>)

+ +<%= labelled_form_for @auth_source, :as => :auth_source, :url => auth_source_path(@auth_source), :html => {:id => 'auth_source_form'} do |f| %> + <%= render :partial => auth_source_partial_name(@auth_source), :locals => { :f => f } %> + <%= submit_tag l(:button_save) %> +<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/53/5379630b9d6429044c3fd0c2e60e7bdba5976f78.svn-base --- a/.svn/pristine/53/5379630b9d6429044c3fd0c2e60e7bdba5976f78.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,94 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) -require 'welcome_controller' - -# Re-raise errors caught by the controller. -class WelcomeController; def rescue_action(e) raise e end; end - -class WelcomeControllerTest < ActionController::TestCase - fixtures :projects, :news - - def setup - @controller = WelcomeController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - User.current = nil - end - - def test_index - get :index - assert_response :success - assert_template 'index' - assert_not_nil assigns(:news) - assert_not_nil assigns(:projects) - assert !assigns(:projects).include?(Project.find(:first, :conditions => {:is_public => false})) - end - - def test_browser_language - Setting.default_language = 'en' - @request.env['HTTP_ACCEPT_LANGUAGE'] = 'fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3' - get :index - assert_equal :fr, @controller.current_language - end - - def test_browser_language_alternate - Setting.default_language = 'en' - @request.env['HTTP_ACCEPT_LANGUAGE'] = 'zh-TW' - get :index - assert_equal :"zh-TW", @controller.current_language - end - - def test_browser_language_alternate_not_valid - Setting.default_language = 'en' - @request.env['HTTP_ACCEPT_LANGUAGE'] = 'fr-CA' - 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\r?$}) - end - - def test_warn_on_leaving_unsaved_turn_on - user = User.find(2) - user.pref.warn_on_leaving_unsaved = '1' - user.pref.save! - @request.session[:user_id] = 2 - - get :index - assert_tag 'script', - :attributes => {:type => "text/javascript"}, - :content => %r{new WarnLeavingUnsaved} - end - - def test_warn_on_leaving_unsaved_turn_off - user = User.find(2) - user.pref.warn_on_leaving_unsaved = '0' - user.pref.save! - @request.session[:user_id] = 2 - - get :index - assert_no_tag 'script', - :attributes => {:type => "text/javascript"}, - :content => %r{new WarnLeavingUnsaved} - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/53/539b469592377fe5ec04bc7018cfe2f6bf2322bc.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/53/539b469592377fe5ec04bc7018cfe2f6bf2322bc.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,134 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class RoutingIssuesTest < ActionController::IntegrationTest + def test_issues_rest_actions + assert_routing( + { :method => 'get', :path => "/issues" }, + { :controller => 'issues', :action => 'index' } + ) + 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' } + ) + assert_routing( + { :method => 'get', :path => "/issues.xml" }, + { :controller => 'issues', :action => 'index', :format => 'xml' } + ) + assert_routing( + { :method => 'get', :path => "/issues/64" }, + { :controller => 'issues', :action => 'show', :id => '64' } + ) + assert_routing( + { :method => 'get', :path => "/issues/64.pdf" }, + { :controller => 'issues', :action => 'show', :id => '64', + :format => 'pdf' } + ) + assert_routing( + { :method => 'get', :path => "/issues/64.atom" }, + { :controller => 'issues', :action => 'show', :id => '64', + :format => 'atom' } + ) + assert_routing( + { :method => 'get', :path => "/issues/64.xml" }, + { :controller => 'issues', :action => 'show', :id => '64', + :format => 'xml' } + ) + assert_routing( + { :method => 'post', :path => "/issues.xml" }, + { :controller => 'issues', :action => 'create', :format => 'xml' } + ) + assert_routing( + { :method => 'get', :path => "/issues/64/edit" }, + { :controller => 'issues', :action => 'edit', :id => '64' } + ) + assert_routing( + { :method => 'put', :path => "/issues/1.xml" }, + { :controller => 'issues', :action => 'update', :id => '1', + :format => 'xml' } + ) + assert_routing( + { :method => 'delete', :path => "/issues/1.xml" }, + { :controller => 'issues', :action => 'destroy', :id => '1', + :format => 'xml' } + ) + end + + def test_issues_rest_actions_scoped_under_project + assert_routing( + { :method => 'get', :path => "/projects/23/issues" }, + { :controller => 'issues', :action => 'index', :project_id => '23' } + ) + 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' } + ) + assert_routing( + { :method => 'get', :path => "/projects/23/issues.xml" }, + { :controller => 'issues', :action => 'index', :project_id => '23', + :format => 'xml' } + ) + assert_routing( + { :method => 'post', :path => "/projects/23/issues" }, + { :controller => 'issues', :action => 'create', :project_id => '23' } + ) + assert_routing( + { :method => 'get', :path => "/projects/23/issues/new" }, + { :controller => 'issues', :action => 'new', :project_id => '23' } + ) + end + + def test_issues_form_update + ["post", "put"].each do |method| + assert_routing( + { :method => method, :path => "/projects/23/issues/update_form" }, + { :controller => 'issues', :action => 'update_form', :project_id => '23' } + ) + end + end + + def test_issues_extra_actions + assert_routing( + { :method => 'get', :path => "/projects/23/issues/64/copy" }, + { :controller => 'issues', :action => 'new', :project_id => '23', + :copy_from => '64' } + ) + # For updating the bulk edit form + ["get", "post"].each do |method| + assert_routing( + { :method => method, :path => "/issues/bulk_edit" }, + { :controller => 'issues', :action => 'bulk_edit' } + ) + end + assert_routing( + { :method => 'post', :path => "/issues/bulk_update" }, + { :controller => 'issues', :action => 'bulk_update' } + ) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/53/539f4d98713d0fa4ed6fd83111963b94c1350c7c.svn-base --- a/.svn/pristine/53/539f4d98713d0fa4ed6fd83111963b94c1350c7c.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,122 +0,0 @@ - - \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/53/53a91881211d028f264cd0f5ef639191d1262e38.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/53/53a91881211d028f264cd0f5ef639191d1262e38.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,59 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../../test_helper', __FILE__) + +class Redmine::AccessControlTest < ActiveSupport::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 + + def test_read_action_should_return_true_for_read_actions + assert_equal true, @access_module.read_action?(:view_project) + assert_equal true, @access_module.read_action?(:controller => 'projects', :action => 'show') + end + + def test_read_action_should_return_false_for_update_actions + assert_equal false, @access_module.read_action?(:edit_project) + assert_equal false, @access_module.read_action?(:controller => 'projects', :action => 'edit') + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/53/53ae85a75b984c31fad63866e54e9b00f6355b8c.svn-base --- a/.svn/pristine/53/53ae85a75b984c31fad63866e54e9b00f6355b8c.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1008 +0,0 @@ -# Greek translations for Ruby on Rails -# by Vaggelis Typaldos (vtypal@gmail.com), Spyros Raptis (spirosrap@gmail.com) - -el: - direction: ltr - date: - formats: - # Use the strftime parameters for formats. - # When no format has been given, it uses default. - # You can provide other formats here if you like! - default: "%m/%d/%Y" - short: "%b %d" - long: "%B %d, %Y" - - day_names: [ΚυÏιακή, ΔευτέÏα, ΤÏίτη, ΤετάÏτη, Πέμπτη, ΠαÏασκευή, Σάββατο] - abbr_day_names: [ΚυÏ, Δευ, ΤÏι, Τετ, Πεμ, ΠαÏ, Σαβ] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, ΙανουάÏιος, ΦεβÏουάÏιος, ΜάÏτιος, ΑπÏίλιος, Μάϊος, ΙοÏνιος, ΙοÏλιος, ΑÏγουστος, ΣεπτέμβÏιος, ΟκτώβÏιος, ÎοέμβÏιος, ΔεκέμβÏιος] - abbr_month_names: [~, Ιαν, Φεβ, ΜαÏ, ΑπÏ, Μαϊ, Ιον, Ιολ, Αυγ, Σεπ, Οκτ, Îοε, Δεκ] - # Used in date_select and datime_select. - order: - - :year - - :month - - :day - - time: - formats: - default: "%m/%d/%Y %I:%M %p" - time: "%I:%M %p" - short: "%d %b %H:%M" - long: "%B %d, %Y %H:%M" - am: "πμ" - pm: "μμ" - - datetime: - distance_in_words: - half_a_minute: "μισό λεπτό" - less_than_x_seconds: - one: "λιγότεÏο από 1 δευτεÏόλεπτο" - other: "λιγότεÏο από %{count} δευτεÏόλεπτα" - x_seconds: - one: "1 δευτεÏόλεπτο" - other: "%{count} δευτεÏόλεπτα" - less_than_x_minutes: - one: "λιγότεÏο από ένα λεπτό" - other: "λιγότεÏο από %{count} λεπτά" - x_minutes: - one: "1 λεπτό" - other: "%{count} λεπτά" - about_x_hours: - one: "πεÏίπου 1 ÏŽÏα" - other: "πεÏίπου %{count} ÏŽÏες" - x_days: - one: "1 ημέÏα" - other: "%{count} ημέÏες" - about_x_months: - one: "πεÏίπου 1 μήνα" - other: "πεÏίπου %{count} μήνες" - x_months: - one: "1 μήνα" - other: "%{count} μήνες" - about_x_years: - one: "πεÏίπου 1 χÏόνο" - other: "πεÏίπου %{count} χÏόνια" - over_x_years: - one: "πάνω από 1 χÏόνο" - other: "πάνω από %{count} χÏόνια" - almost_x_years: - one: "almost 1 year" - other: "almost %{count} years" - - number: - format: - separator: "." - delimiter: "" - precision: 3 - human: - format: - precision: 1 - delimiter: "" - storage_units: - format: "%n %u" - units: - kb: KB - tb: TB - gb: GB - byte: - one: Byte - other: Bytes - mb: MB - -# Used in array.to_sentence. - support: - array: - sentence_connector: "and" - skip_last_comma: false - - activerecord: - errors: - template: - header: - one: "1 error prohibited this %{model} from being saved" - other: "%{count} errors prohibited this %{model} from being saved" - messages: - inclusion: "δεν πεÏιέχεται στη λίστα" - exclusion: "έχει κατοχυÏωθεί" - invalid: "είναι άκυÏο" - confirmation: "δεν αντιστοιχεί με την επιβεβαίωση" - accepted: "Ï€Ïέπει να γίνει αποδοχή" - empty: "δε μποÏεί να είναι άδειο" - blank: "δε μποÏεί να είναι κενό" - too_long: "έχει πολλοÏÏ‚ (μέγ.επιτÏ. %{count} χαÏακτήÏες)" - too_short: "έχει λίγους (ελάχ.επιτÏ. %{count} χαÏακτήÏες)" - wrong_length: "δεν είναι σωστός ο αÏιθμός χαÏακτήÏων (Ï€Ïέπει να έχει %{count} χαÏακτήÏες)" - taken: "έχει ήδη κατοχυÏωθεί" - not_a_number: "δεν είναι αÏιθμός" - not_a_date: "δεν είναι σωστή ημεÏομηνία" - greater_than: "Ï€Ïέπει να είναι μεγαλÏτεÏο από %{count}" - greater_than_or_equal_to: "Ï€Ïέπει να είναι μεγαλÏτεÏο από ή ίσο με %{count}" - equal_to: "Ï€Ïέπει να είναι ίσον με %{count}" - less_than: "Ï€Ïέπει να είναι μικÏότεÏη από %{count}" - less_than_or_equal_to: "Ï€Ïέπει να είναι μικÏότεÏο από ή ίσο με %{count}" - odd: "Ï€Ïέπει να είναι μονός" - even: "Ï€Ïέπει να είναι ζυγός" - greater_than_start_date: "Ï€Ïέπει να είναι αÏγότεÏα από την ημεÏομηνία έναÏξης" - not_same_project: "δεν ανήκει στο ίδιο έÏγο" - circular_dependency: "Αυτή η σχέση θα δημιουÏγήσει κυκλικές εξαÏτήσεις" - cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" - - actionview_instancetag_blank_option: ΠαÏακαλώ επιλέξτε - - 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_first_day_of_week: '7' - - notice_account_updated: Ο λογαÏιασμός ενημεÏώθηκε επιτυχώς. - notice_account_invalid_creditentials: ΆκυÏο όνομα χÏήστη ή ÎºÏ‰Î´Î¹ÎºÎ¿Ï Ï€Ïόσβασης - notice_account_password_updated: Ο κωδικός Ï€Ïόσβασης ενημεÏώθηκε επιτυχώς. - notice_account_wrong_password: Λάθος κωδικός Ï€Ïόσβασης - notice_account_register_done: Ο λογαÏιασμός δημιουÏγήθηκε επιτυχώς. Για να ενεÏγοποιήσετε το λογαÏιασμό σας, πατήστε το σÏνδεσμο που σας έχει αποσταλεί με email. - notice_account_unknown_email: Άγνωστος χÏήστης. - notice_can_t_change_password: Αυτός ο λογαÏιασμός χÏησιμοποιεί εξωτεÏική πηγή πιστοποίησης. Δεν είναι δυνατόν να αλλάξετε τον κωδικό Ï€Ïόσβασης. - notice_account_lost_email_sent: Σας έχει αποσταλεί email με οδηγίες για την επιλογή νέου ÎºÏ‰Î´Î¹ÎºÎ¿Ï Ï€Ïόσβασης. - notice_account_activated: Ο λογαÏιασμός σας έχει ενεÏγοποιηθεί. ΤώÏα μποÏείτε να συνδεθείτε. - notice_successful_create: Επιτυχής δημιουÏγία. - notice_successful_update: Επιτυχής ενημέÏωση. - notice_successful_delete: Επιτυχής διαγÏαφή. - notice_successful_connection: Επιτυχής σÏνδεση. - notice_file_not_found: Η σελίδα που ζητήσατε δεν υπάÏχει ή έχει αφαιÏεθεί. - notice_locking_conflict: Τα δεδομένα έχουν ενημεÏωθεί από άλλο χÏήστη. - notice_not_authorized: Δεν έχετε δικαίωμα Ï€Ïόσβασης σε αυτή τη σελίδα. - notice_email_sent: "Ένα μήνυμα ηλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï Ï„Î±Ï‡Ï…Î´Ïομείου εστάλη στο %{value}" - notice_email_error: "Σφάλμα κατά την αποστολή του μηνÏματος στο (%{value})" - notice_feeds_access_key_reseted: Έγινε επαναφοÏά στο κλειδί Ï€Ïόσβασης RSS. - notice_failed_to_save_issues: "Αποτυχία αποθήκευσης %{count} θεμα(των) από τα %{total} επιλεγμένα: %{ids}." - notice_no_issue_selected: "Κανένα θέμα δεν είναι επιλεγμένο! ΠαÏακαλοÏμε, ελέγξτε τα θέματα που θέλετε να επεξεÏγαστείτε." - notice_account_pending: "Ο λογαÏιασμός σας έχει δημιουÏγηθεί και είναι σε στάδιο έγκÏισης από τον διαχειÏιστή." - notice_default_data_loaded: Οι Ï€Ïοεπιλεγμένες Ïυθμίσεις φοÏτώθηκαν επιτυχώς. - notice_unable_delete_version: ΑδÏνατον να διαγÏαφεί η έκδοση. - - error_can_t_load_default_data: "Οι Ï€Ïοεπιλεγμένες Ïυθμίσεις δεν μπόÏεσαν να φοÏτωθοÏν:: %{value}" - error_scm_not_found: "Η εγγÏαφή ή η αναθεώÏηση δεν βÏέθηκε στο αποθετήÏιο." - error_scm_command_failed: "ΠαÏουσιάστηκε σφάλμα κατά την Ï€Ïοσπάθεια Ï€Ïόσβασης στο αποθετήÏιο: %{value}" - error_scm_annotate: "Η καταχώÏιση δεν υπάÏχει ή δεν μποÏεί να σχολιαστεί." - error_issue_not_found_in_project: 'Το θέμα δεν βÏέθηκε ή δεν ανήκει σε αυτό το έÏγο' - error_no_tracker_in_project: 'Δεν υπάÏχει ανιχνευτής για αυτό το έÏγο. ΠαÏακαλώ ελέγξτε τις Ïυθμίσεις του έÏγου.' - error_no_default_issue_status: 'Δεν έχει οÏιστεί η Ï€Ïοεπιλογή κατάστασης θεμάτων. ΠαÏακαλώ ελέγξτε τις Ïυθμίσεις σας (Μεταβείτε στην "ΔιαχείÏιση -> Κατάσταση θεμάτων").' - - warning_attachments_not_saved: "%{count} αÏχείο(α) δε μποÏοÏν να αποθηκευτοÏν." - - mail_subject_lost_password: "Ο κωδικός σας %{value}" - mail_body_lost_password: 'Για να αλλάξετε τον κωδικό Ï€Ïόσβασης, πατήστε τον ακόλουθο σÏνδεσμο:' - mail_subject_register: "ΕνεÏγοποίηση του λογαÏÎ¹Î±ÏƒÎ¼Î¿Ï Ï‡Ïήστη %{value} " - mail_body_register: 'Για να ενεÏγοποιήσετε το λογαÏιασμό σας, επιλέξτε τον ακόλουθο σÏνδεσμο:' - mail_body_account_information_external: "ΜποÏείτε να χÏησιμοποιήσετε τον λογαÏιασμό %{value} για να συνδεθείτε." - mail_body_account_information: ΠληÏοφοÏίες του λογαÏÎ¹Î±ÏƒÎ¼Î¿Ï ÏƒÎ±Ï‚ - mail_subject_account_activation_request: "αίτημα ενεÏγοποίησης λογαÏÎ¹Î±ÏƒÎ¼Î¿Ï %{value}" - mail_body_account_activation_request: "'Ένας νέος χÏήστης (%{value}) έχει εγγÏαφεί. Ο λογαÏιασμός είναι σε στάδιο αναμονής της έγκÏισης σας:" - mail_subject_reminder: "%{count} θέμα(τα) με Ï€Ïοθεσμία στις επόμενες %{days} ημέÏες" - mail_body_reminder: "%{count}θέμα(τα) που έχουν ανατεθεί σε σας, με Ï€Ïοθεσμία στις επόμενες %{days} ημέÏες:" - mail_subject_wiki_content_added: "'Ï€Ïοστέθηκε η σελίδα wiki %{id}' " - mail_body_wiki_content_added: "Η σελίδα wiki '%{id}' Ï€Ïοστέθηκε από τον %{author}." - mail_subject_wiki_content_updated: "'ενημεÏώθηκε η σελίδα wiki %{id}' " - mail_body_wiki_content_updated: "Η σελίδα wiki '%{id}' ενημεÏώθηκε από τον %{author}." - - gui_validation_error: 1 σφάλμα - gui_validation_error_plural: "%{count} σφάλματα" - - field_name: Όνομα - field_description: ΠεÏιγÏαφή - field_summary: Συνοπτικά - field_is_required: Απαιτείται - field_firstname: Όνομα - field_lastname: Επώνυμο - field_mail: Email - 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: Κανονική παÏάσταση - field_min_length: Ελάχιστο μήκος - field_max_length: Μέγιστο μήκος - field_value: Τιμή - field_category: ΚατηγοÏία - field_title: Τίτλος - field_project: ΈÏγο - field_issue: Θέμα - field_status: Κατάσταση - field_notes: Σημειώσεις - field_is_closed: Κλειστά θέματα - field_is_default: ΠÏοεπιλεγμένη τιμή - field_tracker: Ανιχνευτής - field_subject: Θέμα - field_due_date: ΠÏοθεσμία - field_assigned_to: Ανάθεση σε - field_priority: ΠÏοτεÏαιότητα - field_fixed_version: Στόχος έκδοσης - field_user: ΧÏήστης - field_role: Ρόλος - field_homepage: ΑÏχική σελίδα - field_is_public: Δημόσιο - field_parent: ΕπιμέÏους έÏγο του - field_is_in_roadmap: ΠÏοβολή θεμάτων στο χάÏτη ποÏείας - field_login: Όνομα χÏήστη - field_mail_notification: Ειδοποιήσεις email - field_admin: ΔιαχειÏιστής - field_last_login_on: Τελευταία σÏνδεση - field_language: Γλώσσα - field_effective_date: ΗμεÏομηνία - field_password: Κωδικός Ï€Ïόσβασης - field_new_password: Îέος κωδικός Ï€Ïόσβασης - field_password_confirmation: Επιβεβαίωση - field_version: Έκδοση - field_type: ΤÏπος - field_host: Κόμβος - field_port: ΘÏÏα - field_account: ΛογαÏιασμός - field_base_dn: Βάση DN - field_attr_login: Ιδιότητα εισόδου - field_attr_firstname: Ιδιότητα ονόματος - field_attr_lastname: Ιδιότητα επωνÏμου - field_attr_mail: Ιδιότητα email - field_onthefly: Άμεση δημιουÏγία χÏήστη - field_start_date: Εκκίνηση - field_done_ratio: "% επιτεÏχθη" - field_auth_source: ΤÏόπος πιστοποίησης - field_hide_mail: ΑπόκÏυψη διεÏθυνσης email - field_comments: Σχόλιο - field_url: URL - field_start_page: ΠÏώτη σελίδα - field_subproject: ΕπιμέÏους έÏγο - field_hours: ÎÏες - field_activity: ΔÏαστηÏιότητα - field_spent_on: ΗμεÏομηνία - field_identifier: Στοιχείο αναγνώÏισης - field_is_filter: ΧÏήση ως φίλτÏο - field_issue_to: Σχετικά θέματα - field_delay: ΚαθυστέÏηση - field_assignable: Θέματα που μποÏοÏν να ανατεθοÏν σε αυτό το Ïόλο - field_redirect_existing_links: ΑνακατεÏθυνση των Ï„Ïεχόντων συνδέσμων - field_estimated_hours: Εκτιμώμενος χÏόνος - field_column_names: Στήλες - field_time_zone: ΩÏιαία ζώνη - field_searchable: ΕÏευνήσιμο - field_default_value: ΠÏοκαθοÏισμένη τιμή - field_comments_sorting: ΠÏοβολή σχολίων - field_parent_title: Γονική σελίδα - field_editable: ΕπεξεÏγάσιμο - field_watcher: ΠαÏατηÏητής - field_identity_url: OpenID URL - field_content: ΠεÏιεχόμενο - field_group_by: Ομαδικά αποτελέσματα από - - setting_app_title: Τίτλος εφαÏμογής - setting_app_subtitle: Υπότιτλος εφαÏμογής - setting_welcome_text: Κείμενο υποδοχής - setting_default_language: ΠÏοεπιλεγμένη γλώσσα - setting_login_required: Απαιτείται πιστοποίηση - setting_self_registration: Αυτο-εγγÏαφή - setting_attachment_max_size: Μέγ. μέγεθος συνημμένου - setting_issues_export_limit: Θέματα πεÏιοÏÎ¹ÏƒÎ¼Î¿Ï ÎµÎ¾Î±Î³Ï‰Î³Î®Ï‚ - setting_mail_from: Μετάδοση διεÏθυνσης email - setting_bcc_recipients: Αποδέκτες κÏυφής κοινοποίησης (bcc) - setting_plain_text_mail: Email Î±Ï€Î»Î¿Ï ÎºÎµÎ¹Î¼Î­Î½Î¿Ï… (όχι HTML) - setting_host_name: Όνομα κόμβου και διαδÏομή - setting_text_formatting: ΜοÏφοποίηση κειμένου - setting_wiki_compression: Συμπίεση ιστοÏÎ¹ÎºÎ¿Ï wiki - setting_feeds_limit: Feed πεÏιοÏÎ¹ÏƒÎ¼Î¿Ï Ï€ÎµÏιεχομένου - setting_default_projects_public: Τα νέα έÏγα έχουν Ï€Ïοεπιλεγεί ως δημόσια - setting_autofetch_changesets: Αυτόματη λήψη commits - setting_sys_api_enabled: ΕνεÏγοποίηση WS για διαχείÏιση αποθετηÏίου - setting_commit_ref_keywords: ΑναφοÏά σε λέξεις-κλειδιά - setting_commit_fix_keywords: ΚαθοÏισμός σε λέξεις-κλειδιά - setting_autologin: Αυτόματη σÏνδεση - setting_date_format: ΜοÏφή ημεÏομηνίας - setting_time_format: ΜοÏφή ÏŽÏας - setting_cross_project_issue_relations: ΕπιτÏέψτε συσχετισμό θεμάτων σε διασταÏÏωση-έÏγων - setting_issue_list_default_columns: ΠÏοκαθοÏισμένες εμφανιζόμενες στήλες στη λίστα θεμάτων - setting_emails_footer: Υποσέλιδο στα email - setting_protocol: ΠÏωτόκολο - setting_per_page_options: Αντικείμενα ανά σελίδα επιλογών - setting_user_format: ΜοÏφή εμφάνισης χÏηστών - setting_activity_days_default: ΗμέÏες που εμφανίζεται στη δÏαστηÏιότητα έÏγου - setting_display_subprojects_issues: Εμφάνιση από Ï€Ïοεπιλογή θεμάτων επιμέÏους έÏγων στα κÏÏια έÏγα - setting_enabled_scm: ΕνεÏγοποίηση SCM - setting_mail_handler_api_enabled: ΕνεÏγοποίηση WS για εισεÏχόμενα email - setting_mail_handler_api_key: κλειδί API - setting_sequential_project_identifiers: ΔημιουÏγία διαδοχικών αναγνωÏιστικών έÏγου - setting_gravatar_enabled: ΧÏήση Gravatar εικονιδίων χÏηστών - setting_diff_max_lines_displayed: Μεγ.αÏιθμός εμφάνισης γÏαμμών diff - setting_file_max_size_displayed: Μεγ.μέγεθος των αÏχείων Î±Ï€Î»Î¿Ï ÎºÎµÎ¹Î¼Î­Î½Î¿Ï… που εμφανίζονται σε σειÏά - setting_repository_log_display_limit: Μέγιστος αÏιθμός αναθεωÏήσεων που εμφανίζονται στο ιστοÏικό αÏχείου - setting_openid: ΕπιτÏέψτε συνδέσεις OpenID και εγγÏαφή - setting_password_min_length: Ελάχιστο μήκος ÎºÏ‰Î´Î¹ÎºÎ¿Ï Ï€Ïόσβασης - setting_new_project_user_role_id: Απόδοση Ïόλου σε χÏήστη μη-διαχειÏιστή όταν δημιουÏγεί ένα έÏγο - - permission_add_project: ΔημιουÏγία έÏγου - 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: ΠÏοβολή διαγÏάμματος 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: ΠÏοβολή changesets - permission_commit_access: ΠÏόσβαση commit - 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: Wiki - project_module_repository: ΑποθετήÏιο - project_module_boards: Πίνακες συζητήσεων - - label_user: ΧÏήστης - label_user_plural: ΧÏήστες - label_user_new: Îέος ΧÏήστης - label_project: ΈÏγο - label_project_new: Îέο έÏγο - label_project_plural: ΈÏγα - label_x_projects: - zero: κανένα έÏγο - one: 1 έÏγο - other: "%{count} έÏγα" - label_project_all: Όλα τα έÏγα - label_project_latest: Τελευταία έÏγα - label_issue: Θέμα - label_issue_new: Îέο θέμα - label_issue_plural: Θέματα - label_issue_view_all: ΠÏοβολή όλων των θεμάτων - label_issues_by: "Θέματα του %{value}" - label_issue_added: Το θέμα Ï€Ïοστέθηκε - label_issue_updated: Το θέμα ενημεÏώθηκε - label_document: ΈγγÏαφο - label_document_new: Îέο έγγÏαφο - label_document_plural: ΈγγÏαφα - label_document_added: ΈγγÏαφο Ï€Ïοστέθηκε - label_role: Ρόλος - label_role_plural: Ρόλοι - label_role_new: Îέος Ïόλος - label_role_and_permissions: Ρόλοι και άδειες - label_member: Μέλος - label_member_new: Îέο μέλος - label_member_plural: Μέλη - label_tracker: Ανιχνευτής - label_tracker_plural: Ανιχνευτές - label_tracker_new: Îέος Ανιχνευτής - label_workflow: Ροή εÏγασίας - label_issue_status: Κατάσταση θέματος - label_issue_status_plural: Κατάσταση θέματος - label_issue_status_new: Îέα κατάσταση - label_issue_category: ΚατηγοÏία θέματος - label_issue_category_plural: ΚατηγοÏίες θεμάτων - label_issue_category_new: Îέα κατηγοÏία - label_custom_field: ΠÏοσαÏμοσμένο πεδίο - label_custom_field_plural: ΠÏοσαÏμοσμένα πεδία - label_custom_field_new: Îέο Ï€ÏοσαÏμοσμένο πεδίο - label_enumerations: ΑπαÏιθμήσεις - label_enumeration_new: Îέα τιμή - label_information: ΠληÏοφοÏία - label_information_plural: ΠληÏοφοÏίες - label_please_login: ΠαÏακαλώ συνδεθείτε - label_register: ΕγγÏαφή - label_login_with_open_id_option: ή συνδεθείτε με OpenID - 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_registered_on: ΕγγÏάφηκε την - label_activity: ΔÏαστηÏιότητα - label_overall_activity: Συνολική δÏαστηÏιότητα - label_user_activity: "δÏαστηÏιότητα του %{value}" - label_new: Îέο - label_logged_as: ΣÏνδεδεμένος ως - label_environment: ΠεÏιβάλλον - label_authentication: Πιστοποίηση - label_auth_source: ΤÏόπος πιστοποίησης - label_auth_source_new: Îέος Ï„Ïόπος πιστοποίησης - label_auth_source_plural: ΤÏόποι πιστοποίησης - label_subproject_plural: ΕπιμέÏους έÏγα - label_and_its_subprojects: "%{value} και τα επιμέÏους έÏγα του" - label_min_max_length: Ελάχ. - Μέγ. μήκος - label_list: Λίστα - label_date: ΗμεÏομηνία - label_integer: ΑκέÏαιος - label_float: ΑÏιθμός κινητής υποδιαστολής - label_boolean: Λογικός - label_string: Κείμενο - label_text: ΜακÏοσκελές κείμενο - label_attribute: Ιδιότητα - label_attribute_plural: Ιδιότητες - label_download: "%{count} ΜεταφόÏτωση" - label_download_plural: "%{count} ΜεταφοÏτώσεις" - 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_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_x_open_issues_abbr_on_total: - zero: 0 ανοικτά / %{total} - one: 1 ανοικτό / %{total} - other: "%{count} ανοικτά / %{total}" - label_x_open_issues_abbr: - zero: 0 ανοικτά - one: 1 ανοικτό - other: "%{count} ανοικτά" - label_x_closed_issues_abbr: - zero: 0 κλειστά - one: 1 κλειστό - other: "%{count} κλειστά" - 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: "Τελευταίες %{count} αλλαγές" - label_change_view_all: ΠÏοβολή όλων των αλλαγών - label_personalize_page: ΠÏοσαÏμογή σελίδας - label_comment: Σχόλιο - label_comment_plural: Σχόλια - label_x_comments: - zero: δεν υπάÏχουν σχόλια - one: 1 σχόλιο - other: "%{count} σχόλια" - label_comment_add: ΠÏοσθήκη σχολίου - label_comment_added: Τα σχόλια Ï€Ïοστέθηκαν - label_comment_delete: ΔιαγÏαφή σχολίων - label_query: ΠÏοσαÏμοσμένη αναζήτηση - label_query_plural: ΠÏοσαÏμοσμένες αναζητήσεις - label_query_new: Îέα αναζήτηση - label_filter_add: ΠÏοσθήκη φίλτÏου - label_filter_plural: ΦίλτÏα - label_equals: είναι - label_not_equals: δεν είναι - label_in_less_than: μικÏότεÏο από - label_in_more_than: πεÏισσότεÏο από - label_greater_or_equal: '>=' - label_less_or_equal: '<=' - label_in: σε - label_today: σήμεÏα - label_all_time: συνέχεια - label_yesterday: χθες - label_this_week: αυτή την εβδομάδα - label_last_week: την Ï€ÏοηγοÏμενη εβδομάδα - label_last_n_days: "τελευταίες %{count} μέÏες" - label_this_month: αυτό το μήνα - label_last_month: τον Ï€ÏοηγοÏμενο μήνα - label_this_year: αυτό το χÏόνο - label_date_range: ΧÏονικό διάστημα - label_less_than_ago: σε λιγότεÏο από ημέÏες Ï€Ïιν - label_more_than_ago: σε πεÏισσότεÏο από ημέÏες Ï€Ïιν - label_ago: ημέÏες Ï€Ïιν - label_contains: πεÏιέχει - label_not_contains: δεν πεÏιέχει - label_day_plural: μέÏες - label_repository: ΑποθετήÏιο - label_repository_plural: ΑποθετήÏια - label_browse: Πλοήγηση - label_modification: "%{count} Ï„Ïοποποίηση" - label_modification_plural: "%{count} Ï„Ïοποποιήσεις" - label_branch: Branch - label_tag: Tag - label_revision: ΑναθεώÏηση - label_revision_plural: ΑναθεωÏήσεις - label_associated_revisions: ΣυνεταιÏικές αναθεωÏήσεις - label_added: Ï€Ïοστέθηκε - label_modified: Ï„Ïοποποιήθηκε - label_copied: αντιγÏάφηκε - label_renamed: μετονομάστηκε - label_deleted: διαγÏάφηκε - label_latest_revision: Τελευταία αναθεώÏιση - label_latest_revision_plural: Τελευταίες αναθεωÏήσεις - label_view_revisions: ΠÏοβολή αναθεωÏήσεων - label_view_all_revisions: ΠÏοβολή όλων των αναθεωÏήσεων - label_max_size: Μέγιστο μέγεθος - label_sort_highest: Μετακίνηση στην κοÏυφή - label_sort_higher: Μετακίνηση Ï€Ïος τα πάνω - label_sort_lower: Μετακίνηση Ï€Ïος τα κάτω - label_sort_lowest: Μετακίνηση στο κατώτατο μέÏος - label_roadmap: ΧάÏτης ποÏείας - label_roadmap_due_in: "ΠÏοθεσμία σε %{value}" - label_roadmap_overdue: "%{value} καθυστεÏημένο" - label_roadmap_no_issues: Δεν υπάÏχουν θέματα για αυτή την έκδοση - label_search: Αναζήτηση - label_result_plural: Αποτελέσματα - label_all_words: Όλες οι λέξεις - label_wiki: Wiki - label_wiki_edit: ΕπεξεÏγασία wiki - label_wiki_edit_plural: ΕπεξεÏγασία wiki - label_wiki_page: Σελίδα Wiki - label_wiki_page_plural: Σελίδες Wiki - label_index_by_title: Δείκτης ανά τίτλο - label_index_by_date: Δείκτης ανά ημεÏομηνία - label_current_version: ΤÏέχουσα έκδοση - label_preview: ΠÏοεπισκόπηση - label_feed_plural: Feeds - label_changes_details: ΛεπτομέÏειες όλων των αλλαγών - label_issue_tracking: Ανίχνευση θεμάτων - label_spent_time: Δαπανημένος χÏόνος - label_f_hour: "%{value} ÏŽÏα" - label_f_hour_plural: "%{value} ÏŽÏες" - label_time_tracking: Ανίχνευση χÏόνου - label_change_plural: Αλλαγές - label_statistics: Στατιστικά - label_commits_per_month: Commits ανά μήνα - label_commits_per_author: Commits ανά συγγÏαφέα - label_view_diff: ΠÏοβολή διαφοÏών - label_diff_inline: σε σειÏά - label_diff_side_by_side: αντικÏυστά - label_options: Επιλογές - label_copy_workflow_from: ΑντιγÏαφή Ïοής εÏγασίας από - label_permissions_report: Συνοπτικός πίνακας αδειών - label_watched_issues: Θέματα υπό παÏακολοÏθηση - label_related_issues: Σχετικά θέματα - label_applied_status: ΕφαÏμογή κατάστασης - label_loading: ΦοÏτώνεται... - label_relation_new: Îέα συσχέτιση - label_relation_delete: ΔιαγÏαφή συσχέτισης - label_relates_to: σχετικό με - label_duplicates: αντίγÏαφα - label_duplicated_by: αντιγÏάφηκε από - label_blocks: φÏαγές - label_blocked_by: φÏαγή από τον - label_precedes: Ï€Ïοηγείται - label_follows: ακολουθεί - label_end_to_start: από το τέλος στην αÏχή - label_end_to_end: από το τέλος στο τέλος - label_start_to_start: από την αÏχή στην αÏχή - label_start_to_end: από την αÏχή στο τέλος - label_stay_logged_in: ΠαÏαμονή σÏνδεσης - label_disabled: απενεÏγοποιημένη - label_show_completed_versions: ΠÏοβολή ολοκληÏωμένων εκδόσεων - label_me: εγώ - label_board: ΦόÏουμ - label_board_new: Îέο φόÏουμ - label_board_plural: ΦόÏουμ - label_topic_plural: Θέματα - label_message_plural: ΜηνÏματα - label_message_last: Τελευταίο μήνυμα - label_message_new: Îέο μήνυμα - label_message_posted: Το μήνυμα Ï€Ïοστέθηκε - label_reply_plural: Απαντήσεις - label_send_information: Αποστολή πληÏοφοÏιών λογαÏÎ¹Î±ÏƒÎ¼Î¿Ï ÏƒÏ„Î¿ χÏήστη - label_year: Έτος - label_month: Μήνας - label_week: Εβδομάδα - label_date_from: Από - label_date_to: Έως - label_language_based: Με βάση τη γλώσσα του χÏήστη - label_sort_by: "Ταξινόμηση ανά %{value}" - label_send_test_email: Αποστολή Î´Î¿ÎºÎ¹Î¼Î±ÏƒÏ„Î¹ÎºÎ¿Ï email - label_feeds_access_key_created_on: "το κλειδί Ï€Ïόσβασης RSS δημιουÏγήθηκε Ï€Ïιν από %{value}" - label_module_plural: Μονάδες - label_added_time_by: "ΠÏοστέθηκε από τον %{author} Ï€Ïιν από %{age}" - label_updated_time_by: "ΕνημεÏώθηκε από τον %{author} Ï€Ïιν από %{age}" - label_updated_time: "ΕνημεÏώθηκε Ï€Ïιν από %{value}" - 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_no_self_notified: "Δεν θέλω να ειδοποιοÏμαι για τις δικές μου αλλαγές" - label_registration_activation_by_email: ενεÏγοποίηση λογαÏÎ¹Î±ÏƒÎ¼Î¿Ï Î¼Îµ email - label_registration_manual_activation: χειÏοκίνητη ενεÏγοποίηση λογαÏÎ¹Î±ÏƒÎ¼Î¿Ï - label_registration_automatic_activation: αυτόματη ενεÏγοποίηση λογαÏÎ¹Î±ÏƒÎ¼Î¿Ï - label_display_per_page: "Ανά σελίδα: %{value}" - label_age: Ηλικία - label_change_properties: Αλλαγή ιδιοτήτων - label_general: Γενικά - label_more: ΠεÏισσότεÏα - label_scm: SCM - label_plugins: Plugins - label_ldap_authentication: Πιστοποίηση LDAP - label_downloads_abbr: Μ/Φ - label_optional_description: ΠÏοαιÏετική πεÏιγÏαφή - label_add_another_file: ΠÏοσθήκη άλλου αÏχείου - label_preferences: ΠÏοτιμήσεις - label_chronological_order: Κατά χÏονολογική σειÏά - label_reverse_chronological_order: Κατά αντίστÏοφη χÏονολογική σειÏά - label_planning: Σχεδιασμός - label_incoming_emails: ΕισεÏχόμενα email - label_generate_key: ΔημιουÏγία ÎºÎ»ÎµÎ¹Î´Î¹Î¿Ï - label_issue_watchers: ΠαÏατηÏητές - label_example: ΠαÏάδειγμα - label_display: ΠÏοβολή - label_sort: Ταξινόμηση - label_ascending: ΑÏξουσα - label_descending: Φθίνουσα - label_date_from_to: Από %{start} έως %{end} - label_wiki_content_added: Η σελίδα Wiki Ï€Ïοστέθηκε - label_wiki_content_updated: Η σελίδα Wiki ενημεÏώθηκε - - button_login: ΣÏνδεση - button_submit: Αποστολή - button_save: Αποθήκευση - button_check_all: Επιλογή όλων - button_uncheck_all: Αποεπιλογή όλων - button_delete: ΔιαγÏαφή - button_create: ΔημιουÏγία - button_create_and_continue: ΔημιουÏγία και συνέχεια - button_test: Τεστ - button_edit: ΕπεξεÏγασία - button_add: ΠÏοσθήκη - button_change: Αλλαγή - button_apply: ΕφαÏμογή - button_clear: ΚαθαÏισμός - button_lock: Κλείδωμα - button_unlock: Ξεκλείδωμα - button_download: ΜεταφόÏτωση - button_list: Λίστα - button_view: ΠÏοβολή - button_move: Μετακίνηση - button_back: Πίσω - button_cancel: ΑκÏÏωση - button_activate: ΕνεÏγοποίηση - button_sort: Ταξινόμηση - button_log_time: ΙστοÏικό χÏόνου - button_rollback: ΕπαναφοÏά σε αυτή την έκδοση - button_watch: ΠαÏακολοÏθηση - button_unwatch: ΑναίÏεση παÏακολοÏθησης - button_reply: Απάντηση - button_archive: ΑÏχειοθέτηση - button_unarchive: ΑναίÏεση αÏχειοθέτησης - button_reset: ΕπαναφοÏά - button_rename: Μετονομασία - button_change_password: Αλλαγή ÎºÏ‰Î´Î¹ÎºÎ¿Ï Ï€Ïόσβασης - button_copy: ΑντιγÏαφή - button_annotate: Σχολιασμός - button_update: ΕνημέÏωση - button_configure: ΡÏθμιση - button_quote: ΠαÏάθεση - - status_active: ενεÏγό(Ï‚)/ή - status_registered: εγεγγÏαμμένο(Ï‚)/η - status_locked: κλειδωμένο(Ï‚)/η - - text_select_mail_notifications: Επιλογή ενεÏγειών για τις οποίες θα Ï€Ïέπει να αποσταλεί ειδοποίηση με email. - text_regexp_info: eg. ^[A-Z0-9]+$ - text_min_max_length_info: 0 σημαίνει ότι δεν υπάÏχουν πεÏιοÏισμοί - text_project_destroy_confirmation: Είστε σίγουÏοι ότι θέλετε να διαγÏάψετε αυτό το έÏγο και τα σχετικά δεδομένα του; - text_subprojects_destroy_warning: "Επίσης το(α) επιμέÏους έÏγο(α): %{value} θα διαγÏαφοÏν." - text_workflow_edit: Επιλέξτε ένα Ïόλο και έναν ανιχνευτή για να επεξεÏγαστείτε τη Ïοή εÏγασίας - text_are_you_sure: Είστε σίγουÏος ; - text_tip_issue_begin_day: καθήκοντα που ξεκινάνε σήμεÏα - text_tip_issue_end_day: καθήκοντα που τελειώνουν σήμεÏα - text_tip_issue_begin_end_day: καθήκοντα που ξεκινάνε και τελειώνουν σήμεÏα - text_project_identifier_info: 'ΕπιτÏέπονται μόνο μικÏά πεζά γÏάμματα (a-z), αÏιθμοί και παÏλες.
Μετά την αποθήκευση, το αναγνωÏιστικό δεν μποÏεί να αλλάξει.' - text_caracters_maximum: "μέγιστος αÏιθμός %{count} χαÏακτήÏες." - text_caracters_minimum: "ΠÏέπει να πεÏιέχει τουλάχιστον %{count} χαÏακτήÏες." - text_length_between: "Μήκος Î¼ÎµÏ„Î±Î¾Ï %{min} και %{max} χαÏακτήÏες." - text_tracker_no_workflow: Δεν έχει οÏιστεί Ïοή εÏγασίας για αυτό τον ανιχνευτή - text_unallowed_characters: Μη επιτÏεπόμενοι χαÏακτήÏες - text_comma_separated: ΕπιτÏέπονται πολλαπλές τιμές (χωÏισμένες με κόμμα). - text_issues_ref_in_commit_messages: ΑναφοÏά και καθοÏισμός θεμάτων σε μηνÏματα commit - text_issue_added: "Το θέμα %{id} παÏουσιάστηκε από τον %{author}." - text_issue_updated: "Το θέμα %{id} ενημεÏώθηκε από τον %{author}." - text_wiki_destroy_confirmation: Είστε σίγουÏοι ότι θέλετε να διαγÏάψετε αυτό το wiki και όλο το πεÏιεχόμενο του ; - text_issue_category_destroy_question: "Κάποια θέματα (%{count}) έχουν εκχωÏηθεί σε αυτή την κατηγοÏία. Τι θέλετε να κάνετε ;" - text_issue_category_destroy_assignments: ΑφαίÏεση εκχωÏήσεων κατηγοÏίας - text_issue_category_reassign_to: ΕπανεκχώÏηση θεμάτων σε αυτή την κατηγοÏία - text_user_mail_option: "Για μη επιλεγμένα έÏγα, θα λάβετε ειδοποιήσεις μόνο για Ï€Ïάγματα που παÏακολουθείτε ή στα οποία συμμετέχω ενεÏγά (Ï€.χ. θέματα των οποίων είστε συγγÏαφέας ή σας έχουν ανατεθεί)." - text_no_configuration_data: "Οι Ïόλοι, οι ανιχνευτές, η κατάσταση των θεμάτων και η Ïοή εÏγασίας δεν έχουν Ïυθμιστεί ακόμα.\nΣυνιστάται ιδιαίτεÏα να φοÏτώσετε τις Ï€Ïοεπιλεγμένες Ïυθμίσεις. Θα είστε σε θέση να τις Ï„Ïοποποιήσετε μετά τη φόÏτωση τους." - text_load_default_configuration: ΦόÏτωση Ï€Ïοεπιλεγμένων Ïυθμίσεων - text_status_changed_by_changeset: "ΕφαÏμόστηκε στο changeset %{value}." - text_issues_destroy_confirmation: 'Είστε σίγουÏος ότι θέλετε να διαγÏάψετε το επιλεγμένο θέμα(τα);' - text_select_project_modules: 'Επιλέξτε ποιες μονάδες θα ενεÏγοποιήσετε για αυτό το έÏγο:' - text_default_administrator_account_changed: Ο Ï€ÏοκαθοÏισμένος λογαÏιασμός του διαχειÏιστή άλλαξε - text_file_repository_writable: ΕγγÏάψιμος κατάλογος συνημμένων - text_plugin_assets_writable: ΕγγÏάψιμος κατάλογος plugin assets - text_rmagick_available: Διαθέσιμο RMagick (Ï€ÏοαιÏετικό) - text_destroy_time_entries_question: "%{hours} δαπανήθηκαν σχετικά με τα θέματα που Ï€Ïόκειται να διαγÏάψετε. Τι θέλετε να κάνετε ;" - text_destroy_time_entries: ΔιαγÏαφή αναφεÏόμενων ωÏών - text_assign_time_entries_to_project: Ανάθεση αναφεÏόμενων ωÏών στο έÏγο - text_reassign_time_entries: 'Ανάθεση εκ νέου των αναφεÏόμενων ωÏών στο θέμα:' - text_user_wrote: "%{value} έγÏαψε:" - text_enumeration_destroy_question: "%{count} αντικείμενα έχουν τεθεί σε αυτή την τιμή." - text_enumeration_category_reassign_to: 'ΕπανεκχώÏηση τους στην παÏοÏσα αξία:' - text_email_delivery_not_configured: "Δεν έχουν γίνει Ïυθμίσεις παÏάδοσης email, και οι ειδοποιήσεις είναι απενεÏγοποιημένες.\nΔηλώστε τον εξυπηÏετητή SMTP στο config/configuration.yml και κάντε επανακκίνηση την εφαÏμογή για να τις Ïυθμίσεις." - text_repository_usernames_mapping: "Επιλέξτε ή ενημεÏώστε τον χÏήστη Redmine που αντιστοιχεί σε κάθε όνομα χÏήστη στο ιστοÏικό του αποθετηÏίου.\nΧÏήστες με το ίδιο όνομα χÏήστη ή email στο Redmine και στο αποθετηÏίο αντιστοιχίζονται αυτόματα." - text_diff_truncated: '... Αυτό το diff εχεί κοπεί επειδή υπεÏβαίνει το μέγιστο μέγεθος που μποÏεί να Ï€Ïοβληθεί.' - text_custom_field_possible_values_info: 'Μία γÏαμμή για κάθε τιμή' - text_wiki_page_destroy_question: "Αυτή η σελίδα έχει %{descendants} σελίδες τέκνων και απογόνων. Τι θέλετε να κάνετε ;" - text_wiki_page_nullify_children: "ΔιατηÏήστε τις σελίδες τέκνων ως σελίδες root" - text_wiki_page_destroy_children: "ΔιαγÏάψτε όλες τις σελίδες τέκνων και των απογόνων τους" - text_wiki_page_reassign_children: "ΕπανεκχώÏιση των σελίδων τέκνων στη γονική σελίδα" - - default_role_manager: Manager - default_role_developer: Developer - default_role_reporter: Reporter - default_tracker_bug: Σφάλματα - default_tracker_feature: ΛειτουÏγίες - default_tracker_support: ΥποστήÏιξη - default_issue_status_new: Îέα - default_issue_status_in_progress: In Progress - default_issue_status_resolved: Επιλυμένο - default_issue_status_feedback: Σχόλια - default_issue_status_closed: Κλειστό - default_issue_status_rejected: ΑποÏÏιπτέο - default_doc_category_user: ΤεκμηÏίωση χÏήστη - default_doc_category_tech: Τεχνική τεκμηÏίωση - default_priority_low: Χαμηλή - default_priority_normal: Κανονική - default_priority_high: Υψηλή - default_priority_urgent: Επείγον - default_priority_immediate: Άμεση - default_activity_design: Σχεδιασμός - default_activity_development: Ανάπτυξη - - enumeration_issue_priorities: ΠÏοτεÏαιότητα θέματος - enumeration_doc_categories: ΚατηγοÏία εγγÏάφων - enumeration_activities: ΔÏαστηÏιότητες (κατακεÏματισμός χÏόνου) - text_journal_changed: "%{label} άλλαξε από %{old} σε %{new}" - text_journal_set_to: "%{label} οÏίζεται σε %{value}" - text_journal_deleted: "%{label} διαγÏάφηκε (%{old})" - label_group_plural: Ομάδες - label_group: Ομάδα - label_group_new: Îέα ομάδα - label_time_entry_plural: ΧÏόνος που δαπανήθηκε - text_journal_added: "%{label} %{value} added" - field_active: Active - enumeration_system_activity: System Activity - permission_delete_issue_watchers: Delete watchers - version_status_closed: closed - version_status_locked: locked - version_status_open: open - error_can_not_reopen_issue_on_closed_version: An issue assigned to a closed version can not be reopened - label_user_anonymous: Anonymous - button_move_and_follow: Move and follow - setting_default_projects_modules: Default enabled modules for new projects - setting_gravatar_default: Default Gravatar image - field_sharing: Sharing - label_version_sharing_hierarchy: With project hierarchy - label_version_sharing_system: With all projects - label_version_sharing_descendants: With subprojects - label_version_sharing_tree: With project tree - label_version_sharing_none: Not shared - error_can_not_archive_project: This project can not be archived - button_duplicate: Duplicate - button_copy_and_follow: Copy and follow - label_copy_source: Source - setting_issue_done_ratio: Calculate the issue done ratio with - setting_issue_done_ratio_issue_status: Use the issue status - error_issue_done_ratios_not_updated: Issue done ratios not updated. - error_workflow_copy_target: Please select target tracker(s) and role(s) - setting_issue_done_ratio_issue_field: Use the issue field - label_copy_same_as_target: Same as target - label_copy_target: Target - notice_issue_done_ratios_updated: Issue done ratios updated. - error_workflow_copy_source: Please select a source tracker or role - label_update_issue_done_ratios: Update issue done ratios - setting_start_of_week: Start calendars on - permission_view_issues: View Issues - label_display_used_statuses_only: Only display statuses that are used by this tracker - label_revision_id: Revision %{value} - label_api_access_key: API access key - label_api_access_key_created_on: API access key created %{value} ago - label_feeds_access_key: RSS access key - notice_api_access_key_reseted: Your API access key was reset. - setting_rest_api_enabled: Enable REST web service - label_missing_api_access_key: Missing an API access key - label_missing_feeds_access_key: Missing a RSS access key - button_show: Show - text_line_separated: Multiple values allowed (one line for each value). - setting_mail_handler_body_delimiters: Truncate emails after one of these lines - permission_add_subprojects: Create subprojects - label_subproject_new: New subproject - text_own_membership_delete_confirmation: |- - You are about to remove some or all of your permissions and may no longer be able to edit this project after that. - Are you sure you want to continue? - label_close_versions: Close completed versions - label_board_sticky: Sticky - label_board_locked: Locked - permission_export_wiki_pages: Export wiki pages - setting_cache_formatted_text: Cache formatted text - permission_manage_project_activities: Manage project activities - error_unable_delete_issue_status: Unable to delete issue status - label_profile: Profile - permission_manage_subtasks: Manage subtasks - field_parent_issue: Parent task - label_subtask_plural: Subtasks - label_project_copy_notifications: Send email notifications during the project copy - error_can_not_delete_custom_field: Unable to delete custom field - error_unable_to_connect: Unable to connect (%{value}) - error_can_not_remove_role: This role is in use and can not be deleted. - error_can_not_delete_tracker: This tracker contains issues and can't be deleted. - field_principal: Principal - label_my_page_block: My page block - notice_failed_to_save_members: "Failed to save member(s): %{errors}." - text_zoom_out: Zoom out - text_zoom_in: Zoom in - notice_unable_delete_time_entry: Unable to delete time log entry. - label_overall_spent_time: Overall spent time - field_time_entries: Log time - project_module_gantt: Gantt - project_module_calendar: Calendar - button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" - text_are_you_sure_with_children: Delete issue and all child issues? - field_text: Text field - label_user_mail_option_only_owner: Only for things I am the owner of - setting_default_notification_option: Default notification option - label_user_mail_option_only_my_events: Only for things I watch or I'm involved in - label_user_mail_option_only_assigned: Only for things I am assigned to - label_user_mail_option_none: No events - field_member_of_group: Assignee's group - field_assigned_to_role: Assignee's role - notice_not_authorized_archived_project: The project you're trying to access has been archived. - label_principal_search: "Search for user or group:" - label_user_search: "Search for user:" - field_visible: Visible - setting_emails_header: Emails header - setting_commit_logtime_activity_id: Activity for logged time - text_time_logged_by_changeset: Applied in changeset %{value}. - setting_commit_logtime_enabled: Enable time logging - notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) - setting_gantt_items_limit: Maximum number of items displayed on the gantt chart - field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text - text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. - label_my_queries: My custom queries - text_journal_changed_no_detail: "%{label} updated" - label_news_comment_added: Comment added to a news - button_expand_all: Expand all - button_collapse_all: Collapse all - label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee - label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author - label_bulk_edit_selected_time_entries: Bulk edit selected time entries - text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? - label_role_anonymous: Anonymous - label_role_non_member: Non member - label_issue_note_added: Note added - label_issue_status_updated: Status updated - label_issue_priority_updated: Priority updated - label_issues_visibility_own: Issues created by or assigned to the user - field_issues_visibility: Issues visibility - label_issues_visibility_all: All issues - permission_set_own_issues_private: Set own issues public or private - field_is_private: Private - permission_set_issues_private: Set issues public or private - label_issues_visibility_public: All non private issues - text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). - field_commit_logs_encoding: Κωδικοποίηση μηνυμάτων commit - field_scm_path_encoding: Path encoding - text_scm_path_encoding_note: "Default: UTF-8" - field_path_to_repository: Path to repository - field_root_directory: Root directory - field_cvs_module: Module - field_cvsroot: CVSROOT - text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) - text_scm_command: Command - text_scm_command_version: Version - label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. - notice_issue_successful_create: Issue %{id} created. - label_between: between - setting_issue_group_assignment: Allow issue assignment to groups - label_diff: diff - text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) - description_query_sort_criteria_direction: Sort direction - description_project_scope: Search scope - description_filter: Filter - description_user_mail_notification: Mail notification settings - description_date_from: Enter start date - description_message_content: Message content - description_available_columns: Available Columns - description_date_range_interval: Choose range by selecting start and end date - description_issue_category_reassign: Choose issue category - description_search: Searchfield - description_notes: Notes - description_date_range_list: Choose range from list - description_choose_project: Projects - description_date_to: Enter end date - description_query_sort_criteria_attribute: Sort attribute - description_wiki_subpages_reassign: Choose new parent page - description_selected_columns: Selected Columns - label_parent_revision: Parent - label_child_revision: Child - error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. - setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues - button_edit_section: Edit this section - setting_repositories_encodings: Attachments and repositories encodings - description_all_columns: All Columns - button_export: Export - label_export_options: "%{export_format} export options" - error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/53/53e7bfd13c1bbfd84e32b60344b616bb91f37951.svn-base --- a/.svn/pristine/53/53e7bfd13c1bbfd84e32b60344b616bb91f37951.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,401 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class MailHandler < ActionMailer::Base - include ActionView::Helpers::SanitizeHelper - include Redmine::I18n - - class UnauthorizedAction < StandardError; end - class MissingInformation < StandardError; end - - attr_reader :email, :user - - def self.receive(email, options={}) - @@handler_options = options.dup - - @@handler_options[:issue] ||= {} - - @@handler_options[:allow_override] = @@handler_options[:allow_override].split(',').collect(&:strip) if @@handler_options[:allow_override].is_a?(String) - @@handler_options[:allow_override] ||= [] - # Project needs to be overridable if not specified - @@handler_options[:allow_override] << 'project' unless @@handler_options[:issue].has_key?(:project) - # Status overridable by default - @@handler_options[:allow_override] << 'status' unless @@handler_options[:issue].has_key?(:status) - - @@handler_options[:no_permission_check] = (@@handler_options[:no_permission_check].to_s == '1' ? true : false) - super email - end - - # Processes incoming emails - # Returns the created object (eg. an issue, a message) or false - def receive(email) - @email = email - sender_email = email.from.to_a.first.to_s.strip - # Ignore emails received from the application emission address to avoid hell cycles - if sender_email.downcase == Setting.mail_from.to_s.strip.downcase - logger.info "MailHandler: ignoring email from Redmine emission address [#{sender_email}]" if logger && logger.info - return false - end - @user = User.find_by_mail(sender_email) if sender_email.present? - if @user && !@user.active? - logger.info "MailHandler: ignoring email from non-active user [#{@user.login}]" if logger && logger.info - return false - end - if @user.nil? - # Email was submitted by an unknown user - case @@handler_options[:unknown_user] - when 'accept' - @user = User.anonymous - when 'create' - @user = create_user_from_email(email) - if @user - logger.info "MailHandler: [#{@user.login}] account created" if logger && logger.info - Mailer.deliver_account_information(@user, @user.password) - else - logger.error "MailHandler: could not create account for [#{sender_email}]" if logger && logger.error - return false - end - else - # Default behaviour, emails from unknown users are ignored - logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]" if logger && logger.info - return false - end - end - User.current = @user - dispatch - end - - private - - MESSAGE_ID_RE = %r{^ e - # TODO: send a email to the user - logger.error e.message if logger - false - rescue MissingInformation => e - logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger - false - rescue UnauthorizedAction => e - logger.error "MailHandler: unauthorized attempt from #{user}" if logger - false - end - - def dispatch_to_default - receive_issue - end - - # Creates a new issue - def receive_issue - project = target_project - # check permission - unless @@handler_options[:no_permission_check] - raise UnauthorizedAction unless user.allowed_to?(:add_issues, project) - end - - issue = Issue.new(:author => user, :project => project) - issue.safe_attributes = issue_attributes_from_keywords(issue) - issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)} - issue.subject = email.subject.to_s.chomp[0,255] - if issue.subject.blank? - issue.subject = '(no subject)' - end - issue.description = cleaned_up_text_body - - # add To and Cc as watchers before saving so the watchers can reply to Redmine - add_watchers(issue) - issue.save! - add_attachments(issue) - logger.info "MailHandler: issue ##{issue.id} created by #{user}" if logger && logger.info - issue - end - - # Adds a note to an existing issue - def receive_issue_reply(issue_id) - issue = Issue.find_by_id(issue_id) - return unless issue - # check permission - unless @@handler_options[:no_permission_check] - raise UnauthorizedAction unless user.allowed_to?(:add_issue_notes, issue.project) || user.allowed_to?(:edit_issues, issue.project) - end - - # ignore CLI-supplied defaults for new issues - @@handler_options[:issue].clear - - journal = issue.init_journal(user) - issue.safe_attributes = issue_attributes_from_keywords(issue) - issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)} - journal.notes = cleaned_up_text_body - add_attachments(issue) - issue.save! - logger.info "MailHandler: issue ##{issue.id} updated by #{user}" if logger && logger.info - 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 - - unless @@handler_options[:no_permission_check] - raise UnauthorizedAction unless user.allowed_to?(:add_messages, message.project) - end - - if !message.locked? - reply = Message.new(:subject => email.subject.gsub(%r{^.*msg\d+\]}, '').strip, - :content => cleaned_up_text_body) - reply.author = user - reply.board = message.board - message.children << reply - add_attachments(reply) - reply - else - logger.info "MailHandler: ignoring reply from [#{sender_email}] to a locked topic" if logger && logger.info - end - end - end - - def add_attachments(obj) - if email.attachments && email.attachments.any? - email.attachments.each do |attachment| - obj.attachments << Attachment.create(:container => obj, - :file => attachment, - :author => user, - :content_type => attachment.content_type) - end - end - end - - # 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 - - 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)) && (v = extract_keyword!(plain_text_body, attr, options[:format])) - v - elsif !@@handler_options[:issue][attr].blank? - @@handler_options[:issue][attr] - end - end - end - end - - # Destructively extracts the value for +attr+ in +text+ - # Returns nil if no matching keyword found - def extract_keyword!(text, attr, format=nil) - keys = [attr.to_s.humanize] - if attr.is_a?(Symbol) - keys << l("field_#{attr}", :default => '', :locale => user.language) if user && user.language.present? - keys << l("field_#{attr}", :default => '', :locale => Setting.default_language) if Setting.default_language.present? - end - keys.reject! {|k| k.blank?} - keys.collect! {|k| Regexp.escape(k)} - format ||= '.+' - text.gsub!(/^(#{keys.join('|')})[ \t]*:[ \t]*(#{format})\s*$/i, '') - $2 && $2.strip - end - - def target_project - # TODO: other ways to specify project: - # * parse the email To field - # * specific project (eg. Setting.mail_handler_target_project) - target = Project.find_by_identifier(get_keyword(:project)) - raise MissingInformation.new('Unable to determine target project') if target.nil? - target - end - - # Returns a Hash of issue attributes extracted from keywords in the email body - def issue_attributes_from_keywords(issue) - assigned_to = (k = get_keyword(:assigned_to, :override => true)) && find_assignee_from_keyword(k, issue) - - attrs = { - 'tracker_id' => (k = get_keyword(:tracker)) && issue.project.trackers.named(k).first.try(:id), - 'status_id' => (k = get_keyword(:status)) && IssueStatus.named(k).first.try(:id), - 'priority_id' => (k = get_keyword(:priority)) && IssuePriority.named(k).first.try(:id), - 'category_id' => (k = get_keyword(:category)) && issue.project.issue_categories.named(k).first.try(:id), - 'assigned_to_id' => assigned_to.try(:id), - 'fixed_version_id' => (k = get_keyword(:fixed_version, :override => true)) && issue.project.shared_versions.named(k).first.try(:id), - 'start_date' => get_keyword(:start_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'), - 'due_date' => get_keyword(:due_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'), - 'estimated_hours' => get_keyword(:estimated_hours, :override => true), - 'done_ratio' => get_keyword(:done_ratio, :override => true, :format => '(\d|10)?0') - }.delete_if {|k, v| v.blank? } - - if issue.new_record? && attrs['tracker_id'].nil? - attrs['tracker_id'] = issue.project.trackers.find(:first).try(:id) - end - - attrs - end - - # Returns a Hash of issue custom field values extracted from keywords in the email body - def custom_field_values_from_keywords(customized) - customized.custom_field_values.inject({}) do |h, v| - if value = get_keyword(v.custom_field.name, :override => true) - h[v.custom_field.id.to_s] = value - end - h - 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? - 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{^ ]*(#{ delimiters.join('|') })\s*[\r\n].*", Regexp::MULTILINE) - body = body.gsub(regex, '') - end - body.strip - end - - def find_assignee_from_keyword(keyword, issue) - keyword = keyword.to_s.downcase - assignable = issue.assignable_users - assignee = nil - assignee ||= assignable.detect {|a| a.mail.to_s.downcase == keyword || a.login.to_s.downcase == keyword} - if assignee.nil? && keyword.match(/ /) - firstname, lastname = *(keyword.split) # "First Last Throwaway" - assignee ||= assignable.detect {|a| a.is_a?(User) && a.firstname.to_s.downcase == firstname && a.lastname.to_s.downcase == lastname} - end - if assignee.nil? - assignee ||= assignable.detect {|a| a.is_a?(Group) && a.name.downcase == keyword} - end - assignee - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/53/53f766f688d3e6af398e819fa2270ab5c1d44704.svn-base --- a/.svn/pristine/53/53f766f688d3e6af398e819fa2270ab5c1d44704.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -<%= TestHelper.view_path_for __FILE__ %> (from alpha_plugin) \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/54/5417555cbbf2ff6b51352d6d03cb030a8f16ca7e.svn-base --- a/.svn/pristine/54/5417555cbbf2ff6b51352d6d03cb030a8f16ca7e.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class WelcomeController < ApplicationController - caches_action :robots - - def index - @news = News.latest User.current - @projects = Project.latest User.current - end - - def robots - @projects = Project.all_public.active - render :layout => false, :content_type => 'text/plain' - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/54/543c086fba1141b0a1920a3d969a31cde9c83e3e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/54/543c086fba1141b0a1920a3d969a31cde9c83e3e.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,99 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../../../../test_helper', __FILE__) +begin + require 'mocha' + + class CvsAdapterTest < ActiveSupport::TestCase + REPOSITORY_PATH = Rails.root.join('tmp/test/cvs_repository').to_s + REPOSITORY_PATH.gsub!(/\//, "\\") if Redmine::Platform.mswin? + MODULE_NAME = 'test' + + if File.directory?(REPOSITORY_PATH) + def setup + @adapter = Redmine::Scm::Adapters::CvsAdapter.new(MODULE_NAME, REPOSITORY_PATH) + end + + def test_scm_version + to_test = { "\nConcurrent Versions System (CVS) 1.12.13 (client/server)\n" => [1,12,13], + "\r\n1.12.12\r\n1.12.11" => [1,12,12], + "1.12.11\r\n1.12.10\r\n" => [1,12,11]} + to_test.each do |s, v| + test_scm_version_for(s, v) + end + end + + def test_revisions_all + cnt = 0 + @adapter.revisions('', nil, nil, :log_encoding => 'UTF-8') do |revision| + cnt += 1 + end + assert_equal 16, cnt + end + + def test_revisions_from_rev3 + rev3_committed_on = Time.gm(2007, 12, 13, 16, 27, 22) + cnt = 0 + @adapter.revisions('', rev3_committed_on, nil, :log_encoding => 'UTF-8') do |revision| + cnt += 1 + end + assert_equal 4, cnt + end + + def test_entries_rev3 + rev3_committed_on = Time.gm(2007, 12, 13, 16, 27, 22) + entries = @adapter.entries('sources', rev3_committed_on) + assert_equal 2, entries.size + assert_equal entries[0].name, "watchers_controller.rb" + assert_equal entries[0].lastrev.time, Time.gm(2007, 12, 13, 16, 27, 22) + end + + def test_path_encoding_default_utf8 + adpt1 = Redmine::Scm::Adapters::CvsAdapter.new( + MODULE_NAME, + REPOSITORY_PATH + ) + assert_equal "UTF-8", adpt1.path_encoding + adpt2 = Redmine::Scm::Adapters::CvsAdapter.new( + MODULE_NAME, + REPOSITORY_PATH, + nil, + nil, + "" + ) + assert_equal "UTF-8", adpt2.path_encoding + end + + private + + def test_scm_version_for(scm_command_version, version) + @adapter.class.expects(:scm_version_from_command_line).returns(scm_command_version) + assert_equal version, @adapter.class.scm_command_version + end + else + puts "Cvs test repository NOT FOUND. Skipping unit tests !!!" + def test_fake; assert true end + end + end + +rescue LoadError + class CvsMochaFake < ActiveSupport::TestCase + def test_fake; assert(false, "Requires mocha to run those tests") end + end +end + diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/54/545555b790077e8ce0b627745954990d978f492e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/54/545555b790077e8ce0b627745954990d978f492e.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,43 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class EnabledModuleTest < ActiveSupport::TestCase + fixtures :projects, :wikis + + def test_enabling_wiki_should_create_a_wiki + CustomField.delete_all + project = Project.create!(:name => 'Project with wiki', :identifier => 'wikiproject') + assert_nil project.wiki + project.enabled_module_names = ['wiki'] + project.reload + assert_not_nil project.wiki + assert_equal 'Wiki', project.wiki.start_page + end + + def test_reenabling_wiki_should_not_create_another_wiki + project = Project.find(1) + assert_not_nil project.wiki + project.enabled_module_names = [] + project.reload + assert_no_difference 'Wiki.count' do + project.enabled_module_names = ['wiki'] + end + assert_not_nil project.wiki + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/54/54759b2876d30caf9b91631c76f57195d0fdce73.svn-base --- a/.svn/pristine/54/54759b2876d30caf9b91631c76f57195d0fdce73.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ -<%= error_messages_for 'role' %> - -
-<% unless @role.builtin? %> -

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

-

<%= f.check_box :assignable %>

-<% end %> -

<%= f.select :issues_visibility, Role::ISSUES_VISIBILITY_OPTIONS.collect {|v| [l(v.last), v.first]} %>

-<% if @role.new_record? && @roles.any? %> -

-<%= select_tag(:copy_workflow_from, content_tag("option") + options_from_collection_for_select(@roles, :id, :name)) %>

-<% end %> -
- -

<%= l(:label_permissions) %>

-
-<% perms_by_module = @permissions.group_by {|p| p.project_module.to_s} %> -<% perms_by_module.keys.sort.each do |mod| %> -
<%= mod.blank? ? l(:label_project) : l_or_humanize(mod, :prefix => 'project_module_') %> - <% perms_by_module[mod].each do |permission| %> - - <% end %> -
-<% end %> -
<%= check_all_links 'permissions' %> -<%= hidden_field_tag 'role[permissions][]', '' %> -
diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/54/549732f3fd96b4524d8aa62cc5177f9b0ad9d268.svn-base --- a/.svn/pristine/54/549732f3fd96b4524d8aa62cc5177f9b0ad9d268.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,47 +0,0 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../../test_helper', __FILE__) - -class SearchHelperTest < ActionView::TestCase - include SearchHelper - - def test_highlight_single_token - assert_equal 'This is a token.', - highlight_tokens('This is a token.', %w(token)) - end - - def test_highlight_multiple_tokens - assert_equal 'This is a token and another token.', - highlight_tokens('This is a token and another token.', %w(token another)) - end - - def test_highlight_should_not_exceed_maximum_length - s = (('1234567890' * 100) + ' token ') * 100 - r = highlight_tokens(s, %w(token)) - assert r.include?('token') - assert r.length <= 1300 - end - - def test_highlight_multibyte - s = ('й' * 200) + ' token ' + ('й' * 200) - r = highlight_tokens(s, %w(token)) - assert_equal ('й' * 45) + ' ... ' + ('й' * 44) + ' token ' + ('й' * 44) + ' ... ' + ('й' * 45), r - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/54/549c0979db66eee16f159587f161c81dea52c794.svn-base --- a/.svn/pristine/54/549c0979db66eee16f159587f161c81dea52c794.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,28 +0,0 @@ -

<%= l(:label_revision) %> <%= @diff_format_revisions %> <%=h @path %>

- - -<% form_tag({:path => to_path_param(@path)}, :method => 'get') do %> - <%= hidden_field_tag('rev', params[:rev]) if params[:rev] %> - <%= hidden_field_tag('rev_to', params[:rev_to]) if params[:rev_to] %> -

- - <%= select_tag 'type', - options_for_select( - [[l(:label_diff_inline), "inline"], [l(:label_diff_side_by_side), "sbs"]], @diff_type), - :onchange => "if (this.value != '') {this.form.submit()}" %> -

-<% end %> - -<% cache(@cache_key) do -%> -<%= render :partial => 'common/diff', :locals => {:diff => @diff, :diff_type => @diff_type} %> -<% end -%> - -<% other_formats_links do |f| %> - <%= f.link_to 'Diff', :url => params, :caption => 'Unified diff' %> -<% end %> - -<% html_title(with_leading_slash(@path), 'Diff') -%> - -<% content_for :header_tags do %> -<%= stylesheet_link_tag "scm" %> -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/54/54aaade6975dce4afcd932f697233c2ac56da5c0.svn-base --- a/.svn/pristine/54/54aaade6975dce4afcd932f697233c2ac56da5c0.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -class SharedPluginController < ApplicationController - def an_action - render_class_and_action 'from beta_plugin' - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/55/550aea64d9d567b8fd19eb6154f19d0a52250270.svn-base --- a/.svn/pristine/55/550aea64d9d567b8fd19eb6154f19d0a52250270.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,205 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# Copyright (C) 2007 Patrick Aljord patcito@ŋmail.com -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -require 'redmine/scm/adapters/git_adapter' - -class Repository::Git < Repository - attr_protected :root_url - validates_presence_of :url - - def self.human_attribute_name(attribute_key_name) - attr_name = attribute_key_name - if attr_name == "url" - attr_name = "path_to_repository" - end - super(attr_name) - end - - def self.scm_adapter_class - Redmine::Scm::Adapters::GitAdapter - end - - def self.scm_name - 'Git' - end - - def report_last_commit - extra_report_last_commit - end - - def extra_report_last_commit - return false if extra_info.nil? - v = extra_info["extra_report_last_commit"] - return false if v.nil? - v.to_s != '0' - end - - def supports_directory_revisions? - true - end - - def supports_revision_graph? - true - end - - def repo_log_encoding - 'UTF-8' - end - - # Returns the identifier for the given git changeset - def self.changeset_identifier(changeset) - changeset.scmid - end - - # Returns the readable identifier for the given git changeset - def self.format_changeset_identifier(changeset) - changeset.revision[0, 8] - end - - def branches - scm.branches - end - - def tags - scm.tags - end - - def default_branch - scm.default_branch - rescue Exception => e - logger.error "git: error during get default branch: #{e.message}" - nil - end - - def find_changeset_by_name(name) - return nil if name.nil? || name.empty? - e = changesets.find(:first, :conditions => ['revision = ?', name.to_s]) - return e if e - changesets.find(:first, :conditions => ['scmid LIKE ?', "#{name}%"]) - end - - def entries(path=nil, identifier=nil) - scm.entries(path, - identifier, - options = {:report_last_commit => extra_report_last_commit}) - end - - # With SCMs that have a sequential commit numbering, - # such as Subversion and Mercurial, - # Redmine is able to be clever and only fetch changesets - # going forward from the most recent one it knows about. - # - # However, Git does not have a sequential commit numbering. - # - # In order to fetch only new adding revisions, - # Redmine needs to parse revisions per branch. - # Branch "last_scmid" is for this requirement. - # - # In Git and Mercurial, revisions are not in date order. - # Redmine Mercurial fixed issues. - # * Redmine Takes Too Long On Large Mercurial Repository - # http://www.redmine.org/issues/3449 - # * Sorting for changesets might go wrong on Mercurial repos - # http://www.redmine.org/issues/3567 - # - # Database revision column is text, so Redmine can not sort by revision. - # Mercurial has revision number, and revision number guarantees revision order. - # Redmine Mercurial model stored revisions ordered by database id to database. - # So, Redmine Mercurial model can use correct ordering revisions. - # - # Redmine Mercurial adapter uses "hg log -r 0:tip --limit 10" - # to get limited revisions from old to new. - # But, Git 1.7.3.4 does not support --reverse with -n or --skip. - # - # The repository can still be fully reloaded by calling #clear_changesets - # before fetching changesets (eg. for offline resync) - def fetch_changesets - scm_brs = branches - return if scm_brs.nil? || scm_brs.empty? - h1 = extra_info || {} - h = h1.dup - h["branches"] ||= {} - h["db_consistent"] ||= {} - if changesets.count == 0 - h["db_consistent"]["ordering"] = 1 - merge_extra_info(h) - self.save - elsif ! h["db_consistent"].has_key?("ordering") - h["db_consistent"]["ordering"] = 0 - merge_extra_info(h) - self.save - end - scm_brs.each do |br1| - br = br1.to_s - from_scmid = nil - from_scmid = h["branches"][br]["last_scmid"] if h["branches"][br] - h["branches"][br] ||= {} - scm.revisions('', from_scmid, br, {:reverse => true}) do |rev| - db_rev = find_changeset_by_name(rev.revision) - transaction do - if db_rev.nil? - db_saved_rev = save_revision(rev) - parents = {} - parents[db_saved_rev] = rev.parents unless rev.parents.nil? - parents.each do |ch, chparents| - ch.parents = chparents.collect{|rp| find_changeset_by_name(rp)}.compact - end - end - h["branches"][br]["last_scmid"] = rev.scmid - merge_extra_info(h) - self.save - end - end - end - end - - def save_revision(rev) - changeset = Changeset.new( - :repository => self, - :revision => rev.identifier, - :scmid => rev.scmid, - :committer => rev.author, - :committed_on => rev.time, - :comments => rev.message - ) - if changeset.save - rev.paths.each do |file| - Change.create( - :changeset => changeset, - :action => file[:action], - :path => file[:path]) - end - end - changeset - end - private :save_revision - - def latest_changesets(path,rev,limit=10) - revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false) - return [] if revisions.nil? || revisions.empty? - - changesets.find( - :all, - :conditions => [ - "scmid IN (?)", - revisions.map!{|c| c.scmid} - ], - :order => 'committed_on DESC' - ) - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/55/551de1919df18269d3487971094e6e1b67a8e45d.svn-base --- a/.svn/pristine/55/551de1919df18269d3487971094e6e1b67a8e45d.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1818 +0,0 @@ -/* Copyright Mihai Bazon, 2002-2005 | www.bazon.net/mishoo - * ----------------------------------------------------------- - * - * The DHTML Calendar, version 1.0 "It is happening again" - * - * Details and latest version at: - * www.dynarch.com/projects/calendar - * - * This script is developed by Dynarch.com. Visit us at www.dynarch.com. - * - * This script is distributed under the GNU Lesser General Public License. - * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html - */ - -// $Id: calendar.js,v 1.51 2005/03/07 16:44:31 mishoo Exp $ - -/** The Calendar object constructor. */ -Calendar = function (firstDayOfWeek, dateStr, onSelected, onClose) { - // member variables - this.activeDiv = null; - this.currentDateEl = null; - this.getDateStatus = null; - this.getDateToolTip = null; - this.getDateText = null; - this.timeout = null; - this.onSelected = onSelected || null; - this.onClose = onClose || null; - this.dragging = false; - this.hidden = false; - this.minYear = 1970; - this.maxYear = 2050; - this.dateFormat = Calendar._TT["DEF_DATE_FORMAT"]; - this.ttDateFormat = Calendar._TT["TT_DATE_FORMAT"]; - this.isPopup = true; - this.weekNumbers = true; - this.firstDayOfWeek = typeof firstDayOfWeek == "number" ? firstDayOfWeek : Calendar._FD; // 0 for Sunday, 1 for Monday, etc. - this.showsOtherMonths = false; - this.dateStr = dateStr; - this.ar_days = null; - this.showsTime = false; - this.time24 = true; - this.yearStep = 2; - this.hiliteToday = true; - this.multiple = null; - // HTML elements - this.table = null; - this.element = null; - this.tbody = null; - this.firstdayname = null; - // Combo boxes - this.monthsCombo = null; - this.yearsCombo = null; - this.hilitedMonth = null; - this.activeMonth = null; - this.hilitedYear = null; - this.activeYear = null; - // Information - this.dateClicked = false; - - // one-time initializations - if (typeof Calendar._SDN == "undefined") { - // table of short day names - if (typeof Calendar._SDN_len == "undefined") - Calendar._SDN_len = 3; - var ar = new Array(); - for (var i = 8; i > 0;) { - ar[--i] = Calendar._DN[i].substr(0, Calendar._SDN_len); - } - Calendar._SDN = ar; - // table of short month names - if (typeof Calendar._SMN_len == "undefined") - Calendar._SMN_len = 3; - ar = new Array(); - for (var i = 12; i > 0;) { - ar[--i] = Calendar._MN[i].substr(0, Calendar._SMN_len); - } - Calendar._SMN = ar; - } -}; - -// ** constants - -/// "static", needed for event handlers. -Calendar._C = null; - -/// detect a special case of "web browser" -Calendar.is_ie = ( /msie/i.test(navigator.userAgent) && - !/opera/i.test(navigator.userAgent) ); - -Calendar.is_ie5 = ( Calendar.is_ie && /msie 5\.0/i.test(navigator.userAgent) ); - -/// detect Opera browser -Calendar.is_opera = /opera/i.test(navigator.userAgent); - -/// detect KHTML-based browsers -Calendar.is_khtml = /Konqueror|Safari|KHTML/i.test(navigator.userAgent); - -// BEGIN: UTILITY FUNCTIONS; beware that these might be moved into a separate -// library, at some point. - -Calendar.getAbsolutePos = function(el) { - var SL = 0, ST = 0; - var is_div = /^div$/i.test(el.tagName); - if (is_div && el.scrollLeft) - SL = el.scrollLeft; - if (is_div && el.scrollTop) - ST = el.scrollTop; - var r = { x: el.offsetLeft - SL, y: el.offsetTop - ST }; - if (el.offsetParent) { - var tmp = this.getAbsolutePos(el.offsetParent); - r.x += tmp.x; - r.y += tmp.y; - } - return r; -}; - -Calendar.isRelated = function (el, evt) { - var related = evt.relatedTarget; - if (!related) { - var type = evt.type; - if (type == "mouseover") { - related = evt.fromElement; - } else if (type == "mouseout") { - related = evt.toElement; - } - } - while (related) { - if (related == el) { - return true; - } - related = related.parentNode; - } - return false; -}; - -Calendar.removeClass = function(el, className) { - if (!(el && el.className)) { - return; - } - var cls = el.className.split(" "); - var ar = new Array(); - for (var i = cls.length; i > 0;) { - if (cls[--i] != className) { - ar[ar.length] = cls[i]; - } - } - el.className = ar.join(" "); -}; - -Calendar.addClass = function(el, className) { - Calendar.removeClass(el, className); - el.className += " " + className; -}; - -// FIXME: the following 2 functions totally suck, are useless and should be replaced immediately. -Calendar.getElement = function(ev) { - var f = Calendar.is_ie ? window.event.srcElement : ev.currentTarget; - while (f.nodeType != 1 || /^div$/i.test(f.tagName)) - f = f.parentNode; - return f; -}; - -Calendar.getTargetElement = function(ev) { - var f = Calendar.is_ie ? window.event.srcElement : ev.target; - while (f.nodeType != 1) - f = f.parentNode; - return f; -}; - -Calendar.stopEvent = function(ev) { - ev || (ev = window.event); - if (Calendar.is_ie) { - ev.cancelBubble = true; - ev.returnValue = false; - } else { - ev.preventDefault(); - ev.stopPropagation(); - } - return false; -}; - -Calendar.addEvent = function(el, evname, func) { - if (el.attachEvent) { // IE - el.attachEvent("on" + evname, func); - } else if (el.addEventListener) { // Gecko / W3C - el.addEventListener(evname, func, true); - } else { - el["on" + evname] = func; - } -}; - -Calendar.removeEvent = function(el, evname, func) { - if (el.detachEvent) { // IE - el.detachEvent("on" + evname, func); - } else if (el.removeEventListener) { // Gecko / W3C - el.removeEventListener(evname, func, true); - } else { - el["on" + evname] = null; - } -}; - -Calendar.createElement = function(type, parent) { - var el = null; - if (document.createElementNS) { - // use the XHTML namespace; IE won't normally get here unless - // _they_ "fix" the DOM2 implementation. - el = document.createElementNS("http://www.w3.org/1999/xhtml", type); - } else { - el = document.createElement(type); - } - if (typeof parent != "undefined") { - parent.appendChild(el); - } - return el; -}; - -// END: UTILITY FUNCTIONS - -// BEGIN: CALENDAR STATIC FUNCTIONS - -/** Internal -- adds a set of events to make some element behave like a button. */ -Calendar._add_evs = function(el) { - with (Calendar) { - addEvent(el, "mouseover", dayMouseOver); - addEvent(el, "mousedown", dayMouseDown); - addEvent(el, "mouseout", dayMouseOut); - if (is_ie) { - addEvent(el, "dblclick", dayMouseDblClick); - el.setAttribute("unselectable", true); - } - } -}; - -Calendar.findMonth = function(el) { - if (typeof el.month != "undefined") { - return el; - } else if (typeof el.parentNode.month != "undefined") { - return el.parentNode; - } - return null; -}; - -Calendar.findYear = function(el) { - if (typeof el.year != "undefined") { - return el; - } else if (typeof el.parentNode.year != "undefined") { - return el.parentNode; - } - return null; -}; - -Calendar.showMonthsCombo = function () { - var cal = Calendar._C; - if (!cal) { - return false; - } - var cal = cal; - var cd = cal.activeDiv; - var mc = cal.monthsCombo; - if (cal.hilitedMonth) { - Calendar.removeClass(cal.hilitedMonth, "hilite"); - } - if (cal.activeMonth) { - Calendar.removeClass(cal.activeMonth, "active"); - } - var mon = cal.monthsCombo.getElementsByTagName("div")[cal.date.getMonth()]; - Calendar.addClass(mon, "active"); - cal.activeMonth = mon; - var s = mc.style; - s.display = "block"; - if (cd.navtype < 0) - s.left = cd.offsetLeft + "px"; - else { - var mcw = mc.offsetWidth; - if (typeof mcw == "undefined") - // Konqueror brain-dead techniques - mcw = 50; - s.left = (cd.offsetLeft + cd.offsetWidth - mcw) + "px"; - } - s.top = (cd.offsetTop + cd.offsetHeight) + "px"; -}; - -Calendar.showYearsCombo = function (fwd) { - var cal = Calendar._C; - if (!cal) { - return false; - } - var cal = cal; - var cd = cal.activeDiv; - var yc = cal.yearsCombo; - if (cal.hilitedYear) { - Calendar.removeClass(cal.hilitedYear, "hilite"); - } - if (cal.activeYear) { - Calendar.removeClass(cal.activeYear, "active"); - } - cal.activeYear = null; - var Y = cal.date.getFullYear() + (fwd ? 1 : -1); - var yr = yc.firstChild; - var show = false; - for (var i = 12; i > 0; --i) { - if (Y >= cal.minYear && Y <= cal.maxYear) { - yr.innerHTML = Y; - yr.year = Y; - yr.style.display = "block"; - show = true; - } else { - yr.style.display = "none"; - } - yr = yr.nextSibling; - Y += fwd ? cal.yearStep : -cal.yearStep; - } - if (show) { - var s = yc.style; - s.display = "block"; - if (cd.navtype < 0) - s.left = cd.offsetLeft + "px"; - else { - var ycw = yc.offsetWidth; - if (typeof ycw == "undefined") - // Konqueror brain-dead techniques - ycw = 50; - s.left = (cd.offsetLeft + cd.offsetWidth - ycw) + "px"; - } - s.top = (cd.offsetTop + cd.offsetHeight) + "px"; - } -}; - -// event handlers - -Calendar.tableMouseUp = function(ev) { - var cal = Calendar._C; - if (!cal) { - return false; - } - if (cal.timeout) { - clearTimeout(cal.timeout); - } - var el = cal.activeDiv; - if (!el) { - return false; - } - var target = Calendar.getTargetElement(ev); - ev || (ev = window.event); - Calendar.removeClass(el, "active"); - if (target == el || target.parentNode == el) { - Calendar.cellClick(el, ev); - } - var mon = Calendar.findMonth(target); - var date = null; - if (mon) { - date = new Date(cal.date); - if (mon.month != date.getMonth()) { - date.setMonth(mon.month); - cal.setDate(date); - cal.dateClicked = false; - cal.callHandler(); - } - } else { - var year = Calendar.findYear(target); - if (year) { - date = new Date(cal.date); - if (year.year != date.getFullYear()) { - date.setFullYear(year.year); - cal.setDate(date); - cal.dateClicked = false; - cal.callHandler(); - } - } - } - with (Calendar) { - removeEvent(document, "mouseup", tableMouseUp); - removeEvent(document, "mouseover", tableMouseOver); - removeEvent(document, "mousemove", tableMouseOver); - cal._hideCombos(); - _C = null; - return stopEvent(ev); - } -}; - -Calendar.tableMouseOver = function (ev) { - var cal = Calendar._C; - if (!cal) { - return; - } - var el = cal.activeDiv; - var target = Calendar.getTargetElement(ev); - if (target == el || target.parentNode == el) { - Calendar.addClass(el, "hilite active"); - Calendar.addClass(el.parentNode, "rowhilite"); - } else { - if (typeof el.navtype == "undefined" || (el.navtype != 50 && (el.navtype == 0 || Math.abs(el.navtype) > 2))) - Calendar.removeClass(el, "active"); - Calendar.removeClass(el, "hilite"); - Calendar.removeClass(el.parentNode, "rowhilite"); - } - ev || (ev = window.event); - if (el.navtype == 50 && target != el) { - var pos = Calendar.getAbsolutePos(el); - var w = el.offsetWidth; - var x = ev.clientX; - var dx; - var decrease = true; - if (x > pos.x + w) { - dx = x - pos.x - w; - decrease = false; - } else - dx = pos.x - x; - - if (dx < 0) dx = 0; - var range = el._range; - var current = el._current; - var count = Math.floor(dx / 10) % range.length; - for (var i = range.length; --i >= 0;) - if (range[i] == current) - break; - while (count-- > 0) - if (decrease) { - if (--i < 0) - i = range.length - 1; - } else if ( ++i >= range.length ) - i = 0; - var newval = range[i]; - el.innerHTML = newval; - - cal.onUpdateTime(); - } - var mon = Calendar.findMonth(target); - if (mon) { - if (mon.month != cal.date.getMonth()) { - if (cal.hilitedMonth) { - Calendar.removeClass(cal.hilitedMonth, "hilite"); - } - Calendar.addClass(mon, "hilite"); - cal.hilitedMonth = mon; - } else if (cal.hilitedMonth) { - Calendar.removeClass(cal.hilitedMonth, "hilite"); - } - } else { - if (cal.hilitedMonth) { - Calendar.removeClass(cal.hilitedMonth, "hilite"); - } - var year = Calendar.findYear(target); - if (year) { - if (year.year != cal.date.getFullYear()) { - if (cal.hilitedYear) { - Calendar.removeClass(cal.hilitedYear, "hilite"); - } - Calendar.addClass(year, "hilite"); - cal.hilitedYear = year; - } else if (cal.hilitedYear) { - Calendar.removeClass(cal.hilitedYear, "hilite"); - } - } else if (cal.hilitedYear) { - Calendar.removeClass(cal.hilitedYear, "hilite"); - } - } - return Calendar.stopEvent(ev); -}; - -Calendar.tableMouseDown = function (ev) { - if (Calendar.getTargetElement(ev) == Calendar.getElement(ev)) { - return Calendar.stopEvent(ev); - } -}; - -Calendar.calDragIt = function (ev) { - var cal = Calendar._C; - if (!(cal && cal.dragging)) { - return false; - } - var posX; - var posY; - if (Calendar.is_ie) { - posY = window.event.clientY + document.body.scrollTop; - posX = window.event.clientX + document.body.scrollLeft; - } else { - posX = ev.pageX; - posY = ev.pageY; - } - cal.hideShowCovered(); - var st = cal.element.style; - st.left = (posX - cal.xOffs) + "px"; - st.top = (posY - cal.yOffs) + "px"; - return Calendar.stopEvent(ev); -}; - -Calendar.calDragEnd = function (ev) { - var cal = Calendar._C; - if (!cal) { - return false; - } - cal.dragging = false; - with (Calendar) { - removeEvent(document, "mousemove", calDragIt); - removeEvent(document, "mouseup", calDragEnd); - tableMouseUp(ev); - } - cal.hideShowCovered(); -}; - -Calendar.dayMouseDown = function(ev) { - var el = Calendar.getElement(ev); - if (el.disabled) { - return false; - } - var cal = el.calendar; - cal.activeDiv = el; - Calendar._C = cal; - if (el.navtype != 300) with (Calendar) { - if (el.navtype == 50) { - el._current = el.innerHTML; - addEvent(document, "mousemove", tableMouseOver); - } else - addEvent(document, Calendar.is_ie5 ? "mousemove" : "mouseover", tableMouseOver); - addClass(el, "hilite active"); - addEvent(document, "mouseup", tableMouseUp); - } else if (cal.isPopup) { - cal._dragStart(ev); - } - if (el.navtype == -1 || el.navtype == 1) { - if (cal.timeout) clearTimeout(cal.timeout); - cal.timeout = setTimeout("Calendar.showMonthsCombo()", 250); - } else if (el.navtype == -2 || el.navtype == 2) { - if (cal.timeout) clearTimeout(cal.timeout); - cal.timeout = setTimeout((el.navtype > 0) ? "Calendar.showYearsCombo(true)" : "Calendar.showYearsCombo(false)", 250); - } else { - cal.timeout = null; - } - return Calendar.stopEvent(ev); -}; - -Calendar.dayMouseDblClick = function(ev) { - Calendar.cellClick(Calendar.getElement(ev), ev || window.event); - if (Calendar.is_ie) { - document.selection.empty(); - } -}; - -Calendar.dayMouseOver = function(ev) { - var el = Calendar.getElement(ev); - if (Calendar.isRelated(el, ev) || Calendar._C || el.disabled) { - return false; - } - if (el.ttip) { - if (el.ttip.substr(0, 1) == "_") { - el.ttip = el.caldate.print(el.calendar.ttDateFormat) + el.ttip.substr(1); - } - el.calendar.tooltips.innerHTML = el.ttip; - } - if (el.navtype != 300) { - Calendar.addClass(el, "hilite"); - if (el.caldate) { - Calendar.addClass(el.parentNode, "rowhilite"); - } - } - return Calendar.stopEvent(ev); -}; - -Calendar.dayMouseOut = function(ev) { - with (Calendar) { - var el = getElement(ev); - if (isRelated(el, ev) || _C || el.disabled) - return false; - removeClass(el, "hilite"); - if (el.caldate) - removeClass(el.parentNode, "rowhilite"); - if (el.calendar) - el.calendar.tooltips.innerHTML = _TT["SEL_DATE"]; - return stopEvent(ev); - } -}; - -/** - * A generic "click" handler :) handles all types of buttons defined in this - * calendar. - */ -Calendar.cellClick = function(el, ev) { - var cal = el.calendar; - var closing = false; - var newdate = false; - var date = null; - if (typeof el.navtype == "undefined") { - if (cal.currentDateEl) { - Calendar.removeClass(cal.currentDateEl, "selected"); - Calendar.addClass(el, "selected"); - closing = (cal.currentDateEl == el); - if (!closing) { - cal.currentDateEl = el; - } - } - cal.date.setDateOnly(el.caldate); - date = cal.date; - var other_month = !(cal.dateClicked = !el.otherMonth); - if (!other_month && !cal.currentDateEl) - cal._toggleMultipleDate(new Date(date)); - else - newdate = !el.disabled; - // a date was clicked - if (other_month) - cal._init(cal.firstDayOfWeek, date); - } else { - if (el.navtype == 200) { - Calendar.removeClass(el, "hilite"); - cal.callCloseHandler(); - return; - } - date = new Date(cal.date); - if (el.navtype == 0) - date.setDateOnly(new Date()); // TODAY - // unless "today" was clicked, we assume no date was clicked so - // the selected handler will know not to close the calenar when - // in single-click mode. - // cal.dateClicked = (el.navtype == 0); - cal.dateClicked = false; - var year = date.getFullYear(); - var mon = date.getMonth(); - function setMonth(m) { - var day = date.getDate(); - var max = date.getMonthDays(m); - if (day > max) { - date.setDate(max); - } - date.setMonth(m); - }; - switch (el.navtype) { - case 400: - Calendar.removeClass(el, "hilite"); - var text = Calendar._TT["ABOUT"]; - if (typeof text != "undefined") { - text += cal.showsTime ? Calendar._TT["ABOUT_TIME"] : ""; - } else { - // FIXME: this should be removed as soon as lang files get updated! - text = "Help and about box text is not translated into this language.\n" + - "If you know this language and you feel generous please update\n" + - "the corresponding file in \"lang\" subdir to match calendar-en.js\n" + - "and send it back to to get it into the distribution ;-)\n\n" + - "Thank you!\n" + - "http://dynarch.com/mishoo/calendar.epl\n"; - } - alert(text); - return; - case -2: - if (year > cal.minYear) { - date.setFullYear(year - 1); - } - break; - case -1: - if (mon > 0) { - setMonth(mon - 1); - } else if (year-- > cal.minYear) { - date.setFullYear(year); - setMonth(11); - } - break; - case 1: - if (mon < 11) { - setMonth(mon + 1); - } else if (year < cal.maxYear) { - date.setFullYear(year + 1); - setMonth(0); - } - break; - case 2: - if (year < cal.maxYear) { - date.setFullYear(year + 1); - } - break; - case 100: - cal.setFirstDayOfWeek(el.fdow); - return; - case 50: - var range = el._range; - var current = el.innerHTML; - for (var i = range.length; --i >= 0;) - if (range[i] == current) - break; - if (ev && ev.shiftKey) { - if (--i < 0) - i = range.length - 1; - } else if ( ++i >= range.length ) - i = 0; - var newval = range[i]; - el.innerHTML = newval; - cal.onUpdateTime(); - return; - case 0: - // TODAY will bring us here - if ((typeof cal.getDateStatus == "function") && - cal.getDateStatus(date, date.getFullYear(), date.getMonth(), date.getDate())) { - return false; - } - break; - } - if (!date.equalsTo(cal.date)) { - cal.setDate(date); - newdate = true; - } else if (el.navtype == 0) - newdate = closing = true; - } - if (newdate) { - ev && cal.callHandler(); - } - if (closing) { - Calendar.removeClass(el, "hilite"); - ev && cal.callCloseHandler(); - } -}; - -// END: CALENDAR STATIC FUNCTIONS - -// BEGIN: CALENDAR OBJECT FUNCTIONS - -/** - * This function creates the calendar inside the given parent. If _par is - * null than it creates a popup calendar inside the BODY element. If _par is - * an element, be it BODY, then it creates a non-popup calendar (still - * hidden). Some properties need to be set before calling this function. - */ -Calendar.prototype.create = function (_par) { - var parent = null; - if (! _par) { - // default parent is the document body, in which case we create - // a popup calendar. - parent = document.getElementsByTagName("body")[0]; - this.isPopup = true; - } else { - parent = _par; - this.isPopup = false; - } - this.date = this.dateStr ? new Date(this.dateStr) : new Date(); - - var table = Calendar.createElement("table"); - this.table = table; - table.cellSpacing = 0; - table.cellPadding = 0; - table.calendar = this; - Calendar.addEvent(table, "mousedown", Calendar.tableMouseDown); - - var div = Calendar.createElement("div"); - this.element = div; - div.className = "calendar"; - if (this.isPopup) { - div.style.position = "absolute"; - div.style.display = "none"; - } - div.appendChild(table); - - var thead = Calendar.createElement("thead", table); - var cell = null; - var row = null; - - var cal = this; - var hh = function (text, cs, navtype) { - cell = Calendar.createElement("td", row); - cell.colSpan = cs; - cell.className = "button"; - if (navtype != 0 && Math.abs(navtype) <= 2) - cell.className += " nav"; - Calendar._add_evs(cell); - cell.calendar = cal; - cell.navtype = navtype; - cell.innerHTML = "
" + text + "
"; - return cell; - }; - - row = Calendar.createElement("tr", thead); - var title_length = 6; - (this.isPopup) && --title_length; - (this.weekNumbers) && ++title_length; - - hh("?", 1, 400).ttip = Calendar._TT["INFO"]; - this.title = hh("", title_length, 300); - this.title.className = "title"; - if (this.isPopup) { - this.title.ttip = Calendar._TT["DRAG_TO_MOVE"]; - this.title.style.cursor = "move"; - hh("×", 1, 200).ttip = Calendar._TT["CLOSE"]; - } - - row = Calendar.createElement("tr", thead); - row.className = "headrow"; - - this._nav_py = hh("«", 1, -2); - this._nav_py.ttip = Calendar._TT["PREV_YEAR"]; - - this._nav_pm = hh("‹", 1, -1); - this._nav_pm.ttip = Calendar._TT["PREV_MONTH"]; - - this._nav_now = hh(Calendar._TT["TODAY"], this.weekNumbers ? 4 : 3, 0); - this._nav_now.ttip = Calendar._TT["GO_TODAY"]; - - this._nav_nm = hh("›", 1, 1); - this._nav_nm.ttip = Calendar._TT["NEXT_MONTH"]; - - this._nav_ny = hh("»", 1, 2); - this._nav_ny.ttip = Calendar._TT["NEXT_YEAR"]; - - // day names - row = Calendar.createElement("tr", thead); - row.className = "daynames"; - if (this.weekNumbers) { - cell = Calendar.createElement("td", row); - cell.className = "name wn"; - cell.innerHTML = Calendar._TT["WK"]; - } - for (var i = 7; i > 0; --i) { - cell = Calendar.createElement("td", row); - if (!i) { - cell.navtype = 100; - cell.calendar = this; - Calendar._add_evs(cell); - } - } - this.firstdayname = (this.weekNumbers) ? row.firstChild.nextSibling : row.firstChild; - this._displayWeekdays(); - - var tbody = Calendar.createElement("tbody", table); - this.tbody = tbody; - - for (i = 6; i > 0; --i) { - row = Calendar.createElement("tr", tbody); - if (this.weekNumbers) { - cell = Calendar.createElement("td", row); - } - for (var j = 7; j > 0; --j) { - cell = Calendar.createElement("td", row); - cell.calendar = this; - Calendar._add_evs(cell); - } - } - - if (this.showsTime) { - row = Calendar.createElement("tr", tbody); - row.className = "time"; - - cell = Calendar.createElement("td", row); - cell.className = "time"; - cell.colSpan = 2; - cell.innerHTML = Calendar._TT["TIME"] || " "; - - cell = Calendar.createElement("td", row); - cell.className = "time"; - cell.colSpan = this.weekNumbers ? 4 : 3; - - (function(){ - function makeTimePart(className, init, range_start, range_end) { - var part = Calendar.createElement("span", cell); - part.className = className; - part.innerHTML = init; - part.calendar = cal; - part.ttip = Calendar._TT["TIME_PART"]; - part.navtype = 50; - part._range = []; - if (typeof range_start != "number") - part._range = range_start; - else { - for (var i = range_start; i <= range_end; ++i) { - var txt; - if (i < 10 && range_end >= 10) txt = '0' + i; - else txt = '' + i; - part._range[part._range.length] = txt; - } - } - Calendar._add_evs(part); - return part; - }; - var hrs = cal.date.getHours(); - var mins = cal.date.getMinutes(); - var t12 = !cal.time24; - var pm = (hrs > 12); - if (t12 && pm) hrs -= 12; - var H = makeTimePart("hour", hrs, t12 ? 1 : 0, t12 ? 12 : 23); - var span = Calendar.createElement("span", cell); - span.innerHTML = ":"; - span.className = "colon"; - var M = makeTimePart("minute", mins, 0, 59); - var AP = null; - cell = Calendar.createElement("td", row); - cell.className = "time"; - cell.colSpan = 2; - if (t12) - AP = makeTimePart("ampm", pm ? "pm" : "am", ["am", "pm"]); - else - cell.innerHTML = " "; - - cal.onSetTime = function() { - var pm, hrs = this.date.getHours(), - mins = this.date.getMinutes(); - if (t12) { - pm = (hrs >= 12); - if (pm) hrs -= 12; - if (hrs == 0) hrs = 12; - AP.innerHTML = pm ? "pm" : "am"; - } - H.innerHTML = (hrs < 10) ? ("0" + hrs) : hrs; - M.innerHTML = (mins < 10) ? ("0" + mins) : mins; - }; - - cal.onUpdateTime = function() { - var date = this.date; - var h = parseInt(H.innerHTML, 10); - if (t12) { - if (/pm/i.test(AP.innerHTML) && h < 12) - h += 12; - else if (/am/i.test(AP.innerHTML) && h == 12) - h = 0; - } - var d = date.getDate(); - var m = date.getMonth(); - var y = date.getFullYear(); - date.setHours(h); - date.setMinutes(parseInt(M.innerHTML, 10)); - date.setFullYear(y); - date.setMonth(m); - date.setDate(d); - this.dateClicked = false; - this.callHandler(); - }; - })(); - } else { - this.onSetTime = this.onUpdateTime = function() {}; - } - - var tfoot = Calendar.createElement("tfoot", table); - - row = Calendar.createElement("tr", tfoot); - row.className = "footrow"; - - cell = hh(Calendar._TT["SEL_DATE"], this.weekNumbers ? 8 : 7, 300); - cell.className = "ttip"; - if (this.isPopup) { - cell.ttip = Calendar._TT["DRAG_TO_MOVE"]; - cell.style.cursor = "move"; - } - this.tooltips = cell; - - div = Calendar.createElement("div", this.element); - this.monthsCombo = div; - div.className = "combo"; - for (i = 0; i < Calendar._MN.length; ++i) { - var mn = Calendar.createElement("div"); - mn.className = Calendar.is_ie ? "label-IEfix" : "label"; - mn.month = i; - mn.innerHTML = Calendar._SMN[i]; - div.appendChild(mn); - } - - div = Calendar.createElement("div", this.element); - this.yearsCombo = div; - div.className = "combo"; - for (i = 12; i > 0; --i) { - var yr = Calendar.createElement("div"); - yr.className = Calendar.is_ie ? "label-IEfix" : "label"; - div.appendChild(yr); - } - - this._init(this.firstDayOfWeek, this.date); - parent.appendChild(this.element); -}; - -/** keyboard navigation, only for popup calendars */ -Calendar._keyEvent = function(ev) { - var cal = window._dynarch_popupCalendar; - if (!cal || cal.multiple) - return false; - (Calendar.is_ie) && (ev = window.event); - var act = (Calendar.is_ie || ev.type == "keypress"), - K = ev.keyCode; - if (ev.ctrlKey) { - switch (K) { - case 37: // KEY left - act && Calendar.cellClick(cal._nav_pm); - break; - case 38: // KEY up - act && Calendar.cellClick(cal._nav_py); - break; - case 39: // KEY right - act && Calendar.cellClick(cal._nav_nm); - break; - case 40: // KEY down - act && Calendar.cellClick(cal._nav_ny); - break; - default: - return false; - } - } else switch (K) { - case 32: // KEY space (now) - Calendar.cellClick(cal._nav_now); - break; - case 27: // KEY esc - act && cal.callCloseHandler(); - break; - case 37: // KEY left - case 38: // KEY up - case 39: // KEY right - case 40: // KEY down - if (act) { - var prev, x, y, ne, el, step; - prev = K == 37 || K == 38; - step = (K == 37 || K == 39) ? 1 : 7; - function setVars() { - el = cal.currentDateEl; - var p = el.pos; - x = p & 15; - y = p >> 4; - ne = cal.ar_days[y][x]; - };setVars(); - function prevMonth() { - var date = new Date(cal.date); - date.setDate(date.getDate() - step); - cal.setDate(date); - }; - function nextMonth() { - var date = new Date(cal.date); - date.setDate(date.getDate() + step); - cal.setDate(date); - }; - while (1) { - switch (K) { - case 37: // KEY left - if (--x >= 0) - ne = cal.ar_days[y][x]; - else { - x = 6; - K = 38; - continue; - } - break; - case 38: // KEY up - if (--y >= 0) - ne = cal.ar_days[y][x]; - else { - prevMonth(); - setVars(); - } - break; - case 39: // KEY right - if (++x < 7) - ne = cal.ar_days[y][x]; - else { - x = 0; - K = 40; - continue; - } - break; - case 40: // KEY down - if (++y < cal.ar_days.length) - ne = cal.ar_days[y][x]; - else { - nextMonth(); - setVars(); - } - break; - } - break; - } - if (ne) { - if (!ne.disabled) - Calendar.cellClick(ne); - else if (prev) - prevMonth(); - else - nextMonth(); - } - } - break; - case 13: // KEY enter - if (act) - Calendar.cellClick(cal.currentDateEl, ev); - break; - default: - return false; - } - return Calendar.stopEvent(ev); -}; - -/** - * (RE)Initializes the calendar to the given date and firstDayOfWeek - */ -Calendar.prototype._init = function (firstDayOfWeek, date) { - var today = new Date(), - TY = today.getFullYear(), - TM = today.getMonth(), - TD = today.getDate(); - this.table.style.visibility = "hidden"; - var year = date.getFullYear(); - if (year < this.minYear) { - year = this.minYear; - date.setFullYear(year); - } else if (year > this.maxYear) { - year = this.maxYear; - date.setFullYear(year); - } - this.firstDayOfWeek = firstDayOfWeek; - this.date = new Date(date); - var month = date.getMonth(); - var mday = date.getDate(); - var no_days = date.getMonthDays(); - - // calendar voodoo for computing the first day that would actually be - // displayed in the calendar, even if it's from the previous month. - // WARNING: this is magic. ;-) - date.setDate(1); - var day1 = (date.getDay() - this.firstDayOfWeek) % 7; - if (day1 < 0) - day1 += 7; - date.setDate(0-day1); - date.setDate(date.getDate() + 1); - - var row = this.tbody.firstChild; - var MN = Calendar._SMN[month]; - var ar_days = this.ar_days = new Array(); - var weekend = Calendar._TT["WEEKEND"]; - var dates = this.multiple ? (this.datesCells = {}) : null; - for (var i = 0; i < 6; ++i, row = row.nextSibling) { - var cell = row.firstChild; - if (this.weekNumbers) { - cell.className = "day wn"; - cell.innerHTML = date.getWeekNumber(); - cell = cell.nextSibling; - } - row.className = "daysrow"; - var hasdays = false, iday, dpos = ar_days[i] = []; - for (var j = 0; j < 7; ++j, cell = cell.nextSibling, date.setDate(iday + 1)) { - iday = date.getDate(); - var wday = date.getDay(); - cell.className = "day"; - cell.pos = i << 4 | j; - dpos[j] = cell; - var current_month = (date.getMonth() == month); - if (!current_month) { - if (this.showsOtherMonths) { - cell.className += " othermonth"; - cell.otherMonth = true; - } else { - cell.className = "emptycell"; - cell.innerHTML = " "; - cell.disabled = true; - continue; - } - } else { - cell.otherMonth = false; - hasdays = true; - } - cell.disabled = false; - cell.innerHTML = this.getDateText ? this.getDateText(date, iday) : iday; - if (dates) - dates[date.print("%Y%m%d")] = cell; - if (this.getDateStatus) { - var status = this.getDateStatus(date, year, month, iday); - if (this.getDateToolTip) { - var toolTip = this.getDateToolTip(date, year, month, iday); - if (toolTip) - cell.title = toolTip; - } - if (status === true) { - cell.className += " disabled"; - cell.disabled = true; - } else { - if (/disabled/i.test(status)) - cell.disabled = true; - cell.className += " " + status; - } - } - if (!cell.disabled) { - cell.caldate = new Date(date); - cell.ttip = "_"; - if (!this.multiple && current_month - && iday == mday && this.hiliteToday) { - cell.className += " selected"; - this.currentDateEl = cell; - } - if (date.getFullYear() == TY && - date.getMonth() == TM && - iday == TD) { - cell.className += " today"; - cell.ttip += Calendar._TT["PART_TODAY"]; - } - if (weekend.indexOf(wday.toString()) != -1) - cell.className += cell.otherMonth ? " oweekend" : " weekend"; - } - } - if (!(hasdays || this.showsOtherMonths)) - row.className = "emptyrow"; - } - this.title.innerHTML = Calendar._MN[month] + ", " + year; - this.onSetTime(); - this.table.style.visibility = "visible"; - this._initMultipleDates(); - // PROFILE - // this.tooltips.innerHTML = "Generated in " + ((new Date()) - today) + " ms"; -}; - -Calendar.prototype._initMultipleDates = function() { - if (this.multiple) { - for (var i in this.multiple) { - var cell = this.datesCells[i]; - var d = this.multiple[i]; - if (!d) - continue; - if (cell) - cell.className += " selected"; - } - } -}; - -Calendar.prototype._toggleMultipleDate = function(date) { - if (this.multiple) { - var ds = date.print("%Y%m%d"); - var cell = this.datesCells[ds]; - if (cell) { - var d = this.multiple[ds]; - if (!d) { - Calendar.addClass(cell, "selected"); - this.multiple[ds] = date; - } else { - Calendar.removeClass(cell, "selected"); - delete this.multiple[ds]; - } - } - } -}; - -Calendar.prototype.setDateToolTipHandler = function (unaryFunction) { - this.getDateToolTip = unaryFunction; -}; - -/** - * Calls _init function above for going to a certain date (but only if the - * date is different than the currently selected one). - */ -Calendar.prototype.setDate = function (date) { - if (!date.equalsTo(this.date)) { - this._init(this.firstDayOfWeek, date); - } -}; - -/** - * Refreshes the calendar. Useful if the "disabledHandler" function is - * dynamic, meaning that the list of disabled date can change at runtime. - * Just * call this function if you think that the list of disabled dates - * should * change. - */ -Calendar.prototype.refresh = function () { - this._init(this.firstDayOfWeek, this.date); -}; - -/** Modifies the "firstDayOfWeek" parameter (pass 0 for Synday, 1 for Monday, etc.). */ -Calendar.prototype.setFirstDayOfWeek = function (firstDayOfWeek) { - this._init(firstDayOfWeek, this.date); - this._displayWeekdays(); -}; - -/** - * Allows customization of what dates are enabled. The "unaryFunction" - * parameter must be a function object that receives the date (as a JS Date - * object) and returns a boolean value. If the returned value is true then - * the passed date will be marked as disabled. - */ -Calendar.prototype.setDateStatusHandler = Calendar.prototype.setDisabledHandler = function (unaryFunction) { - this.getDateStatus = unaryFunction; -}; - -/** Customization of allowed year range for the calendar. */ -Calendar.prototype.setRange = function (a, z) { - this.minYear = a; - this.maxYear = z; -}; - -/** Calls the first user handler (selectedHandler). */ -Calendar.prototype.callHandler = function () { - if (this.onSelected) { - this.onSelected(this, this.date.print(this.dateFormat)); - } -}; - -/** Calls the second user handler (closeHandler). */ -Calendar.prototype.callCloseHandler = function () { - if (this.onClose) { - this.onClose(this); - } - this.hideShowCovered(); -}; - -/** Removes the calendar object from the DOM tree and destroys it. */ -Calendar.prototype.destroy = function () { - var el = this.element.parentNode; - el.removeChild(this.element); - Calendar._C = null; - window._dynarch_popupCalendar = null; -}; - -/** - * Moves the calendar element to a different section in the DOM tree (changes - * its parent). - */ -Calendar.prototype.reparent = function (new_parent) { - var el = this.element; - el.parentNode.removeChild(el); - new_parent.appendChild(el); -}; - -// This gets called when the user presses a mouse button anywhere in the -// document, if the calendar is shown. If the click was outside the open -// calendar this function closes it. -Calendar._checkCalendar = function(ev) { - var calendar = window._dynarch_popupCalendar; - if (!calendar) { - return false; - } - var el = Calendar.is_ie ? Calendar.getElement(ev) : Calendar.getTargetElement(ev); - for (; el != null && el != calendar.element; el = el.parentNode); - if (el == null) { - // calls closeHandler which should hide the calendar. - window._dynarch_popupCalendar.callCloseHandler(); - return Calendar.stopEvent(ev); - } -}; - -/** Shows the calendar. */ -Calendar.prototype.show = function () { - var rows = this.table.getElementsByTagName("tr"); - for (var i = rows.length; i > 0;) { - var row = rows[--i]; - Calendar.removeClass(row, "rowhilite"); - var cells = row.getElementsByTagName("td"); - for (var j = cells.length; j > 0;) { - var cell = cells[--j]; - Calendar.removeClass(cell, "hilite"); - Calendar.removeClass(cell, "active"); - } - } - this.element.style.display = "block"; - this.hidden = false; - if (this.isPopup) { - window._dynarch_popupCalendar = this; - Calendar.addEvent(document, "keydown", Calendar._keyEvent); - Calendar.addEvent(document, "keypress", Calendar._keyEvent); - Calendar.addEvent(document, "mousedown", Calendar._checkCalendar); - } - this.hideShowCovered(); -}; - -/** - * Hides the calendar. Also removes any "hilite" from the class of any TD - * element. - */ -Calendar.prototype.hide = function () { - if (this.isPopup) { - Calendar.removeEvent(document, "keydown", Calendar._keyEvent); - Calendar.removeEvent(document, "keypress", Calendar._keyEvent); - Calendar.removeEvent(document, "mousedown", Calendar._checkCalendar); - } - this.element.style.display = "none"; - this.hidden = true; - this.hideShowCovered(); -}; - -/** - * Shows the calendar at a given absolute position (beware that, depending on - * the calendar element style -- position property -- this might be relative - * to the parent's containing rectangle). - */ -Calendar.prototype.showAt = function (x, y) { - var s = this.element.style; - s.left = x + "px"; - s.top = y + "px"; - this.show(); -}; - -/** Shows the calendar near a given element. */ -Calendar.prototype.showAtElement = function (el, opts) { - var self = this; - var p = Calendar.getAbsolutePos(el); - if (!opts || typeof opts != "string") { - this.showAt(p.x, p.y + el.offsetHeight); - return true; - } - function fixPosition(box) { - if (box.x < 0) - box.x = 0; - if (box.y < 0) - box.y = 0; - var cp = document.createElement("div"); - var s = cp.style; - s.position = "absolute"; - s.right = s.bottom = s.width = s.height = "0px"; - document.body.appendChild(cp); - var br = Calendar.getAbsolutePos(cp); - document.body.removeChild(cp); - if (Calendar.is_ie) { - br.y += document.body.scrollTop; - br.x += document.body.scrollLeft; - } else { - br.y += window.scrollY; - br.x += window.scrollX; - } - var tmp = box.x + box.width - br.x; - if (tmp > 0) box.x -= tmp; - tmp = box.y + box.height - br.y; - if (tmp > 0) box.y -= tmp; - }; - this.element.style.display = "block"; - Calendar.continuation_for_the_fucking_khtml_browser = function() { - var w = self.element.offsetWidth; - var h = self.element.offsetHeight; - self.element.style.display = "none"; - var valign = opts.substr(0, 1); - var halign = "l"; - if (opts.length > 1) { - halign = opts.substr(1, 1); - } - // vertical alignment - switch (valign) { - case "T": p.y -= h; break; - case "B": p.y += el.offsetHeight; break; - case "C": p.y += (el.offsetHeight - h) / 2; break; - case "t": p.y += el.offsetHeight - h; break; - case "b": break; // already there - } - // horizontal alignment - switch (halign) { - case "L": p.x -= w; break; - case "R": p.x += el.offsetWidth; break; - case "C": p.x += (el.offsetWidth - w) / 2; break; - case "l": p.x += el.offsetWidth - w; break; - case "r": break; // already there - } - p.width = w; - p.height = h + 40; - self.monthsCombo.style.display = "none"; - fixPosition(p); - self.showAt(p.x, p.y); - }; - if (Calendar.is_khtml) - setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()", 10); - else - Calendar.continuation_for_the_fucking_khtml_browser(); -}; - -/** Customizes the date format. */ -Calendar.prototype.setDateFormat = function (str) { - this.dateFormat = str; -}; - -/** Customizes the tooltip date format. */ -Calendar.prototype.setTtDateFormat = function (str) { - this.ttDateFormat = str; -}; - -/** - * Tries to identify the date represented in a string. If successful it also - * calls this.setDate which moves the calendar to the given date. - */ -Calendar.prototype.parseDate = function(str, fmt) { - if (!fmt) - fmt = this.dateFormat; - this.setDate(Date.parseDate(str, fmt)); -}; - -Calendar.prototype.hideShowCovered = function () { - if (!Calendar.is_ie && !Calendar.is_opera) - return; - function getVisib(obj){ - var value = obj.style.visibility; - if (!value) { - if (document.defaultView && typeof (document.defaultView.getComputedStyle) == "function") { // Gecko, W3C - if (!Calendar.is_khtml) - value = document.defaultView. - getComputedStyle(obj, "").getPropertyValue("visibility"); - else - value = ''; - } else if (obj.currentStyle) { // IE - value = obj.currentStyle.visibility; - } else - value = ''; - } - return value; - }; - - var tags = new Array("applet", "iframe", "select"); - var el = this.element; - - var p = Calendar.getAbsolutePos(el); - var EX1 = p.x; - var EX2 = el.offsetWidth + EX1; - var EY1 = p.y; - var EY2 = el.offsetHeight + EY1; - - for (var k = tags.length; k > 0; ) { - var ar = document.getElementsByTagName(tags[--k]); - var cc = null; - - for (var i = ar.length; i > 0;) { - cc = ar[--i]; - - p = Calendar.getAbsolutePos(cc); - var CX1 = p.x; - var CX2 = cc.offsetWidth + CX1; - var CY1 = p.y; - var CY2 = cc.offsetHeight + CY1; - - if (this.hidden || (CX1 > EX2) || (CX2 < EX1) || (CY1 > EY2) || (CY2 < EY1)) { - if (!cc.__msh_save_visibility) { - cc.__msh_save_visibility = getVisib(cc); - } - cc.style.visibility = cc.__msh_save_visibility; - } else { - if (!cc.__msh_save_visibility) { - cc.__msh_save_visibility = getVisib(cc); - } - cc.style.visibility = "hidden"; - } - } - } -}; - -/** Internal function; it displays the bar with the names of the weekday. */ -Calendar.prototype._displayWeekdays = function () { - var fdow = this.firstDayOfWeek; - var cell = this.firstdayname; - var weekend = Calendar._TT["WEEKEND"]; - for (var i = 0; i < 7; ++i) { - cell.className = "day name"; - var realday = (i + fdow) % 7; - if (i) { - cell.ttip = Calendar._TT["DAY_FIRST"].replace("%s", Calendar._DN[realday]); - cell.navtype = 100; - cell.calendar = this; - cell.fdow = realday; - Calendar._add_evs(cell); - } - if (weekend.indexOf(realday.toString()) != -1) { - Calendar.addClass(cell, "weekend"); - } - cell.innerHTML = Calendar._SDN[(i + fdow) % 7]; - cell = cell.nextSibling; - } -}; - -/** Internal function. Hides all combo boxes that might be displayed. */ -Calendar.prototype._hideCombos = function () { - this.monthsCombo.style.display = "none"; - this.yearsCombo.style.display = "none"; -}; - -/** Internal function. Starts dragging the element. */ -Calendar.prototype._dragStart = function (ev) { - if (this.dragging) { - return; - } - this.dragging = true; - var posX; - var posY; - if (Calendar.is_ie) { - posY = window.event.clientY + document.body.scrollTop; - posX = window.event.clientX + document.body.scrollLeft; - } else { - posY = ev.clientY + window.scrollY; - posX = ev.clientX + window.scrollX; - } - var st = this.element.style; - this.xOffs = posX - parseInt(st.left); - this.yOffs = posY - parseInt(st.top); - with (Calendar) { - addEvent(document, "mousemove", calDragIt); - addEvent(document, "mouseup", calDragEnd); - } -}; - -// BEGIN: DATE OBJECT PATCHES - -/** Adds the number of days array to the Date object. */ -Date._MD = new Array(31,28,31,30,31,30,31,31,30,31,30,31); - -/** Constants used for time computations */ -Date.SECOND = 1000 /* milliseconds */; -Date.MINUTE = 60 * Date.SECOND; -Date.HOUR = 60 * Date.MINUTE; -Date.DAY = 24 * Date.HOUR; -Date.WEEK = 7 * Date.DAY; - -Date.parseDate = function(str, fmt) { - var today = new Date(); - var y = 0; - var m = -1; - var d = 0; - var a = str.split(/\W+/); - var b = fmt.match(/%./g); - var i = 0, j = 0; - var hr = 0; - var min = 0; - for (i = 0; i < a.length; ++i) { - if (!a[i]) - continue; - switch (b[i]) { - case "%d": - case "%e": - d = parseInt(a[i], 10); - break; - - case "%m": - m = parseInt(a[i], 10) - 1; - break; - - case "%Y": - case "%y": - y = parseInt(a[i], 10); - (y < 100) && (y += (y > 29) ? 1900 : 2000); - break; - - case "%b": - case "%B": - for (j = 0; j < 12; ++j) { - if (Calendar._MN[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { m = j; break; } - } - break; - - case "%H": - case "%I": - case "%k": - case "%l": - hr = parseInt(a[i], 10); - break; - - case "%P": - case "%p": - if (/pm/i.test(a[i]) && hr < 12) - hr += 12; - else if (/am/i.test(a[i]) && hr >= 12) - hr -= 12; - break; - - case "%M": - min = parseInt(a[i], 10); - break; - } - } - if (isNaN(y)) y = today.getFullYear(); - if (isNaN(m)) m = today.getMonth(); - if (isNaN(d)) d = today.getDate(); - if (isNaN(hr)) hr = today.getHours(); - if (isNaN(min)) min = today.getMinutes(); - if (y != 0 && m != -1 && d != 0) - return new Date(y, m, d, hr, min, 0); - y = 0; m = -1; d = 0; - for (i = 0; i < a.length; ++i) { - if (a[i].search(/[a-zA-Z]+/) != -1) { - var t = -1; - for (j = 0; j < 12; ++j) { - if (Calendar._MN[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { t = j; break; } - } - if (t != -1) { - if (m != -1) { - d = m+1; - } - m = t; - } - } else if (parseInt(a[i], 10) <= 12 && m == -1) { - m = a[i]-1; - } else if (parseInt(a[i], 10) > 31 && y == 0) { - y = parseInt(a[i], 10); - (y < 100) && (y += (y > 29) ? 1900 : 2000); - } else if (d == 0) { - d = a[i]; - } - } - if (y == 0) - y = today.getFullYear(); - if (m != -1 && d != 0) - return new Date(y, m, d, hr, min, 0); - return today; -}; - -/** Returns the number of days in the current month */ -Date.prototype.getMonthDays = function(month) { - var year = this.getFullYear(); - if (typeof month == "undefined") { - month = this.getMonth(); - } - if (((0 == (year%4)) && ( (0 != (year%100)) || (0 == (year%400)))) && month == 1) { - return 29; - } else { - return Date._MD[month]; - } -}; - -/** Returns the number of day in the year. */ -Date.prototype.getDayOfYear = function() { - var now = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0); - var then = new Date(this.getFullYear(), 0, 0, 0, 0, 0); - var time = now - then; - return Math.floor(time / Date.DAY); -}; - -/** Returns the number of the week in year, as defined in ISO 8601. - This function is only correct if `this` is the first day of the week. */ -Date.prototype.getWeekNumber = function() { - var d = new Date(this.getFullYear(), this.getMonth(), this.getDate()); - var days = 1000*60*60*24; // one day in milliseconds - - // get the thursday of the current week - var this_thursday = new Date( - d.valueOf() // selected date - - (d.getDay() % 7)*days // previous sunday - + 4*days // + 4 days - ).valueOf(); - - // the thursday in the first week of the year - var first_thursday = new Date( - new Date(this.getFullYear(), 0, 4).valueOf() // January 4 is in the first week by definition - - (d.getDay() % 7)*days // previous sunday - + 4*days // + 4 days - ).valueOf(); - - return Math.round((this_thursday - first_thursday) / (7*days)) + 1; -}; - -/** Checks date and time equality */ -Date.prototype.equalsTo = function(date) { - return ((this.getFullYear() == date.getFullYear()) && - (this.getMonth() == date.getMonth()) && - (this.getDate() == date.getDate()) && - (this.getHours() == date.getHours()) && - (this.getMinutes() == date.getMinutes())); -}; - -/** Set only the year, month, date parts (keep existing time) */ -Date.prototype.setDateOnly = function(date) { - var tmp = new Date(date); - this.setDate(1); - this.setFullYear(tmp.getFullYear()); - this.setMonth(tmp.getMonth()); - this.setDate(tmp.getDate()); -}; - -/** Prints the date in a string according to the given format. */ -Date.prototype.print = function (str) { - var m = this.getMonth(); - var d = this.getDate(); - var y = this.getFullYear(); - var wn = this.getWeekNumber(); - var w = this.getDay(); - var s = {}; - var hr = this.getHours(); - var pm = (hr >= 12); - var ir = (pm) ? (hr - 12) : hr; - var dy = this.getDayOfYear(); - if (ir == 0) - ir = 12; - var min = this.getMinutes(); - var sec = this.getSeconds(); - s["%a"] = Calendar._SDN[w]; // abbreviated weekday name [FIXME: I18N] - s["%A"] = Calendar._DN[w]; // full weekday name - s["%b"] = Calendar._SMN[m]; // abbreviated month name [FIXME: I18N] - s["%B"] = Calendar._MN[m]; // full month name - // FIXME: %c : preferred date and time representation for the current locale - s["%C"] = 1 + Math.floor(y / 100); // the century number - s["%d"] = (d < 10) ? ("0" + d) : d; // the day of the month (range 01 to 31) - s["%e"] = d; // the day of the month (range 1 to 31) - // FIXME: %D : american date style: %m/%d/%y - // FIXME: %E, %F, %G, %g, %h (man strftime) - s["%H"] = (hr < 10) ? ("0" + hr) : hr; // hour, range 00 to 23 (24h format) - s["%I"] = (ir < 10) ? ("0" + ir) : ir; // hour, range 01 to 12 (12h format) - s["%j"] = (dy < 100) ? ((dy < 10) ? ("00" + dy) : ("0" + dy)) : dy; // day of the year (range 001 to 366) - s["%k"] = hr; // hour, range 0 to 23 (24h format) - s["%l"] = ir; // hour, range 1 to 12 (12h format) - s["%m"] = (m < 9) ? ("0" + (1+m)) : (1+m); // month, range 01 to 12 - s["%M"] = (min < 10) ? ("0" + min) : min; // minute, range 00 to 59 - s["%n"] = "\n"; // a newline character - s["%p"] = pm ? "PM" : "AM"; - s["%P"] = pm ? "pm" : "am"; - // FIXME: %r : the time in am/pm notation %I:%M:%S %p - // FIXME: %R : the time in 24-hour notation %H:%M - s["%s"] = Math.floor(this.getTime() / 1000); - s["%S"] = (sec < 10) ? ("0" + sec) : sec; // seconds, range 00 to 59 - s["%t"] = "\t"; // a tab character - // FIXME: %T : the time in 24-hour notation (%H:%M:%S) - s["%U"] = s["%W"] = s["%V"] = (wn < 10) ? ("0" + wn) : wn; - s["%u"] = w + 1; // the day of the week (range 1 to 7, 1 = MON) - s["%w"] = w; // the day of the week (range 0 to 6, 0 = SUN) - // FIXME: %x : preferred date representation for the current locale without the time - // FIXME: %X : preferred time representation for the current locale without the date - s["%y"] = ('' + y).substr(2, 2); // year without the century (range 00 to 99) - s["%Y"] = y; // year with the century - s["%%"] = "%"; // a literal '%' character - - var re = /%./g; - if (!Calendar.is_ie5 && !Calendar.is_khtml) - return str.replace(re, function (par) { return s[par] || par; }); - - var a = str.match(re); - for (var i = 0; i < a.length; i++) { - var tmp = s[a[i]]; - if (tmp) { - re = new RegExp(a[i], 'g'); - str = str.replace(re, tmp); - } - } - - return str; -}; - -Date.prototype.__msh_oldSetFullYear = Date.prototype.setFullYear; -Date.prototype.setFullYear = function(y) { - var d = new Date(this); - d.__msh_oldSetFullYear(y); - if (d.getMonth() != this.getMonth()) - this.setDate(28); - this.__msh_oldSetFullYear(y); -}; - -// END: DATE OBJECT PATCHES - - -// global object that remembers the calendar -window._dynarch_popupCalendar = null; diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/55/5525a45bbbb907719075729bed82fd9497b6bd66.svn-base --- a/.svn/pristine/55/5525a45bbbb907719075729bed82fd9497b6bd66.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -<% form_remote_tag(:url => {}, :html => { :id => "journal-#{@journal.id}-form" }) do %> - <%= label_tag "notes", l(:description_notes), :class => "hidden-for-sighted" %> - <%= text_area_tag :notes, @journal.notes, - :id => "journal_#{@journal.id}_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}) %> -

<%= submit_tag l(:button_save) %> - <%= link_to_remote l(:label_preview), - { :url => preview_issue_path(:project_id => @project, :id => @journal.issue), - :method => 'post', - :update => "journal_#{@journal.id}_preview", - :with => "Form.serialize('journal-#{@journal.id}-form')", - :complete => "Element.scrollTo('journal_#{@journal.id}_preview')" - }, :accesskey => accesskey(:preview) %> - | - <%= link_to l(:button_cancel), '#', :onclick => "Element.remove('journal-#{@journal.id}-form'); " + - "Element.show('journal-#{@journal.id}-notes'); return false;" %>

- -
-<% end %> -<%= wikitoolbar_for "journal_#{@journal.id}_notes" %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/55/5526a744674ffd2fe7e8f915bcf310ae2c2760f4.svn-base --- a/.svn/pristine/55/5526a744674ffd2fe7e8f915bcf310ae2c2760f4.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,23 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class TimeEntryCustomField < CustomField - def type_name - :label_spent_time - end -end - diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/55/552dd1073a880087085df9bfb9b43d69f28e0bfa.svn-base --- a/.svn/pristine/55/552dd1073a880087085df9bfb9b43d69f28e0bfa.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) - -class DefaultDataTest < ActiveSupport::TestCase - include Redmine::I18n - fixtures :roles - - def test_no_data - assert !Redmine::DefaultData::Loader::no_data? - Role.delete_all("builtin = 0") - Tracker.delete_all - IssueStatus.delete_all - Enumeration.delete_all - assert Redmine::DefaultData::Loader::no_data? - end - - def test_load - valid_languages.each do |lang| - begin - Role.delete_all("builtin = 0") - Tracker.delete_all - IssueStatus.delete_all - Enumeration.delete_all - assert Redmine::DefaultData::Loader::load(lang) - assert_not_nil DocumentCategory.first - assert_not_nil IssuePriority.first - assert_not_nil TimeEntryActivity.first - rescue ActiveRecord::RecordInvalid => e - assert false, ":#{lang} default data is invalid (#{e.message})." - end - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/55/554119d7707f8ea022be31bc48239ea95d10b4bf.svn-base --- a/.svn/pristine/55/554119d7707f8ea022be31bc48239ea95d10b4bf.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,161 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class CustomField < ActiveRecord::Base - has_many :custom_values, :dependent => :delete_all - acts_as_list :scope => 'type = \'#{self.class}\'' - serialize :possible_values - - validates_presence_of :name, :field_format - validates_uniqueness_of :name, :scope => :type - validates_length_of :name, :maximum => 30 - validates_inclusion_of :field_format, :in => Redmine::CustomFieldFormat.available_formats - - validate :validate_values - - def initialize(attributes = nil) - super - self.possible_values ||= [] - end - - def before_validation - # make sure these fields are not searchable - self.searchable = false if %w(int float date bool).include?(field_format) - true - end - - def validate_values - if self.field_format == "list" - errors.add(:possible_values, :blank) if self.possible_values.nil? || self.possible_values.empty? - errors.add(:possible_values, :invalid) unless self.possible_values.is_a? Array - end - - if regexp.present? - begin - Regexp.new(regexp) - rescue - errors.add(:regexp, :invalid) - end - end - - # validate default value - v = CustomValue.new(:custom_field => self.clone, :value => default_value, :customized => nil) - v.custom_field.is_required = false - errors.add(:default_value, :invalid) unless v.valid? - end - - def possible_values_options(obj=nil) - case field_format - when 'user', 'version' - if obj.respond_to?(:project) && obj.project - case field_format - when 'user' - obj.project.users.sort.collect {|u| [u.to_s, u.id.to_s]} - when 'version' - obj.project.shared_versions.sort.collect {|u| [u.to_s, u.id.to_s]} - end - elsif obj.is_a?(Array) - obj.collect {|o| possible_values_options(o)}.inject {|memo, v| memo & v} - else - [] - end - else - read_attribute :possible_values - end - end - - def possible_values(obj=nil) - case field_format - when 'user', 'version' - possible_values_options(obj).collect(&:last) - else - read_attribute :possible_values - end - 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 - - def cast_value(value) - casted = nil - unless value.blank? - case field_format - when 'string', 'text', 'list' - casted = value - when 'date' - casted = begin; value.to_date; rescue; nil end - when 'bool' - casted = (value == '1' ? true : false) - when 'int' - casted = value.to_i - when 'float' - casted = value.to_f - when 'user', 'version' - casted = (value.blank? ? nil : field_format.classify.constantize.find_by_id(value.to_i)) - end - end - casted - 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') - end - - def type_name - nil - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/55/5598548a6818651218cba8411c616699a4a94a8f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/55/5598548a6818651218cba8411c616699a4a94a8f.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,9 @@ +class AddAttachmentsDiskDirectory < ActiveRecord::Migration + def up + add_column :attachments, :disk_directory, :string + end + + def down + remove_column :attachments, :disk_directory + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/55/559c056bb0193e75aa2a9f749bccfd079cee7e4f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/55/559c056bb0193e75aa2a9f749bccfd079cee7e4f.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,22 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 DocumentObserver < ActiveRecord::Observer + def after_create(document) + Mailer.document_added(document).deliver if Setting.notified_events.include?('document_added') + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/55/55bd4be64003cd23d5039420dcbb14a174a1cede.svn-base --- a/.svn/pristine/55/55bd4be64003cd23d5039420dcbb14a174a1cede.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -class AppAndPluginController < ApplicationController - def an_action - render_class_and_action 'from alpha_plugin' - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/55/55c44f6ea2479c136aea73b874b97160faaae047.svn-base --- a/.svn/pristine/55/55c44f6ea2479c136aea73b874b97160faaae047.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,46 +0,0 @@ -module CodeRay -module Encoders - - # Concats the tokens into a single string, resulting in the original - # code string if no tokens were removed. - # - # Alias: +plain+, +plaintext+ - # - # == Options - # - # === :separator - # A separator string to join the tokens. - # - # Default: empty String - class Text < Encoder - - register_for :text - - FILE_EXTENSION = 'txt' - - DEFAULT_OPTIONS = { - :separator => nil - } - - def text_token text, kind - super - - if @first - @first = false - else - @out << @sep - end if @sep - end - - protected - def setup options - super - - @first = true - @sep = options[:separator] - end - - end - -end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/56/560f0241ba8e6899cca067c7cd63919a0ce16a9c.svn-base --- a/.svn/pristine/56/560f0241ba8e6899cca067c7cd63919a0ce16a9c.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,65 +0,0 @@ -module CodeRay -module Scanners - - # = Debug Scanner - # - # Interprets the output of the Encoders::Debug encoder. - class Debug < Scanner - - register_for :debug - title 'CodeRay Token Dump Import' - - protected - - def scan_tokens encoder, options - - opened_tokens = [] - - until eos? - - if match = scan(/\s+/) - encoder.text_token match, :space - - elsif match = scan(/ (\w+) \( ( [^\)\\]* ( \\. [^\)\\]* )* ) \)? /x) - kind = self[1].to_sym - match = self[2].gsub(/\\(.)/m, '\1') - unless TokenKinds.has_key? kind - kind = :error - match = matched - end - encoder.text_token match, kind - - elsif match = scan(/ (\w+) ([<\[]) /x) - kind = self[1].to_sym - opened_tokens << kind - case self[2] - when '<' - encoder.begin_group kind - when '[' - encoder.begin_line kind - else - raise 'CodeRay bug: This case should not be reached.' - end - - elsif !opened_tokens.empty? && match = scan(/ > /x) - encoder.end_group opened_tokens.pop - - elsif !opened_tokens.empty? && match = scan(/ \] /x) - encoder.end_line opened_tokens.pop - - else - encoder.text_token getch, :space - - end - - end - - encoder.end_group opened_tokens.pop until opened_tokens.empty? - - encoder - end - - end - -end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/56/5616c9d52a62f8dbe0d12f53a6285e1110cf74e0.svn-base --- a/.svn/pristine/56/5616c9d52a62f8dbe0d12f53a6285e1110cf74e0.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,115 +0,0 @@ -module CodeRay -module Encoders - - class HTML - - module Numbering # :nodoc: - - def self.number! output, mode = :table, options = {} - return self unless mode - - options = DEFAULT_OPTIONS.merge options - - start = options[:line_number_start] - unless start.is_a? Integer - raise ArgumentError, "Invalid value %p for :line_number_start; Integer expected." % start - end - - anchor_prefix = options[:line_number_anchors] - anchor_prefix = 'line' if anchor_prefix == true - anchor_prefix = anchor_prefix.to_s[/\w+/] if anchor_prefix - anchoring = - if anchor_prefix - proc do |line| - line = line.to_s - anchor = anchor_prefix + line - "#{line}" - end - else - proc { |line| line.to_s } # :to_s.to_proc in Ruby 1.8.7+ - end - - bold_every = options[:bold_every] - highlight_lines = options[:highlight_lines] - bolding = - if bold_every == false && highlight_lines == nil - anchoring - elsif highlight_lines.is_a? Enumerable - highlight_lines = highlight_lines.to_set - proc do |line| - if highlight_lines.include? line - "#{anchoring[line]}" # highlighted line numbers in bold - else - anchoring[line] - end - end - elsif bold_every.is_a? Integer - raise ArgumentError, ":bolding can't be 0." if bold_every == 0 - proc do |line| - if line % bold_every == 0 - "#{anchoring[line]}" # every bold_every-th number in bold - else - anchoring[line] - end - end - else - raise ArgumentError, 'Invalid value %p for :bolding; false or Integer expected.' % bold_every - end - - line_count = output.count("\n") - position_of_last_newline = output.rindex(RUBY_VERSION >= '1.9' ? /\n/ : ?\n) - if position_of_last_newline - after_last_newline = output[position_of_last_newline + 1 .. -1] - ends_with_newline = after_last_newline[/\A(?:<\/span>)*\z/] - line_count += 1 if not ends_with_newline - end - - case mode - when :inline - max_width = (start + line_count).to_s.size - line_number = start - nesting = [] - output.gsub!(/^.*$\n?/) do |line| - line.chomp! - open = nesting.join - line.scan(%r!<(/)?span[^>]*>?!) do |close,| - if close - nesting.pop - else - nesting << $& - end - end - close = '' * nesting.size - - line_number_text = bolding.call line_number - indent = ' ' * (max_width - line_number.to_s.size) # TODO: Optimize (10^x) - line_number += 1 - "#{indent}#{line_number_text}#{open}#{line}#{close}\n" - end - - when :table - line_numbers = (start ... start + line_count).map(&bolding).join("\n") - line_numbers << "\n" - line_numbers_table_template = Output::TABLE.apply('LINE_NUMBERS', line_numbers) - - output.gsub!(/<\/div>\n/, '
') - output.wrap_in! line_numbers_table_template - output.wrapped_in = :div - - when :list - raise NotImplementedError, 'The :list option is no longer available. Use :table.' - - else - raise ArgumentError, 'Unknown value %p for mode: expected one of %p' % - [mode, [:table, :inline]] - end - - output - end - - end - - end - -end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/56/56225b936777b98c482ec9e85d159d74d839691e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/56/56225b936777b98c482ec9e85d159d74d839691e.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,362 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../../../test_helper', __FILE__) + +class Redmine::WikiFormatting::MacrosTest < ActionView::TestCase + include ApplicationHelper + include ActionView::Helpers::TextHelper + include ActionView::Helpers::SanitizeHelper + include ERB::Util + extend ActionView::Helpers::SanitizeHelper::ClassMethods + + 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_registration + Redmine::WikiFormatting::Macros.register do + macro :foo do |obj, args| + "Foo: #{args.size} (#{args.join(',')}) (#{args.class.name})" + end + end + + assert_equal '

Foo: 0 () (Array)

', textilizable("{{foo}}") + assert_equal '

Foo: 0 () (Array)

', textilizable("{{foo()}}") + assert_equal '

Foo: 1 (arg1) (Array)

', textilizable("{{foo(arg1)}}") + assert_equal '

Foo: 2 (arg1,arg2) (Array)

', textilizable("{{foo(arg1, arg2)}}") + end + + def test_macro_registration_parse_args_set_to_false_should_disable_arguments_parsing + Redmine::WikiFormatting::Macros.register do + macro :bar, :parse_args => false do |obj, args| + "Bar: (#{args}) (#{args.class.name})" + end + end + + assert_equal '

Bar: (args, more args) (String)

', textilizable("{{bar(args, more args)}}") + assert_equal '

Bar: () (String)

', textilizable("{{bar}}") + assert_equal '

Bar: () (String)

', textilizable("{{bar()}}") + end + + def test_macro_registration_with_3_args_should_receive_text_argument + Redmine::WikiFormatting::Macros.register do + macro :baz do |obj, args, text| + "Baz: (#{args.join(',')}) (#{text.class.name}) (#{text})" + end + end + + assert_equal "

Baz: () (NilClass) ()

", textilizable("{{baz}}") + assert_equal "

Baz: () (NilClass) ()

", textilizable("{{baz()}}") + assert_equal "

Baz: () (String) (line1\nline2)

", textilizable("{{baz()\nline1\nline2\n}}") + assert_equal "

Baz: (arg1,arg2) (String) (line1\nline2)

", textilizable("{{baz(arg1, arg2)\nline1\nline2\n}}") + end + + def test_macro_name_with_upper_case + Redmine::WikiFormatting::Macros.macro(:UpperCase) {|obj, args| "Upper"} + + assert_equal "

Upper

", textilizable("{{UpperCase}}") + end + + def test_multiple_macros_on_the_same_line + Redmine::WikiFormatting::Macros.macro :foo do |obj, args| + args.any? ? "args: #{args.join(',')}" : "no args" + end + + assert_equal '

no args no args

', textilizable("{{foo}} {{foo}}") + assert_equal '

args: a,b no args

', textilizable("{{foo(a,b)}} {{foo}}") + assert_equal '

args: a,b args: c,d

', textilizable("{{foo(a,b)}} {{foo(c,d)}}") + assert_equal '

no args args: c,d

', textilizable("{{foo}} {{foo(c,d)}}") + end + + def test_macro_should_receive_the_object_as_argument_when_with_object_and_attribute + issue = Issue.find(1) + issue.description = "{{hello_world}}" + assert_equal '

Hello world! Object: Issue, Called with no argument and no block of text.

', textilizable(issue, :description) + end + + def test_macro_should_receive_the_object_as_argument_when_called_with_object_option + text = "{{hello_world}}" + assert_equal '

Hello world! Object: Issue, Called with no argument and no block of text.

', textilizable(text, :object => Issue.find(1)) + end + + def test_extract_macro_options_should_with_args + options = extract_macro_options(["arg1", "arg2"], :foo, :size) + assert_equal([["arg1", "arg2"], {}], options) + end + + def test_extract_macro_options_should_with_options + options = extract_macro_options(["foo=bar", "size=2"], :foo, :size) + assert_equal([[], {:foo => "bar", :size => "2"}], options) + end + + def test_extract_macro_options_should_with_args_and_options + options = extract_macro_options(["arg1", "arg2", "foo=bar", "size=2"], :foo, :size) + assert_equal([["arg1", "arg2"], {:foo => "bar", :size => "2"}], options) + end + + def test_extract_macro_options_should_parse_options_lazily + options = extract_macro_options(["params=x=1&y=2"], :params) + assert_equal([[], {:params => "x=1&y=2"}], options) + end + + def test_macro_exception_should_be_displayed + Redmine::WikiFormatting::Macros.macro :exception do |obj, args| + raise "My message" + end + + text = "{{exception}}" + assert_include '
Error executing the exception macro (My message)
', textilizable(text) + end + + def test_macro_arguments_should_not_be_parsed_by_formatters + text = '{{hello_world(http://www.redmine.org, #1)}}' + assert_include 'Arguments: http://www.redmine.org, #1', textilizable(text) + end + + def test_exclamation_mark_should_not_run_macros + text = "!{{hello_world}}" + assert_equal '

{{hello_world}}

', textilizable(text) + end + + def test_exclamation_mark_should_escape_macros + text = "!{{hello_world()}}" + assert_equal '

{{hello_world(<tag>)}}

', textilizable(text) + end + + def test_unknown_macros_should_not_be_replaced + text = "{{unknown}}" + assert_equal '

{{unknown}}

', textilizable(text) + end + + def test_unknown_macros_should_parsed_as_text + text = "{{unknown(*test*)}}" + assert_equal '

{{unknown(test)}}

', textilizable(text) + end + + def test_unknown_macros_should_be_escaped + text = "{{unknown()}}" + assert_equal '

{{unknown(<tag>)}}

', textilizable(text) + end + + def test_html_safe_macro_output_should_not_be_escaped + Redmine::WikiFormatting::Macros.macro :safe_macro do |obj, args| + "".html_safe + end + assert_equal '

', textilizable("{{safe_macro}}") + end + + def test_macro_hello_world + text = "{{hello_world}}" + assert textilizable(text).match(/Hello world!/) + end + + def test_macro_hello_world_should_escape_arguments + text = "{{hello_world()}}" + assert_include 'Arguments: <tag>', textilizable(text) + end + + def test_macro_macro_list + text = "{{macro_list}}" + assert_match %r{hello_world}, textilizable(text) + end + + def test_macro_include + @project = Project.find(1) + # include a page of the current project wiki + text = "{{include(Another page)}}" + assert_include 'This is a link to a ticket', textilizable(text) + + @project = nil + # include a page of a specific project wiki + text = "{{include(ecookbook:Another page)}}" + assert_include 'This is a link to a ticket', textilizable(text) + + text = "{{include(ecookbook:)}}" + assert_include 'CookBook documentation', textilizable(text) + + text = "{{include(unknowidentifier:somepage)}}" + assert_include 'Page not found', textilizable(text) + end + + def test_macro_collapse + text = "{{collapse\n*Collapsed* block of text\n}}" + result = textilizable(text) + + assert_select_in result, 'div.collapsed-text' + assert_select_in result, 'strong', :text => 'Collapsed' + assert_select_in result, 'a.collapsible.collapsed', :text => 'Show' + assert_select_in result, 'a.collapsible', :text => 'Hide' + end + + def test_macro_collapse_with_one_arg + text = "{{collapse(Example)\n*Collapsed* block of text\n}}" + result = textilizable(text) + + assert_select_in result, 'div.collapsed-text' + assert_select_in result, 'strong', :text => 'Collapsed' + assert_select_in result, 'a.collapsible.collapsed', :text => 'Example' + assert_select_in result, 'a.collapsible', :text => 'Example' + end + + def test_macro_collapse_with_two_args + text = "{{collapse(Show example, Hide example)\n*Collapsed* block of text\n}}" + result = textilizable(text) + + assert_select_in result, 'div.collapsed-text' + assert_select_in result, 'strong', :text => 'Collapsed' + assert_select_in result, 'a.collapsible.collapsed', :text => 'Show example' + assert_select_in result, 'a.collapsible', :text => 'Hide example' + end + + def test_macro_child_pages + expected = "

\n

" + + @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_parent_option + expected = "

\n

" + + @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 + + def test_macro_child_pages_with_depth_option + expected = "

\n

" + + @project = Project.find(1) + assert_equal expected, textilizable("{{child_pages(depth=1)}}", :object => WikiPage.find(2).content) + end + + def test_macro_child_pages_without_wiki_page_should_fail + assert_match /can be called from wiki pages only/, textilizable("{{child_pages}}") + end + + def test_macro_thumbnail + assert_equal '

testfile.PNG

', + textilizable("{{thumbnail(testfile.png)}}", :object => Issue.find(14)) + end + + def test_macro_thumbnail_with_size + assert_equal '

testfile.PNG

', + textilizable("{{thumbnail(testfile.png, size=200)}}", :object => Issue.find(14)) + end + + def test_macro_thumbnail_with_title + assert_equal '

testfile.PNG

', + textilizable("{{thumbnail(testfile.png, title=Cool image)}}", :object => Issue.find(14)) + end + + def test_macro_thumbnail_with_invalid_filename_should_fail + assert_include 'test.png not found', + textilizable("{{thumbnail(test.png)}}", :object => Issue.find(14)) + end + + def test_macros_should_not_be_executed_in_pre_tags + text = <<-RAW +{{hello_world(foo)}} + +
+{{hello_world(pre)}}
+!{{hello_world(pre)}}
+
+ +{{hello_world(bar)}} +RAW + + expected = <<-EXPECTED +

Hello world! Object: NilClass, Arguments: foo and no block of text.

+ +
+{{hello_world(pre)}}
+!{{hello_world(pre)}}
+
+ +

Hello world! Object: NilClass, Arguments: bar and no block of text.

+EXPECTED + + assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(text).gsub(%r{[\r\n\t]}, '') + end + + def test_macros_should_be_escaped_in_pre_tags + text = "
{{hello_world()}}
" + assert_equal "
{{hello_world(<tag>)}}
", textilizable(text) + end + + def test_macros_should_not_mangle_next_macros_outputs + text = '{{macro(2)}} !{{macro(2)}} {{hello_world(foo)}}' + assert_equal '

{{macro(2)}} {{macro(2)}} Hello world! Object: NilClass, Arguments: foo and no block of text.

', textilizable(text) + end + + def test_macros_with_text_should_not_mangle_following_macros + text = <<-RAW +{{hello_world +Line of text +}} + +{{hello_world +Another line of text +}} +RAW + + expected = <<-EXPECTED +

Hello world! Object: NilClass, Called with no argument and a 12 bytes long block of text.

+

Hello world! Object: NilClass, Called with no argument and a 20 bytes long block of text.

+EXPECTED + + assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(text).gsub(%r{[\r\n\t]}, '') + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/56/56493cc79ece7ebee244c20a035044b3abd7b20d.svn-base --- a/.svn/pristine/56/56493cc79ece7ebee244c20a035044b3abd7b20d.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,192 +0,0 @@ -module CodeRay -module Scanners - - class CSS < Scanner - - register_for :css - - KINDS_NOT_LOC = [ - :comment, - :class, :pseudo_class, :type, - :constant, :directive, - :key, :value, :operator, :color, :float, :string, - :error, :important, - ] # :nodoc: - - module RE # :nodoc: - Hex = /[0-9a-fA-F]/ - Unicode = /\\#{Hex}{1,6}(?:\r\n|\s)?/ # differs from standard because it allows uppercase hex too - Escape = /#{Unicode}|\\[^\r\n\f0-9a-fA-F]/ - NMChar = /[-_a-zA-Z0-9]|#{Escape}/ - NMStart = /[_a-zA-Z]|#{Escape}/ - NL = /\r\n|\r|\n|\f/ - String1 = /"(?:[^\n\r\f\\"]|\\#{NL}|#{Escape})*"?/ # TODO: buggy regexp - String2 = /'(?:[^\n\r\f\\']|\\#{NL}|#{Escape})*'?/ # TODO: buggy regexp - String = /#{String1}|#{String2}/ - - HexColor = /#(?:#{Hex}{6}|#{Hex}{3})/ - Color = /#{HexColor}/ - - Num = /-?(?:[0-9]+|[0-9]*\.[0-9]+)/ - Name = /#{NMChar}+/ - Ident = /-?#{NMStart}#{NMChar}*/ - AtKeyword = /@#{Ident}/ - Percentage = /#{Num}%/ - - reldimensions = %w[em ex px] - absdimensions = %w[in cm mm pt pc] - Unit = Regexp.union(*(reldimensions + absdimensions)) - - Dimension = /#{Num}#{Unit}/ - - Comment = %r! /\* (?: .*? \*/ | .* ) !mx - Function = /(?:url|alpha|attr|counters?)\((?:[^)\n\r\f]|\\\))*\)?/ - - Id = /##{Name}/ - Class = /\.#{Name}/ - PseudoClass = /:#{Name}/ - AttributeSelector = /\[[^\]]*\]?/ - end - - protected - - def scan_tokens encoder, options - - value_expected = nil - states = [:initial] - - until eos? - - if match = scan(/\s+/) - encoder.text_token match, :space - - elsif case states.last - when :initial, :media - if match = scan(/(?>#{RE::Ident})(?!\()|\*/ox) - encoder.text_token match, :type - next - elsif match = scan(RE::Class) - encoder.text_token match, :class - next - elsif match = scan(RE::Id) - encoder.text_token match, :constant - next - elsif match = scan(RE::PseudoClass) - encoder.text_token match, :pseudo_class - next - elsif match = scan(RE::AttributeSelector) - # TODO: Improve highlighting inside of attribute selectors. - encoder.text_token match[0,1], :operator - encoder.text_token match[1..-2], :attribute_name if match.size > 2 - encoder.text_token match[-1,1], :operator if match[-1] == ?] - next - elsif match = scan(/@media/) - encoder.text_token match, :directive - states.push :media_before_name - next - end - - when :block - if match = scan(/(?>#{RE::Ident})(?!\()/ox) - if value_expected - encoder.text_token match, :value - else - encoder.text_token match, :key - end - next - end - - when :media_before_name - if match = scan(RE::Ident) - encoder.text_token match, :type - states[-1] = :media_after_name - next - end - - when :media_after_name - if match = scan(/\{/) - encoder.text_token match, :operator - states[-1] = :media - next - end - - else - #:nocov: - raise_inspect 'Unknown state', encoder - #:nocov: - - end - - elsif match = scan(/\/\*(?:.*?\*\/|\z)/m) - encoder.text_token match, :comment - - elsif match = scan(/\{/) - value_expected = false - encoder.text_token match, :operator - states.push :block - - elsif match = scan(/\}/) - value_expected = false - if states.last == :block || states.last == :media - encoder.text_token match, :operator - states.pop - else - encoder.text_token match, :error - end - - elsif match = scan(/#{RE::String}/o) - encoder.begin_group :string - encoder.text_token match[0, 1], :delimiter - encoder.text_token match[1..-2], :content if match.size > 2 - encoder.text_token match[-1, 1], :delimiter if match.size >= 2 - encoder.end_group :string - - elsif match = scan(/#{RE::Function}/o) - encoder.begin_group :string - start = match[/^\w+\(/] - encoder.text_token start, :delimiter - if match[-1] == ?) - encoder.text_token match[start.size..-2], :content - encoder.text_token ')', :delimiter - else - encoder.text_token match[start.size..-1], :content - end - encoder.end_group :string - - elsif match = scan(/(?: #{RE::Dimension} | #{RE::Percentage} | #{RE::Num} )/ox) - encoder.text_token match, :float - - elsif match = scan(/#{RE::Color}/o) - encoder.text_token match, :color - - elsif match = scan(/! *important/) - encoder.text_token match, :important - - elsif match = scan(/(?:rgb|hsl)a?\([^()\n]*\)?/) - encoder.text_token match, :color - - elsif match = scan(RE::AtKeyword) - encoder.text_token match, :directive - - elsif match = scan(/ [+>:;,.=()\/] /x) - if match == ':' - value_expected = true - elsif match == ';' - value_expected = false - end - encoder.text_token match, :operator - - else - encoder.text_token getch, :error - - end - - end - - encoder - end - - end - -end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/57/570f0eb4fa534b2c22e5b144e19ab45bcea2dbda.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/57/570f0eb4fa534b2c22e5b144e19ab45bcea2dbda.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,87 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 SafeAttributes + def self.included(base) + base.extend(ClassMethods) + end + + module ClassMethods + # Declares safe attributes + # An optional Proc can be given for conditional inclusion + # + # Example: + # safe_attributes 'title', 'pages' + # safe_attributes 'isbn', :if => {|book, user| book.author == user} + def safe_attributes(*args) + @safe_attributes ||= [] + if args.empty? + if superclass.include?(Redmine::SafeAttributes) + @safe_attributes + superclass.safe_attributes + else + @safe_attributes + end + else + options = args.last.is_a?(Hash) ? args.pop : {} + @safe_attributes << [args, options] + end + end + end + + # Returns an array that can be safely set by user or current user + # + # Example: + # book.safe_attributes # => ['title', 'pages'] + # book.safe_attributes(book.author) # => ['title', 'pages', 'isbn'] + def safe_attribute_names(user=nil) + return @safe_attribute_names if @safe_attribute_names && user.nil? + names = [] + self.class.safe_attributes.collect do |attrs, options| + if options[:if].nil? || options[:if].call(self, user || User.current) + names += attrs.collect(&:to_s) + end + end + names.uniq! + @safe_attribute_names = names if user.nil? + names + end + + # Returns true if attr can be set by user or the current user + def safe_attribute?(attr, user=nil) + safe_attribute_names(user).include?(attr.to_s) + end + + # Returns a hash with unsafe attributes removed + # from the given attrs hash + # + # Example: + # book.delete_unsafe_attributes({'title' => 'My book', 'foo' => 'bar'}) + # # => {'title' => 'My book'} + def delete_unsafe_attributes(attrs, user=User.current) + safe = safe_attribute_names(user) + attrs.dup.delete_if {|k,v| !safe.include?(k)} + end + + # Sets attributes from attrs that are safe + # attrs is a Hash with string keys + def safe_attributes=(attrs, user=User.current) + return unless attrs.is_a?(Hash) + self.attributes = delete_unsafe_attributes(attrs, user) + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/57/5731b1ac892937f048c7117fb4cf074f794ff9f0.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/57/5731b1ac892937f048c7117fb4cf074f794ff9f0.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,58 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class Redmine::ApiTest::QueriesTest < Redmine::ApiTest::Base + fixtures :projects, :trackers, :issue_statuses, :issues, + :enumerations, :users, :issue_categories, + :projects_trackers, + :roles, + :member_roles, + :members, + :enabled_modules, + :queries + + def setup + Setting.rest_api_enabled = '1' + end + + context "/queries" do + context "GET" do + + should "return queries" do + get '/queries.xml' + + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_tag :tag => 'queries', + :attributes => {:type => 'array'}, + :child => { + :tag => 'query', + :child => { + :tag => 'id', + :content => '4', + :sibling => { + :tag => 'name', + :content => 'Public query for all projects' + } + } + } + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/57/5743d81934695d3c76c9de1e610ada18ddc3f631.svn-base --- a/.svn/pristine/57/5743d81934695d3c76c9de1e610ada18ddc3f631.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,138 +0,0 @@ -# One of the magic features that that engines plugin provides is the ability to -# override selected methods in controllers and helpers from your application. -# This is achieved by trapping requests to load those files, and then mixing in -# code from plugins (in the order the plugins were loaded) before finally loading -# any versions from the main +app+ directory. -# -# The behaviour of this extension is output to the log file for help when -# debugging. -# -# == Example -# -# A plugin contains the following controller in plugin/app/controllers/my_controller.rb: -# -# class MyController < ApplicationController -# def index -# @name = "HAL 9000" -# end -# def list -# @robots = Robot.find(:all) -# end -# end -# -# In one application that uses this plugin, we decide that the name used in the -# index action should be "Robbie", not "HAL 9000". To override this single method, -# we create the corresponding controller in our application -# (RAILS_ROOT/app/controllers/my_controller.rb), and redefine the method: -# -# class MyController < ApplicationController -# def index -# @name = "Robbie" -# end -# end -# -# The list method remains as it was defined in the plugin controller. -# -# The same basic principle applies to helpers, and also views and partials (although -# view overriding is performed in Engines::RailsExtensions::Templates; see that -# module for more information). -# -# === What about models? -# -# Unfortunately, it's not possible to provide this kind of magic for models. -# The only reason why it's possible for controllers and helpers is because -# they can be recognised by their filenames ("whatever_controller", "jazz_helper"), -# whereas models appear the same as any other typical Ruby library ("node", -# "user", "image", etc.). -# -# If mixing were allowed in models, it would mean code mixing for *every* -# file that was loaded via +require_or_load+, and this could result in -# problems where, for example, a Node model might start to include -# functionality from another file called "node" somewhere else in the -# $LOAD_PATH. -# -# One way to overcome this is to provide model functionality as a module in -# a plugin, which developers can then include into their own model -# implementations. -# -# Another option is to provide an abstract model (see the ActiveRecord::Base -# documentation) and have developers subclass this model in their own -# application if they must. -# -# --- -# -# The Engines::RailsExtensions::Dependencies module includes a method to -# override Dependencies.require_or_load, which is called to load code needed -# by Rails as it encounters constants that aren't defined. -# -# This method is enhanced with the code-mixing features described above. -# -module Engines::RailsExtensions::Dependencies - def self.included(base) #:nodoc: - base.class_eval { alias_method_chain :require_or_load, :engine_additions } - end - - # Attempt to load the given file from any plugins, as well as the application. - # This performs the 'code mixing' magic, allowing application controllers and - # helpers to override single methods from those in plugins. - # If the file can be found in any plugins, it will be loaded first from those - # locations. Finally, the application version is loaded, using Ruby's behaviour - # to replace existing methods with their new definitions. - # - # If Engines.disable_code_mixing == true, the first controller/helper on the - # $LOAD_PATH will be used (plugins' +app+ directories are always lower on the - # $LOAD_PATH than the main +app+ directory). - # - # If Engines.disable_application_code_loading == true, controllers will - # not be loaded from the main +app+ directory *if* they are present in any - # plugins. - # - # Returns true if the file could be loaded (from anywhere); false otherwise - - # mirroring the behaviour of +require_or_load+ from Rails (which mirrors - # that of Ruby's own +require+, I believe). - def require_or_load_with_engine_additions(file_name, const_path=nil) - return require_or_load_without_engine_additions(file_name, const_path) if Engines.disable_code_mixing - - file_loaded = false - - # try and load the plugin code first - # can't use model, as there's nothing in the name to indicate that the file is a 'model' file - # rather than a library or anything else. - Engines.code_mixing_file_types.each do |file_type| - # if we recognise this type - # (this regexp splits out the module/filename from any instances of app/#{type}, so that - # modules are still respected.) - if file_name =~ /^(.*app\/#{file_type}s\/)+(.*_#{file_type})(\.rb)?$/ - base_name = $2 - # ... go through the plugins from first started to last, so that - # code with a high precedence (started later) will override lower precedence - # implementations - Engines.plugins.each do |plugin| - plugin_file_name = File.expand_path(File.join(plugin.directory, 'app', "#{file_type}s", base_name)) - if File.file?("#{plugin_file_name}.rb") - file_loaded = true if require_or_load_without_engine_additions(plugin_file_name, const_path) - end - end - - # finally, load any application-specific controller classes using the 'proper' - # rails load mechanism, EXCEPT when we're testing engines and could load this file - # from an engine - unless Engines.disable_application_code_loading - # Ensure we are only loading from the /app directory at this point - app_file_name = File.join(RAILS_ROOT, 'app', "#{file_type}s", "#{base_name}") - if File.file?("#{app_file_name}.rb") - file_loaded = true if require_or_load_without_engine_additions(app_file_name, const_path) - end - end - end - end - - # if we managed to load a file, return true. If not, default to the original method. - # Note that this relies on the RHS of a boolean || not to be evaluated if the LHS is true. - file_loaded || require_or_load_without_engine_additions(file_name, const_path) - end -end - -module ActiveSupport::Dependencies #:nodoc: - include Engines::RailsExtensions::Dependencies -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/57/5776267a0b45dc755c049113094ec8f5df6e6255.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/57/5776267a0b45dc755c049113094ec8f5df6e6255.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,28 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 WikiContentObserver < ActiveRecord::Observer + def after_create(wiki_content) + Mailer.wiki_content_added(wiki_content).deliver if Setting.notified_events.include?('wiki_content_added') + end + + def after_update(wiki_content) + if wiki_content.text_changed? + Mailer.wiki_content_updated(wiki_content).deliver if Setting.notified_events.include?('wiki_content_updated') + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/57/579026907a1e3f3daa7c80962425f77970ae109e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/57/579026907a1e3f3daa7c80962425f77970ae109e.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,1091 @@ +# Redmine catalan translation: +# by Joan Duran + +ca: + # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl) + direction: ltr + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%d-%m-%Y" + short: "%e de %b" + long: "%a, %e de %b de %Y" + + day_names: [Diumenge, Dilluns, Dimarts, Dimecres, Dijous, Divendres, Dissabte] + abbr_day_names: [dg, dl, dt, dc, dj, dv, ds] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, Gener, Febrer, Març, Abril, Maig, Juny, Juliol, Agost, Setembre, Octubre, Novembre, Desembre] + abbr_month_names: [~, Gen, Feb, Mar, Abr, Mai, Jun, Jul, Ago, Set, Oct, Nov, Des] + # Used in date_select and datime_select. + order: + - :year + - :month + - :day + + time: + formats: + default: "%d-%m-%Y %H:%M" + time: "%H:%M" + short: "%e de %b, %H:%M" + long: "%a, %e de %b de %Y, %H:%M" + am: "am" + pm: "pm" + + datetime: + distance_in_words: + half_a_minute: "mig minut" + less_than_x_seconds: + one: "menys d'un segon" + other: "menys de %{count} segons" + x_seconds: + one: "1 segons" + other: "%{count} segons" + less_than_x_minutes: + one: "menys d'un minut" + other: "menys de %{count} minuts" + x_minutes: + one: "1 minut" + other: "%{count} minuts" + about_x_hours: + one: "aproximadament 1 hora" + other: "aproximadament %{count} hores" + x_hours: + one: "1 hora" + other: "%{count} hores" + x_days: + one: "1 dia" + other: "%{count} dies" + about_x_months: + one: "aproximadament 1 mes" + other: "aproximadament %{count} mesos" + x_months: + one: "1 mes" + other: "%{count} mesos" + about_x_years: + one: "aproximadament 1 any" + other: "aproximadament %{count} anys" + over_x_years: + one: "més d'un any" + other: "més de %{count} anys" + almost_x_years: + one: "almost 1 year" + other: "almost %{count} years" + + number: + # Default format for numbers + format: + separator: "." + delimiter: "" + precision: 3 + human: + format: + delimiter: "" + precision: 3 + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + +# Used in array.to_sentence. + support: + array: + sentence_connector: "i" + skip_last_comma: false + + activerecord: + errors: + template: + header: + one: "1 error prohibited this %{model} from being saved" + other: "%{count} errors prohibited this %{model} from being saved" + messages: + inclusion: "no està inclòs a la llista" + exclusion: "està reservat" + invalid: "no és vàlid" + confirmation: "la confirmació no coincideix" + accepted: "s'ha d'acceptar" + empty: "no pot estar buit" + blank: "no pot estar en blanc" + too_long: "és massa llarg" + too_short: "és massa curt" + wrong_length: "la longitud és incorrecta" + taken: "ja s'està utilitzant" + not_a_number: "no és un número" + not_a_date: "no és una data vàlida" + greater_than: "ha de ser més gran que %{count}" + greater_than_or_equal_to: "ha de ser més gran o igual a %{count}" + equal_to: "ha de ser igual a %{count}" + less_than: "ha de ser menys que %{count}" + less_than_or_equal_to: "ha de ser menys o igual a %{count}" + odd: "ha de ser senar" + even: "ha de ser parell" + greater_than_start_date: "ha de ser superior que la data inicial" + not_same_project: "no pertany al mateix projecte" + circular_dependency: "Aquesta relació crearia una dependència circular" + cant_link_an_issue_with_a_descendant: "Un assumpte no es pot enllaçar a una de les seves subtasques" + + actionview_instancetag_blank_option: Seleccioneu + + general_text_No: 'No' + general_text_Yes: 'Si' + general_text_no: 'no' + general_text_yes: 'si' + general_lang_name: 'Català' + general_csv_separator: ';' + general_csv_decimal_separator: ',' + general_csv_encoding: ISO-8859-15 + general_pdf_encoding: UTF-8 + general_first_day_of_week: '1' + + notice_account_updated: "El compte s'ha actualitzat correctament." + notice_account_invalid_creditentials: Usuari o contrasenya invàlid + notice_account_password_updated: "La contrasenya s'ha modificat correctament." + notice_account_wrong_password: Contrasenya incorrecta + notice_account_register_done: "El compte s'ha creat correctament. Per a activar el compte, feu clic en l'enllaç que us han enviat per correu electrònic." + notice_account_unknown_email: Usuari desconegut. + notice_can_t_change_password: "Aquest compte utilitza una font d'autenticació externa. No és possible canviar la contrasenya." + notice_account_lost_email_sent: "S'ha enviat un correu electrònic amb instruccions per a seleccionar una contrasenya nova." + notice_account_activated: "El compte s'ha activat. Ara podeu entrar." + notice_successful_create: "S'ha creat correctament." + notice_successful_update: "S'ha modificat correctament." + notice_successful_delete: "S'ha suprimit correctament." + notice_successful_connection: "S'ha connectat correctament." + notice_file_not_found: "La pàgina a la que intenteu accedir no existeix o s'ha suprimit." + notice_locking_conflict: Un altre usuari ha actualitzat les dades. + notice_not_authorized: No teniu permís per a accedir a aquesta pàgina. + notice_email_sent: "S'ha enviat un correu electrònic a %{value}" + notice_email_error: "S'ha produït un error en enviar el correu (%{value})" + notice_feeds_access_key_reseted: "S'ha reiniciat la clau d'accés del RSS." + notice_api_access_key_reseted: "S'ha reiniciat la clau d'accés a l'API." + notice_failed_to_save_issues: "No s'han pogut desar %{count} assumptes de %{total} seleccionats: %{ids}." + notice_failed_to_save_members: "No s'han pogut desar els membres: %{errors}." + 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ó." + notice_unable_delete_time_entry: "No s'ha pogut suprimir l'entrada del registre de temps." + notice_issue_done_ratios_updated: "S'ha actualitzat el tant per cent dels assumptes." + + error_can_t_load_default_data: "No s'ha pogut carregar la configuració predeterminada: %{value} " + error_scm_not_found: "No s'ha trobat l'entrada o la revisió en el dipòsit." + error_scm_command_failed: "S'ha produït un error en intentar accedir al dipòsit: %{value}" + error_scm_annotate: "L'entrada no existeix o no s'ha pogut anotar." + error_issue_not_found_in_project: "No s'ha trobat l'assumpte o no pertany a aquest projecte" + error_no_tracker_in_project: "Aquest projecte no té seguidor associat. Comproveu els paràmetres del projecte." + error_no_default_issue_status: "No s'ha definit cap estat d'assumpte predeterminat. Comproveu la configuració (aneu a «Administració -> Estats de l'assumpte»)." + error_can_not_delete_custom_field: "No s'ha pogut suprimir el camp personalitat" + error_can_not_delete_tracker: "Aquest seguidor conté assumptes i no es pot suprimir." + error_can_not_remove_role: "Aquest rol s'està utilitzant i no es pot suprimir." + error_can_not_reopen_issue_on_closed_version: "Un assumpte assignat a una versió tancada no es pot tornar a obrir" + error_can_not_archive_project: "Aquest projecte no es pot arxivar" + error_issue_done_ratios_not_updated: "No s'ha actualitza el tant per cent dels assumptes." + error_workflow_copy_source: "Seleccioneu un seguidor o rol font" + error_workflow_copy_target: "Seleccioneu seguidors i rols objectiu" + error_unable_delete_issue_status: "No s'ha pogut suprimir l'estat de l'assumpte" + error_unable_to_connect: "No s'ha pogut connectar (%{value})" + warning_attachments_not_saved: "No s'han pogut desar %{count} fitxers." + + mail_subject_lost_password: "Contrasenya de %{value}" + mail_body_lost_password: "Per a canviar la contrasenya, feu clic en l'enllaç següent:" + mail_subject_register: "Activació del compte de %{value}" + mail_body_register: "Per a activar el compte, feu clic en l'enllaç següent:" + mail_body_account_information_external: "Podeu utilitzar el compte «%{value}» per a entrar." + mail_body_account_information: Informació del compte + mail_subject_account_activation_request: "Sol·licitud d'activació del compte de %{value}" + mail_body_account_activation_request: "S'ha registrat un usuari nou (%{value}). El seu compte està pendent d'aprovació:" + mail_subject_reminder: "%{count} assumptes venceran els següents %{days} dies" + mail_body_reminder: "%{count} assumptes que teniu assignades venceran els següents %{days} dies:" + mail_subject_wiki_content_added: "S'ha afegit la pàgina wiki «%{id}»" + mail_body_wiki_content_added: "En %{author} ha afegit la pàgina wiki «%{id}»." + mail_subject_wiki_content_updated: "S'ha actualitzat la pàgina wiki «%{id}»" + mail_body_wiki_content_updated: "En %{author} ha actualitzat la pàgina wiki «%{id}»." + + + field_name: Nom + field_description: Descripció + field_summary: Resum + field_is_required: Necessari + field_firstname: Nom + field_lastname: Cognom + field_mail: Correu electrònic + field_filename: Fitxer + field_filesize: Mida + field_downloads: Baixades + field_author: Autor + field_created_on: Creat + field_updated_on: Actualitzat + field_field_format: Format + field_is_for_all: Per a tots els projectes + field_possible_values: Valores possibles + field_regexp: Expressió regular + field_min_length: Longitud mínima + field_max_length: Longitud màxima + field_value: Valor + field_category: Categoria + field_title: Títol + field_project: Projecte + field_issue: Assumpte + field_status: Estat + field_notes: Notes + field_is_closed: Assumpte tancat + field_is_default: Estat predeterminat + field_tracker: Seguidor + field_subject: Tema + field_due_date: Data de venciment + field_assigned_to: Assignat a + field_priority: Prioritat + field_fixed_version: Versió objectiu + field_user: Usuari + field_principal: Principal + field_role: Rol + field_homepage: Pàgina web + field_is_public: Públic + field_parent: Subprojecte de + field_is_in_roadmap: Assumptes mostrats en la planificació + field_login: Entrada + field_mail_notification: Notificacions per correu electrònic + field_admin: Administrador + field_last_login_on: Última connexió + field_language: Idioma + field_effective_date: Data + field_password: Contrasenya + field_new_password: Contrasenya nova + field_password_confirmation: Confirmació + field_version: Versió + field_type: Tipus + field_host: Ordinador + field_port: Port + field_account: Compte + field_base_dn: Base DN + field_attr_login: "Atribut d'entrada" + field_attr_firstname: Atribut del nom + field_attr_lastname: Atribut del cognom + field_attr_mail: Atribut del correu electrònic + field_onthefly: "Creació de l'usuari «al vol»" + 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_comments: Comentari + field_url: URL + field_start_page: Pàgina inicial + field_subproject: Subprojecte + field_hours: Hores + field_activity: Activitat + field_spent_on: Data + field_identifier: Identificador + field_is_filter: "S'ha utilitzat com a filtre" + field_issue_to: Assumpte relacionat + field_delay: Retard + field_assignable: Es poden assignar assumptes a aquest rol + field_redirect_existing_links: Redirigeix els enllaços existents + field_estimated_hours: Temps previst + field_column_names: Columnes + field_time_entries: "Registre de temps" + field_time_zone: Zona horària + field_searchable: Es pot cercar + field_default_value: Valor predeterminat + field_comments_sorting: Mostra els comentaris + field_parent_title: Pàgina pare + field_editable: Es pot editar + field_watcher: Vigilància + field_identity_url: URL OpenID + field_content: Contingut + field_group_by: "Agrupa els resultats per" + field_sharing: Compartició + field_parent_issue: "Tasca pare" + + setting_app_title: "Títol de l'aplicació" + setting_app_subtitle: "Subtítol de l'aplicació" + setting_welcome_text: Text de benvinguda + setting_default_language: Idioma predeterminat + setting_login_required: Es necessita autenticació + setting_self_registration: Registre automàtic + setting_attachment_max_size: Mida màxima dels adjunts + setting_issues_export_limit: "Límit d'exportació d'assumptes" + setting_mail_from: "Adreça de correu electrònic d'emissió" + setting_bcc_recipients: Vincula els destinataris de les còpies amb carbó (bcc) + setting_plain_text_mail: només text pla (no HTML) + setting_host_name: "Nom de l'ordinador" + setting_text_formatting: Format del text + setting_wiki_compression: "Comprimeix l'historial del wiki" + setting_feeds_limit: Límit de contingut del canal + setting_default_projects_public: Els projectes nous són públics per defecte + setting_autofetch_changesets: Omple automàticament les publicacions + setting_sys_api_enabled: Habilita el WS per a la gestió del dipòsit + setting_commit_ref_keywords: Paraules claus per a la referència + setting_commit_fix_keywords: Paraules claus per a la correcció + setting_autologin: Entrada automàtica + setting_date_format: Format de la data + 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_emails_footer: Peu dels correus electrònics + setting_protocol: Protocol + setting_per_page_options: Opcions dels objectes per pàgina + setting_user_format: "Format de com mostrar l'usuari" + setting_activity_days_default: "Dies a mostrar l'activitat del projecte" + setting_display_subprojects_issues: "Mostra els assumptes d'un subprojecte en el projecte pare per defecte" + setting_enabled_scm: "Habilita l'SCM" + setting_mail_handler_body_delimiters: "Trunca els correus electrònics després d'una d'aquestes línies" + 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 + setting_gravatar_enabled: "Utilitza les icones d'usuari Gravatar" + setting_gravatar_default: "Imatge Gravatar predeterminada" + setting_diff_max_lines_displayed: Número màxim de línies amb diferències mostrades + setting_file_max_size_displayed: Mida màxima dels fitxers de text mostrats en línia + setting_repository_log_display_limit: Número màxim de revisions que es mostren al registre de fitxers + setting_openid: "Permet entrar i registrar-se amb l'OpenID" + setting_password_min_length: "Longitud mínima de la contrasenya" + setting_new_project_user_role_id: "Aquest rol es dóna a un usuari no administrador per a crear projectes" + setting_default_projects_modules: "Mòduls activats per defecte en els projectes nous" + setting_issue_done_ratio: "Calcula tant per cent realitzat de l'assumpte amb" + setting_issue_done_ratio_issue_status: "Utilitza l'estat de l'assumpte" + setting_issue_done_ratio_issue_field: "Utilitza el camp de l'assumpte" + setting_start_of_week: "Inicia les setmanes en" + setting_rest_api_enabled: "Habilita el servei web REST" + setting_cache_formatted_text: Cache formatted text + + permission_add_project: "Crea projectes" + permission_add_subprojects: "Crea subprojectes" + permission_edit_project: Edita el projecte + permission_select_project_modules: Selecciona els mòduls del projecte + permission_manage_members: Gestiona els membres + permission_manage_project_activities: "Gestiona les activitats del projecte" + permission_manage_versions: Gestiona les versions + permission_manage_categories: Gestiona les categories dels assumptes + permission_view_issues: "Visualitza els assumptes" + permission_add_issues: Afegeix assumptes + permission_edit_issues: Edita els assumptes + permission_manage_issue_relations: Gestiona les relacions dels assumptes + permission_add_issue_notes: Afegeix notes + permission_edit_issue_notes: Edita les notes + permission_edit_own_issue_notes: Edita les notes pròpies + permission_move_issues: Mou els assumptes + permission_delete_issues: Suprimeix els assumptes + permission_manage_public_queries: Gestiona les consultes públiques + permission_save_queries: Desa les consultes + permission_view_gantt: Visualitza la gràfica de Gantt + permission_view_calendar: Visualitza el calendari + permission_view_issue_watchers: Visualitza la llista de vigilàncies + permission_add_issue_watchers: Afegeix vigilàncies + permission_delete_issue_watchers: Suprimeix els vigilants + permission_log_time: Registra el temps invertit + permission_view_time_entries: Visualitza el temps invertit + permission_edit_time_entries: Edita els registres de temps + permission_edit_own_time_entries: Edita els registres de temps propis + permission_manage_news: Gestiona les noticies + permission_comment_news: Comenta les noticies + permission_view_documents: Visualitza els documents + permission_manage_files: Gestiona els fitxers + permission_view_files: Visualitza els fitxers + permission_manage_wiki: Gestiona el wiki + permission_rename_wiki_pages: Canvia el nom de les pàgines wiki + permission_delete_wiki_pages: Suprimeix les pàgines wiki + permission_view_wiki_pages: Visualitza el wiki + permission_view_wiki_edits: "Visualitza l'historial del wiki" + permission_edit_wiki_pages: Edita les pàgines wiki + permission_delete_wiki_pages_attachments: Suprimeix adjunts + permission_protect_wiki_pages: Protegeix les pàgines wiki + permission_manage_repository: Gestiona el dipòsit + permission_browse_repository: Navega pel dipòsit + permission_view_changesets: Visualitza els canvis realitzats + permission_commit_access: Accés a les publicacions + permission_manage_boards: Gestiona els taulers + permission_view_messages: Visualitza els missatges + permission_add_messages: Envia missatges + permission_edit_messages: Edita els missatges + permission_edit_own_messages: Edita els missatges propis + permission_delete_messages: Suprimeix els missatges + permission_delete_own_messages: Suprimeix els missatges propis + permission_export_wiki_pages: "Exporta les pàgines wiki" + permission_manage_subtasks: "Gestiona subtasques" + + project_module_issue_tracking: "Seguidor d'assumptes" + project_module_time_tracking: Seguidor de temps + project_module_news: Noticies + project_module_documents: Documents + project_module_files: Fitxers + project_module_wiki: Wiki + project_module_repository: Dipòsit + project_module_boards: Taulers + project_module_calendar: Calendari + project_module_gantt: Gantt + + label_user: Usuari + label_user_plural: Usuaris + label_user_new: Usuari nou + label_user_anonymous: Anònim + label_project: Projecte + label_project_new: Projecte nou + label_project_plural: Projectes + label_x_projects: + zero: cap projecte + one: 1 projecte + other: "%{count} projectes" + label_project_all: Tots els projectes + label_project_latest: Els últims projectes + label_issue: Assumpte + label_issue_new: Assumpte nou + label_issue_plural: Assumptes + label_issue_view_all: Visualitza tots els assumptes + label_issues_by: "Assumptes per %{value}" + label_issue_added: Assumpte afegit + label_issue_updated: Assumpte actualitzat + label_document: Document + label_document_new: Document nou + label_document_plural: Documents + label_document_added: Document afegit + label_role: Rol + label_role_plural: Rols + label_role_new: Rol nou + label_role_and_permissions: Rols i permisos + label_member: Membre + label_member_new: Membre nou + label_member_plural: Membres + label_tracker: Seguidor + label_tracker_plural: Seguidors + label_tracker_new: Seguidor nou + label_workflow: Flux de treball + label_issue_status: "Estat de l'assumpte" + label_issue_status_plural: "Estats de l'assumpte" + label_issue_status_new: Estat nou + label_issue_category: "Categoria de l'assumpte" + label_issue_category_plural: "Categories de l'assumpte" + label_issue_category_new: Categoria nova + label_custom_field: Camp personalitzat + label_custom_field_plural: Camps personalitzats + label_custom_field_new: Camp personalitzat nou + label_enumerations: Enumeracions + label_enumeration_new: Valor nou + label_information: Informació + label_information_plural: Informació + label_please_login: Entreu + label_register: Registre + label_login_with_open_id_option: "o entra amb l'OpenID" + label_password_lost: Contrasenya perduda + label_home: Inici + label_my_page: La meva pàgina + label_my_account: El meu compte + label_my_projects: Els meus projectes + label_my_page_block: "Els meus blocs de pàgina" + label_administration: Administració + label_login: Entra + label_logout: Surt + label_help: Ajuda + label_reported_issues: Assumptes informats + label_assigned_to_me_issues: Assumptes assignats a mi + label_last_login: Última connexió + label_registered_on: Informat el + label_activity: Activitat + label_overall_activity: Activitat global + label_user_activity: "Activitat de %{value}" + label_new: Nou + label_logged_as: Heu entrat com a + label_environment: Entorn + label_authentication: Autenticació + label_auth_source: "Mode d'autenticació" + label_auth_source_new: "Mode d'autenticació nou" + label_auth_source_plural: "Modes d'autenticació" + label_subproject_plural: Subprojectes + label_subproject_new: "Subprojecte nou" + label_and_its_subprojects: "%{value} i els seus subprojectes" + label_min_max_length: Longitud mín - max + label_list: Llist + label_date: Data + label_integer: Enter + label_float: Flotant + label_boolean: Booleà + label_string: Text + label_text: Text llarg + label_attribute: Atribut + label_attribute_plural: Atributs + label_no_data: Sense dades a mostrar + label_change_status: "Canvia l'estat" + label_history: Historial + label_attachment: Fitxer + label_attachment_new: Fitxer nou + label_attachment_delete: Suprimeix el fitxer + label_attachment_plural: Fitxers + label_file_added: Fitxer afegit + label_report: Informe + label_report_plural: Informes + label_news: Noticies + label_news_new: Afegeix noticies + label_news_plural: Noticies + label_news_latest: Últimes noticies + label_news_view_all: Visualitza totes les noticies + label_news_added: Noticies afegides + label_settings: Paràmetres + label_overview: Resum + label_version: Versió + label_version_new: Versió nova + label_version_plural: Versions + label_close_versions: "Tanca les versions completades" + label_confirmation: Confirmació + label_export_to: "També disponible a:" + label_read: Llegeix... + label_public_projects: Projectes públics + label_open_issues: obert + label_open_issues_plural: oberts + label_closed_issues: tancat + label_closed_issues_plural: tancats + label_x_open_issues_abbr_on_total: + zero: 0 oberts / %{total} + one: 1 obert / %{total} + other: "%{count} oberts / %{total}" + label_x_open_issues_abbr: + zero: 0 oberts + one: 1 obert + other: "%{count} oberts" + label_x_closed_issues_abbr: + zero: 0 tancats + one: 1 tancat + other: "%{count} tancats" + label_total: Total + label_permissions: Permisos + label_current_status: Estat actual + label_new_statuses_allowed: Nous estats autoritzats + label_all: tots + label_none: cap + label_nobody: ningú + label_next: Següent + label_previous: Anterior + label_used_by: Utilitzat per + label_details: Detalls + label_add_note: Afegeix una nota + label_per_page: Per pàgina + label_calendar: Calendari + label_months_from: mesos des de + label_gantt: Gantt + label_internal: Intern + label_last_changes: "últims %{count} canvis" + label_change_view_all: Visualitza tots els canvis + label_personalize_page: Personalitza aquesta pàgina + label_comment: Comentari + label_comment_plural: Comentaris + label_x_comments: + zero: sense comentaris + one: 1 comentari + other: "%{count} comentaris" + label_comment_add: Afegeix un comentari + label_comment_added: Comentari afegit + label_comment_delete: Suprimeix comentaris + label_query: Consulta personalitzada + label_query_plural: Consultes personalitzades + label_query_new: Consulta nova + label_filter_add: Afegeix un filtre + label_filter_plural: Filtres + label_equals: és + label_not_equals: no és + label_in_less_than: en menys de + label_in_more_than: en més de + label_greater_or_equal: ">=" + label_less_or_equal: <= + label_in: en + label_today: avui + label_all_time: tot el temps + label_yesterday: ahir + label_this_week: aquesta setmana + label_last_week: "l'última setmana" + label_last_n_days: "els últims %{count} dies" + label_this_month: aquest més + label_last_month: "l'últim més" + label_this_year: aquest any + label_date_range: Abast de les dates + label_less_than_ago: fa menys de + label_more_than_ago: fa més de + label_ago: fa + label_contains: conté + label_not_contains: no conté + label_day_plural: dies + label_repository: Dipòsit + label_repository_plural: Dipòsits + label_browse: Navega + label_branch: Branca + label_tag: Etiqueta + label_revision: Revisió + label_revision_plural: Revisions + label_revision_id: "Revisió %{value}" + label_associated_revisions: Revisions associades + label_added: afegit + label_modified: modificat + label_copied: copiat + label_renamed: reanomenat + label_deleted: suprimit + label_latest_revision: Última revisió + label_latest_revision_plural: Últimes revisions + label_view_revisions: Visualitza les revisions + label_view_all_revisions: "Visualitza totes les revisions" + label_max_size: Mida màxima + label_sort_highest: Mou a la part superior + label_sort_higher: Mou cap amunt + label_sort_lower: Mou cap avall + label_sort_lowest: Mou a la part inferior + label_roadmap: Planificació + label_roadmap_due_in: "Venç en %{value}" + label_roadmap_overdue: "%{value} tard" + label_roadmap_no_issues: No hi ha assumptes per a aquesta versió + label_search: Cerca + label_result_plural: Resultats + label_all_words: Totes les paraules + label_wiki: Wiki + label_wiki_edit: Edició wiki + label_wiki_edit_plural: Edicions wiki + label_wiki_page: Pàgina wiki + label_wiki_page_plural: Pàgines wiki + label_index_by_title: Ãndex per títol + label_index_by_date: Ãndex per data + label_current_version: Versió actual + label_preview: Previsualització + label_feed_plural: Canals + label_changes_details: Detalls de tots els canvis + label_issue_tracking: "Seguiment d'assumptes" + label_spent_time: Temps invertit + label_overall_spent_time: "Temps total invertit" + label_f_hour: "%{value} hora" + label_f_hour_plural: "%{value} hores" + label_time_tracking: Temps de seguiment + label_change_plural: Canvis + label_statistics: Estadístiques + label_commits_per_month: Publicacions per mes + label_commits_per_author: Publicacions per autor + label_view_diff: Visualitza les diferències + label_diff_inline: en línia + label_diff_side_by_side: costat per costat + label_options: Opcions + label_copy_workflow_from: Copia el flux de treball des de + label_permissions_report: Informe de permisos + label_watched_issues: Assumptes vigilats + label_related_issues: Assumptes relacionats + label_applied_status: Estat aplicat + label_loading: "S'està carregant..." + label_relation_new: Relació nova + label_relation_delete: Suprimeix la relació + label_relates_to: relacionat amb + label_duplicates: duplicats + label_duplicated_by: duplicat per + label_blocks: bloqueja + label_blocked_by: bloquejats per + label_precedes: anterior a + label_follows: posterior a + 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" + label_disabled: inhabilitat + label_show_completed_versions: Mostra les versions completes + label_me: jo mateix + label_board: Fòrum + label_board_new: Fòrum nou + label_board_plural: Fòrums + label_board_locked: Bloquejat + label_board_sticky: Sticky + label_topic_plural: Temes + label_message_plural: Missatges + label_message_last: Últim missatge + label_message_new: Missatge nou + label_message_posted: Missatge afegit + label_reply_plural: Respostes + label_send_information: "Envia la informació del compte a l'usuari" + label_year: Any + label_month: Mes + label_week: Setmana + label_date_from: Des de + label_date_to: A + label_language_based: "Basat en l'idioma de l'usuari" + label_sort_by: "Ordena per %{value}" + label_send_test_email: Envia un correu electrònic de prova + label_feeds_access_key: "Clau d'accés del RSS" + label_missing_feeds_access_key: "Falta una clau d'accés del RSS" + label_feeds_access_key_created_on: "Clau d'accés del RSS creada fa %{value}" + label_module_plural: Mòduls + label_added_time_by: "Afegit per %{author} fa %{age}" + label_updated_time_by: "Actualitzat per %{author} fa %{age}" + label_updated_time: "Actualitzat fa %{value}" + label_jump_to_a_project: Salta al projecte... + label_file_plural: Fitxers + label_changeset_plural: Conjunt de canvis + label_default_columns: Columnes predeterminades + label_no_change_option: (sense canvis) + label_bulk_edit_selected_issues: Edita en bloc els assumptes seleccionats + label_theme: Tema + label_default: Predeterminat + label_search_titles_only: Cerca només en els títols + label_user_mail_option_all: "Per qualsevol esdeveniment en tots els meus projectes" + label_user_mail_option_selected: "Per qualsevol esdeveniment en els projectes seleccionats..." + label_user_mail_no_self_notified: "No vull ser notificat pels canvis que faig jo mateix" + label_registration_activation_by_email: activació del compte per correu electrònic + label_registration_manual_activation: activació del compte manual + label_registration_automatic_activation: activació del compte automàtica + label_display_per_page: "Per pàgina: %{value}" + label_age: Edat + label_change_properties: Canvia les propietats + label_general: General + label_more: Més + label_scm: SCM + label_plugins: Connectors + label_ldap_authentication: Autenticació LDAP + label_downloads_abbr: Baixades + label_optional_description: Descripció opcional + label_add_another_file: Afegeix un altre fitxer + label_preferences: Preferències + label_chronological_order: En ordre cronològic + label_reverse_chronological_order: En ordre cronològic invers + label_planning: Planificació + label_incoming_emails: "Correu electrònics d'entrada" + label_generate_key: Genera una clau + label_issue_watchers: Vigilàncies + label_example: Exemple + label_display: Mostra + label_sort: Ordena + label_ascending: Ascendent + label_descending: Descendent + label_date_from_to: Des de %{start} a %{end} + label_wiki_content_added: "S'ha afegit la pàgina wiki" + label_wiki_content_updated: "S'ha actualitzat la pàgina wiki" + label_group: Grup + label_group_plural: Grups + label_group_new: Grup nou + label_time_entry_plural: Temps invertit + label_version_sharing_hierarchy: "Amb la jerarquia del projecte" + label_version_sharing_system: "Amb tots els projectes" + label_version_sharing_descendants: "Amb tots els subprojectes" + label_version_sharing_tree: "Amb l'arbre del projecte" + label_version_sharing_none: "Sense compartir" + label_update_issue_done_ratios: "Actualitza el tant per cent dels assumptes realitzats" + label_copy_source: Font + label_copy_target: Objectiu + label_copy_same_as_target: "El mateix que l'objectiu" + label_display_used_statuses_only: "Mostra només els estats que utilitza aquest seguidor" + label_api_access_key: "Clau d'accés a l'API" + label_missing_api_access_key: "Falta una clau d'accés de l'API" + label_api_access_key_created_on: "Clau d'accés de l'API creada fa %{value}" + label_profile: Perfil + label_subtask_plural: Subtasques + label_project_copy_notifications: "Envia notificacions de correu electrònic durant la còpia del projecte" + + button_login: Entra + button_submit: Tramet + button_save: Desa + button_check_all: Activa-ho tot + button_uncheck_all: Desactiva-ho tot + button_delete: Suprimeix + button_create: Crea + button_create_and_continue: Crea i continua + button_test: Test + button_edit: Edit + button_add: Afegeix + button_change: Canvia + button_apply: Aplica + button_clear: Neteja + button_lock: Bloca + button_unlock: Desbloca + button_download: Baixa + button_list: Llista + button_view: Visualitza + button_move: Mou + button_move_and_follow: "Mou i segueix" + button_back: Enrere + button_cancel: Cancel·la + button_activate: Activa + button_sort: Ordena + button_log_time: "Registre de temps" + button_rollback: Torna a aquesta versió + button_watch: Vigila + button_unwatch: No vigilis + button_reply: Resposta + button_archive: Arxiva + button_unarchive: Desarxiva + button_reset: Reinicia + button_rename: Reanomena + button_change_password: Canvia la contrasenya + button_copy: Copia + button_copy_and_follow: "Copia i segueix" + button_annotate: Anota + button_update: Actualitza + button_configure: Configura + button_quote: Cita + button_duplicate: Duplica + button_show: Mostra + + status_active: actiu + status_registered: informat + status_locked: bloquejat + + version_status_open: oberta + version_status_locked: bloquejada + version_status_closed: tancada + + field_active: Actiu + + text_select_mail_notifications: "Seleccioneu les accions per les quals s'hauria d'enviar una notificació per correu electrònic." + text_regexp_info: ex. ^[A-Z0-9]+$ + text_min_max_length_info: 0 significa sense restricció + text_project_destroy_confirmation: Segur que voleu suprimir aquest projecte i les dades relacionades? + text_subprojects_destroy_warning: "També seran suprimits els seus subprojectes: %{value}." + text_workflow_edit: Seleccioneu un rol i un seguidor per a editar el flux de treball + text_are_you_sure: Segur? + text_journal_changed: "%{label} ha canviat de %{old} a %{new}" + text_journal_set_to: "%{label} s'ha establert a %{value}" + text_journal_deleted: "%{label} s'ha suprimit (%{old})" + text_journal_added: "S'ha afegit %{label} %{value}" + text_tip_issue_begin_day: "tasca que s'inicia aquest dia" + text_tip_issue_end_day: tasca que finalitza aquest dia + text_tip_issue_begin_end_day: "tasca que s'inicia i finalitza aquest dia" + text_caracters_maximum: "%{count} caràcters com a màxim." + text_caracters_minimum: "Com a mínim ha de tenir %{count} caràcters." + text_length_between: "Longitud entre %{min} i %{max} caràcters." + text_tracker_no_workflow: "No s'ha definit cap flux de treball per a aquest seguidor" + text_unallowed_characters: Caràcters no permesos + text_comma_separated: Es permeten valors múltiples (separats per una coma). + text_line_separated: "Es permeten diversos valors (una línia per cada valor)." + text_issues_ref_in_commit_messages: Referència i soluciona els assumptes en els missatges publicats + text_issue_added: "L'assumpte %{id} ha sigut informat per %{author}." + text_issue_updated: "L'assumpte %{id} ha sigut actualitzat per %{author}." + text_wiki_destroy_confirmation: Segur que voleu suprimir aquest wiki i tots els seus continguts? + text_issue_category_destroy_question: "Alguns assumptes (%{count}) estan assignats a aquesta categoria. Què voleu fer?" + text_issue_category_destroy_assignments: Suprimeix les assignacions de la categoria + text_issue_category_reassign_to: Torna a assignar els assumptes a aquesta categoria + text_user_mail_option: "Per als projectes no seleccionats, només rebreu notificacions sobre les coses que vigileu o que hi esteu implicat (ex. assumptes que en sou l'autor o hi esteu assignat)." + text_no_configuration_data: "Encara no s'han configurat els rols, seguidors, estats de l'assumpte i flux de treball.\nÉs altament recomanable que carregueu la configuració predeterminada. Podreu modificar-la un cop carregada." + text_load_default_configuration: Carrega la configuració predeterminada + text_status_changed_by_changeset: "Aplicat en el conjunt de canvis %{value}." + text_issues_destroy_confirmation: "Segur que voleu suprimir els assumptes seleccionats?" + text_select_project_modules: "Seleccioneu els mòduls a habilitar per a aquest projecte:" + text_default_administrator_account_changed: "S'ha canviat el compte d'administrador predeterminat" + text_file_repository_writable: Es pot escriure en el dipòsit de fitxers + text_plugin_assets_writable: Es pot escriure als connectors actius + text_rmagick_available: RMagick disponible (opcional) + text_destroy_time_entries_question: "S'han informat %{hours} hores en els assumptes que aneu a suprimir. Què voleu fer?" + text_destroy_time_entries: Suprimeix les hores informades + text_assign_time_entries_to_project: Assigna les hores informades al projecte + text_reassign_time_entries: "Torna a assignar les hores informades a aquest assumpte:" + text_user_wrote: "%{value} va escriure:" + text_enumeration_destroy_question: "%{count} objectes estan assignats a aquest valor." + text_enumeration_category_reassign_to: "Torna a assignar-los a aquest valor:" + text_email_delivery_not_configured: "El lliurament per correu electrònic no està configurat i les notificacions estan inhabilitades.\nConfigureu el servidor SMTP a config/configuration.yml i reinicieu l'aplicació per habilitar-lo." + text_repository_usernames_mapping: "Seleccioneu l'assignació entre els usuaris del Redmine i cada nom d'usuari trobat al dipòsit.\nEls usuaris amb el mateix nom d'usuari o correu del Redmine i del dipòsit s'assignaran automàticament." + text_diff_truncated: "... Aquestes diferències s'han trucat perquè excedeixen la mida màxima que es pot mostrar." + text_custom_field_possible_values_info: "Una línia per a cada valor" + text_wiki_page_destroy_question: "Aquesta pàgina té %{descendants} pàgines fill i descendents. Què voleu fer?" + text_wiki_page_nullify_children: "Deixa les pàgines fill com a pàgines arrel" + text_wiki_page_destroy_children: "Suprimeix les pàgines fill i tots els seus descendents" + text_wiki_page_reassign_children: "Reasigna les pàgines fill a aquesta pàgina pare" + text_own_membership_delete_confirmation: "Esteu a punt de suprimir algun o tots els vostres permisos i potser no podreu editar més aquest projecte.\nSegur que voleu continuar?" + text_zoom_in: Redueix + text_zoom_out: Amplia + + default_role_manager: Gestor + default_role_developer: Desenvolupador + default_role_reporter: Informador + default_tracker_bug: Error + default_tracker_feature: Característica + default_tracker_support: Suport + default_issue_status_new: Nou + default_issue_status_in_progress: In Progress + default_issue_status_resolved: Resolt + default_issue_status_feedback: Comentaris + default_issue_status_closed: Tancat + default_issue_status_rejected: Rebutjat + default_doc_category_user: "Documentació d'usuari" + default_doc_category_tech: Documentació tècnica + default_priority_low: Baixa + default_priority_normal: Normal + default_priority_high: Alta + default_priority_urgent: Urgent + default_priority_immediate: Immediata + default_activity_design: Disseny + default_activity_development: Desenvolupament + + enumeration_issue_priorities: Prioritat dels assumptes + enumeration_doc_categories: Categories del document + enumeration_activities: Activitats (seguidor de temps) + enumeration_system_activity: Activitat del sistema + + button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" + field_text: Text field + label_user_mail_option_only_owner: Only for things I am the owner of + setting_default_notification_option: Default notification option + label_user_mail_option_only_my_events: Only for things I watch or I'm involved in + label_user_mail_option_only_assigned: Only for things I am assigned to + label_user_mail_option_none: No events + field_member_of_group: Assignee's group + field_assigned_to_role: Assignee's role + notice_not_authorized_archived_project: The project you're trying to access has been archived. + label_principal_search: "Search for user or group:" + label_user_search: "Search for user:" + field_visible: Visible + setting_commit_logtime_activity_id: Activity for logged time + text_time_logged_by_changeset: Applied in changeset %{value}. + setting_commit_logtime_enabled: Enable time logging + notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) + setting_gantt_items_limit: Maximum number of items displayed on the gantt chart + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. + label_my_queries: My custom queries + text_journal_changed_no_detail: "%{label} updated" + label_news_comment_added: Comment added to a news + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + label_bulk_edit_selected_time_entries: Bulk edit selected time entries + text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? + label_role_anonymous: Anonymous + label_role_non_member: Non member + label_issue_note_added: Note added + label_issue_status_updated: Status updated + label_issue_priority_updated: Priority updated + label_issues_visibility_own: Issues created by or assigned to the user + field_issues_visibility: Issues visibility + label_issues_visibility_all: All issues + permission_set_own_issues_private: Set own issues public or private + field_is_private: Private + permission_set_issues_private: Set issues public or private + label_issues_visibility_public: All non private issues + text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). + field_commit_logs_encoding: Codificació dels missatges publicats + field_scm_path_encoding: Path encoding + text_scm_path_encoding_note: "Default: UTF-8" + field_path_to_repository: Path to repository + field_root_directory: Root directory + field_cvs_module: Module + field_cvsroot: CVSROOT + text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) + text_scm_command: Command + text_scm_command_version: Version + label_git_report_last_commit: Report last commit for files and directories + notice_issue_successful_create: Issue %{id} created. + label_between: between + setting_issue_group_assignment: Allow issue assignment to groups + label_diff: diff + text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) + description_query_sort_criteria_direction: Sort direction + description_project_scope: Search scope + description_filter: Filter + description_user_mail_notification: Mail notification settings + description_date_from: Enter start date + description_message_content: Message content + description_available_columns: Available Columns + description_date_range_interval: Choose range by selecting start and end date + description_issue_category_reassign: Choose issue category + description_search: Searchfield + description_notes: Notes + description_date_range_list: Choose range from list + description_choose_project: Projects + description_date_to: Enter end date + description_query_sort_criteria_attribute: Sort attribute + description_wiki_subpages_reassign: Choose new parent page + description_selected_columns: Selected Columns + label_parent_revision: Parent + label_child_revision: Child + error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. + setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues + button_edit_section: Edit this section + setting_repositories_encodings: Attachments and repositories encodings + description_all_columns: All Columns + button_export: Export + label_export_options: "%{export_format} export options" + error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) + notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." + label_x_issues: + zero: 0 assumpte + one: 1 assumpte + other: "%{count} assumptes" + label_repository_new: New repository + field_repository_is_default: Main repository + label_copy_attachments: Copy attachments + label_item_position: "%{position}/%{count}" + label_completed_versions: Completed versions + text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
Once saved, the identifier cannot be changed. + field_multiple: Multiple values + setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed + text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes + text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) + notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. + text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} + permission_manage_related_issues: Manage related issues + field_auth_source_ldap_filter: LDAP filter + label_search_for_watchers: Search for watchers to add + notice_account_deleted: Your account has been permanently deleted. + setting_unsubscribe: Allow users to delete their own account + button_delete_my_account: Delete my account + text_account_destroy_confirmation: |- + Are you sure you want to proceed? + Your account will be permanently deleted, with no way to reactivate it. + error_session_expired: Your session has expired. Please login again. + text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." + setting_session_lifetime: Session maximum lifetime + setting_session_timeout: Session inactivity timeout + label_session_expiration: Session expiration + permission_close_project: Close / reopen the project + label_show_closed_projects: View closed projects + button_close: Close + button_reopen: Reopen + project_status_active: active + project_status_closed: closed + project_status_archived: archived + text_project_closed: This project is closed and read-only. + notice_user_successful_create: User %{id} created. + field_core_fields: Standard fields + field_timeout: Timeout (in seconds) + setting_thumbnails_enabled: Display attachment thumbnails + setting_thumbnails_size: Thumbnails size (in pixels) + label_status_transitions: Status transitions + label_fields_permissions: Fields permissions + label_readonly: Read-only + label_required: Required + text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
Once saved, the identifier cannot be changed. + field_board_parent: Parent forum + label_attribute_of_project: Project's %{name} + label_attribute_of_author: Author's %{name} + label_attribute_of_assigned_to: Assignee's %{name} + label_attribute_of_fixed_version: Target version's %{name} + label_copy_subtasks: Copy subtasks + label_copied_to: copied to + label_copied_from: copied from + label_any_issues_in_project: any issues in project + label_any_issues_not_in_project: any issues not in project + field_private_notes: Private notes + permission_view_private_notes: View private notes + permission_set_notes_private: Set notes as private + label_no_issues_in_project: no issues in project + label_any: tots + label_last_n_weeks: last %{count} weeks + setting_cross_project_subtasks: Allow cross-project subtasks + label_cross_project_descendants: "Amb tots els subprojectes" + label_cross_project_tree: "Amb l'arbre del projecte" + label_cross_project_hierarchy: "Amb la jerarquia del projecte" + label_cross_project_system: "Amb tots els projectes" + button_hide: Hide + setting_non_working_week_days: Non-working days + label_in_the_next_days: in the next + label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Total + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/57/57c1667a6b0b8222beaeafcee0db4e38818fa2d0.svn-base --- a/.svn/pristine/57/57c1667a6b0b8222beaeafcee0db4e38818fa2d0.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -require File.dirname(__FILE__) + "/rails/init" diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/57/57d9fc955c1c0bed1abfed5bf47b0ed281a3ff4b.svn-base --- a/.svn/pristine/57/57d9fc955c1c0bed1abfed5bf47b0ed281a3ff4b.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1009 +0,0 @@ -mn: - direction: ltr - date: - formats: - # Use the strftime parameters for formats. - # When no format has been given, it uses default. - # You can provide other formats here if you like! - default: "%Y/%m/%d" - short: "%b %d" - long: "%Y, %B %d" - - day_names: [Даваа, МÑгмар, Лхагва, ПүрÑв, БааÑан, БÑмба, ÐÑм] - abbr_day_names: [Дав, МÑг, Лха, Пүр, БÑн, БÑм, ÐÑм] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, 1-Ñ€ Ñар, 2-Ñ€ Ñар, 3-Ñ€ Ñар, 4-Ñ€ Ñар, 5-Ñ€ Ñар, 6-Ñ€ Ñар, 7-Ñ€ Ñар, 8-Ñ€ Ñар, 9-Ñ€ Ñар, 10-Ñ€ Ñар, 11-Ñ€ Ñар, 12-Ñ€ Ñар] - abbr_month_names: [~, 1Ñар, 2Ñар, 3Ñар, 4Ñар, 5Ñар, 6Ñар, 7Ñар, 8Ñар, 9Ñар, 10Ñар, 11Ñар, 12Ñар] - # Used in date_select and datime_select. - order: - - :day - - :month - - :year - - time: - formats: - default: "%Y/%m/%d %I:%M %p" - time: "%I:%M %p" - short: "%d %b %H:%M" - long: "%Y, %B %d %H:%M" - am: "am" - pm: "pm" - - datetime: - distance_in_words: - half_a_minute: "Ñ…Ð°Ð³Ð°Ñ Ð¼Ð¸Ð½ÑƒÑ‚" - less_than_x_seconds: - one: "Ñекунд орчим" - other: "%{count} ÑекундÑÑÑ Ð±Ð°Ð³Ð° хугацаа" - x_seconds: - one: "1 Ñекунд" - other: "%{count} Ñекунд" - less_than_x_minutes: - one: "Ð¼Ð¸Ð½ÑƒÑ‚Ð°Ð°Ñ Ð±Ð°Ð³Ð° хугацаа" - other: "%{count} Ð¼Ð¸Ð½ÑƒÑ‚Ð°Ð°Ñ Ð±Ð°Ð³Ð° хугацаа" - x_minutes: - one: "1 минут" - other: "%{count} минут" - about_x_hours: - one: "1 цаг орчим" - other: "ойролцоогоор %{count} цаг" - x_days: - one: "1 өдөр" - other: "%{count} өдөр" - about_x_months: - one: "1 Ñар орчим" - other: "ойролцоогоор %{count} Ñар" - x_months: - one: "1 Ñар" - other: "%{count} Ñар" - about_x_years: - one: "ойролцоогоор 1 жил" - other: "ойролцоогоор %{count} жил" - over_x_years: - one: "1 жилÑÑÑ Ð¸Ñ…" - other: "%{count} жилÑÑÑ Ð¸Ñ…" - almost_x_years: - one: "бараг 1 жил" - other: "бараг %{count} жил" - - number: - format: - separator: "." - delimiter: "" - precision: 3 - human: - format: - delimiter: "" - precision: 1 - storage_units: - format: "%n %u" - units: - byte: - one: "Байт" - other: "Байт" - kb: "KB" - mb: "MB" - gb: "GB" - tb: "TB" - - -# Used in array.to_sentence. - support: - array: - sentence_connector: "баÑ" - skip_last_comma: false - - activerecord: - errors: - template: - header: - one: "1 error prohibited this %{model} from being saved" - other: "%{count} errors prohibited this %{model} from being saved" - messages: - inclusion: "жагÑаалтад заагдаагүй байна" - exclusion: "нөөцлөгдÑөн" - invalid: "буруу" - confirmation: "баталгаажÑан өгөгдөлтÑй таарахгүй байна" - accepted: "хүлÑÑж авах Ñ‘Ñтой" - empty: "хооÑон байж болохгүй" - blank: "бланк байж болохгүй" - too_long: "дÑндүү урт байна (хамгийн ихдÑÑ %{count} Ñ‚ÑмдÑгт)" - too_short: "дÑндүү богино байна (хамгийн багадаа %{count} Ñ‚ÑмдÑгт)" - wrong_length: "буруу урттай байна (заавал %{count} Ñ‚ÑмдÑгт)" - taken: "аль Ñ…ÑÐ´Ð¸Ð¹Ð½Ñ Ð°Ð²Ñан байна" - not_a_number: "тоо биш байна" - not_a_date: "зөв огноо биш байна" - greater_than: "%{count} их байх Ñ‘Ñтой" - greater_than_or_equal_to: "must be greater than or equal to %{count}" - equal_to: "must be equal to %{count}" - less_than: "must be less than %{count}" - less_than_or_equal_to: "must be less than or equal to %{count}" - odd: "заавал Ñондгой" - even: "заавал Ñ‚Ñгш" - greater_than_start_date: "must be greater than start date" - not_same_project: "нÑг ижил төÑөлд хамаарахгүй байна" - circular_dependency: "Ð­Ð½Ñ Ñ…Ð°Ñ€ÑŒÑ†Ð°Ð° нь гинжин(рекурÑив) харьцаа Ò¯Ò¯ÑгÑÑ… юм байна" - cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" - - actionview_instancetag_blank_option: Сонгоно уу - - general_text_No: 'Үгүй' - general_text_Yes: 'Тийм' - general_text_no: 'үгүй' - general_text_yes: 'тийм' - general_lang_name: 'Mongolian (Монгол)' - general_csv_separator: ',' - general_csv_decimal_separator: '.' - general_csv_encoding: UTF-8 - general_pdf_encoding: UTF-8 - general_first_day_of_week: '7' - - notice_account_updated: ДанÑыг амжилттай өөрчиллөө. - notice_account_invalid_creditentials: Ð¥ÑÑ€ÑглÑгчийн нÑÑ€ ÑÑвÑл нууц үг буруу байна - notice_account_password_updated: Ðууц үгийг амжилттай өөрчиллөө. - notice_account_wrong_password: Буруу нууц үг - notice_account_register_done: Ð¨Ð¸Ð½Ñ Ñ…ÑÑ€ÑглÑгч амжилттай Ò¯Ò¯ÑгÑлÑÑ. ИдÑвхжүүлÑхийн тулд, бидний тань луу илгÑÑÑÑн мÑйл дотор байгаа Ñ…Ð¾Ð»Ð±Ð¾Ð¾Ñ Ð´ÑÑÑ€ дараарай. - notice_account_unknown_email: Үл мÑдÑгдÑÑ… Ñ…ÑÑ€ÑглÑгч. - notice_can_t_change_password: Ð­Ð½Ñ Ñрх гадаад нÑвтрÑлтÑд ашигладаг ÑƒÑ‡Ñ€Ð°Ð°Ñ Ð½ÑƒÑƒÑ† үгийг өөрчлөх боломжгүй. - notice_account_lost_email_sent: Бид таньд мÑйлÑÑÑ€ нууц үгÑÑ Ó©Ó©Ñ€Ñ‡Ð»Ó©Ñ… зааврыг илгÑÑÑÑн байгаа. - notice_account_activated: Таны Ð´Ð°Ð½Ñ Ð¸Ð´ÑвхжлÑÑ. Одоо нÑвтÑрч орж болно. - notice_successful_create: Ðмжилттай Ò¯Ò¯ÑгÑлÑÑ. - notice_successful_update: Ðмжилттай өөрчиллөө. - notice_successful_delete: Ðмжилттай уÑтгалаа. - notice_successful_connection: Ðмжилттай холбогдлоо. - notice_file_not_found: Таны үзÑÑ… гÑÑÑн Ñ…ÑƒÑƒÐ´Ð°Ñ Ð±Ð°Ð¹Ñ…Ð³Ò¯Ð¹ юмуу уÑтгагдÑан байна. - notice_locking_conflict: Өгөгдлийг Ó©Ó©Ñ€ хүн өөрчилÑөн байна. - notice_not_authorized: Танд ÑÐ½Ñ Ñ…ÑƒÑƒÐ´Ñыг үзÑÑ… Ñрх байхгүй байна. - notice_email_sent: "%{value} - руу мÑйл илгÑÑлÑÑ" - notice_email_error: "МÑйл илгÑÑÑ…Ñд алдаа гарлаа (%{value})" - notice_feeds_access_key_reseted: Таны RSS хандалтын түлхүүрийг дахин ÑхлүүллÑÑ. - notice_api_access_key_reseted: Your API access key was reset. - notice_failed_to_save_issues: "%{total} аÑуудал ÑонгогдÑÐ¾Ð½Ð¾Ð¾Ñ %{count} аÑуудлыг нь хадгалахад алдаа гарлаа: %{ids}." - notice_no_issue_selected: "Ямар ч аÑуудал Ñонгогдоогүй байна! ЗаÑварлах аÑуудлуудаа Ñонгоно уу." - notice_account_pending: "Таны данÑыг Ò¯Ò¯ÑгÑж дууÑлаа, админиÑтратор баталгаажуулах хүртÑл хүлÑÑÐ½Ñ Ò¯Ò¯." - notice_default_data_loaded: Стандарт тохиргоог амжилттай ачааллаа. - notice_unable_delete_version: Хувилбарыг уÑтгах боломжгүй. - notice_issue_done_ratios_updated: Issue done ratios updated. - - error_can_t_load_default_data: "Стандарт тохиргоог ачаалж чадÑангүй: %{value}" - error_scm_not_found: "Repository дотор тухайн бичлÑг ÑÑвÑл хувилбарыг олÑонгүй." - error_scm_command_failed: "Repository-д хандахад алдаа гарлаа: %{value}" - error_scm_annotate: "БичлÑг байхгүй байна, ÑÑвÑл бичлÑгт тайлбар хавÑаргаж болохгүй." - error_issue_not_found_in_project: 'СонгоÑон аÑуудал ÑÐ½Ñ Ñ‚Ó©Ñөлд хамаардаггүй юм уу ÑÑвÑл ÑиÑтемд байхгүй байна.' - error_no_tracker_in_project: 'No tracker is associated to this project. Please check the Project settings.' - error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").' - error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version can not be reopened' - error_can_not_archive_project: This project can not be archived - error_issue_done_ratios_not_updated: "Issue done ratios not updated." - error_workflow_copy_source: 'Please select a source tracker or role' - error_workflow_copy_target: 'Please select target tracker(s) and role(s)' - - warning_attachments_not_saved: "%{count} file(s) файлыг хадгалж чадÑангүй." - - mail_subject_lost_password: "Таны %{value} нууц үг" - mail_body_lost_password: 'Ðууц үгÑÑ Ó©Ó©Ñ€Ñ‡Ð»Ó©Ñ…Ð¸Ð¹Ð½ тулд доорх Ñ…Ð¾Ð»Ð±Ð¾Ð¾Ñ Ð´ÑÑÑ€ дарна уу:' - mail_subject_register: "Таны %{value} данÑыг идÑвхжүүлÑÑ…" - mail_body_register: 'ДанÑаа идÑвхжүүлÑхийн тулд доорх Ñ…Ð¾Ð»Ð±Ð¾Ð¾Ñ Ð´ÑÑÑ€ дарна уу:' - mail_body_account_information_external: "Та өөрийнхөө %{value} данÑыг ашиглаж холбогдож болно." - mail_body_account_information: Таны данÑны тухай мÑдÑÑлÑл - mail_subject_account_activation_request: "%{value} данÑыг идÑвхжүүлÑÑ… Ñ…Ò¯ÑÑлт" - mail_body_account_activation_request: "Ð¨Ð¸Ð½Ñ Ñ…ÑÑ€ÑглÑгч (%{value}) бүртгүүлÑÑн байна. Таны баталгаажуулахыг хүлÑÑж байна:" - mail_subject_reminder: "Дараагийн өдрүүдÑд %{count} аÑуудлыг шийдÑÑ… Ñ…ÑÑ€ÑгтÑй (%{days})" - mail_body_reminder: "Танд оноогдÑон %{count} аÑуудлуудыг дараагийн %{days} өдрүүдÑд шийдÑÑ… Ñ…ÑÑ€ÑгтÑй:" - mail_subject_wiki_content_added: "'%{id}' wiki page has been added" - mail_body_wiki_content_added: "The '%{id}' wiki page has been added by %{author}." - mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated" - mail_body_wiki_content_updated: "The '%{id}' wiki page has been updated by %{author}." - - gui_validation_error: 1 алдаа - gui_validation_error_plural: "%{count} алдаа" - - 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: Энгийн илÑрхийлÑл - field_min_length: Минимум урт - field_max_length: МакÑимум урт - field_value: Утга - field_category: Төрөл - field_title: Гарчиг - field_project: ТөÑөл - field_issue: ÐÑуудал - field_status: Төлөв - field_notes: ТÑмдÑглÑлүүд - field_is_closed: ÐÑуудал хаагдÑан - field_is_default: Стандарт утга - field_tracker: ЧиглÑл - field_subject: Гарчиг - field_due_date: ДууÑах огноо - field_assigned_to: ОноогдÑон - field_priority: ЗÑÑ€ÑглÑл - field_fixed_version: Хувилбар - field_user: Ð¥ÑÑ€ÑглÑгч - field_role: Хандалтын Ñрх - field_homepage: Ðүүр Ñ…ÑƒÑƒÐ´Ð°Ñ - field_is_public: Олон нийтийн - field_parent: ЭцÑг төÑөл нь - field_is_in_roadmap: ÐÑуудлуудыг Ñвцын зураг дÑÑÑ€ харуулах - field_login: ÐÑвтрÑÑ… нÑÑ€ - field_mail_notification: ИмÑйл мÑдÑгдлүүд - field_admin: ÐдминиÑтратор - field_last_login_on: Сүүлийн холбоо - field_language: Ð¥Ñл - field_effective_date: Огноо - field_password: Ðууц үг - field_new_password: Ð¨Ð½Ð½Ñ Ð½ÑƒÑƒÑ† үг - field_password_confirmation: Баталгаажуулах - field_version: Хувилбар - field_type: Төрөл - field_host: ХоÑÑ‚ - field_port: Порт - field_account: Ð”Ð°Ð½Ñ - field_base_dn: ҮндÑÑн ДР- field_attr_login: ÐÑвтрÑÑ… аттрибут - field_attr_firstname: Таны нÑÑ€ аттрибут - field_attr_lastname: Овог аттрибут - field_attr_mail: ИмÑйл аттрибут - field_onthefly: Ð¥Ò¯ÑÑÑн үедÑÑ Ñ…ÑÑ€ÑглÑгч Ò¯Ò¯ÑгÑÑ… - field_start_date: ЭхлÑл - field_done_ratio: "%% ГүйцÑтгÑÑÑн" - field_auth_source: ÐÑвтрÑÑ… арга - field_hide_mail: Миний имÑйл хаÑгийг нуу - field_comments: Тайлбар - field_url: URL ХаÑг - field_start_page: ТÑргүүн Ñ…ÑƒÑƒÐ´Ð°Ñ - field_subproject: ДÑд төÑөл - field_hours: Цаг - field_activity: Үйл ажиллагаа - field_spent_on: Огноо - field_identifier: ТөÑлийн глобал нÑÑ€ - field_is_filter: Шүүлтүүр болгон Ñ…ÑÑ€ÑглÑгддÑг - field_issue_to: Хамаатай аÑуудал - field_delay: Хоцролт - field_assignable: Ð­Ð½Ñ Ñ…Ð°Ð½Ð´Ð°Ð»Ñ‚Ñ‹Ð½ ÑрхÑд аÑуудлуудыг оноож өгч болно - field_redirect_existing_links: Байгаа холбооÑуудыг дахин чиглүүлÑÑ… - field_estimated_hours: БарагцаалÑан цаг - field_column_names: Баганууд - field_time_zone: Цагын Ð±Ò¯Ñ - field_searchable: Хайж болох - field_default_value: Стандарт утга - field_comments_sorting: Тайлбаруудыг харуул - field_parent_title: ЭцÑг Ñ…ÑƒÑƒÐ´Ð°Ñ - field_editable: ЗаÑварлагдана - field_watcher: Харна - field_identity_url: OpenID URL - field_content: Ðгуулга - field_group_by: Үр дүнгÑÑÑ€ бүлÑглÑÑ… - field_sharing: Sharing - - setting_app_title: Программын гарчиг - setting_app_subtitle: Программын дÑд гарчиг - setting_welcome_text: МÑндчилгÑÑ - setting_default_language: Стандарт Ñ…Ñл - setting_login_required: ÐÑвтрÑÑ… шаардлагатай - setting_self_registration: Өөрийгөө бүртгүүлÑÑ… - setting_attachment_max_size: ХавÑралт файлын дÑÑд Ñ…ÑмжÑÑ - setting_issues_export_limit: ÐÑуудал ÑкÑпортлох Ñ…Ñзгаар - setting_mail_from: Ямар имÑйл хаÑг Ò¯Ò¯ÑгÑÑ… - setting_bcc_recipients: BCC талбарын хаÑгууд (bcc) - setting_plain_text_mail: дан текÑÑ‚ мÑйл (HTML биш) - setting_host_name: ХоÑтын нÑÑ€ болон зам - setting_text_formatting: ТекÑÑ‚ Ñ…ÑлбÑржүүлÑлт - setting_wiki_compression: Вики хуудÑуудын түүх дÑÑÑ€ шахалт хийх - setting_feeds_limit: Фийд агуулгын Ñ…Ñзгаар - setting_default_projects_public: Ð¨Ð¸Ð½Ñ Ñ‚Ó©Ñлүүд автоматаар олон нийтийнх байна - setting_autofetch_changesets: Комитуудыг автоматаар татаж авах - setting_sys_api_enabled: Репозитори менежментÑд зориулан WS-ийг идÑвхжүүлÑÑ… - setting_commit_ref_keywords: Хамааруулах түлхүүр Ò¯Ð³Ñ - setting_commit_fix_keywords: Зоолттой түлхүүр Ò¯Ð³Ñ - setting_autologin: Компьютер дÑÑÑ€ Ñанах - setting_date_format: Огнооны формат - setting_time_format: Цагийн формат - setting_cross_project_issue_relations: ТөÑөл хооронд аÑуудал хамааруулахыг зөвшөөрөх - setting_issue_list_default_columns: ÐÑуудлуудыг харуулах Ñтандарт баганууд - setting_emails_footer: ИмÑйлүүдийн хөл Ñ…ÑÑÑг - setting_protocol: Протокол - setting_per_page_options: ÐÑг хуудÑанд байх обьектуудын тохиргоо - setting_user_format: Ð¥ÑÑ€ÑглÑгчдийг харуулах формат - setting_activity_days_default: ТөÑлийн үйл ажиллагаа Ñ…ÑÑÑгт үзүүлÑÑ… өдрийн тоо - setting_display_subprojects_issues: ДÑд төÑлүүдийн аÑуудлуудыг автоматаар гол төÑөл дÑÑÑ€ харуулах - setting_enabled_scm: SCM - ийг идÑвхжүүлÑÑ… - setting_mail_handler_body_delimiters: "Truncate emails after one of these lines" - setting_mail_handler_api_enabled: ИрÑÑн мÑйлүүдийн хувьд WS-ийг идÑвхжүүлÑÑ… - setting_mail_handler_api_key: API түлхүүр - setting_sequential_project_identifiers: ДÑÑ Ð´Ð°Ñ€Ð°Ð°Ð»Ñан төÑлийн глобал нÑÑ€ Ò¯Ò¯ÑгÑж байх - setting_gravatar_enabled: Gravatar дүрÑүүдийг Ñ…ÑÑ€ÑглÑгчдÑд Ñ…ÑÑ€ÑглÑж байх - setting_gravatar_default: Default Gravatar image - setting_diff_max_lines_displayed: Ялгаатай мөрүүдийн тоо (дÑÑд тал нь) - setting_file_max_size_displayed: Max size of text files displayed inline - setting_repository_log_display_limit: Maximum number of revisions displayed on file log - setting_openid: Allow OpenID login and registration - setting_password_min_length: Minimum password length - setting_new_project_user_role_id: Role given to a non-admin user who creates a project - setting_default_projects_modules: Default enabled modules for new projects - setting_issue_done_ratio: Calculate the issue done ratio with - setting_issue_done_ratio_issue_field: Use the issue field - setting_issue_done_ratio_issue_status: Use the issue status - setting_start_of_week: Start calendars on - setting_rest_api_enabled: Enable REST web service - setting_cache_formatted_text: Cache formatted text - - permission_add_project: Create project - permission_add_subprojects: Create subprojects - permission_edit_project: ТөÑлийг заÑварлах - permission_select_project_modules: ТөÑлийн модулуудийг Ñонгоно уу - permission_manage_members: СиÑтемийн Ñ…ÑÑ€ÑглÑгчид - permission_manage_project_activities: Manage project activities - permission_manage_versions: Хувилбарууд - permission_manage_categories: ÐÑуудлын ангиллууд - permission_view_issues: ÐÑуудлуудыг харах - 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_delete_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: Вики удирдах - 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: Вики хуудÑуудыг хамгаалах - 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: Өөрийн зурваÑуудыг уÑтгах - permission_export_wiki_pages: Вики хуудÑуудыг ÑкÑпорт хийх - - project_module_issue_tracking: ÐÑуудал Ñ…Ñнах - project_module_time_tracking: Хугацаа Ñ…Ñнах - project_module_news: МÑдÑÑ Ð¼ÑдÑÑллүүд - project_module_documents: Бичиг баримтууд - project_module_files: Файлууд - project_module_wiki: Вики - project_module_repository: Репозитори - project_module_boards: Самбарууд - - label_user: Ð¥ÑÑ€ÑглÑгч - label_user_plural: Ð¥ÑÑ€ÑглÑгчид - label_user_new: Ð¨Ð¸Ð½Ñ Ñ…ÑÑ€ÑглÑгч - label_user_anonymous: Хамаагүй Ñ…ÑÑ€ÑглÑгч - label_project: ТөÑөл - label_project_new: Ð¨Ð¸Ð½Ñ Ñ‚Ó©Ñөл - label_project_plural: ТөÑлүүд - label_x_projects: - zero: төÑөл байхгүй - one: 1 төÑөл - other: "%{count} төÑлүүд" - label_project_all: Бүх ТөÑлүүд - label_project_latest: Сүүлийн үеийн төÑлүүд - label_issue: ÐÑуудал - label_issue_new: Ð¨Ð¸Ð½Ñ Ð°Ñуудал - label_issue_plural: ÐÑуудлууд - label_issue_view_all: Бүх аÑуудлуудыг харах - label_issues_by: "%{value} - н аÑуудлууд" - label_issue_added: ÐÑуудал нÑмÑгдлÑÑ - label_issue_updated: ÐÑуудал өөрчлөгдлөө - label_document: Бичиг баримт - label_document_new: Ð¨Ð¸Ð½Ñ Ð±Ð¸Ñ‡Ð¸Ð³ баримт - label_document_plural: Бичиг баримтууд - label_document_added: Бичиг баримт нÑмÑгдлÑÑ - label_role: Хандалтын Ñрх - label_role_plural: Хандалтын Ñрхүүд - label_role_new: Ð¨Ð¸Ð½Ñ Ñ…Ð°Ð½Ð´Ð°Ð»Ñ‚Ñ‹Ð½ Ñрх - label_role_and_permissions: Хандалтын Ñрхүүд болон зөвшөөрлүүд - label_member: Гишүүн - label_member_new: Ð¨Ð¸Ð½Ñ Ð³Ð¸ÑˆÒ¯Ò¯Ð½ - label_member_plural: Гишүүд - label_tracker: ЧиглÑл - label_tracker_plural: ЧиглÑлүүд - label_tracker_new: Ð¨Ð¸Ð½Ñ Ñ‡Ð¸Ð³Ð»Ñл - label_workflow: Ðжлын дараалал - label_issue_status: ÐÑуудлын төлөв - label_issue_status_plural: ÐÑуудлын төлвүүд - label_issue_status_new: Ð¨Ð¸Ð½Ñ Ñ‚Ó©Ð»Ó©Ð² - label_issue_category: ÐÑуудлын ангилал - label_issue_category_plural: ÐÑуудлын ангиллууд - label_issue_category_new: Ð¨Ð¸Ð½Ñ Ð°Ð½Ð³Ð¸Ð»Ð°Ð» - label_custom_field: Ð¥ÑÑ€ÑглÑгчийн тодорхойлÑон талбар - label_custom_field_plural: Ð¥ÑÑ€ÑглÑгчийн тодорхойлÑон талбарууд - label_custom_field_new: ШинÑÑÑ€ Ñ…ÑÑ€ÑглÑгчийн тодорхойлÑон талбар Ò¯Ò¯ÑгÑÑ… - label_enumerations: Ðнгиллууд - label_enumeration_new: Ð¨Ð¸Ð½Ñ ÑƒÑ‚Ð³Ð° - label_information: МÑдÑÑлÑл - label_information_plural: МÑдÑÑллүүд - label_please_login: ÐÑвтÑрч орно уу - label_register: БүртгүүлÑÑ… - label_login_with_open_id_option: or login with OpenID - 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_registered_on: БүртгүүлÑÑн огноо - label_activity: Үйл ажиллагаа - label_overall_activity: Ерөнхий үйл ажиллагаа - label_user_activity: "%{value}-ийн үйл ажиллагаа" - label_new: Ð¨Ð¸Ð½Ñ - label_logged_as: ХолбогдÑон нÑÑ€ - label_environment: Орчин - label_authentication: ÐÑвтрÑÑ… - label_auth_source: ÐÑвтрÑÑ… арга - label_auth_source_new: Ð¨Ð¸Ð½Ñ Ð½ÑвтрÑÑ… арга - label_auth_source_plural: ÐÑвтрÑÑ… аргууд - label_subproject_plural: ДÑд төÑлүүд - label_subproject_new: Ð¨Ð¸Ð½Ñ Ð´Ñд төÑөл - label_and_its_subprojects: "%{value} болон холбогдох дÑд төÑлүүд" - label_min_max_length: ДÑÑд - Доод урт - label_list: ЖагÑаалт - label_date: Огноо - label_integer: БүхÑл тоо - label_float: Бутархай тоо - label_boolean: ҮнÑн худал утга - label_string: ТекÑÑ‚ - label_text: Урт текÑÑ‚ - label_attribute: Ðттрибут - label_attribute_plural: Ðттрибутууд - label_download: "%{count} Татаж авÑан зүйл" - label_download_plural: "%{count} Татаж авÑан зүйлÑ" - 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_close_versions: ГүйцÑÑ‚ хувилбаруудыг хаалаа - 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_x_open_issues_abbr_on_total: - zero: 0 нÑÑлттÑй / %{total} - one: 1 нÑÑлттÑй / %{total} - other: "%{count} нÑÑлттÑй / %{total}" - label_x_open_issues_abbr: - zero: 0 нÑÑлттÑй - one: 1 нÑÑлттÑй - other: "%{count} нÑÑлттÑй" - label_x_closed_issues_abbr: - zero: 0 хаалттай - one: 1 хаалттай - other: "%{count} хаалттай" - 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: Гант диаграм - label_internal: Дотоод - label_last_changes: "Ñүүлийн %{count} өөрчлөлтүүд" - label_change_view_all: Бүх өөрчлөлтүүдийг харах - label_personalize_page: Ð­Ð½Ñ Ñ…ÑƒÑƒÐ´Ñыг өөрт зориулан өөрчлөх - label_comment: Тайлбар - label_comment_plural: Тайлбарууд - label_x_comments: - zero: ÑÑтгÑгдÑл байхгүй - one: 1 ÑÑтгÑгдÑлтÑй - other: "%{count} ÑÑтгÑгдÑлтÑй" - label_comment_add: Тайлбар нÑмÑÑ… - label_comment_added: Тайлбар нÑмÑгдлÑÑ - label_comment_delete: Тайлбарууд уÑтгах - label_query: Ð¥ÑÑ€ÑглÑгчийн тодорхойлÑон аÑуулт - label_query_plural: Ð¥ÑÑ€ÑглÑгчийн тодорхойлÑон аÑуултууд - label_query_new: ШинÑÑÑ€ Ñ…ÑÑ€ÑглÑгчийн тодорхойлÑон аÑуулт Ò¯Ò¯ÑгÑÑ… - label_filter_add: Шүүлтүүр нÑмÑÑ… - label_filter_plural: Шүүлтүүрүүд - label_equals: бол - label_not_equals: биш - label_in_less_than: Ð°Ð°Ñ Ð±Ð°Ð³Ð° - label_in_more_than: Ð°Ð°Ñ Ð¸Ñ… - label_greater_or_equal: '>=' - label_less_or_equal: '<=' - label_in: дотор - label_today: өнөөдөр - label_all_time: бүх хугацаа - label_yesterday: өчигдөр - label_this_week: ÑÐ½Ñ Ð´Ð¾Ð»Ð¾Ð¾ хоног - label_last_week: өнгөрÑөн долоо хоног - label_last_n_days: "Ñүүлийн %{count} өдрүүд" - label_this_month: ÑÐ½Ñ Ñар - label_last_month: Ñүүлийн Ñар - label_this_year: ÑÐ½Ñ Ð¶Ð¸Ð» - label_date_range: Ð¥Ñзгаар огноо - label_less_than_ago: бага өдрийн дотор - label_more_than_ago: их өдрийн дотор - label_ago: өдрийн өмнө - label_contains: агуулж байгаа - label_not_contains: агуулаагүй - label_day_plural: өдрүүд - label_repository: Репозитори - label_repository_plural: Репозиторууд - label_browse: ҮзÑÑ… - label_modification: "%{count} өөрчлөлт" - label_modification_plural: "%{count} өөрчлөлтүүд" - label_branch: Салбар - label_tag: Шошго - label_revision: Хувилбар - label_revision_plural: Хувилбарууд - label_revision_id: "%{value} Хувилбар" - label_associated_revisions: Хамааралтай хувилбарууд - label_added: нÑмÑгдÑÑн - label_modified: өөрчлөгдÑөн - label_copied: хуулÑан - label_renamed: нÑрийг нь өөрчилÑөн - label_deleted: уÑтгаÑан - label_latest_revision: Сүүлийн үеийн хувилбар - label_latest_revision_plural: Сүүлийн үеийн хувилбарууд - label_view_revisions: Хувилбаруудыг харах - label_view_all_revisions: Бүх хувилбаруудыг харах - label_max_size: Maximum size - label_sort_highest: Хамгийн дÑÑÑ€ - label_sort_higher: ДÑÑш нь - label_sort_lower: Доош нь - label_sort_lowest: Хамгийн доор - label_roadmap: Хөтөч - label_roadmap_due_in: "%{value} дотор дууÑгах" - label_roadmap_overdue: "%{value} оройтÑон" - 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: Гарчгаар ÑÑ€ÑмбÑлÑÑ… - label_index_by_date: Огноогоор ÑÑ€ÑмбÑлÑÑ… - label_current_version: Одоогийн хувилбар - label_preview: Ямар харагдахыг шалгах - label_feed_plural: Feeds - label_changes_details: Бүх өөрчлөлтүүдийн дÑлгÑÑ€Ñнгүй - label_issue_tracking: ÐÑуудал Ñ…Ñнах - label_spent_time: ЗарцуулÑан хугацаа - label_f_hour: "%{value} цаг" - label_f_hour_plural: "%{value} цаг" - label_time_tracking: Хугацааг Ñ…Ñнах - label_change_plural: Өөрчлөлтүүд - label_statistics: СтатиÑтик - label_commits_per_month: Сард хийÑÑн коммитын тоо - label_commits_per_author: Зохиогч бүрийн хувьд коммитын тоо - label_view_diff: Ялгаануудыг харах - label_diff_inline: дотор нь - label_diff_side_by_side: зÑÑ€Ñгцүүлж - label_options: Тохиргоо - label_copy_workflow_from: Ðжлын дарааллыг хуулах - label_permissions_report: Зөвшөөрлүүдийн таблиц - label_watched_issues: Ðжиглагдаж байгаа аÑуудлууд - label_related_issues: Хамааралтай аÑуудлууд - label_applied_status: ОлгоÑон төлөв - label_loading: Ðчаалж байна... - label_relation_new: Ð¨Ð¸Ð½Ñ Ñ…Ð°Ð¼Ð°Ð°Ñ€Ð°Ð» - label_relation_delete: Хамаарлыг уÑтгах - label_relates_to: Ñнгийн хамааралтай - label_duplicates: Ñ…Ð¾Ñ Ñ…Ð°Ð¼Ð°Ð°Ñ€Ð°Ð»Ñ‚Ð°Ð¹ - label_duplicated_by: давхардуулÑан ÑзÑн - label_blocks: шаардах хамааралтай - label_blocked_by: блоколÑон ÑзÑн - label_precedes: урьдчилах хамааралтай - label_follows: дагаж - label_end_to_start: Ñ…Ð¾Ð¹Ð½Ð¾Ð¾Ñ Ð½ÑŒ урагшаа - label_end_to_end: Ñ…Ð¾Ð¹Ð½Ð¾Ð¾Ñ Ð½ÑŒ хойшоо - label_start_to_start: ÑƒÑ€Ð´Ð°Ð°Ñ Ð½ÑŒ урагаа - label_start_to_end: ÑƒÑ€Ð´Ð°Ð°Ñ Ð½ÑŒ хойшоо - label_stay_logged_in: Ð­Ð½Ñ ÐºÐ¾Ð¼ÑŒÑŽÑ‚ÐµÑ€ дÑÑÑ€ Ñанах - label_disabled: идÑвхгүй болÑон - label_show_completed_versions: ГүйцÑд хувилбаруудыг харуулах - label_me: би - label_board: Форум - label_board_new: Ð¨Ð¸Ð½Ñ Ñ„Ð¾Ñ€ÑƒÐ¼ - label_board_plural: Форумууд - label_board_locked: ТүгжÑÑÑ‚Ñй - label_board_sticky: Sticky - label_topic_plural: СÑдвүүд - label_message_plural: ЗурваÑууд - label_message_last: Сүүлийн Ð·ÑƒÑ€Ð²Ð°Ñ - label_message_new: Ð¨Ð¸Ð½Ñ Ð·ÑƒÑ€Ð²Ð°Ñ - label_message_posted: Ð—ÑƒÑ€Ð²Ð°Ñ Ð½ÑмÑгдлÑÑ - label_reply_plural: Хариултууд - label_send_information: ДанÑны мÑдÑÑллийг Ñ…ÑÑ€ÑглÑгчид илгÑÑÑ… - label_year: Жил - label_month: Сар - label_week: Долоо хоног - label_date_from: Ð¥ÑзÑÑнÑÑÑ - label_date_to: Ð¥Ñдий хүртÑл - label_language_based: Ð¥ÑÑ€ÑглÑгчийн Ñ…ÑÐ»Ð½Ð°Ñ ÑˆÐ°Ð»Ñ‚Ð³Ð°Ð°Ð»Ð°Ð½ - label_sort_by: "%{value} талбараар нь ÑÑ€ÑмбÑлÑÑ…" - label_send_test_email: Турших мÑйл илгÑÑÑ… - label_feeds_access_key: RSS хандах түлхүүр - label_missing_feeds_access_key: RSS хандах түлхүүр алга - label_feeds_access_key_created_on: "RSS хандалтын түлхүүр %{value}-ийн өмнө Ò¯Ò¯ÑÑÑн" - label_module_plural: Модулууд - label_added_time_by: "%{author} %{age}-ийн өмнө нÑмÑÑн" - label_updated_time_by: "%{author} %{age}-ийн өмнө өөрчилÑөн" - label_updated_time: "%{value} -ийн өмнө өөрчлөгдÑөн" - label_jump_to_a_project: ТөÑөл Ñ€Ò¯Ò¯ очих... - label_file_plural: Файлууд - label_changeset_plural: Өөрчлөлтүүд - label_default_columns: Стандарт баганууд - label_no_change_option: (Өөрчлөлт байхгүй) - label_bulk_edit_selected_issues: СонгогдÑон аÑуудлуудыг бөөнөөр заÑварлах - label_theme: СиÑтемийн Дизайн - label_default: Стандарт - label_search_titles_only: Зөвхөн гарчиг хайх - label_user_mail_option_all: "Миний бүх төÑөл дÑÑрх бүх үзÑгдлүүдийн хувьд" - label_user_mail_option_selected: "СонгогдÑон төÑлүүдийн хувьд бүх үзÑгдÑл дÑÑÑ€..." - label_user_mail_no_self_notified: "Миний өөрийн хийÑÑн өөрчлөлтүүдийн тухай надад мÑдÑгдÑÑ… Ñ…ÑÑ€Ñггүй" - label_registration_activation_by_email: данÑыг имÑйлÑÑÑ€ идÑвхжүүлÑÑ… - label_registration_manual_activation: данÑыг гараар идÑвхжүүлÑÑ… - label_registration_automatic_activation: данÑыг автоматаар идÑвхжүүлÑÑ… - label_display_per_page: 'ÐÑг хуудÑанд: %{value}' - label_age: ÐÐ°Ñ - label_change_properties: Тохиргоог өөрчлөх - label_general: Ерөнхий - label_more: Цааш нь - label_scm: SCM - label_plugins: Модулууд - label_ldap_authentication: LDAP нÑвтрÑÑ… горим - label_downloads_abbr: D/L - label_optional_description: Дурын тайлбар - label_add_another_file: Дахин файл нÑмÑÑ… - label_preferences: Тохиргоо - label_chronological_order: Цагаан толгойн Ò¯Ñгийн дарааллаар - label_reverse_chronological_order: Урвуу цагаан толгойн Ò¯Ñгийн дарааллаар - label_planning: Төлөвлөлт - label_incoming_emails: ИрÑÑн мÑйлүүд - label_generate_key: Түлхүүр Ò¯Ò¯ÑгÑÑ… - label_issue_watchers: Ðжиглагчид - label_example: ЖишÑÑ - label_display: Display - label_sort: Sort - label_ascending: Ascending - label_descending: Descending - label_date_from_to: From %{start} to %{end} - label_wiki_content_added: Wiki page added - label_wiki_content_updated: Wiki page updated - label_group: Group - label_group_plural: Groups - label_group_new: New group - label_time_entry_plural: Spent time - label_version_sharing_none: Not shared - label_version_sharing_descendants: With subprojects - label_version_sharing_hierarchy: With project hierarchy - label_version_sharing_tree: With project tree - label_version_sharing_system: With all projects - label_update_issue_done_ratios: Update issue done ratios - label_copy_source: Source - label_copy_target: Target - label_copy_same_as_target: Same as target - label_display_used_statuses_only: Only display statuses that are used by this tracker - label_api_access_key: API access key - label_missing_api_access_key: Missing an API access key - label_api_access_key_created_on: "API access key created %{value} ago" - - button_login: ÐÑвтрÑÑ… - button_submit: ИлгÑÑÑ… - button_save: Хадгалах - button_check_all: Бүгдийг Ñонго - button_uncheck_all: Бүгдийг үл Ñонго - button_delete: УÑтгах - button_create: Ò®Ò¯ÑгÑÑ… - button_create_and_continue: Ò®Ò¯ÑгÑÑд цааш үргÑлжлүүлÑÑ… - 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_move_and_follow: Зөө Ð±Ð°Ñ Ð´Ð°Ð³Ð° - button_back: Буцах - button_cancel: Болих - button_activate: ИдÑвхжүүлÑÑ… - button_sort: ЭрÑмбÑлÑÑ… - button_log_time: Лог хийÑÑн хугацаа - button_rollback: Ð­Ð½Ñ Ñ…ÑƒÐ²Ð¸Ð»Ð±Ð°Ñ€ руу буцах - button_watch: Ðжиглах - button_unwatch: Ðжиглахаа болих - button_reply: Хариулах - button_archive: Ðрхивлах - button_unarchive: Ðрхивыг задлах - button_reset: Ðнхны утгууд - button_rename: ÐÑрийг нь Ñолих - button_change_password: Ðууц үгÑÑ Ó©Ó©Ñ€Ñ‡Ð»Ó©Ñ… - button_copy: Хуулах - button_copy_and_follow: Зөө Ð±Ð°Ñ Ð´Ð°Ð³Ð° - button_annotate: Тайлбар хавÑаргах - button_update: ШинÑчлÑÑ… - button_configure: Тохируулах - button_quote: ИшлÑл - button_duplicate: Хуулбар - button_show: ҮзÑÑ… - - status_active: идÑвхтÑй - status_registered: бүртгүүлÑÑн - status_locked: түгжÑÑÑ‚Ñй - - version_status_open: нÑÑлттÑй - version_status_locked: түгжÑÑÑ‚Ñй - version_status_closed: хаалттай - - field_active: идÑвхтÑй - - text_select_mail_notifications: Ямар үед имÑйлÑÑÑ€ мÑдÑгдÑл илгÑÑхийг Ñонгоно уу. - text_regexp_info: eg. ^[A-Z0-9]+$ - text_min_max_length_info: 0 гÑвÑл Ñмар ч Ñ…Ñзгааргүй гÑÑÑн үг - text_project_destroy_confirmation: Та ÑÐ½Ñ Ñ‚Ó©Ñөл болоод буÑад мÑдÑÑллийг нь үнÑÑ…ÑÑÑ€ уÑтгамаар байна уу ? - text_subprojects_destroy_warning: "Уг төÑлийн дÑд төÑлүүд : %{value} нь Ð±Ð°Ñ ÑƒÑтгагдах болно." - text_workflow_edit: Ðжлын дарааллыг өөрчлөхийн тулд хандалтын Ñрх болон аÑуудлын чиглÑлийг Ñонгоно уу - text_are_you_sure: Та итгÑлтÑй байна уу ? - text_journal_changed: "%{label} %{old} байÑан нь %{new} болов" - text_journal_set_to: "%{label} %{value} болгож өөрчиллөө" - text_journal_deleted: "%{label} уÑÑ‚Ñан (%{old})" - text_journal_added: "%{label} %{value} нÑмÑгдÑÑн" - text_tip_issue_begin_day: ÑÐ½Ñ Ó©Ð´Ó©Ñ€ ÑхлÑÑ… ажил - text_tip_issue_end_day: ÑÐ½Ñ Ó©Ð´Ó©Ñ€ дууÑах ажил - text_tip_issue_begin_end_day: ÑÐ½Ñ Ó©Ð´Ó©Ñ€ ÑхлÑÑд мөн дууÑч байгаа ажил - text_project_identifier_info: 'Зөвхөн жижиг Ò¯Ñгүүд болон (a-z), тоо and дундуур Ð·ÑƒÑ€Ð°Ð°Ñ Ð°ÑˆÐ¸Ð³Ð»Ð°Ð¶ болно.
ÐÑгÑнт хадгалÑан хойно, төÑлийн глобал нÑрийг өөрлчөх боломжгүй.' - text_caracters_maximum: "дÑÑд тал нь %{count} Ò¯ÑÑг." - text_caracters_minimum: "Хамгийн багадаа Ñдаж %{count} Ñ‚ÑмдÑгт байх." - text_length_between: "Урт нь багадаа %{min}, ихдÑÑ %{max} Ñ‚ÑмдÑгт." - text_tracker_no_workflow: ЭнÑÑ…Ò¯Ò¯ аÑуудлын чиглÑлд Ñмар ч ажлын дараалал тодорхойлогдоогүй байна - text_unallowed_characters: Ð¥ÑÑ€ÑглÑж болохгүй Ñ‚ÑмдÑгтүүд - text_comma_separated: ТаÑлалаар зааглан олон утга оруулж болно. - text_line_separated: Multiple values allowed (one line for each value). - text_issues_ref_in_commit_messages: Коммитийн зурваÑуудад хамааруулÑан болон байнгын аÑуудлууд - text_issue_added: "ÐÑуудал %{id} - ийг Ñ…ÑÑ€ÑглÑгч %{author} мÑдÑгдÑÑн байна." - text_issue_updated: "ÐÑуудал %{id} - ийг Ñ…ÑÑ€ÑглÑгч %{author} өөрчилÑөн байна." - text_wiki_destroy_confirmation: Та ÑÐ½Ñ Ð²Ð¸ÐºÐ¸ болон холбогдох бүх мÑдÑÑллийг үнÑÑ…ÑÑÑ€ уÑтгамаар байна уу ? - text_issue_category_destroy_question: "Ð­Ð½Ñ Ð°Ð½Ð³Ð¸Ð»Ð°Ð»Ð´ зарим аÑуудлууд (%{count}) орÑон байна. Та Ñах Ð²Ñ ?" - text_issue_category_destroy_assignments: ÐÑуудлуудыг ÑÐ½Ñ Ð°Ð½Ð³Ð¸Ð»Ð»Ð°Ð°Ñ Ð°Ð²Ð°Ñ… - text_issue_category_reassign_to: ÐÑуудлуудыг ÑÐ½Ñ Ð°Ð½Ð³Ð¸Ð»Ð°Ð»Ð´ дахин оноох - text_user_mail_option: "Сонгогдоогүй төÑлүүдийн хувьд, та зөвхөн өөрийнхөө ажиглаж байгаа Ð·Ò¯Ð¹Ð»Ñ ÑŽÐ¼ÑƒÑƒ танд хамаатай зүйлÑийн талаар мÑдÑгдÑл авах болно (Таны оруулÑан аÑуудал, ÑÑвÑл танд онооÑон гÑÑ… мÑÑ‚)." - text_no_configuration_data: "Хандалтын Ñрхүүд, чиглÑлүүд, аÑуудлын төлвүүд болон ажлын дарааллын тухай мÑдÑÑллийг хараахан оруулаагүй байна.\nТа Ñтандарт өгөгдлүүдийг даруйхан оруулахыг зөвлөж байна, оруулÑан хойно та заÑварлаж болно." - text_load_default_configuration: Стандарт өгөгдлийг ачаалах - text_status_changed_by_changeset: "%{value} өөрчлөлтөд хийгдÑÑн." - text_issues_destroy_confirmation: 'Та ÑонгогдÑон аÑуудлуудыг үнÑÑ…ÑÑÑ€ уÑтгамаар байна уу ?' - text_select_project_modules: 'Ð­Ð½Ñ Ñ‚Ó©Ñлийн хувьд идÑвхжүүлÑÑ… модулуудаа Ñонгоно уу:' - text_default_administrator_account_changed: Стандарт админиÑтраторын бүртгÑл өөрчлөгдлөө - text_file_repository_writable: ХавÑралт файл хадгалах Ñ…Ð°Ð²Ñ‚Ð°Ñ Ñ€ÑƒÑƒ бичих ÑрхтÑй - text_plugin_assets_writable: Плагин модулийн аÑÑет Ñ…Ð°Ð²Ñ‚Ð°Ñ Ñ€ÑƒÑƒ бичих ÑрхтÑй - text_rmagick_available: RMagick ÑуулгагдÑан (заавал биш) - text_destroy_time_entries_question: "Таны уÑтгах гÑж байгаа аÑуудлууд дÑÑÑ€ нийт %{hours} цаг зарцуулÑан юм байна, та Ñах Ð²Ñ ?" - text_destroy_time_entries: МÑдÑгдÑÑн цагуудыг уÑтгах - text_assign_time_entries_to_project: МÑдÑгдÑÑн аÑуудлуудыг төÑөлд оноох - text_reassign_time_entries: 'МÑдÑгдÑÑн аÑуудлуудыг ÑÐ½Ñ Ð°Ñуудалд дахин оноо:' - text_user_wrote: "%{value} бичихдÑÑ:" - text_enumeration_destroy_question: "Ð­Ð½Ñ ÑƒÑ‚Ð³Ð°Ð´ %{count} обьект оноогдÑон байна." - text_enumeration_category_reassign_to: 'ТÑдгÑÑрийг ÑÐ½Ñ ÑƒÑ‚Ð³Ð°Ð´ дахин оноо:' - text_email_delivery_not_configured: "ИмÑйлийн тохиргоог хараахан тохируулаагүй байна, тиймÑÑÑ Ð¸Ð¼Ñйл мÑдÑгдÑл Ñвуулах боломжгүй байна.\nSMTP ÑервÑÑ€ÑÑ config/configuration.yml файл дотор тохируулаад төÑлийн менежерÑÑ Ð´Ð°Ñ…Ð¸Ð°Ð´ ÑхлүүлÑÑÑ€Ñй." - text_repository_usernames_mapping: "Репозиторийн логд байгаа бүх Ñ…ÑÑ€ÑглÑгчийн нÑрүүдÑд харгалзÑан ТөÑлийн Менежер ÑиÑтемд бүртгÑлтÑй Ñ…ÑÑ€ÑглÑгчдийг Сонгох юмуу шинÑÑ‡Ð¸Ð»Ð½Ñ Ò¯Ò¯.\nТөÑлийн менежер болон репозиторид байгаа ижилхÑн нÑÑ€ юмуу имÑйлтÑй Ñ…ÑÑ€ÑглÑгчид харилцан харгалзна." - text_diff_truncated: '... Файлын Ñлгаврын Ñ…ÑмжÑÑ Ò¯Ð·Ò¯Ò¯Ð»ÑÑ…Ñд дÑндүү урт байгаа ÑƒÑ‡Ñ€Ð°Ð°Ñ Ñ‚Ó©Ð³ÑÐ³Ó©Ð»Ó©Ó©Ñ Ð½ÑŒ хаÑч үзүүлÑв.' - text_custom_field_possible_values_info: 'One line for each value' - text_wiki_page_destroy_question: "This page has %{descendants} child page(s) and descendant(s). What do you want to do?" - text_wiki_page_nullify_children: "Keep child pages as root pages" - text_wiki_page_destroy_children: "Delete child pages and all their descendants" - text_wiki_page_reassign_children: "Reassign child pages to this parent page" - text_own_membership_delete_confirmation: "You are about to remove some or all of your permissions and may no longer be able to edit this project after that.\nAre you sure you want to continue?" - - default_role_manager: Менежер - default_role_developer: ХөгжүүлÑгч - default_role_reporter: МÑдÑгдÑгч - default_tracker_bug: Ðлдаа - default_tracker_feature: Онцлог - default_tracker_support: ТуÑламж - default_issue_status_new: Ð¨Ð¸Ð½Ñ - default_issue_status_in_progress: Ðхицтай - 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: Үйл ажиллагаанууд (хугацааг Ñ…Ñнах) - enumeration_system_activity: СиÑтемийн үйл ажиллагаа - - permission_manage_subtasks: Manage subtasks - label_profile: Profile - field_parent_issue: Parent task - error_unable_delete_issue_status: Unable to delete issue status - label_subtask_plural: Subtasks - label_project_copy_notifications: Send email notifications during the project copy - error_can_not_delete_custom_field: Unable to delete custom field - error_unable_to_connect: Unable to connect (%{value}) - error_can_not_remove_role: This role is in use and can not be deleted. - error_can_not_delete_tracker: This tracker contains issues and can't be deleted. - field_principal: Principal - label_my_page_block: My page block - notice_failed_to_save_members: "Failed to save member(s): %{errors}." - text_zoom_out: Zoom out - text_zoom_in: Zoom in - notice_unable_delete_time_entry: Unable to delete time log entry. - label_overall_spent_time: Overall spent time - field_time_entries: Log time - project_module_gantt: Gantt - project_module_calendar: Calendar - button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" - text_are_you_sure_with_children: Delete issue and all child issues? - field_text: Text field - label_user_mail_option_only_owner: Only for things I am the owner of - setting_default_notification_option: Default notification option - label_user_mail_option_only_my_events: Only for things I watch or I'm involved in - label_user_mail_option_only_assigned: Only for things I am assigned to - label_user_mail_option_none: No events - field_member_of_group: Assignee's group - field_assigned_to_role: Assignee's role - notice_not_authorized_archived_project: The project you're trying to access has been archived. - label_principal_search: "Search for user or group:" - label_user_search: "Search for user:" - field_visible: Visible - setting_emails_header: Emails header - setting_commit_logtime_activity_id: Activity for logged time - text_time_logged_by_changeset: Applied in changeset %{value}. - setting_commit_logtime_enabled: Enable time logging - notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) - setting_gantt_items_limit: Maximum number of items displayed on the gantt chart - field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text - text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. - label_my_queries: My custom queries - text_journal_changed_no_detail: "%{label} updated" - label_news_comment_added: Comment added to a news - button_expand_all: Expand all - button_collapse_all: Collapse all - label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee - label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author - label_bulk_edit_selected_time_entries: Bulk edit selected time entries - text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? - label_role_anonymous: Anonymous - label_role_non_member: Non member - label_issue_note_added: Note added - label_issue_status_updated: Status updated - label_issue_priority_updated: Priority updated - label_issues_visibility_own: Issues created by or assigned to the user - field_issues_visibility: Issues visibility - label_issues_visibility_all: All issues - permission_set_own_issues_private: Set own issues public or private - field_is_private: Private - permission_set_issues_private: Set issues public or private - label_issues_visibility_public: All non private issues - text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). - field_commit_logs_encoding: Коммит хийх үед харуулах текÑтүүдийн Ñнкодинг - field_scm_path_encoding: Path encoding - text_scm_path_encoding_note: "Default: UTF-8" - field_path_to_repository: Path to repository - field_root_directory: Root directory - field_cvs_module: Module - field_cvsroot: CVSROOT - text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) - text_scm_command: Command - text_scm_command_version: Version - label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. - notice_issue_successful_create: Issue %{id} created. - label_between: between - setting_issue_group_assignment: Allow issue assignment to groups - label_diff: diff - text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) - description_query_sort_criteria_direction: Sort direction - description_project_scope: Search scope - description_filter: Filter - description_user_mail_notification: Mail notification settings - description_date_from: Enter start date - description_message_content: Message content - description_available_columns: Available Columns - description_date_range_interval: Choose range by selecting start and end date - description_issue_category_reassign: Choose issue category - description_search: Searchfield - description_notes: Notes - description_date_range_list: Choose range from list - description_choose_project: Projects - description_date_to: Enter end date - description_query_sort_criteria_attribute: Sort attribute - description_wiki_subpages_reassign: Choose new parent page - description_selected_columns: Selected Columns - label_parent_revision: Parent - label_child_revision: Child - error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. - setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues - button_edit_section: Edit this section - setting_repositories_encodings: Attachments and repositories encodings - description_all_columns: All Columns - button_export: Export - label_export_options: "%{export_format} export options" - error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/57/57ecc0dda1965991f4066469497ab0ab44365ffc.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/57/57ecc0dda1965991f4066469497ab0ab44365ffc.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,355 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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. + +RedmineApp::Application.routes.draw do + root :to => 'welcome#index', :as => 'home' + + match 'login', :to => 'account#login', :as => 'signin', :via => [:get, :post] + match 'logout', :to => 'account#logout', :as => 'signout', :via => [:get, :post] + match 'account/register', :to => 'account#register', :via => [:get, :post], :as => 'register' + match 'account/lost_password', :to => 'account#lost_password', :via => [:get, :post], :as => 'lost_password' + match 'account/activate', :to => 'account#activate', :via => :get + + match '/news/preview', :controller => 'previews', :action => 'news', :as => 'preview_news', :via => [:get, :post, :put] + match '/issues/preview/new/:project_id', :to => 'previews#issue', :as => 'preview_new_issue', :via => [:get, :post, :put] + match '/issues/preview/edit/:id', :to => 'previews#issue', :as => 'preview_edit_issue', :via => [:get, :post, :put] + match '/issues/preview', :to => 'previews#issue', :as => 'preview_issue', :via => [:get, :post, :put] + + match 'projects/:id/wiki', :to => 'wikis#edit', :via => :post + match 'projects/:id/wiki/destroy', :to => 'wikis#destroy', :via => [:get, :post] + + match 'boards/:board_id/topics/new', :to => 'messages#new', :via => [:get, :post], :as => 'new_board_message' + get 'boards/:board_id/topics/:id', :to => 'messages#show', :as => 'board_message' + match 'boards/:board_id/topics/quote/:id', :to => 'messages#quote', :via => [:get, :post] + get 'boards/:board_id/topics/:id/edit', :to => 'messages#edit' + + post 'boards/:board_id/topics/preview', :to => 'messages#preview', :as => 'preview_board_message' + post 'boards/:board_id/topics/:id/replies', :to => 'messages#reply' + post 'boards/:board_id/topics/:id/edit', :to => 'messages#edit' + post 'boards/:board_id/topics/:id/destroy', :to => 'messages#destroy' + + # Misc issue routes. TODO: move into resources + match '/issues/auto_complete', :to => 'auto_completes#issues', :via => :get, :as => 'auto_complete_issues' + match '/issues/context_menu', :to => 'context_menus#issues', :as => 'issues_context_menu', :via => [:get, :post] + match '/issues/changes', :to => 'journals#index', :as => 'issue_changes', :via => :get + match '/issues/:id/quoted', :to => 'journals#new', :id => /\d+/, :via => :post, :as => 'quoted_issue' + + match '/journals/diff/:id', :to => 'journals#diff', :id => /\d+/, :via => :get + match '/journals/edit/:id', :to => 'journals#edit', :id => /\d+/, :via => [:get, :post] + + get '/projects/:project_id/issues/gantt', :to => 'gantts#show', :as => 'project_gantt' + get '/issues/gantt', :to => 'gantts#show' + + get '/projects/:project_id/issues/calendar', :to => 'calendars#show', :as => 'project_calendar' + get '/issues/calendar', :to => 'calendars#show' + + get 'projects/:id/issues/report', :to => 'reports#issue_report', :as => 'project_issues_report' + get 'projects/:id/issues/report/:detail', :to => 'reports#issue_report_details', :as => 'project_issues_report_details' + + match 'my/account', :controller => 'my', :action => 'account', :via => [:get, :post] + match 'my/account/destroy', :controller => 'my', :action => 'destroy', :via => [:get, :post] + match 'my/page', :controller => 'my', :action => 'page', :via => :get + match 'my', :controller => 'my', :action => 'index', :via => :get # Redirects to my/page + match 'my/reset_rss_key', :controller => 'my', :action => 'reset_rss_key', :via => :post + match 'my/reset_api_key', :controller => 'my', :action => 'reset_api_key', :via => :post + match 'my/password', :controller => 'my', :action => 'password', :via => [:get, :post] + match 'my/page_layout', :controller => 'my', :action => 'page_layout', :via => :get + match 'my/add_block', :controller => 'my', :action => 'add_block', :via => :post + match 'my/remove_block', :controller => 'my', :action => 'remove_block', :via => :post + match 'my/order_blocks', :controller => 'my', :action => 'order_blocks', :via => :post + + resources :users + match 'users/:id/memberships/:membership_id', :to => 'users#edit_membership', :via => :put, :as => 'user_membership' + match 'users/:id/memberships/:membership_id', :to => 'users#destroy_membership', :via => :delete + match 'users/:id/memberships', :to => 'users#edit_membership', :via => :post, :as => 'user_memberships' + + post 'watchers/watch', :to => 'watchers#watch', :as => 'watch' + delete 'watchers/watch', :to => 'watchers#unwatch' + get 'watchers/new', :to => 'watchers#new' + post 'watchers', :to => 'watchers#create' + post 'watchers/append', :to => 'watchers#append' + delete 'watchers', :to => 'watchers#destroy' + get 'watchers/autocomplete_for_user', :to => 'watchers#autocomplete_for_user' + # Specific routes for issue watchers API + post 'issues/:object_id/watchers', :to => 'watchers#create', :object_type => 'issue' + delete 'issues/:object_id/watchers/:user_id' => 'watchers#destroy', :object_type => 'issue' + + resources :projects do + member do + get 'settings(/:tab)', :action => 'settings', :as => 'settings' + post 'modules' + post 'archive' + post 'unarchive' + post 'close' + post 'reopen' + match 'copy', :via => [:get, :post] + end + + shallow do + resources :memberships, :controller => 'members', :only => [:index, :show, :new, :create, :update, :destroy] do + collection do + get 'autocomplete' + end + end + end + + resource :enumerations, :controller => 'project_enumerations', :only => [:update, :destroy] + + get 'issues/:copy_from/copy', :to => 'issues#new', :as => 'copy_issue' + resources :issues, :only => [:index, :new, :create] do + resources :time_entries, :controller => 'timelog' do + collection do + get 'report' + end + end + end + # issue form update + match 'issues/update_form', :controller => 'issues', :action => 'update_form', :via => [:put, :post], :as => 'issue_form' + + resources :files, :only => [:index, :new, :create] + + resources :versions, :except => [:index, :show, :edit, :update, :destroy] do + collection do + put 'close_completed' + end + end + get 'versions.:format', :to => 'versions#index' + get 'roadmap', :to => 'versions#index', :format => false + get 'versions', :to => 'versions#index' + + resources :news, :except => [:show, :edit, :update, :destroy] + resources :time_entries, :controller => 'timelog' do + get 'report', :on => :collection + end + resources :queries, :only => [:new, :create] + shallow do + resources :issue_categories + end + resources :documents, :except => [:show, :edit, :update, :destroy] + resources :boards + shallow do + resources :repositories, :except => [:index, :show] do + member do + match 'committers', :via => [:get, :post] + end + end + end + + match 'wiki/index', :controller => 'wiki', :action => 'index', :via => :get + resources :wiki, :except => [:index, :new, :create], :as => 'wiki_page' do + member do + get 'rename' + post 'rename' + get 'history' + get 'diff' + match 'preview', :via => [:post, :put] + post 'protect' + post 'add_attachment' + end + collection do + get 'export' + get 'date_index' + end + end + match 'wiki', :controller => 'wiki', :action => 'show', :via => :get + get 'wiki/:id/:version', :to => 'wiki#show', :constraints => {:version => /\d+/} + delete 'wiki/:id/:version', :to => 'wiki#destroy_version' + get 'wiki/:id/:version/annotate', :to => 'wiki#annotate' + get 'wiki/:id/:version/diff', :to => 'wiki#diff' + end + + resources :issues do + collection do + match 'bulk_edit', :via => [:get, :post] + post 'bulk_update' + end + resources :time_entries, :controller => 'timelog' do + collection do + get 'report' + end + end + shallow do + resources :relations, :controller => 'issue_relations', :only => [:index, :show, :create, :destroy] + end + end + match '/issues', :controller => 'issues', :action => 'destroy', :via => :delete + + resources :queries, :except => [:show] + + resources :news, :only => [:index, :show, :edit, :update, :destroy] + match '/news/:id/comments', :to => 'comments#create', :via => :post + match '/news/:id/comments/:comment_id', :to => 'comments#destroy', :via => :delete + + resources :versions, :only => [:show, :edit, :update, :destroy] do + post 'status_by', :on => :member + end + + resources :documents, :only => [:show, :edit, :update, :destroy] do + post 'add_attachment', :on => :member + end + + match '/time_entries/context_menu', :to => 'context_menus#time_entries', :as => :time_entries_context_menu, :via => [:get, :post] + + resources :time_entries, :controller => 'timelog', :except => :destroy do + collection do + get 'report' + get 'bulk_edit' + post 'bulk_update' + end + end + match '/time_entries/:id', :to => 'timelog#destroy', :via => :delete, :id => /\d+/ + # TODO: delete /time_entries for bulk deletion + match '/time_entries/destroy', :to => 'timelog#destroy', :via => :delete + + get 'projects/:id/activity', :to => 'activities#index' + get 'projects/:id/activity.:format', :to => 'activities#index' + get 'activity', :to => 'activities#index' + + # repositories routes + get 'projects/:id/repository/:repository_id/statistics', :to => 'repositories#stats' + get 'projects/:id/repository/:repository_id/graph', :to => 'repositories#graph' + + get 'projects/:id/repository/:repository_id/changes(/*path(.:ext))', + :to => 'repositories#changes' + + get 'projects/:id/repository/:repository_id/revisions/:rev', :to => 'repositories#revision' + get 'projects/:id/repository/:repository_id/revision', :to => 'repositories#revision' + post 'projects/:id/repository/:repository_id/revisions/:rev/issues', :to => 'repositories#add_related_issue' + delete 'projects/:id/repository/:repository_id/revisions/:rev/issues/:issue_id', :to => 'repositories#remove_related_issue' + get 'projects/:id/repository/:repository_id/revisions', :to => 'repositories#revisions' + get 'projects/:id/repository/:repository_id/revisions/:rev/:action(/*path(.:ext))', + :controller => 'repositories', + :format => false, + :constraints => { + :action => /(browse|show|entry|raw|annotate|diff)/, + :rev => /[a-z0-9\.\-_]+/ + } + + get 'projects/:id/repository/statistics', :to => 'repositories#stats' + get 'projects/:id/repository/graph', :to => 'repositories#graph' + + get 'projects/:id/repository/changes(/*path(.:ext))', + :to => 'repositories#changes' + + get 'projects/:id/repository/revisions', :to => 'repositories#revisions' + get 'projects/:id/repository/revisions/:rev', :to => 'repositories#revision' + get 'projects/:id/repository/revision', :to => 'repositories#revision' + post 'projects/:id/repository/revisions/:rev/issues', :to => 'repositories#add_related_issue' + delete 'projects/:id/repository/revisions/:rev/issues/:issue_id', :to => 'repositories#remove_related_issue' + get 'projects/:id/repository/revisions/:rev/:action(/*path(.:ext))', + :controller => 'repositories', + :format => false, + :constraints => { + :action => /(browse|show|entry|raw|annotate|diff)/, + :rev => /[a-z0-9\.\-_]+/ + } + get 'projects/:id/repository/:repository_id/:action(/*path(.:ext))', + :controller => 'repositories', + :action => /(browse|show|entry|raw|changes|annotate|diff)/ + get 'projects/:id/repository/:action(/*path(.:ext))', + :controller => 'repositories', + :action => /(browse|show|entry|raw|changes|annotate|diff)/ + + get 'projects/:id/repository/:repository_id', :to => 'repositories#show', :path => nil + get 'projects/:id/repository', :to => 'repositories#show', :path => nil + + # additional routes for having the file name at the end of url + get 'attachments/:id/:filename', :to => 'attachments#show', :id => /\d+/, :filename => /.*/, :as => 'named_attachment' + get 'attachments/download/:id/:filename', :to => 'attachments#download', :id => /\d+/, :filename => /.*/, :as => 'download_named_attachment' + get 'attachments/download/:id', :to => 'attachments#download', :id => /\d+/ + get 'attachments/thumbnail/:id(/:size)', :to => 'attachments#thumbnail', :id => /\d+/, :size => /\d+/, :as => 'thumbnail' + resources :attachments, :only => [:show, :destroy] + + resources :groups do + member do + get 'autocomplete_for_user' + end + end + + match 'groups/:id/users', :controller => 'groups', :action => 'add_users', :id => /\d+/, :via => :post, :as => 'group_users' + match 'groups/:id/users/:user_id', :controller => 'groups', :action => 'remove_user', :id => /\d+/, :via => :delete, :as => 'group_user' + match 'groups/destroy_membership/:id', :controller => 'groups', :action => 'destroy_membership', :id => /\d+/, :via => :post + match 'groups/edit_membership/:id', :controller => 'groups', :action => 'edit_membership', :id => /\d+/, :via => :post + + resources :trackers, :except => :show do + collection do + match 'fields', :via => [:get, :post] + end + end + resources :issue_statuses, :except => :show do + collection do + post 'update_issue_done_ratio' + end + end + resources :custom_fields, :except => :show + resources :roles do + collection do + match 'permissions', :via => [:get, :post] + end + end + resources :enumerations, :except => :show + match 'enumerations/:type', :to => 'enumerations#index', :via => :get + + get 'projects/:id/search', :controller => 'search', :action => 'index' + get 'search', :controller => 'search', :action => 'index' + + match 'mail_handler', :controller => 'mail_handler', :action => 'index', :via => :post + + match 'admin', :controller => 'admin', :action => 'index', :via => :get + match 'admin/projects', :controller => 'admin', :action => 'projects', :via => :get + match 'admin/plugins', :controller => 'admin', :action => 'plugins', :via => :get + match 'admin/info', :controller => 'admin', :action => 'info', :via => :get + match 'admin/test_email', :controller => 'admin', :action => 'test_email', :via => :get + match 'admin/default_configuration', :controller => 'admin', :action => 'default_configuration', :via => :post + + resources :auth_sources do + member do + get 'test_connection', :as => 'try_connection' + end + collection do + get 'autocomplete_for_new_user' + end + end + + match 'workflows', :controller => 'workflows', :action => 'index', :via => :get + match 'workflows/edit', :controller => 'workflows', :action => 'edit', :via => [:get, :post] + match 'workflows/permissions', :controller => 'workflows', :action => 'permissions', :via => [:get, :post] + match 'workflows/copy', :controller => 'workflows', :action => 'copy', :via => [:get, :post] + match 'settings', :controller => 'settings', :action => 'index', :via => :get + match 'settings/edit', :controller => 'settings', :action => 'edit', :via => [:get, :post] + match 'settings/plugin/:id', :controller => 'settings', :action => 'plugin', :via => [:get, :post], :as => 'plugin_settings' + + match 'sys/projects', :to => 'sys#projects', :via => :get + match 'sys/projects/:id/repository', :to => 'sys#create_project_repository', :via => :post + match 'sys/fetch_changesets', :to => 'sys#fetch_changesets', :via => :get + + match 'uploads', :to => 'attachments#upload', :via => :post + + get 'robots.txt', :to => 'welcome#robots' + + Dir.glob File.expand_path("plugins/*", Rails.root) do |plugin_dir| + file = File.join(plugin_dir, "config/routes.rb") + if File.exists?(file) + begin + instance_eval File.read(file) + rescue Exception => e + puts "An error occurred while loading the routes definition of #{File.basename(plugin_dir)} plugin (#{file}): #{e.message}." + exit 1 + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/58/5822b09752fa0149f57efed74cf7dcc3c9694bb5.svn-base --- a/.svn/pristine/58/5822b09752fa0149f57efed74cf7dcc3c9694bb5.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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 -r 0a574315af3e -r 4f746d8966dd .svn/pristine/58/5852d7fa42575c6a27e640ff68cec09c3d3e0a43.svn-base --- a/.svn/pristine/58/5852d7fa42575c6a27e640ff68cec09c3d3e0a43.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,157 +0,0 @@ -# = Redmine configuration file -# -# Each environment has it's own configuration options. If you are only -# running in production, only the production block needs to be configured. -# Environment specific configuration options override the default ones. -# -# Note that this file needs to be a valid YAML file. -# DO NOT USE TABS! Use 2 spaces instead of tabs for identation. -# -# == Outgoing email settings (email_delivery setting) -# -# === Common configurations -# -# ==== Sendmail command -# -# production: -# email_delivery: -# delivery_method: :sendmail -# -# ==== Simple SMTP server at localhost -# -# production: -# email_delivery: -# delivery_method: :smtp -# smtp_settings: -# address: "localhost" -# port: 25 -# -# ==== SMTP server at example.com using LOGIN authentication and checking HELO for foo.com -# -# production: -# email_delivery: -# delivery_method: :smtp -# smtp_settings: -# address: "example.com" -# port: 25 -# authentication: :login -# domain: 'foo.com' -# user_name: 'myaccount' -# password: 'password' -# -# ==== SMTP server at example.com using PLAIN authentication -# -# production: -# email_delivery: -# delivery_method: :smtp -# smtp_settings: -# address: "example.com" -# port: 25 -# authentication: :plain -# domain: 'example.com' -# user_name: 'myaccount' -# password: 'password' -# -# ==== SMTP server at using TLS (GMail) -# -# This requires some additional configuration. See the article at: -# http://redmineblog.com/articles/setup-redmine-to-send-email-using-gmail/ -# -# production: -# email_delivery: -# delivery_method: :smtp -# smtp_settings: -# tls: true -# enable_starttls_auto: true -# address: "smtp.gmail.com" -# port: 587 -# domain: "smtp.gmail.com" # 'your.domain.com' for GoogleApps -# authentication: :plain -# user_name: "your_email@gmail.com" -# password: "your_password" -# -# -# === More configuration options -# -# See the "Configuration options" at the following website for a list of the -# full options allowed: -# -# http://wiki.rubyonrails.org/rails/pages/HowToSendEmailsWithActionMailer - - -# default configuration options for all environments -default: - # Outgoing emails configuration (see examples above) - email_delivery: - delivery_method: :smtp - smtp_settings: - address: smtp.example.net - port: 25 - domain: example.net - authentication: :login - user_name: "redmine@example.net" - password: "redmine" - - # Absolute path to the directory where attachments are stored. - # The default is the 'files' directory in your Redmine instance. - # Your Redmine instance needs to have write permission on this - # directory. - # Examples: - # attachments_storage_path: /var/redmine/files - # attachments_storage_path: D:/redmine/files - attachments_storage_path: - - # Configuration of the autologin cookie. - # autologin_cookie_name: the name of the cookie (default: autologin) - # autologin_cookie_path: the cookie path (default: /) - # autologin_cookie_secure: true sets the cookie secure flag (default: false) - autologin_cookie_name: - autologin_cookie_path: - autologin_cookie_secure: - - # Configuration of SCM executable command. - # - # Absolute path (e.g. /usr/local/bin/hg) or command name (e.g. hg.exe, bzr.exe) - # On Windows + CRuby, *.cmd, *.bat (e.g. hg.cmd, bzr.bat) does not work. - # - # On Windows + JRuby 1.6.2, path which contains spaces does not work. - # For example, "C:\Program Files\TortoiseHg\hg.exe". - # If you want to this feature, you need to install to the path which does not contains spaces. - # For example, "C:\TortoiseHg\hg.exe". - # - # Examples: - # scm_subversion_command: svn # (default: svn) - # scm_mercurial_command: C:\Program Files\TortoiseHg\hg.exe # (default: hg) - # scm_git_command: /usr/local/bin/git # (default: git) - # scm_cvs_command: cvs # (default: cvs) - # scm_bazaar_command: bzr.exe # (default: bzr) - # scm_darcs_command: darcs-1.0.9-i386-linux # (default: darcs) - # - scm_subversion_command: - scm_mercurial_command: - scm_git_command: - scm_cvs_command: - scm_bazaar_command: - scm_darcs_command: - - # Key used to encrypt sensitive data in the database (SCM and LDAP passwords). - # If you don't want to enable data encryption, just leave it blank. - # WARNING: losing/changing this key will make encrypted data unreadable. - # - # If you want to encrypt existing passwords in your database: - # * set the cipher key here in your configuration file - # * encrypt data using 'rake db:encrypt RAILS_ENV=production' - # - # If you have encrypted data and want to change this key, you have to: - # * decrypt data using 'rake db:decrypt RAILS_ENV=production' first - # * change the cipher key here in your configuration file - # * encrypt data using 'rake db:encrypt RAILS_ENV=production' - database_cipher_key: - -# specific configuration options for production environment -# that overrides the default ones -production: - -# specific configuration options for development environment -# that overrides the default ones -development: diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/58/5878eef8c7c288582eab7e2a8dc69cc546b48c08.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/58/5878eef8c7c288582eab7e2a8dc69cc546b48c08.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,27 @@ +

<%= l(:permission_add_issue_watchers) %>

+ +<%= form_tag({:controller => 'watchers', + :action => (watched ? 'create' : 'append'), + :object_type => (watched && watched.class.name.underscore), + :object_id => watched, + :project_id => @project}, + :remote => true, + :method => :post, + :id => 'new-watcher-form') do %> + +

<%= label_tag 'user_search', l(:label_user_search) %><%= text_field_tag 'user_search', nil %>

+ <%= javascript_tag "observeSearchfield('user_search', 'users_for_watcher', '#{ escape_javascript url_for(:controller => 'watchers', + :action => 'autocomplete_for_user', + :object_type => (watched && watched.class.name.underscore), + :object_id => watched, + :project_id => @project) }')" %> + +
+ <%= principals_check_box_tags 'watcher[user_ids][]', (watched ? watched.addable_watcher_users : User.active.all(:limit => 100)) %> +
+ +

+ <%= submit_tag l(:button_add), :name => nil, :onclick => "hideModal(this);" %> + <%= submit_tag l(:button_cancel), :name => nil, :onclick => "hideModal(this);", :type => 'button' %> +

+<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/58/587f513ddb7f708d124448f5d5cd571d4d406ca2.svn-base --- a/.svn/pristine/58/587f513ddb7f708d124448f5d5cd571d4d406ca2.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,87 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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 IssueMovesController < ApplicationController - menu_item :issues - - default_search_scope :issues - before_filter :find_issues, :check_project_uniqueness - before_filter :authorize - - def new - prepare_for_issue_move - render :layout => false if request.xhr? - end - - def create - prepare_for_issue_move - - if request.post? - new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id]) - unsaved_issue_ids = [] - moved_issues = [] - @issues.each do |issue| - issue.reload - issue.init_journal(User.current) - issue.current_journal.notes = @notes if @notes.present? - call_hook(:controller_issues_move_before_save, { :params => params, :issue => issue, :target_project => @target_project, :copy => !!@copy }) - if r = issue.move_to_project(@target_project, new_tracker, {:copy => @copy, :attributes => extract_changed_attributes_for_move(params)}) - moved_issues << r - else - unsaved_issue_ids << issue.id - end - end - set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids) - - if params[:follow] - if @issues.size == 1 && moved_issues.size == 1 - redirect_to :controller => 'issues', :action => 'show', :id => moved_issues.first - else - redirect_to :controller => 'issues', :action => 'index', :project_id => (@target_project || @project) - end - else - redirect_to :controller => 'issues', :action => 'index', :project_id => @project - end - return - end - end - - private - - def prepare_for_issue_move - @issues.sort! - @copy = params[:copy_options] && params[:copy_options][:copy] - @allowed_projects = Issue.allowed_target_projects_on_move - @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id] - @target_project ||= @project - @trackers = @target_project.trackers - @available_statuses = Workflow.available_statuses(@project) - @notes = params[:notes] - @notes ||= '' - end - - def extract_changed_attributes_for_move(params) - changed_attributes = {} - [:assigned_to_id, :status_id, :start_date, :due_date, :priority_id].each do |valid_attribute| - unless params[valid_attribute].blank? - changed_attributes[valid_attribute] = (params[valid_attribute] == 'none' ? nil : params[valid_attribute]) - end - end - changed_attributes - end - -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/58/58ad4095c1a6118f6d53fd92a945cd194604e422.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/58/58ad4095c1a6118f6d53fd92a945cd194604e422.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,81 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class PreviewsControllerTest < ActionController::TestCase + fixtures :projects, :trackers, :issue_statuses, :issues, + :enumerations, :users, :issue_categories, + :projects_trackers, + :roles, + :member_roles, + :members, + :enabled_modules, + :journals, :journal_details, + :news + + def test_preview_new_issue + @request.session[:user_id] = 2 + post :issue, :project_id => '1', :issue => {:description => 'Foo'} + assert_response :success + assert_template 'preview' + assert_not_nil assigns(:description) + end + + def test_preview_issue_notes + @request.session[:user_id] = 2 + post :issue, :project_id => '1', :id => 1, + :issue => {:description => Issue.find(1).description, :notes => 'Foo'} + assert_response :success + assert_template 'preview' + assert_not_nil assigns(:notes) + end + + def test_preview_journal_notes_for_update + @request.session[:user_id] = 2 + post :issue, :project_id => '1', :id => 1, :notes => 'Foo' + assert_response :success + assert_template 'preview' + assert_not_nil assigns(:notes) + assert_tag :p, :content => 'Foo' + end + + def test_preview_new_news + get :news, :project_id => 1, + :news => {:title => '', + :description => 'News description', + :summary => ''} + assert_response :success + assert_template 'common/_preview' + assert_tag :tag => 'fieldset', :attributes => { :class => 'preview' }, + :content => /News description/ + end + + def test_existing_new_news + get :news, :project_id => 1, :id => 2, + :news => {:title => '', + :description => 'News description', + :summary => ''} + assert_response :success + assert_template 'common/_preview' + assert_equal News.find(2), assigns(:previewed) + assert_not_nil assigns(:attachments) + + assert_tag :tag => 'fieldset', :attributes => { :class => 'preview' }, + :content => /News description/ + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/58/58ae812bb411d7616ba525dd2ae2b02bda08fbd2.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/58/58ae812bb411d7616ba525dd2ae2b02bda08fbd2.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,59 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class UserPreference < ActiveRecord::Base + belongs_to :user + serialize :others + + attr_protected :others, :user_id + + before_save :set_others_hash + + def initialize(attributes=nil, *args) + super + self.others ||= {} + end + + def set_others_hash + self.others ||= {} + end + + def [](attr_name) + if attribute_present? attr_name + super + else + others ? others[attr_name] : nil + end + end + + def []=(attr_name, value) + if attribute_present? attr_name + super + else + h = (read_attribute(:others) || {}).dup + h.update(attr_name => value) + write_attribute(:others, h) + value + end + end + + def comments_sorting; self[:comments_sorting] end + def comments_sorting=(order); self[:comments_sorting]=order end + + def warn_on_leaving_unsaved; self[:warn_on_leaving_unsaved] || '1'; end + def warn_on_leaving_unsaved=(value); self[:warn_on_leaving_unsaved]=value; end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/58/58b49876d90ca4f57b376fabd8862c7bd7f14741.svn-base --- a/.svn/pristine/58/58b49876d90ca4f57b376fabd8862c7bd7f14741.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,172 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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 GroupsController < ApplicationController - layout 'admin' - - before_filter :require_admin - - helper :custom_fields - - # GET /groups - # GET /groups.xml - def index - @groups = Group.find(:all, :order => 'lastname') - - respond_to do |format| - format.html # index.html.erb - format.xml { render :xml => @groups } - end - end - - # GET /groups/1 - # GET /groups/1.xml - def show - @group = Group.find(params[:id]) - - respond_to do |format| - format.html # show.html.erb - format.xml { render :xml => @group } - end - end - - # GET /groups/new - # GET /groups/new.xml - def new - @group = Group.new - - respond_to do |format| - format.html # new.html.erb - format.xml { render :xml => @group } - end - end - - # GET /groups/1/edit - def edit - @group = Group.find(params[:id], :include => :projects) - end - - # POST /groups - # POST /groups.xml - def create - @group = Group.new(params[:group]) - - respond_to do |format| - if @group.save - format.html { - flash[:notice] = l(:notice_successful_create) - redirect_to(params[:continue] ? new_group_path : groups_path) - } - format.xml { render :xml => @group, :status => :created, :location => @group } - else - format.html { render :action => "new" } - format.xml { render :xml => @group.errors, :status => :unprocessable_entity } - end - end - end - - # PUT /groups/1 - # PUT /groups/1.xml - def update - @group = Group.find(params[:id]) - - respond_to do |format| - if @group.update_attributes(params[:group]) - flash[:notice] = l(:notice_successful_update) - format.html { redirect_to(groups_path) } - format.xml { head :ok } - else - format.html { render :action => "edit" } - format.xml { render :xml => @group.errors, :status => :unprocessable_entity } - end - end - end - - # DELETE /groups/1 - # DELETE /groups/1.xml - def destroy - @group = Group.find(params[:id]) - @group.destroy - - respond_to do |format| - format.html { redirect_to(groups_url) } - format.xml { head :ok } - end - end - - def add_users - @group = Group.find(params[:id]) - users = User.find_all_by_id(params[:user_ids]) - @group.users << users if request.post? - respond_to do |format| - format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'users' } - format.js { - render(:update) {|page| - page.replace_html "tab-content-users", :partial => 'groups/users' - users.each {|user| page.visual_effect(:highlight, "user-#{user.id}") } - } - } - end - end - - def remove_user - @group = Group.find(params[:id]) - @group.users.delete(User.find(params[:user_id])) if request.delete? - respond_to do |format| - format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'users' } - format.js { render(:update) {|page| page.replace_html "tab-content-users", :partial => 'groups/users'} } - end - end - - def autocomplete_for_user - @group = Group.find(params[:id]) - @users = User.active.not_in_group(@group).like(params[:q]).all(:limit => 100) - render :layout => false - end - - def edit_membership - @group = Group.find(params[:id]) - @membership = Member.edit_membership(params[:membership_id], params[:membership], @group) - @membership.save if request.post? - respond_to do |format| - if @membership.valid? - format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'memberships' } - format.js { - render(:update) {|page| - page.replace_html "tab-content-memberships", :partial => 'groups/memberships' - page.visual_effect(:highlight, "member-#{@membership.id}") - } - } - else - format.js { - render(:update) {|page| - page.alert(l(:notice_failed_to_save_members, :errors => @membership.errors.full_messages.join(', '))) - } - } - end - end - end - - def destroy_membership - @group = Group.find(params[:id]) - Member.find(params[:membership_id]).destroy if request.post? - respond_to do |format| - format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'memberships' } - format.js { render(:update) {|page| page.replace_html "tab-content-memberships", :partial => 'groups/memberships'} } - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/58/58b9e1838c5a7e00090791cde1a148c4ef4ea8e1.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/58/58b9e1838c5a7e00090791cde1a148c4ef4ea8e1.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,82 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +module WatchersHelper + + def watcher_tag(object, user, options={}) + ActiveSupport::Deprecation.warn "#watcher_tag is deprecated and will be removed in Redmine 3.0. Use #watcher_link instead." + watcher_link(object, user) + end + + def watcher_link(objects, user) + return '' unless user && user.logged? + objects = Array.wrap(objects) + + watched = objects.any? {|object| object.watched_by?(user)} + css = [watcher_css(objects), watched ? 'icon icon-fav' : 'icon icon-fav-off'].join(' ') + text = watched ? l(:button_unwatch) : l(:button_watch) + url = watch_path( + :object_type => objects.first.class.to_s.underscore, + :object_id => (objects.size == 1 ? objects.first.id : objects.map(&:id).sort) + ) + method = watched ? 'delete' : 'post' + + link_to text, url, :remote => true, :method => method, :class => css + end + + # Returns the css class used to identify watch links for a given +object+ + def watcher_css(objects) + objects = Array.wrap(objects) + id = (objects.size == 1 ? objects.first.id : 'bulk') + "#{objects.first.class.to_s.underscore}-#{id}-watcher" + end + + # Returns a comma separated list of users watching the given object + def watchers_list(object) + remove_allowed = User.current.allowed_to?("delete_#{object.class.name.underscore}_watchers".to_sym, object.project) + content = ''.html_safe + lis = object.watcher_users.collect do |user| + s = ''.html_safe + s << avatar(user, :size => "16").to_s + s << link_to_user(user, :class => 'user') + if remove_allowed + url = {:controller => 'watchers', + :action => 'destroy', + :object_type => object.class.to_s.underscore, + :object_id => object.id, + :user_id => user} + s << ' ' + s << link_to(image_tag('delete.png'), url, + :remote => true, :method => 'delete', :class => "delete") + end + content << content_tag('li', s, :class => "user-#{user.id}") + end + content.present? ? content_tag('ul', content, :class => 'watchers') : content + end + + def watchers_checkboxes(object, users, checked=nil) + users.map do |user| + c = checked.nil? ? object.watched_by?(user) : checked + tag = check_box_tag 'issue[watcher_user_ids][]', user.id, c, :id => nil + content_tag 'label', "#{tag} #{h(user)}".html_safe, + :id => "issue_watcher_user_ids_#{user.id}", + :class => "floating" + end.join.html_safe + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/58/58db9baf0f6d2aa4b263068238c559860f800493.svn-base --- a/.svn/pristine/58/58db9baf0f6d2aa4b263068238c559860f800493.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,59 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../../../test_helper', __FILE__) - -class Redmine::ThemesTest < ActiveSupport::TestCase - - def test_themes - themes = Redmine::Themes.themes - assert_kind_of Array, themes - assert_kind_of Redmine::Themes::Theme, themes.first - end - - def test_rescan - Redmine::Themes.themes.pop - - assert_difference 'Redmine::Themes.themes.size' do - Redmine::Themes.rescan - end - end - - def test_theme_loaded - theme = Redmine::Themes.themes.last - - assert_equal theme, Redmine::Themes.theme(theme.id) - end - - def test_theme_loaded_without_rescan - theme = Redmine::Themes.themes.last - - assert_equal theme, Redmine::Themes.theme(theme.id, :rescan => false) - end - - def test_theme_not_loaded - theme = Redmine::Themes.themes.pop - - assert_equal theme, Redmine::Themes.theme(theme.id) - end - - def test_theme_not_loaded_without_rescan - theme = Redmine::Themes.themes.pop - - assert_nil Redmine::Themes.theme(theme.id, :rescan => false) - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/59/590eaa24ab9f834ac16f2b590030d2f8381b4376.svn-base --- a/.svn/pristine/59/590eaa24ab9f834ac16f2b590030d2f8381b4376.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,111 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) - -class RoleTest < ActiveSupport::TestCase - fixtures :roles, :workflows - - def test_copy_workflows - source = Role.find(1) - assert_equal 90, source.workflows.size - - target = Role.new(:name => 'Target') - assert target.save - target.workflows.copy(source) - target.reload - assert_equal 90, target.workflows.size - end - - def test_add_permission - role = Role.find(1) - size = role.permissions.size - role.add_permission!("apermission", "anotherpermission") - role.reload - assert role.permissions.include?(:anotherpermission) - assert_equal size + 2, role.permissions.size - end - - def test_remove_permission - role = Role.find(1) - size = role.permissions.size - perm = role.permissions[0..1] - role.remove_permission!(*perm) - role.reload - assert ! role.permissions.include?(perm[0]) - assert_equal size - 2, role.permissions.size - end - - def test_name - I18n.locale = 'fr' - assert_equal 'Manager', Role.find(1).name - assert_equal 'Anonyme', Role.anonymous.name - assert_equal 'Non membre', Role.non_member.name - end - - context "#anonymous" do - should "return the anonymous role" do - role = Role.anonymous - assert role.builtin? - assert_equal Role::BUILTIN_ANONYMOUS, role.builtin - end - - context "with a missing anonymous role" do - setup do - Role.delete_all("builtin = #{Role::BUILTIN_ANONYMOUS}") - end - - should "create a new anonymous role" do - assert_difference('Role.count') do - Role.anonymous - end - end - - should "return the anonymous role" do - role = Role.anonymous - assert role.builtin? - assert_equal Role::BUILTIN_ANONYMOUS, role.builtin - end - end - end - - context "#non_member" do - should "return the non-member role" do - role = Role.non_member - assert role.builtin? - assert_equal Role::BUILTIN_NON_MEMBER, role.builtin - end - - context "with a missing non-member role" do - setup do - Role.delete_all("builtin = #{Role::BUILTIN_NON_MEMBER}") - end - - should "create a new non-member role" do - assert_difference('Role.count') do - Role.non_member - end - end - - should "return the non-member role" do - role = Role.non_member - assert role.builtin? - assert_equal Role::BUILTIN_NON_MEMBER, role.builtin - end - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/59/594645e33ca990837dd2f730162a5f369b707902.svn-base --- a/.svn/pristine/59/594645e33ca990837dd2f730162a5f369b707902.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,449 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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 'tree' # gem install rubytree - -# Monkey patch the TreeNode to add on a few more methods :nodoc: -module TreeNodePatch - def self.included(base) - base.class_eval do - attr_reader :last_items_count - - alias :old_initilize :initialize - def initialize(name, content = nil) - old_initilize(name, content) - @childrenHash ||= {} - @last_items_count = 0 - extend(InstanceMethods) - end - end - end - - module InstanceMethods - # Adds the specified child node to the receiver node. The child node's - # parent is set to be the receiver. The child is added as the first child in - # the current list of children for the receiver node. - def prepend(child) - raise "Child already added" if @childrenHash.has_key?(child.name) - - @childrenHash[child.name] = child - @children = [child] + @children - child.parent = self - return child - - end - - # Adds the specified child node to the receiver node. The child node's - # parent is set to be the receiver. The child is added at the position - # into the current list of children for the receiver node. - def add_at(child, position) - raise "Child already added" if @childrenHash.has_key?(child.name) - - @childrenHash[child.name] = child - @children = @children.insert(position, child) - child.parent = self - return child - - end - - def add_last(child) - raise "Child already added" if @childrenHash.has_key?(child.name) - - @childrenHash[child.name] = child - @children << child - @last_items_count += 1 - child.parent = self - return child - - end - - # Adds the specified child node to the receiver node. The child node's - # parent is set to be the receiver. The child is added as the last child in - # the current list of children for the receiver node. - def add(child) - raise "Child already added" if @childrenHash.has_key?(child.name) - - @childrenHash[child.name] = child - position = @children.size - @last_items_count - @children.insert(position, child) - child.parent = self - return child - - end - - # Wrapp remove! making sure to decrement the last_items counter if - # the removed child was a last item - def remove!(child) - @last_items_count -= +1 if child && child.last - super - end - - - # Will return the position (zero-based) of the current child in - # it's parent - def position - self.parent.children.index(self) - end - end -end -Tree::TreeNode.send(:include, TreeNodePatch) - -module Redmine - module MenuManager - class MenuError < StandardError #:nodoc: - end - - module MenuController - def self.included(base) - base.extend(ClassMethods) - end - - module ClassMethods - @@menu_items = Hash.new {|hash, key| hash[key] = {:default => key, :actions => {}}} - mattr_accessor :menu_items - - # Set the menu item name for a controller or specific actions - # Examples: - # * menu_item :tickets # => sets the menu name to :tickets for the whole controller - # * menu_item :tickets, :only => :list # => sets the menu name to :tickets for the 'list' action only - # * menu_item :tickets, :only => [:list, :show] # => sets the menu name to :tickets for 2 actions only - # - # The default menu item name for a controller is controller_name by default - # Eg. the default menu item name for ProjectsController is :projects - def menu_item(id, options = {}) - if actions = options[:only] - actions = [] << actions unless actions.is_a?(Array) - actions.each {|a| menu_items[controller_name.to_sym][:actions][a.to_sym] = id} - else - menu_items[controller_name.to_sym][:default] = id - end - end - end - - def menu_items - self.class.menu_items - end - - # Returns the menu item name according to the current action - def current_menu_item - @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 - - module MenuHelper - # Returns the current menu item name - def current_menu_item - controller.current_menu_item - end - - # Renders the application main menu - def render_main_menu(project) - render_menu((project && !project.new_record?) ? :project_menu : :application_menu, project) - end - - def display_main_menu?(project) - menu_name = project && !project.new_record? ? :project_menu : :application_menu - Redmine::MenuManager.items(menu_name).size > 1 # 1 element is the root - end - - def render_menu(menu, project=nil) - links = [] - menu_items_for(menu, project) do |node| - links << render_menu_node(node, project) - end - links.empty? ? nil : content_tag('ul', links.join("\n").html_safe) - end - - def render_menu_node(node, project=nil) - if node.hasChildren? || !node.child_menus.nil? - return render_menu_node_with_children(node, project) - else - caption, url, selected = extract_node_details(node, project) - return content_tag('li', - render_single_menu_node(node, caption, url, selected)) - end - end - - def render_menu_node_with_children(node, project=nil) - caption, url, selected = extract_node_details(node, project) - - html = [].tap do |html| - html << '
  • ' - # Parent - html << render_single_menu_node(node, caption, url, selected) - - # Standard children - standard_children_list = "".tap do |child_html| - node.children.each do |child| - child_html << render_menu_node(child, project) - end - end - - html << content_tag(:ul, standard_children_list, :class => 'menu-children') unless standard_children_list.empty? - - # Unattached children - unattached_children_list = render_unattached_children_menu(node, project) - html << content_tag(:ul, unattached_children_list, :class => 'menu-children unattached') unless unattached_children_list.blank? - - html << '
  • ' - end - return html.join("\n") - end - - # Returns a list of unattached children menu items - def render_unattached_children_menu(node, project) - return nil unless node.child_menus - - "".tap do |child_html| - unattached_children = node.child_menus.call(project) - # Tree nodes support #each so we need to do object detection - if unattached_children.is_a? Array - unattached_children.each do |child| - child_html << content_tag(:li, render_unattached_menu_item(child, project)) - end - else - raise MenuError, ":child_menus must be an array of MenuItems" - end - end - end - - def render_single_menu_node(item, caption, url, selected) - link_to(h(caption), url, item.html_options(:selected => selected)) - end - - def render_unattached_menu_item(menu_item, project) - raise MenuError, ":child_menus must be an array of MenuItems" unless menu_item.is_a? MenuItem - - if User.current.allowed_to?(menu_item.url, project) - link_to(h(menu_item.caption), - menu_item.url, - menu_item.html_options) - end - end - - def menu_items_for(menu, project=nil) - items = [] - Redmine::MenuManager.items(menu).root.children.each do |node| - if allowed_node?(node, User.current, project) - if block_given? - yield node - else - items << node # TODO: not used? - end - end - end - return block_given? ? nil : items - end - - def extract_node_details(node, project=nil) - item = node - url = case item.url - when Hash - project.nil? ? item.url : {item.param => project}.merge(item.url) - when Symbol - send(item.url) - else - item.url - end - caption = item.caption(project) - return [caption, url, (current_menu_item == item.name)] - end - - # Checks if a user is allowed to access the menu item by: - # - # * Checking the conditions of the item - # * Checking the url target (project only) - def allowed_node?(node, user, project) - if node.condition && !node.condition.call(project) - # Condition that doesn't pass - return false - end - - if project - return user && user.allowed_to?(node.url, project) - else - # outside a project, all menu items allowed - return true - end - end - end - - class << self - def map(menu_name) - @items ||= {} - mapper = Mapper.new(menu_name.to_sym, @items) - if block_given? - yield mapper - else - mapper - end - end - - def items(menu_name) - @items[menu_name.to_sym] || Tree::TreeNode.new(:root, {}) - end - end - - class Mapper - def initialize(menu, items) - items[menu] ||= Tree::TreeNode.new(:root, {}) - @menu = menu - @menu_items = items[menu] - end - - @@last_items_count = Hash.new {|h,k| h[k] = 0} - - # Adds an item at the end of the menu. Available options: - # * param: the parameter name that is used for the project id (default is :id) - # * if: a Proc that is called before rendering the item, the item is displayed only if it returns true - # * caption that can be: - # * a localized string Symbol - # * a String - # * a Proc that can take the project as argument - # * before, after: specify where the menu item should be inserted (eg. :after => :activity) - # * parent: menu item will be added as a child of another named menu (eg. :parent => :issues) - # * children: a Proc that is called before rendering the item. The Proc should return an array of MenuItems, which will be added as children to this item. - # eg. :children => Proc.new {|project| [Redmine::MenuManager::MenuItem.new(...)] } - # * last: menu item will stay at the end (eg. :last => true) - # * html_options: a hash of html options that are passed to link_to - def push(name, url, options={}) - options = options.dup - - if options[:parent] - subtree = self.find(options[:parent]) - if subtree - target_root = subtree - else - target_root = @menu_items.root - end - - else - target_root = @menu_items.root - end - - # menu item position - if first = options.delete(:first) - target_root.prepend(MenuItem.new(name, url, options)) - elsif before = options.delete(:before) - - if exists?(before) - target_root.add_at(MenuItem.new(name, url, options), position_of(before)) - else - target_root.add(MenuItem.new(name, url, options)) - end - - elsif after = options.delete(:after) - - if exists?(after) - target_root.add_at(MenuItem.new(name, url, options), position_of(after) + 1) - else - target_root.add(MenuItem.new(name, url, options)) - end - - elsif options[:last] # don't delete, needs to be stored - target_root.add_last(MenuItem.new(name, url, options)) - else - target_root.add(MenuItem.new(name, url, options)) - end - end - - # Removes a menu item - def delete(name) - if found = self.find(name) - @menu_items.remove!(found) - end - end - - # Checks if a menu item exists - def exists?(name) - @menu_items.any? {|node| node.name == name} - end - - def find(name) - @menu_items.find {|node| node.name == name} - end - - def position_of(name) - @menu_items.each do |node| - if node.name == name - return node.position - end - end - end - end - - class MenuItem < Tree::TreeNode - include Redmine::I18n - attr_reader :name, :url, :param, :condition, :parent, :child_menus, :last - - def initialize(name, url, options) - raise ArgumentError, "Invalid option :if for menu item '#{name}'" if options[:if] && !options[:if].respond_to?(:call) - raise ArgumentError, "Invalid option :html for menu item '#{name}'" if options[:html] && !options[:html].is_a?(Hash) - raise ArgumentError, "Cannot set the :parent to be the same as this item" if options[:parent] == name.to_sym - raise ArgumentError, "Invalid option :children for menu item '#{name}'" if options[:children] && !options[:children].respond_to?(:call) - @name = name - @url = url - @condition = options[:if] - @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(' ') - @parent = options[:parent] - @child_menus = options[:children] - @last = options[:last] || false - super @name.to_sym - end - - def caption(project=nil) - if @caption.is_a?(Proc) - c = @caption.call(project).to_s - c = @name.to_s.humanize if c.blank? - c - else - if @caption.nil? - l_or_humanize(name, :prefix => 'label_') - else - @caption.is_a?(Symbol) ? l(@caption) : @caption - end - 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 -r 0a574315af3e -r 4f746d8966dd .svn/pristine/59/595569d84d22df6ad4296b4d377f37df66d05fc6.svn-base --- a/.svn/pristine/59/595569d84d22df6ad4296b4d377f37df66d05fc6.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,298 +0,0 @@ -# -*- coding: utf-8 -*- -require File.expand_path('../../test_helper', __FILE__) - -class TimeEntryReportsControllerTest < ActionController::TestCase - fixtures :projects, :enabled_modules, :roles, :members, :member_roles, - :issues, :time_entries, :users, :trackers, :enumerations, - :issue_statuses, :custom_fields, :custom_values - - include Redmine::I18n - - def setup - Setting.default_language = "en" - end - - def test_report_at_project_level - get :report, :project_id => 'ecookbook' - assert_response :success - assert_template 'report' - assert_tag :form, - :attributes => {:action => "/projects/ecookbook/time_entries/report", :id => 'query_form'} - end - - def test_report_all_projects - get :report - assert_response :success - assert_template 'report' - assert_tag :form, - :attributes => {:action => "/time_entries/report", :id => 'query_form'} - end - - def test_report_all_projects_denied - r = Role.anonymous - r.permissions.delete(:view_time_entries) - r.permissions_will_change! - r.save - get :report - assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Ftime_entries%2Freport' - end - - def test_report_all_projects_one_criteria - get :report, :columns => 'week', :from => "2007-04-01", :to => "2007-04-30", :criterias => ['project'] - assert_response :success - assert_template 'report' - assert_not_nil assigns(:total_hours) - assert_equal "8.65", "%.2f" % assigns(:total_hours) - end - - def test_report_all_time - get :report, :project_id => 1, :criterias => ['project', 'issue'] - assert_response :success - assert_template 'report' - assert_not_nil assigns(:total_hours) - assert_equal "162.90", "%.2f" % assigns(:total_hours) - end - - def test_report_all_time_by_day - get :report, :project_id => 1, :criterias => ['project', 'issue'], :columns => 'day' - assert_response :success - assert_template 'report' - assert_not_nil assigns(:total_hours) - assert_equal "162.90", "%.2f" % assigns(:total_hours) - assert_tag :tag => 'th', :content => '2007-03-12' - end - - def test_report_one_criteria - get :report, :project_id => 1, :columns => 'week', :from => "2007-04-01", :to => "2007-04-30", :criterias => ['project'] - assert_response :success - assert_template 'report' - assert_not_nil assigns(:total_hours) - assert_equal "8.65", "%.2f" % assigns(:total_hours) - end - - def test_report_two_criterias - get :report, :project_id => 1, :columns => 'month', :from => "2007-01-01", :to => "2007-12-31", :criterias => ["member", "activity"] - assert_response :success - assert_template 'report' - assert_not_nil assigns(:total_hours) - assert_equal "162.90", "%.2f" % assigns(:total_hours) - end - - def test_report_one_day - get :report, :project_id => 1, :columns => 'day', :from => "2007-03-23", :to => "2007-03-23", :criterias => ["member", "activity"] - assert_response :success - assert_template 'report' - assert_not_nil assigns(:total_hours) - assert_equal "4.25", "%.2f" % assigns(:total_hours) - end - - def test_report_at_issue_level - get :report, :project_id => 1, :issue_id => 1, :columns => 'month', :from => "2007-01-01", :to => "2007-12-31", :criterias => ["member", "activity"] - assert_response :success - assert_template 'report' - assert_not_nil assigns(:total_hours) - assert_equal "154.25", "%.2f" % assigns(:total_hours) - assert_tag :form, - :attributes => {:action => "/projects/ecookbook/issues/1/time_entries/report", :id => 'query_form'} - end - - def test_report_custom_field_criteria - get :report, :project_id => 1, :criterias => ['project', 'cf_1', 'cf_7'] - assert_response :success - assert_template 'report' - assert_not_nil assigns(:total_hours) - assert_not_nil assigns(:criterias) - assert_equal 3, assigns(:criterias).size - assert_equal "162.90", "%.2f" % assigns(:total_hours) - # Custom field column - assert_tag :tag => 'th', :content => 'Database' - # Custom field row - assert_tag :tag => 'td', :content => 'MySQL', - :sibling => { :tag => 'td', :attributes => { :class => 'hours' }, - :child => { :tag => 'span', :attributes => { :class => 'hours hours-int' }, - :content => '1' }} - # Second custom field column - assert_tag :tag => 'th', :content => 'Billable' - end - - def test_report_one_criteria_no_result - get :report, :project_id => 1, :columns => 'week', :from => "1998-04-01", :to => "1998-04-30", :criterias => ['project'] - assert_response :success - assert_template 'report' - assert_not_nil assigns(:total_hours) - assert_equal "0.00", "%.2f" % assigns(:total_hours) - end - - def test_report_all_projects_csv_export - get :report, :columns => 'month', :from => "2007-01-01", :to => "2007-06-30", - :criterias => ["project", "member", "activity"], :format => "csv" - assert_response :success - assert_equal 'text/csv', @response.content_type - lines = @response.body.chomp.split("\n") - # Headers - assert_equal 'Project,Member,Activity,2007-1,2007-2,2007-3,2007-4,2007-5,2007-6,Total', - lines.first - # Total row - assert_equal 'Total,"","","","",154.25,8.65,"","",162.90', lines.last - end - - def test_report_csv_export - get :report, :project_id => 1, :columns => 'month', - :from => "2007-01-01", :to => "2007-06-30", - :criterias => ["project", "member", "activity"], :format => "csv" - assert_response :success - assert_equal 'text/csv', @response.content_type - lines = @response.body.chomp.split("\n") - # Headers - assert_equal 'Project,Member,Activity,2007-1,2007-2,2007-3,2007-4,2007-5,2007-6,Total', - lines.first - # Total row - assert_equal 'Total,"","","","",154.25,8.65,"","",162.90', lines.last - end - - def test_csv_big_5 - Setting.default_language = "zh-TW" - str_utf8 = "\xe4\xb8\x80\xe6\x9c\x88" - str_big5 = "\xa4@\xa4\xeb" - if str_utf8.respond_to?(:force_encoding) - str_utf8.force_encoding('UTF-8') - str_big5.force_encoding('Big5') - end - user = User.find_by_id(3) - user.firstname = str_utf8 - user.lastname = "test-lastname" - assert user.save - comments = "test_csv_big_5" - te1 = TimeEntry.create(:spent_on => '2011-11-11', - :hours => 7.3, - :project => Project.find(1), - :user => user, - :activity => TimeEntryActivity.find_by_name('Design'), - :comments => comments) - - te2 = TimeEntry.find_by_comments(comments) - assert_not_nil te2 - assert_equal 7.3, te2.hours - assert_equal 3, te2.user_id - - get :report, :project_id => 1, :columns => 'day', - :from => "2011-11-11", :to => "2011-11-11", - :criterias => ["member"], :format => "csv" - assert_response :success - assert_equal 'text/csv', @response.content_type - lines = @response.body.chomp.split("\n") - # Headers - s1 = "\xa6\xa8\xad\xfb,2011-11-11,\xc1`\xadp" - s2 = "\xc1`\xadp" - if s1.respond_to?(:force_encoding) - s1.force_encoding('Big5') - s2.force_encoding('Big5') - end - assert_equal s1, lines.first - # Total row - assert_equal "#{str_big5} #{user.lastname},7.30,7.30", lines[1] - assert_equal "#{s2},7.30,7.30", lines[2] - - str_tw = "Traditional Chinese (\xe7\xb9\x81\xe9\xab\x94\xe4\xb8\xad\xe6\x96\x87)" - if str_tw.respond_to?(:force_encoding) - str_tw.force_encoding('UTF-8') - end - assert_equal str_tw, l(:general_lang_name) - assert_equal 'Big5', l(:general_csv_encoding) - assert_equal ',', l(:general_csv_separator) - assert_equal '.', l(:general_csv_decimal_separator) - end - - def test_csv_cannot_convert_should_be_replaced_big_5 - Setting.default_language = "zh-TW" - str_utf8 = "\xe4\xbb\xa5\xe5\x86\x85" - if str_utf8.respond_to?(:force_encoding) - str_utf8.force_encoding('UTF-8') - end - user = User.find_by_id(3) - user.firstname = str_utf8 - user.lastname = "test-lastname" - assert user.save - comments = "test_replaced" - te1 = TimeEntry.create(:spent_on => '2011-11-11', - :hours => 7.3, - :project => Project.find(1), - :user => user, - :activity => TimeEntryActivity.find_by_name('Design'), - :comments => comments) - - te2 = TimeEntry.find_by_comments(comments) - assert_not_nil te2 - assert_equal 7.3, te2.hours - assert_equal 3, te2.user_id - - get :report, :project_id => 1, :columns => 'day', - :from => "2011-11-11", :to => "2011-11-11", - :criterias => ["member"], :format => "csv" - assert_response :success - assert_equal 'text/csv', @response.content_type - lines = @response.body.chomp.split("\n") - # Headers - s1 = "\xa6\xa8\xad\xfb,2011-11-11,\xc1`\xadp" - if s1.respond_to?(:force_encoding) - s1.force_encoding('Big5') - end - assert_equal s1, lines.first - # Total row - s2 = "" - if s2.respond_to?(:force_encoding) - s2 = "\xa5H?" - s2.force_encoding('Big5') - elsif RUBY_PLATFORM == 'java' - s2 = "??" - else - s2 = "\xa5H???" - end - assert_equal "#{s2} #{user.lastname},7.30,7.30", lines[1] - end - - def test_csv_fr - with_settings :default_language => "fr" do - str1 = "test_csv_fr" - user = User.find_by_id(3) - te1 = TimeEntry.create(:spent_on => '2011-11-11', - :hours => 7.3, - :project => Project.find(1), - :user => user, - :activity => TimeEntryActivity.find_by_name('Design'), - :comments => str1) - - te2 = TimeEntry.find_by_comments(str1) - assert_not_nil te2 - assert_equal 7.3, te2.hours - assert_equal 3, te2.user_id - - get :report, :project_id => 1, :columns => 'day', - :from => "2011-11-11", :to => "2011-11-11", - :criterias => ["member"], :format => "csv" - assert_response :success - assert_equal 'text/csv', @response.content_type - lines = @response.body.chomp.split("\n") - # Headers - s1 = "Membre;2011-11-11;Total" - s2 = "Total" - if s1.respond_to?(:force_encoding) - s1.force_encoding('ISO-8859-1') - s2.force_encoding('ISO-8859-1') - end - assert_equal s1, lines.first - # Total row - assert_equal "#{user.firstname} #{user.lastname};7,30;7,30", lines[1] - assert_equal "#{s2};7,30;7,30", lines[2] - - str_fr = "Fran\xc3\xa7ais" - if str_fr.respond_to?(:force_encoding) - str_fr.force_encoding('UTF-8') - end - assert_equal str_fr, l(:general_lang_name) - assert_equal 'ISO-8859-1', l(:general_csv_encoding) - assert_equal ';', l(:general_csv_separator) - assert_equal ',', l(:general_csv_decimal_separator) - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/59/59560647b1b38d17ca5eda58f2c560506ef65f44.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/59/59560647b1b38d17ca5eda58f2c560506ef65f44.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,68 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 IssuePriority < Enumeration + has_many :issues, :foreign_key => 'priority_id' + + after_destroy {|priority| priority.class.compute_position_names} + after_save {|priority| priority.class.compute_position_names if priority.position_changed? && priority.position} + + OptionName = :enumeration_issue_priorities + + def option_name + OptionName + end + + def objects_count + issues.count + end + + def transfer_relations(to) + issues.update_all("priority_id = #{to.id}") + end + + def css_classes + "priority-#{id} priority-#{position_name}" + end + + # Clears position_name for all priorities + # Called from migration 20121026003537_populate_enumerations_position_name + def self.clear_position_names + update_all :position_name => nil + end + + # Updates position_name for active priorities + # Called from migration 20121026003537_populate_enumerations_position_name + def self.compute_position_names + priorities = where(:active => true).all.sort_by(&:position) + if priorities.any? + default = priorities.detect(&:is_default?) || priorities[(priorities.size - 1) / 2] + priorities.each_with_index do |priority, index| + name = case + when priority.position == default.position + "default" + when priority.position < default.position + index == 0 ? "lowest" : "low#{index+1}" + else + index == (priorities.size - 1) ? "highest" : "high#{priorities.size - index}" + end + + update_all({:position_name => name}, :id => priority.id) + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/59/5957c73ec3e45b1dba5f2bd95614bcd541de4b15.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/59/5957c73ec3e45b1dba5f2bd95614bcd541de4b15.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,128 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 + layout 'admin' + + before_filter :require_admin, :find_roles, :find_trackers + + def index + @workflow_counts = WorkflowTransition.count_by_tracker_and_role + end + + def edit + @role = Role.find_by_id(params[:role_id]) if params[:role_id] + @tracker = Tracker.find_by_id(params[:tracker_id]) if params[:tracker_id] + + if request.post? + WorkflowTransition.destroy_all( ["role_id=? and tracker_id=?", @role.id, @tracker.id]) + (params[:issue_status] || []).each { |status_id, transitions| + transitions.each { |new_status_id, options| + author = options.is_a?(Array) && options.include?('author') && !options.include?('always') + assignee = options.is_a?(Array) && options.include?('assignee') && !options.include?('always') + WorkflowTransition.create(:role_id => @role.id, :tracker_id => @tracker.id, :old_status_id => status_id, :new_status_id => new_status_id, :author => author, :assignee => assignee) + } + } + if @role.save + redirect_to workflows_edit_path(:role_id => @role, :tracker_id => @tracker, :used_statuses_only => params[:used_statuses_only]) + return + end + end + + @used_statuses_only = (params[:used_statuses_only] == '0' ? false : true) + if @tracker && @used_statuses_only && @tracker.issue_statuses.any? + @statuses = @tracker.issue_statuses + end + @statuses ||= IssueStatus.sorted.all + + if @tracker && @role && @statuses.any? + workflows = WorkflowTransition.where(:role_id => @role.id, :tracker_id => @tracker.id).all + @workflows = {} + @workflows['always'] = workflows.select {|w| !w.author && !w.assignee} + @workflows['author'] = workflows.select {|w| w.author} + @workflows['assignee'] = workflows.select {|w| w.assignee} + end + end + + def permissions + @role = Role.find_by_id(params[:role_id]) if params[:role_id] + @tracker = Tracker.find_by_id(params[:tracker_id]) if params[:tracker_id] + + if request.post? && @role && @tracker + WorkflowPermission.replace_permissions(@tracker, @role, params[:permissions] || {}) + redirect_to workflows_permissions_path(:role_id => @role, :tracker_id => @tracker, :used_statuses_only => params[:used_statuses_only]) + return + end + + @used_statuses_only = (params[:used_statuses_only] == '0' ? false : true) + if @tracker && @used_statuses_only && @tracker.issue_statuses.any? + @statuses = @tracker.issue_statuses + end + @statuses ||= IssueStatus.sorted.all + + if @role && @tracker + @fields = (Tracker::CORE_FIELDS_ALL - @tracker.disabled_core_fields).map {|field| [field, l("field_"+field.sub(/_id$/, ''))]} + @custom_fields = @tracker.custom_fields + + @permissions = WorkflowPermission.where(:tracker_id => @tracker.id, :role_id => @role.id).all.inject({}) do |h, w| + h[w.old_status_id] ||= {} + h[w.old_status_id][w.field_name] = w.rule + h + end + @statuses.each {|status| @permissions[status.id] ||= {}} + end + end + + def copy + + if params[:source_tracker_id].blank? || params[:source_tracker_id] == 'any' + @source_tracker = nil + else + @source_tracker = Tracker.find_by_id(params[:source_tracker_id].to_i) + end + if params[:source_role_id].blank? || params[:source_role_id] == 'any' + @source_role = nil + else + @source_role = Role.find_by_id(params[:source_role_id].to_i) + end + + @target_trackers = params[:target_tracker_ids].blank? ? nil : Tracker.find_all_by_id(params[:target_tracker_ids]) + @target_roles = params[:target_role_ids].blank? ? nil : Role.find_all_by_id(params[:target_role_ids]) + + if request.post? + if params[:source_tracker_id].blank? || params[:source_role_id].blank? || (@source_tracker.nil? && @source_role.nil?) + flash.now[:error] = l(:error_workflow_copy_source) + elsif @target_trackers.blank? || @target_roles.blank? + flash.now[:error] = l(:error_workflow_copy_target) + else + WorkflowRule.copy(@source_tracker, @source_role, @target_trackers, @target_roles) + flash[:notice] = l(:notice_successful_update) + redirect_to workflows_copy_path(:source_tracker_id => @source_tracker, :source_role_id => @source_role) + end + end + end + + private + + def find_roles + @roles = Role.sorted.all + end + + def find_trackers + @trackers = Tracker.sorted.all + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/59/596c198f119f4338d149538588dba74545c206cb.svn-base Binary file .svn/pristine/59/596c198f119f4338d149538588dba74545c206cb.svn-base has changed diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/59/59aca64abbe8279f6a8e6795b265ac2c66a49a15.svn-base --- a/.svn/pristine/59/59aca64abbe8279f6a8e6795b265ac2c66a49a15.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1005 +0,0 @@ -th: - direction: ltr - date: - formats: - # Use the strftime parameters for formats. - # When no format has been given, it uses default. - # You can provide other formats here if you like! - default: "%Y-%m-%d" - short: "%b %d" - long: "%B %d, %Y" - - day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday] - abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December] - abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec] - # Used in date_select and datime_select. - order: - - :year - - :month - - :day - - time: - formats: - default: "%a, %d %b %Y %H:%M:%S %z" - time: "%H:%M" - short: "%d %b %H:%M" - long: "%B %d, %Y %H:%M" - am: "am" - pm: "pm" - - datetime: - distance_in_words: - half_a_minute: "half a minute" - less_than_x_seconds: - one: "less than 1 second" - other: "less than %{count} seconds" - x_seconds: - one: "1 second" - other: "%{count} seconds" - less_than_x_minutes: - one: "less than a minute" - other: "less than %{count} minutes" - x_minutes: - one: "1 minute" - other: "%{count} minutes" - about_x_hours: - one: "about 1 hour" - other: "about %{count} hours" - x_days: - one: "1 day" - other: "%{count} days" - about_x_months: - one: "about 1 month" - other: "about %{count} months" - x_months: - one: "1 month" - other: "%{count} months" - about_x_years: - one: "about 1 year" - other: "about %{count} years" - over_x_years: - one: "over 1 year" - other: "over %{count} years" - almost_x_years: - one: "almost 1 year" - other: "almost %{count} years" - - number: - format: - separator: "." - delimiter: "" - precision: 3 - human: - format: - precision: 1 - delimiter: "" - storage_units: - format: "%n %u" - units: - kb: KB - tb: TB - gb: GB - byte: - one: Byte - other: Bytes - mb: MB - -# Used in array.to_sentence. - support: - array: - sentence_connector: "and" - skip_last_comma: false - - activerecord: - errors: - template: - header: - one: "1 error prohibited this %{model} from being saved" - other: "%{count} errors prohibited this %{model} from being saved" - messages: - inclusion: "ไม่อยู่ในรายà¸à¸²à¸£" - exclusion: "ถูà¸à¸ªà¸‡à¸§à¸™à¹„ว้" - invalid: "ไม่ถูà¸à¸•้อง" - confirmation: "พิมพ์ไม่เหมือนเดิม" - accepted: "ต้องยอมรับ" - empty: "ต้องเติม" - blank: "ต้องเติม" - too_long: "ยาวเà¸à¸´à¸™à¹„ป" - too_short: "สั้นเà¸à¸´à¸™à¹„ป" - wrong_length: "ความยาวไม่ถูà¸à¸•้อง" - taken: "ถูà¸à¹ƒà¸Šà¹‰à¹„ปà¹à¸¥à¹‰à¸§" - not_a_number: "ไม่ใช่ตัวเลข" - not_a_date: "ไม่ใช่วันที่ ที่ถูà¸à¸•้อง" - greater_than: "must be greater than %{count}" - greater_than_or_equal_to: "must be greater than or equal to %{count}" - equal_to: "must be equal to %{count}" - less_than: "must be less than %{count}" - less_than_or_equal_to: "must be less than or equal to %{count}" - odd: "must be odd" - even: "must be even" - greater_than_start_date: "ต้องมาà¸à¸à¸§à¹ˆà¸²à¸§à¸±à¸™à¹€à¸£à¸´à¹ˆà¸¡" - not_same_project: "ไม่ได้อยู่ในโครงà¸à¸²à¸£à¹€à¸”ียวà¸à¸±à¸™" - circular_dependency: "ความสัมพันธ์อ้างอิงเป็นวงà¸à¸¥à¸¡" - cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" - - actionview_instancetag_blank_option: à¸à¸£à¸¸à¸“าเลือภ- - general_text_No: 'ไม่' - general_text_Yes: 'ใช่' - general_text_no: 'ไม่' - general_text_yes: 'ใช่' - general_lang_name: 'Thai (ไทย)' - general_csv_separator: ',' - general_csv_decimal_separator: '.' - general_csv_encoding: Windows-874 - general_pdf_encoding: cp874 - general_first_day_of_week: '1' - - notice_account_updated: บัà¸à¸Šà¸µà¹„ด้ถูà¸à¸›à¸£à¸±à¸šà¸›à¸£à¸¸à¸‡à¹à¸¥à¹‰à¸§. - notice_account_invalid_creditentials: ชื้ผู้ใช้หรือรหัสผ่านไม่ถูà¸à¸•้อง - notice_account_password_updated: รหัสได้ถูà¸à¸›à¸£à¸±à¸šà¸›à¸£à¸¸à¸‡à¹à¸¥à¹‰à¸§. - notice_account_wrong_password: รหัสผ่านไม่ถูà¸à¸•้อง - notice_account_register_done: บัà¸à¸Šà¸µà¸–ูà¸à¸ªà¸£à¹‰à¸²à¸‡à¹à¸¥à¹‰à¸§. à¸à¸£à¸¸à¸“าเช็คเมล์ à¹à¸¥à¹‰à¸§à¸„ลิ๊à¸à¸—ี่ลิงค์ในอีเมล์เพื่อเปิดใช้บัà¸à¸Šà¸µ - notice_account_unknown_email: ไม่มีผู้ใช้ที่ใช้อีเมล์นี้. - notice_can_t_change_password: บัà¸à¸Šà¸µà¸™à¸µà¹‰à¹ƒà¸Šà¹‰à¸à¸²à¸£à¸¢à¸·à¸™à¸¢à¸±à¸™à¸•ัวตนจาà¸à¹à¸«à¸¥à¹ˆà¸‡à¸ à¸²à¸¢à¸™à¸­à¸. ไม่สามารถปลี่ยนรหัสผ่านได้. - notice_account_lost_email_sent: เราได้ส่งอีเมล์พร้อมวิธีà¸à¸²à¸£à¸ªà¸£à¹‰à¸²à¸‡à¸£à¸«à¸±à¸µà¸ªà¸œà¹ˆà¸²à¸™à¹ƒà¸«à¸¡à¹ˆà¹ƒà¸«à¹‰à¸„ุณà¹à¸¥à¹‰à¸§ à¸à¸£à¸¸à¸“าเช็คเมล์. - notice_account_activated: บัà¸à¸Šà¸µà¸‚องคุณได้เปิดใช้à¹à¸¥à¹‰à¸§. ตอนนี้คุณสามารถเข้าสู่ระบบได้à¹à¸¥à¹‰à¸§. - notice_successful_create: สร้างเสร็จà¹à¸¥à¹‰à¸§. - notice_successful_update: ปรับปรุงเสร็จà¹à¸¥à¹‰à¸§. - notice_successful_delete: ลบเสร็จà¹à¸¥à¹‰à¸§. - notice_successful_connection: ติดต่อสำเร็จà¹à¸¥à¹‰à¸§. - notice_file_not_found: หน้าที่คุณต้องà¸à¸²à¸£à¸”ูไม่มีอยู่จริง หรือถูà¸à¸¥à¸šà¹„ปà¹à¸¥à¹‰à¸§. - notice_locking_conflict: ข้อมูลถูà¸à¸›à¸£à¸±à¸šà¸›à¸£à¸¸à¸‡à¹‚ดยผู้ใช้คนอื่น. - notice_not_authorized: คุณไม่มีสิทธิเข้าถึงหน้านี้. - notice_email_sent: "อีเมล์ได้ถูà¸à¸ªà¹ˆà¸‡à¸–ึง %{value}" - notice_email_error: "เà¸à¸´à¸”ความผิดพลาดขณะà¸à¸³à¸ªà¹ˆà¸‡à¸­à¸µà¹€à¸¡à¸¥à¹Œ (%{value})" - notice_feeds_access_key_reseted: RSS access key ของคุณถูภreset à¹à¸¥à¹‰à¸§. - notice_failed_to_save_issues: "%{count} ปัà¸à¸«à¸²à¸ˆà¸²à¸ %{total} ปัà¸à¸«à¸²à¸—ี่ถูà¸à¹€à¸¥à¸·à¸­à¸à¹„ม่สามารถจัดเà¸à¹‡à¸š: %{ids}." - notice_no_issue_selected: "ไม่มีปัà¸à¸«à¸²à¸—ี่ถูà¸à¹€à¸¥à¸·à¸­à¸! à¸à¸£à¸¸à¸“าเลือà¸à¸›à¸±à¸à¸«à¸²à¸—ี่คุณต้องà¸à¸²à¸£à¹à¸à¹‰à¹„ข." - notice_account_pending: "บัà¸à¸Šà¸µà¸‚องคุณสร้างเสร็จà¹à¸¥à¹‰à¸§ ขณะนี้รอà¸à¸²à¸£à¸­à¸™à¸¸à¸¡à¸±à¸•ิจาà¸à¸œà¸¹à¹‰à¸šà¸£à¸´à¸«à¸²à¸£à¸ˆà¸±à¸”à¸à¸²à¸£." - notice_default_data_loaded: ค่าเริ่มต้นโหลดเสร็จà¹à¸¥à¹‰à¸§. - - error_can_t_load_default_data: "ค่าเริ่มต้นโหลดไม่สำเร็จ: %{value}" - error_scm_not_found: "ไม่พบรุ่นที่ต้องà¸à¸²à¸£à¹ƒà¸™à¹à¸«à¸¥à¹ˆà¸‡à¹€à¸à¹‡à¸šà¸•้นฉบับ." - error_scm_command_failed: "เà¸à¸´à¸”ความผิดพลาดในà¸à¸²à¸£à¹€à¸‚้าถึงà¹à¸«à¸¥à¹ˆà¸‡à¹€à¸à¹‡à¸šà¸•้นฉบับ: %{value}" - error_scm_annotate: "entry ไม่มีอยู่จริง หรือไม่สามารถเขียนหมายเหตุประà¸à¸­à¸š." - error_issue_not_found_in_project: 'ไม่พบปัà¸à¸«à¸²à¸™à¸µà¹‰ หรือปัà¸à¸«à¸²à¹„ม่ได้อยู่ในโครงà¸à¸²à¸£à¸™à¸µà¹‰' - - mail_subject_lost_password: "รหัสผ่าน %{value} ของคุณ" - mail_body_lost_password: 'คลิ๊à¸à¸—ี่ลิงค์ต่อไปนี้เพื่อเปลี่ยนรหัสผ่าน:' - mail_subject_register: "เปิดบัà¸à¸Šà¸µ %{value} ของคุณ" - mail_body_register: 'คลิ๊à¸à¸—ี่ลิงค์ต่อไปนี้เพื่อเปลี่ยนรหัสผ่าน:' - mail_body_account_information_external: "คุณสามารถใช้บัà¸à¸Šà¸µ %{value} เพื่อเข้าสู่ระบบ." - mail_body_account_information: ข้อมูลบัà¸à¸Šà¸µà¸‚องคุณ - mail_subject_account_activation_request: "à¸à¸£à¸¸à¸“าเปิดบัà¸à¸Šà¸µ %{value}" - mail_body_account_activation_request: "ผู้ใช้ใหม่ (%{value}) ได้ลงทะเบียน. บัà¸à¸Šà¸µà¸‚องเขาà¸à¸³à¸¥à¸±à¸‡à¸£à¸­à¸­à¸™à¸¸à¸¡à¸±à¸•ิ:" - - gui_validation_error: 1 ข้อผิดพลาด - gui_validation_error_plural: "%{count} ข้อผิดพลาด" - - field_name: ชื่อ - field_description: รายละเอียด - field_summary: สรุปย่อ - field_is_required: ต้องใส่ - field_firstname: ชื่อ - field_lastname: นามสà¸à¸¸à¸¥ - field_mail: อีเมล์ - field_filename: à¹à¸Ÿà¹‰à¸¡ - field_filesize: ขนาด - field_downloads: ดาวน์โหลด - field_author: ผู้à¹à¸•่ง - field_created_on: สร้าง - field_updated_on: ปรับปรุง - field_field_format: รูปà¹à¸šà¸š - field_is_for_all: สำหรับทุà¸à¹‚ครงà¸à¸²à¸£ - field_possible_values: ค่าที่เป็นไปได้ - field_regexp: Regular expression - field_min_length: สั้นสุด - field_max_length: ยาวสุด - field_value: ค่า - field_category: ประเภท - field_title: ชื่อเรื่อง - field_project: โครงà¸à¸²à¸£ - field_issue: ปัà¸à¸«à¸² - field_status: สถานะ - field_notes: บันทึภ- field_is_closed: ปัà¸à¸«à¸²à¸ˆà¸š - field_is_default: ค่าเริ่มต้น - field_tracker: à¸à¸²à¸£à¸•ิดตาม - field_subject: เรื่อง - field_due_date: วันครบà¸à¸³à¸«à¸™à¸” - field_assigned_to: มอบหมายให้ - field_priority: ความสำคัภ- field_fixed_version: รุ่น - field_user: ผู้ใช้ - field_role: บทบาท - field_homepage: หน้าà¹à¸£à¸ - field_is_public: สาธารณะ - field_parent: โครงà¸à¸²à¸£à¸¢à¹ˆà¸­à¸¢à¸‚อง - field_is_in_roadmap: ปัà¸à¸«à¸²à¹à¸ªà¸”งใน à¹à¸œà¸™à¸‡à¸²à¸™ - field_login: ชื่อที่ใช้เข้าระบบ - field_mail_notification: à¸à¸²à¸£à¹à¸ˆà¹‰à¸‡à¹€à¸•ือนทางอีเมล์ - field_admin: ผู้บริหารจัดà¸à¸²à¸£ - field_last_login_on: เข้าระบบครั้งสุดท้าย - field_language: ภาษา - field_effective_date: วันที่ - field_password: รหัสผ่าน - field_new_password: รหัสผ่านใหม่ - field_password_confirmation: ยืนยันรหัสผ่าน - field_version: รุ่น - field_type: ชนิด - field_host: โฮสต์ - field_port: พอร์ต - field_account: บัà¸à¸Šà¸µ - field_base_dn: Base DN - field_attr_login: เข้าระบบ attribute - field_attr_firstname: ชื่อ attribute - field_attr_lastname: นามสà¸à¸¸à¸¥ attribute - field_attr_mail: อีเมล์ attribute - field_onthefly: สร้างผู้ใช้ทันที - field_start_date: เริ่ม - field_done_ratio: "% สำเร็จ" - field_auth_source: วิธีà¸à¸²à¸£à¸¢à¸·à¸™à¸¢à¸±à¸™à¸•ัวตน - field_hide_mail: ซ่อนอีเมล์ของฉัน - field_comments: ความเห็น - field_url: URL - field_start_page: หน้าเริ่มต้น - field_subproject: โครงà¸à¸²à¸£à¸¢à¹ˆà¸­à¸¢ - field_hours: ชั่วโมง - field_activity: à¸à¸´à¸ˆà¸à¸£à¸£à¸¡ - field_spent_on: วันที่ - field_identifier: ชื่อเฉพาะ - field_is_filter: ใช้เป็นตัวà¸à¸£à¸­à¸‡ - field_issue_to: ปัà¸à¸«à¸²à¸—ี่เà¸à¸µà¹ˆà¸¢à¸§à¸‚้อง - field_delay: เลื่อน - field_assignable: ปัà¸à¸«à¸²à¸ªà¸²à¸¡à¸²à¸£à¸–มอบหมายให้คนที่ทำบทบาทนี้ - field_redirect_existing_links: ย้ายจุดเชื่อมโยงนี้ - field_estimated_hours: เวลาที่ใช้โดยประมาณ - field_column_names: สดมภ์ - field_time_zone: ย่านเวลา - field_searchable: ค้นหาได้ - field_default_value: ค่าเริ่มต้น - field_comments_sorting: à¹à¸ªà¸”งความเห็น - - setting_app_title: ชื่อโปรà¹à¸à¸£à¸¡ - setting_app_subtitle: ชื่อโปรà¹à¸à¸£à¸¡à¸£à¸­à¸‡ - setting_welcome_text: ข้อความต้อนรับ - setting_default_language: ภาษาเริ่มต้น - setting_login_required: ต้องป้อนผู้ใช้-รหัสผ่าน - setting_self_registration: ลงทะเบียนด้วยตนเอง - setting_attachment_max_size: ขนาดà¹à¸Ÿà¹‰à¸¡à¹à¸™à¸šà¸ªà¸¹à¸‡à¸ªà¸¸à¸” - setting_issues_export_limit: à¸à¸²à¸£à¸ªà¹ˆà¸‡à¸­à¸­à¸à¸›à¸±à¸à¸«à¸²à¸ªà¸¹à¸‡à¸ªà¸¸à¸” - setting_mail_from: อีเมล์ที่ใช้ส่ง - setting_bcc_recipients: ไม่ระบุชื่อผู้รับ (bcc) - setting_host_name: ชื่อโฮสต์ - setting_text_formatting: à¸à¸²à¸£à¸ˆà¸±à¸”รูปà¹à¸šà¸šà¸‚้อความ - setting_wiki_compression: บีบอัดประวัติ Wiki - setting_feeds_limit: จำนวน Feed - setting_default_projects_public: โครงà¸à¸²à¸£à¹ƒà¸«à¸¡à¹ˆà¸¡à¸µà¸„่าเริ่มต้นเป็น สาธารณะ - setting_autofetch_changesets: ดึง commits อัตโนมัติ - setting_sys_api_enabled: เปิดใช้ WS สำหรับà¸à¸²à¸£à¸ˆà¸±à¸”à¸à¸²à¸£à¸—ี่เà¸à¹‡à¸šà¸•้นฉบับ - setting_commit_ref_keywords: คำสำคัภReferencing - setting_commit_fix_keywords: คำสำคัภFixing - setting_autologin: เข้าระบบอัตโนมัติ - setting_date_format: รูปà¹à¸šà¸šà¸§à¸±à¸™à¸—ี่ - setting_time_format: รูปà¹à¸šà¸šà¹€à¸§à¸¥à¸² - setting_cross_project_issue_relations: อนุà¸à¸²à¸•ให้ระบุปัà¸à¸«à¸²à¸‚้ามโครงà¸à¸²à¸£ - setting_issue_list_default_columns: สดมภ์เริ่มต้นà¹à¸ªà¸”งในรายà¸à¸²à¸£à¸›à¸±à¸à¸«à¸² - setting_emails_footer: คำลงท้ายอีเมล์ - setting_protocol: Protocol - setting_per_page_options: ตัวเลือà¸à¸ˆà¸³à¸™à¸§à¸™à¸•่อหน้า - setting_user_format: รูปà¹à¸šà¸šà¸à¸²à¸£à¹à¸ªà¸”งชื่อผู้ใช้ - setting_activity_days_default: จำนวนวันที่à¹à¸ªà¸”งในà¸à¸´à¸ˆà¸à¸£à¸£à¸¡à¸‚องโครงà¸à¸²à¸£ - setting_display_subprojects_issues: à¹à¸ªà¸”งปัà¸à¸«à¸²à¸‚องโครงà¸à¸²à¸£à¸¢à¹ˆà¸­à¸¢à¹ƒà¸™à¹‚ครงà¸à¸²à¸£à¸«à¸¥à¸±à¸ - - project_module_issue_tracking: à¸à¸²à¸£à¸•ิดตามปัà¸à¸«à¸² - project_module_time_tracking: à¸à¸²à¸£à¹ƒà¸Šà¹‰à¹€à¸§à¸¥à¸² - project_module_news: ข่าว - project_module_documents: เอà¸à¸ªà¸²à¸£ - project_module_files: à¹à¸Ÿà¹‰à¸¡ - project_module_wiki: Wiki - project_module_repository: ที่เà¸à¹‡à¸šà¸•้นฉบับ - project_module_boards: à¸à¸£à¸°à¸”านข้อความ - - label_user: ผู้ใช้ - label_user_plural: ผู้ใช้ - label_user_new: ผู้ใช้ใหม่ - label_project: โครงà¸à¸²à¸£ - label_project_new: โครงà¸à¸²à¸£à¹ƒà¸«à¸¡à¹ˆ - label_project_plural: โครงà¸à¸²à¸£ - label_x_projects: - zero: no projects - one: 1 project - other: "%{count} projects" - label_project_all: โครงà¸à¸²à¸£à¸—ั้งหมด - label_project_latest: โครงà¸à¸²à¸£à¸¥à¹ˆà¸²à¸ªà¸¸à¸” - label_issue: ปัà¸à¸«à¸² - label_issue_new: ปัà¸à¸«à¸²à¹ƒà¸«à¸¡à¹ˆ - label_issue_plural: ปัà¸à¸«à¸² - label_issue_view_all: ดูปัà¸à¸«à¸²à¸—ั้งหมด - label_issues_by: "ปัà¸à¸«à¸²à¹‚ดย %{value}" - label_issue_added: ปัà¸à¸«à¸²à¸–ูà¸à¹€à¸žà¸´à¹ˆà¸¡ - label_issue_updated: ปัà¸à¸«à¸²à¸–ูà¸à¸›à¸£à¸±à¸šà¸›à¸£à¸¸à¸‡ - label_document: เอà¸à¸ªà¸²à¸£ - label_document_new: เอà¸à¸ªà¸²à¸£à¹ƒà¸«à¸¡à¹ˆ - label_document_plural: เอà¸à¸ªà¸²à¸£ - label_document_added: เอà¸à¸ªà¸²à¸£à¸–ูà¸à¹€à¸žà¸´à¹ˆà¸¡ - label_role: บทบาท - label_role_plural: บทบาท - label_role_new: บทบาทใหม่ - label_role_and_permissions: บทบาทà¹à¸¥à¸°à¸ªà¸´à¸—ธิ - label_member: สมาชิภ- label_member_new: สมาชิà¸à¹ƒà¸«à¸¡à¹ˆ - label_member_plural: สมาชิภ- label_tracker: à¸à¸²à¸£à¸•ิดตาม - label_tracker_plural: à¸à¸²à¸£à¸•ิดตาม - label_tracker_new: à¸à¸²à¸£à¸•ิดตามใหม่ - label_workflow: ลำดับงาน - label_issue_status: สถานะของปัà¸à¸«à¸² - label_issue_status_plural: สถานะของปัà¸à¸«à¸² - label_issue_status_new: สถานะใหม - label_issue_category: ประเภทของปัà¸à¸«à¸² - label_issue_category_plural: ประเภทของปัà¸à¸«à¸² - label_issue_category_new: ประเภทใหม่ - label_custom_field: เขตข้อมูลà¹à¸šà¸šà¸£à¸°à¸šà¸¸à¹€à¸­à¸‡ - label_custom_field_plural: เขตข้อมูลà¹à¸šà¸šà¸£à¸°à¸šà¸¸à¹€à¸­à¸‡ - label_custom_field_new: สร้างเขตข้อมูลà¹à¸šà¸šà¸£à¸°à¸šà¸¸à¹€à¸­à¸‡ - label_enumerations: รายà¸à¸²à¸£ - label_enumeration_new: สร้างใหม่ - label_information: ข้อมูล - label_information_plural: ข้อมูล - label_please_login: à¸à¸£à¸¸à¸“าเข้าระบบà¸à¹ˆà¸­à¸™ - label_register: ลงทะเบียน - label_password_lost: ลืมรหัสผ่าน - label_home: หน้าà¹à¸£à¸ - label_my_page: หน้าของฉัน - label_my_account: บัà¸à¸Šà¸µà¸‚องฉัน - label_my_projects: โครงà¸à¸²à¸£à¸‚องฉัน - label_administration: บริหารจัดà¸à¸²à¸£ - label_login: เข้าระบบ - label_logout: ออà¸à¸£à¸°à¸šà¸š - label_help: ช่วยเหลือ - label_reported_issues: ปัà¸à¸«à¸²à¸—ี่à¹à¸ˆà¹‰à¸‡à¹„ว้ - label_assigned_to_me_issues: ปัà¸à¸«à¸²à¸—ี่มอบหมายให้ฉัน - label_last_login: ติดต่อครั้งสุดท้าย - label_registered_on: ลงทะเบียนเมื่อ - label_activity: à¸à¸´à¸ˆà¸à¸£à¸£à¸¡ - label_activity_plural: à¸à¸´à¸ˆà¸à¸£à¸£à¸¡ - label_activity_latest: à¸à¸´à¸ˆà¸à¸£à¸£à¸¡à¸¥à¹ˆà¸²à¸ªà¸¸à¸” - label_overall_activity: à¸à¸´à¸ˆà¸à¸£à¸£à¸¡à¹‚ดยรวม - label_new: ใหม่ - label_logged_as: เข้าระบบในชื่อ - label_environment: สภาพà¹à¸§à¸”ล้อม - label_authentication: à¸à¸²à¸£à¸¢à¸·à¸™à¸¢à¸±à¸™à¸•ัวตน - label_auth_source: วิธีà¸à¸²à¸£à¸à¸²à¸£à¸¢à¸·à¸™à¸¢à¸±à¸™à¸•ัวตน - label_auth_source_new: สร้างวิธีà¸à¸²à¸£à¸¢à¸·à¸™à¸¢à¸±à¸™à¸•ัวตนใหม่ - label_auth_source_plural: วิธีà¸à¸²à¸£ Authentication - label_subproject_plural: โครงà¸à¸²à¸£à¸¢à¹ˆà¸­à¸¢ - label_min_max_length: สั้น-ยาว สุดที่ - label_list: รายà¸à¸²à¸£ - label_date: วันที่ - label_integer: จำนวนเต็ม - label_float: จำนวนจริง - label_boolean: ถูà¸à¸œà¸´à¸” - label_string: ข้อความ - label_text: ข้อความขนาดยาว - label_attribute: คุณลัà¸à¸©à¸“ะ - label_attribute_plural: คุณลัà¸à¸©à¸“ะ - label_download: "%{count} ดาวน์โหลด" - label_download_plural: "%{count} ดาวน์โหลด" - 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_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_x_open_issues_abbr_on_total: - zero: 0 open / %{total} - one: 1 open / %{total} - other: "%{count} open / %{total}" - label_x_open_issues_abbr: - zero: 0 open - one: 1 open - other: "%{count} open" - label_x_closed_issues_abbr: - zero: 0 closed - one: 1 closed - other: "%{count} closed" - label_total: จำนวนรวม - label_permissions: สิทธิ - label_current_status: สถานะปัจจุบัน - label_new_statuses_allowed: อนุà¸à¸²à¸•ให้มีสถานะใหม่ - label_all: ทั้งหมด - label_none: ไม่มี - label_nobody: ไม่มีใคร - label_next: ต่อไป - label_previous: à¸à¹ˆà¸­à¸™à¸«à¸™à¹‰à¸² - label_used_by: ถูà¸à¹ƒà¸Šà¹‰à¹‚ดย - label_details: รายละเอียด - label_add_note: เพิ่มบันทึภ- label_per_page: ต่อหน้า - label_calendar: ปà¸à¸´à¸—ิน - label_months_from: เดือนจาภ- label_gantt: Gantt - label_internal: ภายใน - label_last_changes: "last %{count} เปลี่ยนà¹à¸›à¸¥à¸‡" - label_change_view_all: ดูà¸à¸²à¸£à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹à¸›à¸¥à¸‡à¸—ั้งหมด - label_personalize_page: ปรับà¹à¸•่งหน้านี้ - label_comment: ความเห็น - label_comment_plural: ความเห็น - label_x_comments: - zero: no comments - one: 1 comment - other: "%{count} comments" - label_comment_add: เพิ่มความเห็น - label_comment_added: ความเห็นถูà¸à¹€à¸žà¸´à¹ˆà¸¡ - label_comment_delete: ลบความเห็น - label_query: à¹à¸šà¸šà¸ªà¸­à¸šà¸–ามà¹à¸šà¸šà¸à¸³à¸«à¸™à¸”เอง - label_query_plural: à¹à¸šà¸šà¸ªà¸­à¸šà¸–ามà¹à¸šà¸šà¸à¸³à¸«à¸™à¸”เอง - label_query_new: à¹à¸šà¸šà¸ªà¸­à¸šà¸–ามใหม่ - label_filter_add: เพิ่มตัวà¸à¸£à¸­à¸‡ - label_filter_plural: ตัวà¸à¸£à¸­à¸‡ - label_equals: คือ - label_not_equals: ไม่ใช่ - label_in_less_than: น้อยà¸à¸§à¹ˆà¸² - label_in_more_than: มาà¸à¸à¸§à¹ˆà¸² - label_in: ในช่วง - label_today: วันนี้ - label_all_time: ตลอดเวลา - label_yesterday: เมื่อวาน - label_this_week: อาทิตย์นี้ - label_last_week: อาทิตย์ที่à¹à¸¥à¹‰à¸§ - label_last_n_days: "%{count} วันย้อนหลัง" - label_this_month: เดือนนี้ - label_last_month: เดือนที่à¹à¸¥à¹‰à¸§ - label_this_year: ปีนี้ - label_date_range: ช่วงวันที่ - label_less_than_ago: น้อยà¸à¸§à¹ˆà¸²à¸«à¸™à¸¶à¹ˆà¸‡à¸§à¸±à¸™ - label_more_than_ago: มาà¸à¸à¸§à¹ˆà¸²à¸«à¸™à¸¶à¹ˆà¸‡à¸§à¸±à¸™ - label_ago: วันผ่านมาà¹à¸¥à¹‰à¸§ - label_contains: มี... - label_not_contains: ไม่มี... - label_day_plural: วัน - label_repository: ที่เà¸à¹‡à¸šà¸•้นฉบับ - label_repository_plural: ที่เà¸à¹‡à¸šà¸•้นฉบับ - label_browse: เปิดหา - label_modification: "%{count} เปลี่ยนà¹à¸›à¸¥à¸‡" - label_modification_plural: "%{count} เปลี่ยนà¹à¸›à¸¥à¸‡" - label_revision: à¸à¸²à¸£à¹à¸à¹‰à¹„ข - label_revision_plural: à¸à¸²à¸£à¹à¸à¹‰à¹„ข - label_associated_revisions: à¸à¸²à¸£à¹à¸à¹‰à¹„ขที่เà¸à¸µà¹ˆà¸¢à¸§à¸‚้อง - label_added: ถูà¸à¹€à¸žà¸´à¹ˆà¸¡ - label_modified: ถูà¸à¹à¸à¹‰à¹„ข - label_deleted: ถูà¸à¸¥à¸š - label_latest_revision: รุ่นà¸à¸²à¸£à¹à¸à¹‰à¹„ขล่าสุด - label_latest_revision_plural: รุ่นà¸à¸²à¸£à¹à¸à¹‰à¹„ขล่าสุด - label_view_revisions: ดูà¸à¸²à¸£à¹à¸à¹‰à¹„ข - label_max_size: ขนาดใหà¸à¹ˆà¸ªà¸¸à¸” - label_sort_highest: ย้ายไปบนสุด - label_sort_higher: ย้ายขึ้น - label_sort_lower: ย้ายลง - label_sort_lowest: ย้ายไปล่างสุด - label_roadmap: à¹à¸œà¸™à¸‡à¸²à¸™ - label_roadmap_due_in: "ถึงà¸à¸³à¸«à¸™à¸”ใน %{value}" - label_roadmap_overdue: "%{value} ช้าà¸à¸§à¹ˆà¸²à¸à¸³à¸«à¸™à¸”" - label_roadmap_no_issues: ไม่มีปัà¸à¸«à¸²à¸ªà¸³à¸«à¸£à¸±à¸šà¸£à¸¸à¹ˆà¸™à¸™à¸µà¹‰ - label_search: ค้นหา - label_result_plural: ผลà¸à¸²à¸£à¸„้นหา - label_all_words: ทุà¸à¸„ำ - label_wiki: Wiki - label_wiki_edit: à¹à¸à¹‰à¹„ข Wiki - label_wiki_edit_plural: à¹à¸à¹‰à¹„ข Wiki - label_wiki_page: หน้า Wiki - label_wiki_page_plural: หน้า Wiki - label_index_by_title: เรียงตามชื่อเรื่อง - label_index_by_date: เรียงตามวัน - label_current_version: รุ่นปัจจุบัน - label_preview: ตัวอย่างà¸à¹ˆà¸­à¸™à¸ˆà¸±à¸”เà¸à¹‡à¸š - label_feed_plural: Feeds - label_changes_details: รายละเอียดà¸à¸²à¸£à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹à¸›à¸¥à¸‡à¸—ั้งหมด - label_issue_tracking: ติดตามปัà¸à¸«à¸² - label_spent_time: เวลาที่ใช้ - label_f_hour: "%{value} ชั่วโมง" - label_f_hour_plural: "%{value} ชั่วโมง" - label_time_tracking: ติดตามà¸à¸²à¸£à¹ƒà¸Šà¹‰à¹€à¸§à¸¥à¸² - label_change_plural: เปลี่ยนà¹à¸›à¸¥à¸‡ - label_statistics: สถิติ - label_commits_per_month: Commits ต่อเดือน - label_commits_per_author: Commits ต่อผู้à¹à¸•่ง - label_view_diff: ดูความà¹à¸•à¸à¸•่าง - label_diff_inline: inline - label_diff_side_by_side: side by side - label_options: ตัวเลือภ- label_copy_workflow_from: คัดลอà¸à¸¥à¸³à¸”ับงานจาภ- label_permissions_report: รายงานสิทธิ - label_watched_issues: เà¸à¹‰à¸²à¸”ูปัà¸à¸«à¸² - label_related_issues: ปัà¸à¸«à¸²à¸—ี่เà¸à¸µà¹ˆà¸¢à¸§à¸‚้อง - label_applied_status: จัดเà¸à¹‡à¸šà¸ªà¸–านะ - label_loading: à¸à¸³à¸¥à¸±à¸‡à¹‚หลด... - label_relation_new: ความสัมพันธ์ใหม่ - label_relation_delete: ลบความสัมพันธ์ - label_relates_to: สัมพันธ์à¸à¸±à¸š - label_duplicates: ซ้ำ - label_blocks: à¸à¸µà¸”à¸à¸±à¸™ - label_blocked_by: à¸à¸µà¸”à¸à¸±à¸™à¹‚ดย - label_precedes: นำหน้า - label_follows: ตามหลัง - label_end_to_start: จบ-เริ่ม - label_end_to_end: จบ-จบ - label_start_to_start: เริ่ม-เริ่ม - label_start_to_end: เริ่ม-จบ - label_stay_logged_in: อยู่ในระบบต่อ - label_disabled: ไม่ใช้งาน - label_show_completed_versions: à¹à¸ªà¸”งรุ่นที่สมบูรณ์ - label_me: ฉัน - label_board: สภาà¸à¸²à¹à¸Ÿ - label_board_new: สร้างสภาà¸à¸²à¹à¸Ÿ - label_board_plural: สภาà¸à¸²à¹à¸Ÿ - label_topic_plural: หัวข้อ - label_message_plural: ข้อความ - label_message_last: ข้อความล่าสุด - label_message_new: เขียนข้อความใหม่ - label_message_posted: ข้อความถูà¸à¹€à¸žà¸´à¹ˆà¸¡à¹à¸¥à¹‰à¸§ - label_reply_plural: ตอบà¸à¸¥à¸±à¸š - label_send_information: ส่งรายละเอียดของบัà¸à¸Šà¸µà¹ƒà¸«à¹‰à¸œà¸¹à¹‰à¹ƒà¸Šà¹‰ - label_year: ปี - label_month: เดือน - label_week: สัปดาห์ - label_date_from: จาภ- label_date_to: ถึง - label_language_based: ขึ้นอยู่à¸à¸±à¸šà¸ à¸²à¸©à¸²à¸‚องผู้ใช้ - label_sort_by: "เรียงโดย %{value}" - label_send_test_email: ส่งจดหมายทดสอบ - label_feeds_access_key_created_on: "RSS access key สร้างเมื่อ %{value} ที่ผ่านมา" - label_module_plural: ส่วนประà¸à¸­à¸š - label_added_time_by: "เพิ่มโดย %{author} %{age} ที่ผ่านมา" - label_updated_time: "ปรับปรุง %{value} ที่ผ่านมา" - label_jump_to_a_project: ไปที่โครงà¸à¸²à¸£... - label_file_plural: à¹à¸Ÿà¹‰à¸¡ - label_changeset_plural: à¸à¸¥à¸¸à¹ˆà¸¡à¸à¸²à¸£à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹à¸›à¸¥à¸‡ - label_default_columns: สดมภ์เริ่มต้น - label_no_change_option: (ไม่เปลี่ยนà¹à¸›à¸¥à¸‡) - label_bulk_edit_selected_issues: à¹à¸à¹‰à¹„ขปัà¸à¸«à¸²à¸—ี่เลือà¸à¸—ั้งหมด - label_theme: ชุดรูปà¹à¸šà¸š - label_default: ค่าเริ่มต้น - label_search_titles_only: ค้นหาจาà¸à¸Šà¸·à¹ˆà¸­à¹€à¸£à¸·à¹ˆà¸­à¸‡à¹€à¸—่านั้น - label_user_mail_option_all: "ทุà¸à¹† เหตุà¸à¸²à¸£à¸“์ในโครงà¸à¸²à¸£à¸‚องฉัน" - label_user_mail_option_selected: "ทุà¸à¹† เหตุà¸à¸²à¸£à¸“์ในโครงà¸à¸²à¸£à¸—ี่เลือà¸..." - label_user_mail_no_self_notified: "ฉันไม่ต้องà¸à¸²à¸£à¹„ด้รับà¸à¸²à¸£à¹à¸ˆà¹‰à¸‡à¹€à¸•ือนในสิ่งที่ฉันทำเอง" - label_registration_activation_by_email: เปิดบัà¸à¸Šà¸µà¸œà¹ˆà¸²à¸™à¸­à¸µà¹€à¸¡à¸¥à¹Œ - label_registration_manual_activation: อนุมัติโดยผู้บริหารจัดà¸à¸²à¸£ - label_registration_automatic_activation: เปิดบัà¸à¸Šà¸µà¸­à¸±à¸•โนมัติ - label_display_per_page: "ต่อหน้า: %{value}" - label_age: อายุ - label_change_properties: เปลี่ยนคุณสมบัติ - label_general: ทั่วๆ ไป - label_more: อื่น ๆ - label_scm: ตัวจัดà¸à¸²à¸£à¸•้นฉบับ - label_plugins: ส่วนเสริม - label_ldap_authentication: à¸à¸²à¸£à¸¢à¸·à¸™à¸¢à¸±à¸™à¸•ัวตนโดยใช้ LDAP - label_downloads_abbr: D/L - label_optional_description: รายละเอียดเพิ่มเติม - label_add_another_file: เพิ่มà¹à¸Ÿà¹‰à¸¡à¸­à¸·à¹ˆà¸™à¹† - label_preferences: ค่าที่ชอบใจ - label_chronological_order: เรียงจาà¸à¹€à¸à¹ˆà¸²à¹„ปใหม่ - label_reverse_chronological_order: เรียงจาà¸à¹ƒà¸«à¸¡à¹ˆà¹„ปเà¸à¹ˆà¸² - label_planning: à¸à¸²à¸£à¸§à¸²à¸‡à¹à¸œà¸™ - - button_login: เข้าระบบ - button_submit: จัดส่งข้อมูล - button_save: จัดเà¸à¹‡à¸š - button_check_all: เลือà¸à¸—ั้งหมด - button_uncheck_all: ไม่เลือà¸à¸—ั้งหมด - button_delete: ลบ - button_create: สร้าง - button_test: ทดสอบ - button_edit: à¹à¸à¹‰à¹„ข - button_add: เพิ่ม - button_change: เปลี่ยนà¹à¸›à¸¥à¸‡ - button_apply: ประยุà¸à¸•์ใช้ - button_clear: ล้างข้อความ - button_lock: ล็อค - button_unlock: ยà¸à¹€à¸¥à¸´à¸à¸à¸²à¸£à¸¥à¹‡à¸­à¸„ - button_download: ดาวน์โหลด - button_list: รายà¸à¸²à¸£ - button_view: มุมมอง - button_move: ย้าย - button_back: à¸à¸¥à¸±à¸š - button_cancel: ยà¸à¹€à¸¥à¸´à¸ - button_activate: เปิดใช้ - button_sort: จัดเรียง - button_log_time: บันทึà¸à¹€à¸§à¸¥à¸² - button_rollback: ถอยà¸à¸¥à¸±à¸šà¸¡à¸²à¸—ี่รุ่นนี้ - button_watch: เà¸à¹‰à¸²à¸”ู - button_unwatch: เลิà¸à¹€à¸à¹‰à¸²à¸”ู - button_reply: ตอบà¸à¸¥à¸±à¸š - button_archive: เà¸à¹‡à¸šà¹€à¸‚้าโà¸à¸”ัง - button_unarchive: เอาออà¸à¸ˆà¸²à¸à¹‚à¸à¸”ัง - button_reset: เริ่มใหมท - button_rename: เปลี่ยนชื่อ - button_change_password: เปลี่ยนรหัสผ่าน - button_copy: คัดลอภ- button_annotate: หมายเหตุประà¸à¸­à¸š - button_update: ปรับปรุง - button_configure: ปรับà¹à¸•่ง - - status_active: เปิดใช้งานà¹à¸¥à¹‰à¸§ - status_registered: รอà¸à¸²à¸£à¸­à¸™à¸¸à¸¡à¸±à¸•ิ - status_locked: ล็อค - - text_select_mail_notifications: เลือà¸à¸à¸²à¸£à¸à¸£à¸°à¸—ำที่ต้องà¸à¸²à¸£à¹ƒà¸«à¹‰à¸ªà¹ˆà¸‡à¸­à¸µà¹€à¸¡à¸¥à¹Œà¹à¸ˆà¹‰à¸‡. - text_regexp_info: ตัวอย่าง ^[A-Z0-9]+$ - text_min_max_length_info: 0 หมายถึงไม่จำà¸à¸±à¸” - text_project_destroy_confirmation: คุณà¹à¸™à¹ˆà¹ƒà¸ˆà¹„หมว่าต้องà¸à¸²à¸£à¸¥à¸šà¹‚ครงà¸à¸²à¸£à¹à¸¥à¸°à¸‚้อมูลที่เà¸à¸µà¹ˆà¸¢à¸§à¸‚้่อง ? - text_subprojects_destroy_warning: "โครงà¸à¸²à¸£à¸¢à¹ˆà¸­à¸¢: %{value} จะถูà¸à¸¥à¸šà¸”้วย." - text_workflow_edit: เลือà¸à¸šà¸—บาทà¹à¸¥à¸°à¸à¸²à¸£à¸•ิดตาม เพื่อà¹à¸à¹‰à¹„ขลำดับงาน - text_are_you_sure: คุณà¹à¸™à¹ˆà¹ƒà¸ˆà¹„หม ? - text_tip_issue_begin_day: งานที่เริ่มวันนี้ - text_tip_issue_end_day: งานที่จบวันนี้ - text_tip_issue_begin_end_day: งานที่เริ่มà¹à¸¥à¸°à¸ˆà¸šà¸§à¸±à¸™à¸™à¸µà¹‰ - text_project_identifier_info: 'ภาษาอังà¸à¸¤à¸©à¸•ัวเล็à¸(a-z), ตัวเลข(0-9) à¹à¸¥à¸°à¸‚ีด (-) เท่านั้น.
    เมื่อจัดเà¸à¹‡à¸šà¹à¸¥à¹‰à¸§, ชื่อเฉพาะไม่สามารถเปลี่ยนà¹à¸›à¸¥à¸‡à¹„ด้' - text_caracters_maximum: "สูงสุด %{count} ตัวอัà¸à¸©à¸£." - text_caracters_minimum: "ต้องยาวอย่างน้อย %{count} ตัวอัà¸à¸©à¸£." - text_length_between: "ความยาวระหว่าง %{min} ถึง %{max} ตัวอัà¸à¸©à¸£." - text_tracker_no_workflow: ไม่ได้บัà¸à¸à¸±à¸•ิลำดับงานสำหรับà¸à¸²à¸£à¸•ิดตามนี้ - text_unallowed_characters: ตัวอัà¸à¸©à¸£à¸•้องห้าม - text_comma_separated: ใส่ได้หลายค่า โดยคั่นด้วยลูà¸à¸™à¹‰à¸³( ,). - text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages - text_issue_added: "ปัà¸à¸«à¸² %{id} ถูà¸à¹à¸ˆà¹‰à¸‡à¹‚ดย %{author}." - text_issue_updated: "ปัà¸à¸«à¸² %{id} ถูà¸à¸›à¸£à¸±à¸šà¸›à¸£à¸¸à¸‡à¹‚ดย %{author}." - text_wiki_destroy_confirmation: คุณà¹à¸™à¹ˆà¹ƒà¸ˆà¸«à¸£à¸·à¸­à¸§à¹ˆà¸²à¸•้องà¸à¸²à¸£à¸¥à¸š wiki นี้พร้อมทั้งเนี้อหา? - text_issue_category_destroy_question: "บางปัà¸à¸«à¸² (%{count}) อยู่ในประเภทนี้. คุณต้องà¸à¸²à¸£à¸—ำอย่างไร ?" - text_issue_category_destroy_assignments: ลบประเภทนี้ - text_issue_category_reassign_to: ระบุปัà¸à¸«à¸²à¹ƒà¸™à¸›à¸£à¸°à¹€à¸ à¸—นี้ - text_user_mail_option: "ในโครงà¸à¸²à¸£à¸—ี่ไม่ได้เลือà¸, คุณจะได้รับà¸à¸²à¸£à¹à¸ˆà¹‰à¸‡à¹€à¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸ªà¸´à¹ˆà¸‡à¸—ี่คุณเà¸à¹‰à¸²à¸”ูหรือมีส่วนเà¸à¸µà¹ˆà¸¢à¸§à¸‚้อง (เช่นปัà¸à¸«à¸²à¸—ี่คุณà¹à¸ˆà¹‰à¸‡à¹„ว้หรือได้รับมอบหมาย)." - text_no_configuration_data: "บทบาท, à¸à¸²à¸£à¸•ิดตาม, สถานะปัà¸à¸«à¸² à¹à¸¥à¸°à¸¥à¸³à¸”ับงานยังไม่ได้ถูà¸à¸•ั้งค่า.\nขอà¹à¸™à¸°à¸™à¸³à¹ƒà¸«à¹‰à¹‚หลดค่าเริ่มต้น. คุณสามารถà¹à¸à¹‰à¹„ขค่าได้หลังจาà¸à¹‚หลดà¹à¸¥à¹‰à¸§." - text_load_default_configuration: โหลดค่าเริ่มต้น - text_status_changed_by_changeset: "ประยุà¸à¸•์ใช้ในà¸à¸¥à¸¸à¹ˆà¸¡à¸à¸²à¸£à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹à¸›à¸¥à¸‡ %{value}." - text_issues_destroy_confirmation: 'คุณà¹à¸™à¹ˆà¹ƒà¸ˆà¹„หมว่าต้องà¸à¸²à¸£à¸¥à¸šà¸›à¸±à¸à¸«à¸²(ทั้งหลาย)ที่เลือà¸à¹„ว้?' - text_select_project_modules: 'เลือà¸à¸ªà¹ˆà¸§à¸™à¸›à¸£à¸°à¸à¸­à¸šà¸—ี่ต้องà¸à¸²à¸£à¹ƒà¸Šà¹‰à¸‡à¸²à¸™à¸ªà¸³à¸«à¸£à¸±à¸šà¹‚ครงà¸à¸²à¸£à¸™à¸µà¹‰:' - text_default_administrator_account_changed: ค่าเริ่มต้นของบัà¸à¸Šà¸µà¸œà¸¹à¹‰à¸šà¸£à¸´à¸«à¸²à¸£à¸ˆà¸±à¸”à¸à¸²à¸£à¸–ูà¸à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹à¸›à¸¥à¸‡ - text_file_repository_writable: ที่เà¸à¹‡à¸šà¸•้นฉบับสามารถเขียนได้ - text_rmagick_available: RMagick มีให้ใช้ (เป็นตัวเลือà¸) - text_destroy_time_entries_question: "%{hours} ชั่วโมงที่ถูà¸à¹à¸ˆà¹‰à¸‡à¹ƒà¸™à¸›à¸±à¸à¸«à¸²à¸™à¸µà¹‰à¸ˆà¸°à¹‚ดนลบ. คุณต้องà¸à¸²à¸£à¸—ำอย่างไร?" - text_destroy_time_entries: ลบเวลาที่รายงานไว้ - text_assign_time_entries_to_project: ระบุเวลาที่ใช้ในโครงà¸à¸²à¸£à¸™à¸µà¹‰ - text_reassign_time_entries: 'ระบุเวลาที่ใช้ในโครงà¸à¸²à¸£à¸™à¸µà¹ˆà¸­à¸µà¸à¸„รั้ง:' - - default_role_manager: ผู้จัดà¸à¸²à¸£ - default_role_developer: ผู้พัฒนา - default_role_reporter: ผู้รายงาน - default_tracker_bug: บั๊ภ- default_tracker_feature: ลัà¸à¸©à¸“ะเด่น - default_tracker_support: สนับสนุน - default_issue_status_new: เà¸à¸´à¸”ขึ้น - default_issue_status_in_progress: In Progress - default_issue_status_resolved: ดำเนินà¸à¸²à¸£ - default_issue_status_feedback: รอคำตอบ - default_issue_status_closed: จบ - default_issue_status_rejected: ยà¸à¹€à¸¥à¸´à¸ - default_doc_category_user: เอà¸à¸ªà¸²à¸£à¸‚องผู้ใช้ - default_doc_category_tech: เอà¸à¸ªà¸²à¸£à¸—างเทคนิค - default_priority_low: ต่ำ - default_priority_normal: ปà¸à¸•ิ - default_priority_high: สูง - default_priority_urgent: เร่งด่วน - default_priority_immediate: ด่วนมาภ- default_activity_design: ออà¸à¹à¸šà¸š - default_activity_development: พัฒนา - - enumeration_issue_priorities: ความสำคัà¸à¸‚องปัà¸à¸«à¸² - enumeration_doc_categories: ประเภทเอà¸à¸ªà¸²à¸£ - enumeration_activities: à¸à¸´à¸ˆà¸à¸£à¸£à¸¡ (ใช้ในà¸à¸²à¸£à¸•ิดตามเวลา) - label_and_its_subprojects: "%{value} and its subprojects" - mail_body_reminder: "%{count} issue(s) that are assigned to you are due in the next %{days} days:" - mail_subject_reminder: "%{count} issue(s) due in the next %{days} days" - text_user_wrote: "%{value} 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: "%{count} 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/configuration.yml and restart the application to enable them." - field_parent_title: Parent page - label_issue_watchers: Watchers - 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_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: "%{value}'s activity" - label_updated_time_by: "Updated by %{author} %{age} 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: "%{count} 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 - setting_file_max_size_displayed: Max size of text files displayed inline - field_watcher: Watcher - setting_openid: Allow OpenID login and registration - field_identity_url: OpenID URL - label_login_with_open_id_option: or login with OpenID - field_content: Content - label_descending: Descending - label_sort: Sort - label_ascending: Ascending - label_date_from_to: From %{start} to %{end} - label_greater_or_equal: ">=" - label_less_or_equal: <= - text_wiki_page_destroy_question: This page has %{descendants} child page(s) and descendant(s). What do you want to do? - text_wiki_page_reassign_children: Reassign child pages to this parent page - text_wiki_page_nullify_children: Keep child pages as root pages - text_wiki_page_destroy_children: Delete child pages and all their descendants - setting_password_min_length: Minimum password length - field_group_by: Group results by - mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated" - label_wiki_content_added: Wiki page added - mail_subject_wiki_content_added: "'%{id}' wiki page has been added" - mail_body_wiki_content_added: The '%{id}' wiki page has been added by %{author}. - label_wiki_content_updated: Wiki page updated - mail_body_wiki_content_updated: The '%{id}' wiki page has been updated by %{author}. - permission_add_project: Create project - setting_new_project_user_role_id: Role given to a non-admin user who creates a project - label_view_all_revisions: View all revisions - label_tag: Tag - label_branch: Branch - error_no_tracker_in_project: No tracker is associated to this project. Please check the Project settings. - error_no_default_issue_status: No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses"). - text_journal_changed: "%{label} changed from %{old} to %{new}" - text_journal_set_to: "%{label} set to %{value}" - text_journal_deleted: "%{label} deleted (%{old})" - label_group_plural: Groups - label_group: Group - label_group_new: New group - label_time_entry_plural: Spent time - text_journal_added: "%{label} %{value} added" - field_active: Active - enumeration_system_activity: System Activity - permission_delete_issue_watchers: Delete watchers - version_status_closed: closed - version_status_locked: locked - version_status_open: open - error_can_not_reopen_issue_on_closed_version: An issue assigned to a closed version can not be reopened - label_user_anonymous: Anonymous - button_move_and_follow: Move and follow - setting_default_projects_modules: Default enabled modules for new projects - setting_gravatar_default: Default Gravatar image - field_sharing: Sharing - label_version_sharing_hierarchy: With project hierarchy - label_version_sharing_system: With all projects - label_version_sharing_descendants: With subprojects - label_version_sharing_tree: With project tree - label_version_sharing_none: Not shared - error_can_not_archive_project: This project can not be archived - button_duplicate: Duplicate - button_copy_and_follow: Copy and follow - label_copy_source: Source - setting_issue_done_ratio: Calculate the issue done ratio with - setting_issue_done_ratio_issue_status: Use the issue status - error_issue_done_ratios_not_updated: Issue done ratios not updated. - error_workflow_copy_target: Please select target tracker(s) and role(s) - setting_issue_done_ratio_issue_field: Use the issue field - label_copy_same_as_target: Same as target - label_copy_target: Target - notice_issue_done_ratios_updated: Issue done ratios updated. - error_workflow_copy_source: Please select a source tracker or role - label_update_issue_done_ratios: Update issue done ratios - setting_start_of_week: Start calendars on - permission_view_issues: View Issues - label_display_used_statuses_only: Only display statuses that are used by this tracker - label_revision_id: Revision %{value} - label_api_access_key: API access key - label_api_access_key_created_on: API access key created %{value} ago - label_feeds_access_key: RSS access key - notice_api_access_key_reseted: Your API access key was reset. - setting_rest_api_enabled: Enable REST web service - label_missing_api_access_key: Missing an API access key - label_missing_feeds_access_key: Missing a RSS access key - button_show: Show - text_line_separated: Multiple values allowed (one line for each value). - setting_mail_handler_body_delimiters: Truncate emails after one of these lines - permission_add_subprojects: Create subprojects - label_subproject_new: New subproject - text_own_membership_delete_confirmation: |- - You are about to remove some or all of your permissions and may no longer be able to edit this project after that. - Are you sure you want to continue? - label_close_versions: Close completed versions - label_board_sticky: Sticky - label_board_locked: Locked - permission_export_wiki_pages: Export wiki pages - setting_cache_formatted_text: Cache formatted text - permission_manage_project_activities: Manage project activities - error_unable_delete_issue_status: Unable to delete issue status - label_profile: Profile - permission_manage_subtasks: Manage subtasks - field_parent_issue: Parent task - label_subtask_plural: Subtasks - label_project_copy_notifications: Send email notifications during the project copy - error_can_not_delete_custom_field: Unable to delete custom field - error_unable_to_connect: Unable to connect (%{value}) - error_can_not_remove_role: This role is in use and can not be deleted. - error_can_not_delete_tracker: This tracker contains issues and can't be deleted. - field_principal: Principal - label_my_page_block: My page block - notice_failed_to_save_members: "Failed to save member(s): %{errors}." - text_zoom_out: Zoom out - text_zoom_in: Zoom in - notice_unable_delete_time_entry: Unable to delete time log entry. - label_overall_spent_time: Overall spent time - field_time_entries: Log time - project_module_gantt: Gantt - project_module_calendar: Calendar - button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" - text_are_you_sure_with_children: Delete issue and all child issues? - field_text: Text field - label_user_mail_option_only_owner: Only for things I am the owner of - setting_default_notification_option: Default notification option - label_user_mail_option_only_my_events: Only for things I watch or I'm involved in - label_user_mail_option_only_assigned: Only for things I am assigned to - label_user_mail_option_none: No events - field_member_of_group: Assignee's group - field_assigned_to_role: Assignee's role - notice_not_authorized_archived_project: The project you're trying to access has been archived. - label_principal_search: "Search for user or group:" - label_user_search: "Search for user:" - field_visible: Visible - setting_emails_header: Emails header - setting_commit_logtime_activity_id: Activity for logged time - text_time_logged_by_changeset: Applied in changeset %{value}. - setting_commit_logtime_enabled: Enable time logging - notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) - setting_gantt_items_limit: Maximum number of items displayed on the gantt chart - field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text - text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. - label_my_queries: My custom queries - text_journal_changed_no_detail: "%{label} updated" - label_news_comment_added: Comment added to a news - button_expand_all: Expand all - button_collapse_all: Collapse all - label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee - label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author - label_bulk_edit_selected_time_entries: Bulk edit selected time entries - text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? - label_role_anonymous: Anonymous - label_role_non_member: Non member - label_issue_note_added: Note added - label_issue_status_updated: Status updated - label_issue_priority_updated: Priority updated - label_issues_visibility_own: Issues created by or assigned to the user - field_issues_visibility: Issues visibility - label_issues_visibility_all: All issues - permission_set_own_issues_private: Set own issues public or private - field_is_private: Private - permission_set_issues_private: Set issues public or private - label_issues_visibility_public: All non private issues - text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). - field_commit_logs_encoding: Commit messages encoding - field_scm_path_encoding: Path encoding - text_scm_path_encoding_note: "Default: UTF-8" - field_path_to_repository: Path to repository - field_root_directory: Root directory - field_cvs_module: Module - field_cvsroot: CVSROOT - text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) - text_scm_command: Command - text_scm_command_version: Version - label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. - notice_issue_successful_create: Issue %{id} created. - label_between: between - setting_issue_group_assignment: Allow issue assignment to groups - label_diff: diff - text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) - description_query_sort_criteria_direction: Sort direction - description_project_scope: Search scope - description_filter: Filter - description_user_mail_notification: Mail notification settings - description_date_from: Enter start date - description_message_content: Message content - description_available_columns: Available Columns - description_date_range_interval: Choose range by selecting start and end date - description_issue_category_reassign: Choose issue category - description_search: Searchfield - description_notes: Notes - description_date_range_list: Choose range from list - description_choose_project: Projects - description_date_to: Enter end date - description_query_sort_criteria_attribute: Sort attribute - description_wiki_subpages_reassign: Choose new parent page - description_selected_columns: Selected Columns - label_parent_revision: Parent - label_child_revision: Child - error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. - setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues - button_edit_section: Edit this section - setting_repositories_encodings: Attachments and repositories encodings - description_all_columns: All Columns - button_export: Export - label_export_options: "%{export_format} export options" - error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/5a/5a549df68d552afbb24a2891641ac0c3cca19477.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/5a/5a549df68d552afbb24a2891641ac0c3cca19477.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,32 @@ +
    +<% if User.current.allowed_to?(:edit_documents, @project) %> +<%= link_to l(:button_edit), edit_document_path(@document), :class => 'icon icon-edit', :accesskey => accesskey(:edit) %> +<% end %> +<% if User.current.allowed_to?(:delete_documents, @project) %> +<%= delete_link document_path(@document) %> +<% end %> +
    + +

    <%=h @document.title %>

    + +

    <%=h @document.category.name %>
    +<%= format_date @document.created_on %>

    +
    +<%= textilizable @document, :description, :attachments => @document.attachments %> +
    + +

    <%= l(:label_attachment_plural) %>

    +<%= link_to_attachments @document %> + +<% if authorize_for('documents', 'add_attachment') %> +

    <%= link_to l(:label_attachment_new), {}, :onclick => "$('#add_attachment_form').show(); return false;", + :id => 'attach_files_link' %>

    + <%= form_tag({ :controller => 'documents', :action => 'add_attachment', :id => @document }, :multipart => true, :id => "add_attachment_form", :style => "display:none;") do %> +
    +

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

    +
    + <%= submit_tag l(:button_add) %> + <% end %> +<% end %> + +<% html_title @document.title -%> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/5a/5a62234b780985b2a0def003065d9dfb9410c414.svn-base --- a/.svn/pristine/5a/5a62234b780985b2a0def003065d9dfb9410c414.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -= Redmine - -Redmine is a flexible project management web application written using Ruby on Rails framework. - -More details can be found at http://www.redmine.org diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/5a/5a6564a54f44ce0a2f2a1f8ee7e59d575551ecf9.svn-base Binary file .svn/pristine/5a/5a6564a54f44ce0a2f2a1f8ee7e59d575551ecf9.svn-base has changed diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/5a/5aa3c5fccf79f48430b6e2714bcb9f02b7096762.svn-base --- a/.svn/pristine/5a/5aa3c5fccf79f48430b6e2714bcb9f02b7096762.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,55 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) - -class CommentsControllerTest < ActionController::TestCase - fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules, :news, :comments - - def setup - User.current = nil - end - - def test_add_comment - @request.session[:user_id] = 2 - post :create, :id => 1, :comment => { :comments => 'This is a test comment' } - assert_redirected_to '/news/1' - - comment = News.find(1).comments.find(:first, :order => 'created_on DESC') - assert_not_nil comment - assert_equal 'This is a test comment', comment.comments - assert_equal User.find(2), comment.author - end - - def test_empty_comment_should_not_be_added - @request.session[:user_id] = 2 - assert_no_difference 'Comment.count' do - post :create, :id => 1, :comment => { :comments => '' } - assert_response :redirect - assert_redirected_to '/news/1' - end - end - - def test_destroy_comment - comments_count = News.find(1).comments.size - @request.session[:user_id] = 2 - delete :destroy, :id => 1, :comment_id => 2 - assert_redirected_to '/news/1' - assert_nil Comment.find_by_id(2) - assert_equal comments_count - 1, News.find(1).comments.size - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/5a/5ab5d51a3e643e063b36d3e9325764dc09e247cb.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/5a/5ab5d51a3e643e063b36d3e9325764dc09e247cb.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,6 @@ +<%= error_messages_for 'auth_source' %> + +
    +

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

    +

    <%= f.check_box :onthefly_register, :label => :field_onthefly %>

    +
    diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/5b/5b2317c90ed81995ffba20202367b84382beccbf.svn-base --- a/.svn/pristine/5b/5b2317c90ed81995ffba20202367b84382beccbf.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,95 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) - -class WikiContentTest < ActiveSupport::TestCase - fixtures :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions, :users - - def setup - @wiki = Wiki.find(1) - @page = @wiki.pages.first - end - - def test_create - page = WikiPage.new(:wiki => @wiki, :title => "Page") - page.content = WikiContent.new(:text => "Content text", :author => User.find(1), :comments => "My comment") - assert page.save - page.reload - - content = page.content - assert_kind_of WikiContent, content - assert_equal 1, content.version - assert_equal 1, content.versions.length - assert_equal "Content text", content.text - assert_equal "My comment", content.comments - assert_equal User.find(1), content.author - assert_equal content.text, content.versions.last.text - end - - def test_create_should_send_email_notification - Setting.notified_events = ['wiki_content_added'] - ActionMailer::Base.deliveries.clear - page = WikiPage.new(:wiki => @wiki, :title => "A new page") - page.content = WikiContent.new(:text => "Content text", :author => User.find(1), :comments => "My comment") - assert page.save - - assert_equal 1, ActionMailer::Base.deliveries.size - end - - def test_update - content = @page.content - version_count = content.version - content.text = "My new content" - assert content.save - content.reload - assert_equal version_count+1, content.version - assert_equal version_count+1, content.versions.length - end - - def test_update_should_send_email_notification - Setting.notified_events = ['wiki_content_updated'] - ActionMailer::Base.deliveries.clear - content = @page.content - content.text = "My new content" - assert content.save - - assert_equal 1, ActionMailer::Base.deliveries.size - end - - def test_fetch_history - assert !@page.content.versions.empty? - @page.content.versions.each do |version| - assert_kind_of String, version.text - end - end - - def test_large_text_should_not_be_truncated_to_64k - page = WikiPage.new(:wiki => @wiki, :title => "Big page") - page.content = WikiContent.new(:text => "a" * 500.kilobyte, :author => User.find(1)) - assert page.save - page.reload - assert_equal 500.kilobyte, page.content.text.size - end - - def test_current_version - content = WikiContent.find(11) - assert_equal true, content.current_version? - assert_equal true, content.versions.first(:order => 'version DESC').current_version? - assert_equal false, content.versions.first(:order => 'version ASC').current_version? - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/5b/5b5912958e71fec54e66733c9c7981555c23902f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/5b/5b5912958e71fec54e66733c9c7981555c23902f.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,50 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class Redmine::ApiTest::HttpBasicLoginWithApiTokenTest < Redmine::ApiTest::Base + fixtures :projects, :trackers, :issue_statuses, :issues, + :enumerations, :users, :issue_categories, + :projects_trackers, + :roles, + :member_roles, + :members, + :enabled_modules + + def setup + Setting.rest_api_enabled = '1' + Setting.login_required = '1' + end + + def teardown + Setting.rest_api_enabled = '0' + Setting.login_required = '0' + end + + # Using the NewsController because it's a simple API. + context "get /news" do + + context "in :xml format" do + should_allow_http_basic_auth_with_key(:get, "/news.xml") + end + + context "in :json format" do + should_allow_http_basic_auth_with_key(:get, "/news.json") + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/5b/5b67cbc43876349cb50763840008cc0544dfb9b5.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/5b/5b67cbc43876349cb50763840008cc0544dfb9b5.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,136 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class GroupTest < ActiveSupport::TestCase + fixtures :projects, :trackers, :issue_statuses, :issues, + :enumerations, :users, + :projects_trackers, + :roles, + :member_roles, + :members, + :groups_users + + include Redmine::I18n + + def test_create + g = Group.new(:name => 'New group') + assert g.save + g.reload + assert_equal 'New group', g.name + end + + def test_name_should_accept_255_characters + name = 'a' * 255 + g = Group.new(:name => name) + assert g.save + g.reload + assert_equal name, g.name + end + + def test_blank_name_error_message + set_language_if_valid 'en' + g = Group.new + assert !g.save + assert_include "Name can't be blank", g.errors.full_messages + end + + def test_blank_name_error_message_fr + set_language_if_valid 'fr' + str = "Nom doit \xc3\xaatre renseign\xc3\xa9(e)" + str.force_encoding('UTF-8') if str.respond_to?(:force_encoding) + g = Group.new + assert !g.save + assert_include str, g.errors.full_messages + end + + def test_group_roles_should_be_given_to_added_user + group = Group.find(11) + user = User.find(9) + project = Project.first + + Member.create!(:principal => group, :project => project, :role_ids => [1, 2]) + group.users << user + assert user.member_of?(project) + end + + def test_new_roles_should_be_given_to_existing_user + group = Group.find(11) + user = User.find(9) + project = Project.first + + group.users << user + m = Member.create!(:principal => group, :project => project, :role_ids => [1, 2]) + assert user.member_of?(project) + end + + def test_user_roles_should_updated_when_updating_user_ids + group = Group.find(11) + user = User.find(9) + project = Project.first + + Member.create!(:principal => group, :project => project, :role_ids => [1, 2]) + group.user_ids = [user.id] + group.save! + assert User.find(9).member_of?(project) + + group.user_ids = [1] + group.save! + assert !User.find(9).member_of?(project) + end + + def test_user_roles_should_updated_when_updating_group_roles + group = Group.find(11) + user = User.find(9) + project = Project.first + group.users << user + m = Member.create!(:principal => group, :project => project, :role_ids => [1]) + assert_equal [1], user.reload.roles_for_project(project).collect(&:id).sort + + m.role_ids = [1, 2] + assert_equal [1, 2], user.reload.roles_for_project(project).collect(&:id).sort + + m.role_ids = [2] + assert_equal [2], user.reload.roles_for_project(project).collect(&:id).sort + + m.role_ids = [1] + assert_equal [1], user.reload.roles_for_project(project).collect(&:id).sort + end + + def test_user_memberships_should_be_removed_when_removing_group_membership + assert User.find(8).member_of?(Project.find(5)) + Member.find_by_project_id_and_user_id(5, 10).destroy + assert !User.find(8).member_of?(Project.find(5)) + end + + def test_user_roles_should_be_removed_when_removing_user_from_group + assert User.find(8).member_of?(Project.find(5)) + User.find(8).groups = [] + assert !User.find(8).member_of?(Project.find(5)) + end + + def test_destroy_should_unassign_issues + group = Group.first + Issue.update_all(["assigned_to_id = ?", group.id], 'id = 1') + + assert group.destroy + assert group.destroyed? + + assert_equal nil, Issue.find(1).assigned_to_id + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/5b/5ba499ead951b0f7092a76c82d09ab18e9ef0b47.svn-base --- a/.svn/pristine/5b/5ba499ead951b0f7092a76c82d09ab18e9ef0b47.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +0,0 @@ -# $Id: testber.rb 57 2006-04-18 00:18:48Z blackhedd $ -# -# - - -$:.unshift "lib" - -require 'net/ldap' -require 'stringio' - - -class TestBer < Test::Unit::TestCase - - def setup - end - - # TODO: Add some much bigger numbers - # 5000000000 is a Bignum, which hits different code. - def test_ber_integers - assert_equal( "\002\001\005", 5.to_ber ) - assert_equal( "\002\002\203t", 500.to_ber ) - assert_equal( "\002\003\203\206P", 50000.to_ber ) - assert_equal( "\002\005\222\320\227\344\000", 5000000000.to_ber ) - end - - def test_ber_parsing - assert_equal( 6, "\002\001\006".read_ber( Net::LDAP::AsnSyntax )) - assert_equal( "testing", "\004\007testing".read_ber( Net::LDAP::AsnSyntax )) - end - - - def test_ber_parser_on_ldap_bind_request - s = StringIO.new "0$\002\001\001`\037\002\001\003\004\rAdministrator\200\vad_is_bogus" - assert_equal( [1, [3, "Administrator", "ad_is_bogus"]], s.read_ber( Net::LDAP::AsnSyntax )) - end - - - - -end - - diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/5b/5ba4b3bae1ac655e0a9cb68df6a092b2c3782db0.svn-base --- a/.svn/pristine/5b/5ba4b3bae1ac655e0a9cb68df6a092b2c3782db0.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,13 +0,0 @@ -class <%= migration_name %> < ActiveRecord::Migration - def self.up - create_table :<%= table_name %> do |t| -<% for attribute in attributes -%> - t.column :<%= attribute.name %>, :<%= attribute.type %> -<% end -%> - end - end - - def self.down - drop_table :<%= table_name %> - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/5c/5c0d725f7c169077f48040da16fb4cb01381f158.svn-base --- a/.svn/pristine/5c/5c0d725f7c169077f48040da16fb4cb01381f158.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,13 +0,0 @@ -issue_relation_001: - id: 1 - issue_from_id: 10 - issue_to_id: 9 - relation_type: blocks - delay: -issue_relation_002: - id: 2 - issue_from_id: 2 - issue_to_id: 3 - relation_type: relates - delay: - diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/5c/5c1734e1eca1c54bac30b17ddeecdedbc05caf30.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/5c/5c1734e1eca1c54bac30b17ddeecdedbc05caf30.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,35 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + +namespace :db do + desc 'Encrypts SCM and LDAP passwords in the database.' + task :encrypt => :environment do + unless (Repository.encrypt_all(:password) && + AuthSource.encrypt_all(:account_password)) + raise "Some objects could not be saved after encryption, update was rollback'ed." + end + end + + desc 'Decrypts SCM and LDAP passwords in the database.' + task :decrypt => :environment do + unless (Repository.decrypt_all(:password) && + AuthSource.decrypt_all(:account_password)) + raise "Some objects could not be saved after decryption, update was rollback'ed." + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/5d/5d08d1e25f5add23341eebe953f9997482bf5a56.svn-base --- a/.svn/pristine/5d/5d08d1e25f5add23341eebe953f9997482bf5a56.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,69 +0,0 @@ -# $Id: testldif.rb 61 2006-04-18 20:55:55Z blackhedd $ -# -# - - -$:.unshift "lib" - -require 'test/unit' - -require 'net/ldap' -require 'net/ldif' - -require 'sha1' -require 'base64' - -class TestLdif < Test::Unit::TestCase - - TestLdifFilename = "tests/testdata.ldif" - - def test_empty_ldif - ds = Net::LDAP::Dataset::read_ldif( StringIO.new ) - assert_equal( true, ds.empty? ) - end - - def test_ldif_with_comments - str = ["# Hello from LDIF-land", "# This is an unterminated comment"] - io = StringIO.new( str[0] + "\r\n" + str[1] ) - ds = Net::LDAP::Dataset::read_ldif( io ) - assert_equal( str, ds.comments ) - end - - def test_ldif_with_password - psw = "goldbricks" - hashed_psw = "{SHA}" + Base64::encode64( SHA1.new(psw).digest ).chomp - - ldif_encoded = Base64::encode64( hashed_psw ).chomp - ds = Net::LDAP::Dataset::read_ldif( StringIO.new( "dn: Goldbrick\r\nuserPassword:: #{ldif_encoded}\r\n\r\n" )) - recovered_psw = ds["Goldbrick"][:userpassword].shift - assert_equal( hashed_psw, recovered_psw ) - end - - def test_ldif_with_continuation_lines - ds = Net::LDAP::Dataset::read_ldif( StringIO.new( "dn: abcdefg\r\n hijklmn\r\n\r\n" )) - assert_equal( true, ds.has_key?( "abcdefg hijklmn" )) - end - - # TODO, INADEQUATE. We need some more tests - # to verify the content. - def test_ldif - File.open( TestLdifFilename, "r" ) {|f| - ds = Net::LDAP::Dataset::read_ldif( f ) - assert_equal( 13, ds.length ) - } - end - - # TODO, need some tests. - # Must test folded lines and base64-encoded lines as well as normal ones. - def test_to_ldif - File.open( TestLdifFilename, "r" ) {|f| - ds = Net::LDAP::Dataset::read_ldif( f ) - ds.to_ldif - assert_equal( true, false ) # REMOVE WHEN WE HAVE SOME TESTS HERE. - } - end - - -end - - diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/5d/5d26bd43e6982490a87abc50362d543225e9666c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/5d/5d26bd43e6982490a87abc50362d543225e9666c.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,37 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class Change < ActiveRecord::Base + belongs_to :changeset + + validates_presence_of :changeset_id, :action, :path + before_save :init_path + before_validation :replace_invalid_utf8_of_path + + def relative_path + changeset.repository.relative_path(path) + end + + def replace_invalid_utf8_of_path + self.path = Redmine::CodesetUtil.replace_invalid_utf8(self.path) + self.from_path = Redmine::CodesetUtil.replace_invalid_utf8(self.from_path) + end + + def init_path + self.path ||= "" + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/5d/5d3bf2e1b9c594ae744623dfe07d0b12193cbe25.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/5d/5d3bf2e1b9c594ae744623dfe07d0b12193cbe25.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,1110 @@ +# Polish translations for Ruby on Rails +# by Jacek Becela (jacek.becela@gmail.com, http://github.com/ncr) +# by Krzysztof Podejma (kpodejma@customprojects.pl, http://www.customprojects.pl) + +pl: + number: + format: + separator: "," + delimiter: " " + precision: 2 + currency: + format: + format: "%n %u" + unit: "PLN" + percentage: + format: + delimiter: "" + precision: + format: + delimiter: "" + human: + format: + delimiter: "" + precision: 3 + storage_units: + format: "%n %u" + units: + byte: + one: "B" + other: "B" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + + direction: ltr + date: + formats: + default: "%Y-%m-%d" + short: "%d %b" + long: "%d %B %Y" + + day_names: [Niedziela, PoniedziaÅ‚ek, Wtorek, Åšroda, Czwartek, PiÄ…tek, Sobota] + abbr_day_names: [nie, pon, wto, Å›ro, czw, pia, sob] + + month_names: [~, StyczeÅ„, Luty, Marzec, KwiecieÅ„, Maj, Czerwiec, Lipiec, SierpieÅ„, WrzesieÅ„, Październik, Listopad, GrudzieÅ„] + abbr_month_names: [~, sty, lut, mar, kwi, maj, cze, lip, sie, wrz, paź, lis, gru] + order: + - :year + - :month + - :day + + time: + formats: + default: "%a, %d %b %Y, %H:%M:%S %z" + time: "%H:%M" + short: "%d %b, %H:%M" + long: "%d %B %Y, %H:%M" + am: "przed poÅ‚udniem" + pm: "po poÅ‚udniu" + + datetime: + distance_in_words: + half_a_minute: "pół minuty" + less_than_x_seconds: + one: "mniej niż sekundÄ™" + few: "mniej niż %{count} sekundy" + other: "mniej niż %{count} sekund" + x_seconds: + one: "sekundÄ™" + few: "%{count} sekundy" + other: "%{count} sekund" + less_than_x_minutes: + one: "mniej niż minutÄ™" + few: "mniej niż %{count} minuty" + other: "mniej niż %{count} minut" + x_minutes: + one: "minutÄ™" + few: "%{count} minuty" + other: "%{count} minut" + about_x_hours: + one: "okoÅ‚o godziny" + other: "okoÅ‚o %{count} godzin" + x_hours: + one: "1 godzina" + other: "%{count} godzin" + x_days: + one: "1 dzieÅ„" + other: "%{count} dni" + about_x_months: + one: "okoÅ‚o miesiÄ…ca" + other: "okoÅ‚o %{count} miesiÄ™cy" + x_months: + one: "1 miesiÄ…c" + few: "%{count} miesiÄ…ce" + other: "%{count} miesiÄ™cy" + about_x_years: + one: "okoÅ‚o roku" + other: "okoÅ‚o %{count} lat" + over_x_years: + one: "ponad rok" + few: "ponad %{count} lata" + other: "ponad %{count} lat" + almost_x_years: + one: "prawie 1 rok" + few: "prawie %{count} lata" + other: "prawie %{count} lat" + + activerecord: + errors: + template: + header: + one: "%{model} nie zostaÅ‚ zachowany z powodu jednego błędu" + other: "%{model} nie zostaÅ‚ zachowany z powodu %{count} błędów" + body: "Błędy dotyczÄ… nastÄ™pujÄ…cych pól:" + messages: + inclusion: "nie znajduje siÄ™ na liÅ›cie dopuszczalnych wartoÅ›ci" + exclusion: "znajduje siÄ™ na liÅ›cie zabronionych wartoÅ›ci" + invalid: "jest nieprawidÅ‚owe" + confirmation: "nie zgadza siÄ™ z potwierdzeniem" + accepted: "musi być zaakceptowane" + empty: "nie może być puste" + blank: "nie może być puste" + too_long: "jest za dÅ‚ugie (maksymalnie %{count} znaków)" + too_short: "jest za krótkie (minimalnie %{count} znaków)" + wrong_length: "jest nieprawidÅ‚owej dÅ‚ugoÅ›ci (powinna wynosić %{count} znaków)" + taken: "jest już zajÄ™te" + not_a_number: "nie jest liczbÄ…" + greater_than: "musi być wiÄ™ksze niż %{count}" + greater_than_or_equal_to: "musi być wiÄ™ksze lub równe %{count}" + equal_to: "musi być równe %{count}" + less_than: "musi być mniejsze niż %{count}" + less_than_or_equal_to: "musi być mniejsze lub równe %{count}" + odd: "musi być nieparzyste" + even: "musi być parzyste" + greater_than_start_date: "musi być wiÄ™ksze niż poczÄ…tkowa data" + not_same_project: "nie należy do tego samego projektu" + circular_dependency: "Ta relacja może wytworzyć zapÄ™tlonÄ… zależność" + cant_link_an_issue_with_a_descendant: "Zagadnienie nie może zostać powiÄ…zane z jednym z wÅ‚asnych podzagadnieÅ„" + + support: + array: + sentence_connector: "i" + skip_last_comma: true + + # 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, + # Wiktor Wandachowicz , 2010 + + actionview_instancetag_blank_option: ProszÄ™ wybrać + actionview_instancetag_blank_option: ProszÄ™ wybierz + + 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: Dziennik + button_login: Login + button_move: PrzenieÅ› + button_quote: Cytuj + button_rename: ZmieÅ„ nazwÄ™ + button_reply: Odpowiedz + button_reset: Resetuj + button_rollback: Przywróć do tej wersji + button_save: Zapisz + button_sort: Sortuj + button_submit: WyÅ›lij + button_test: Testuj + button_unarchive: Przywróć 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_in_progress: W toku + 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_developer: Programista + default_role_manager: Kierownik + default_role_reporter: ZgÅ‚aszajÄ…cy + 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: %{value}" + error_issue_not_found_in_project: 'Zagadnienie 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: %{value}" + error_scm_not_found: "Obiekt lub wersja nie zostaÅ‚y znalezione w repozytorium." + field_account: Konto + field_activity: Aktywność + field_admin: Administrator + field_assignable: Zagadnienia mogÄ… być przypisane do tej roli + field_assigned_to: Przypisany do + field_attr_firstname: ImiÄ™ atrybut + field_attr_lastname: Nazwisko atrybut + field_attr_login: Login atrybut + field_attr_mail: E-mail atrybut + field_auth_source: Tryb identyfikacji + field_author: Autor + field_base_dn: Base DN + field_category: Kategoria + 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_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_hide_mail: Ukryj mój adres e-mail + field_homepage: Strona www + field_host: Host + field_hours: Godzin + field_identifier: Identyfikator + 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_roadmap: Zagadnienie pokazywane na mapie + field_is_public: Publiczny + field_is_required: Wymagane + field_issue: Zagadnienie + field_issue_to: PowiÄ…zania zagadnienia + field_language: JÄ™zyk + field_last_login_on: Ostatnie połączenie + field_lastname: Nazwisko + field_login: Login + field_mail: E-mail + field_mail_notification: Powiadomienia e-mail + 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: Projekt nadrzÄ™dny + field_parent_title: Strona rodzica + field_password: HasÅ‚o + field_password_confirmation: Potwierdzenie + field_port: Port + 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: Data rozpoczÄ™cia + field_start_page: Strona startowa + field_status: Status + field_subject: Temat + field_subproject: Podprojekt + 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_first_day_of_week: '1' + 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' + + label_activity: Aktywność + label_add_another_file: Dodaj kolejny plik + label_add_note: Dodaj notatkÄ™ + label_added: dodane + label_added_time_by: "Dodane przez %{author} %{age} temu" + label_administration: Administracja + 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: "%{value} i podprojekty" + label_applied_status: Stosowany status + label_assigned_to_me_issues: Zagadnienia przypisane do mnie + 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_authentication: Identyfikacja + label_blocked_by: blokowane przez + label_blocks: blokuje + label_board: Forum + label_board_new: Nowe forum + label_board_plural: Fora + label_boolean: Wartość logiczna + label_browse: PrzeglÄ…d + label_bulk_edit_selected_issues: Zbiorowa edycja zagadnieÅ„ + label_calendar: Kalendarz + 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_x_open_issues_abbr_on_total: + zero: 0 otwartych / %{total} + one: 1 otwarty / %{total} + few: "%{count} otwarte / %{total}" + other: "%{count} otwartych / %{total}" + label_x_open_issues_abbr: + zero: 0 otwartych + one: 1 otwarty + few: "%{count} otwarte" + other: "%{count} otwartych" + label_x_closed_issues_abbr: + zero: 0 zamkniÄ™tych + one: 1 zamkniÄ™ty + few: "%{count} zamkniÄ™te" + other: "%{count} zamkniÄ™tych" + label_comment: Komentarz + label_comment_add: Dodaj komentarz + label_comment_added: Komentarz dodany + label_comment_delete: UsuÅ„ komentarze + label_comment_plural234: Komentarze + label_comment_plural5: Komentarzy + label_comment_plural: Komentarze + label_x_comments: + zero: brak komentarzy + one: 1 komentarz + few: "%{count} komentarze" + other: "%{count} komentarzy" + 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 pracy z + label_current_status: Obecny status + label_current_version: Obecna wersja + label_custom_field: Pole niestandardowe + label_custom_field_new: Nowe pole niestandardowe + label_custom_field_plural: Pola niestandardowe + label_date: Data + label_date_from: Od + label_date_range: Zakres dat + 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Ä™: %{value}" + label_document: Dokument + label_document_added: Dodano dokument + label_document_new: Nowy dokument + label_document_plural: Dokumenty + label_downloads_abbr: Pobieranie + label_duplicated_by: zduplikowane przez + label_duplicates: duplikuje + 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: "%{value} godzina" + label_f_hour_plural: "%{value} godzin" + label_feed_plural: Ilość Atom + label_feeds_access_key_created_on: "Klucz dostÄ™pu do kanaÅ‚u Atom stworzony %{value} temu" + label_file_added: Dodano plik + label_file_plural: Pliki + label_filter_add: Dodaj filtr + label_filter_plural: Filtry + label_float: Liczba zmiennoprzecinkowa + 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_in_less_than: mniejsze niż + label_in_more_than: wiÄ™ksze niż + label_incoming_emails: PrzychodzÄ…ca poczta elektroniczna + label_index_by_date: Indeks wg daty + 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 %{value}" + label_jump_to_a_project: Skocz do projektu... + label_language_based: Na podstawie jÄ™zyka + label_last_changes: "ostatnie %{count} zmian" + label_last_login: Ostatnie połączenie + label_last_month: ostatni miesiÄ…c + label_last_n_days: "ostatnie %{count} dni" + 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_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_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_not_equals: różni siÄ™ + label_open_issues: otwarte + label_open_issues_plural234: otwarte + label_open_issues_plural5: otwartych + 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: Projektów + label_project_plural: Projekty + label_x_projects: + zero: brak projektów + one: 1 projekt + few: "%{count} projekty" + other: "%{count} projektów" + 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: zmieniono nazwÄ™ + label_reply_plural: Odpowiedzi + label_report: Raport + label_report_plural: Raporty + label_reported_issues: Wprowadzone zagadnienia + label_repository: Repozytorium + 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_roadmap: Mapa + label_roadmap_due_in: W czasie + label_roadmap_no_issues: Brak zagadnieÅ„ do tej wersji + label_roadmap_overdue: "%{value} 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_search_titles_only: Przeszukuj tylko tytuÅ‚y + label_send_information: WyÅ›lij informacjÄ™ użytkownikowi + label_send_test_email: WyÅ›lij próbny e-mail + label_settings: Ustawienia + label_show_completed_versions: Pokaż kompletne wersje + label_sort_by: "Sortuj po %{value}" + 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: Przepracowany czas + 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_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_this_year: ten rok + label_time_tracking: Åšledzenie czasu pracy + 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 %{value} temu" + 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_selected: "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 pracy + label_year: Rok + label_yesterday: wczoraj + mail_body_account_activation_request: "Zarejestrowano nowego użytkownika: (%{value}). Konto oczekuje na twoje zatwierdzenie:" + mail_body_account_information: Twoje konto + mail_body_account_information_external: "Możesz użyć Twojego konta %{value} 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 %{count} dni" + mail_subject_account_activation_request: "Zapytanie aktywacyjne konta %{value}" + mail_subject_lost_password: "Twoje hasÅ‚o do %{value}" + mail_subject_register: "Aktywacja konta w %{value}" + mail_subject_reminder: "Uwaga na terminy, masz zagadnienia do obsÅ‚użenia w ciÄ…gu nastÄ™pnych %{count} dni! (%{days})" + 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: E-mail 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." + notice_account_register_done: Konto prawidÅ‚owo utworzone. + 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. + notice_email_error: "WystÄ…piÅ‚ błąd w trakcie wysyÅ‚ania e-maila (%{value})" + notice_email_sent: "E-mail zostaÅ‚ wysÅ‚any do %{value}" + notice_failed_to_save_issues: "Błąd podczas zapisu zagadnieÅ„ %{count} z %{total} zaznaczonych: %{ids}." + notice_feeds_access_key_reseted: Twój klucz dostÄ™pu do kanaÅ‚u Atom zostaÅ‚ zresetowany. + 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 posiadasz autoryzacji do oglÄ…dania tej strony. + 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 dziennika + permission_edit_project: Edycja projektów + permission_edit_time_entries: Edycja wpisów dziennika + permission_edit_wiki_pages: Edycja stron wiki + permission_log_time: Zapisywanie przepracowanego czasu + permission_manage_boards: ZarzÄ…dzanie forami + permission_manage_categories: ZarzÄ…dzanie kategoriami zagadnieÅ„ + 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 przepracowanego czasu + permission_view_wiki_edits: PodglÄ…d historii wiki + permission_view_wiki_pages: PodglÄ…d wiki + project_module_boards: Fora + project_module_documents: Dokumenty + project_module_files: Pliki + project_module_issue_tracking: Åšledzenie zagadnieÅ„ + project_module_news: Komunikaty + project_module_repository: Repozytorium + project_module_time_tracking: Åšledzenie czasu pracy + 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: Automatyczne logowanie + setting_bcc_recipients: Odbiorcy kopii tajnej (kt/bcc) + setting_commit_fix_keywords: SÅ‚owa zmieniajÄ…ce status + 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 Atom + 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 wyÅ›wietlane na liÅ›cie zagadnieÅ„ + setting_issues_export_limit: Limit eksportu zagadnieÅ„ + setting_login_required: Wymagane zalogowanie + setting_mail_from: Adres e-mail 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: Protokół + setting_self_registration: Samodzielna rejestracja użytkowników + 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: WÅ‚asny 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 wpisy dziennika do projektu + text_caracters_maximum: "%{count} znaków maksymalnie." + text_caracters_minimum: "Musi być nie krótsze niż %{count} znaków." + text_comma_separated: Dozwolone wielokrotne wartoÅ›ci (rozdzielone przecinkami). + text_default_administrator_account_changed: Zmieniono domyÅ›lne hasÅ‚o administratora + text_destroy_time_entries: UsuÅ„ wpisy dziennika + text_destroy_time_entries_question: Przepracowano %{hours} 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: "%{count} obiektów jest przypisanych do tej wartoÅ›ci." + text_file_repository_writable: Zapisywalne repozytorium plików + text_issue_added: "Zagadnienie %{id} zostaÅ‚o wprowadzone (przez %{author})." + text_issue_category_destroy_assignments: UsuÅ„ przydziaÅ‚y kategorii + text_issue_category_destroy_question: "Do tej kategorii sÄ… przypisane zagadnienia (%{count}). Co chcesz zrobić?" + text_issue_category_reassign_to: Przydziel zagadnienie do tej kategorii + text_issue_updated: "Zagadnienie %{id} zostaÅ‚o zaktualizowane (przez %{author})." + text_issues_destroy_confirmation: 'Czy jesteÅ› pewien, że chcesz usunąć wskazane zagadnienia?' + text_issues_ref_in_commit_messages: OdwoÅ‚ania do zagadnieÅ„ w komentarzach zatwierdzeÅ„ + text_length_between: "DÅ‚ugość pomiÄ™dzy %{min} i %{max} 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.\nWysoce zalecane jest 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 wszystkie powiÄ…zane dane? + text_reassign_time_entries: 'Przepnij przepracowany czas do tego zagadnienia:' + 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 e-mail 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 e-mailem. + text_select_project_modules: 'Wybierz moduÅ‚y do aktywacji w tym projekcie:' + text_status_changed_by_changeset: "Zastosowane w zmianach %{value}." + text_subprojects_destroy_warning: "Podprojekt(y): %{value} zostanÄ… także usuniÄ™te." + text_tip_issue_begin_day: zadanie zaczynajÄ…ce siÄ™ dzisiaj + text_tip_issue_begin_end_day: zadanie zaczynajÄ…ce i koÅ„czÄ…ce siÄ™ dzisiaj + text_tip_issue_end_day: zadanie koÅ„czÄ…ce siÄ™ dzisiaj + text_tracker_no_workflow: Brak przepÅ‚ywu pracy zdefiniowanego 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 zagadnieÅ„ które obserwujesz, lub w których bierzesz udziaÅ‚ (np. jesteÅ› autorem lub adresatem)." + text_user_wrote: "%{value} napisaÅ‚(a):" + 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 pracy + + label_user_activity: "Aktywność: %{value}" + label_updated_time_by: "Uaktualnione przez %{author} %{age} 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: "%{count} załącznik(ów) nie zostaÅ‚o zapisanych." + field_editable: Edytowalne + label_display: WyglÄ…d + button_create_and_continue: Stwórz i dodaj kolejne + text_custom_field_possible_values_info: 'Każda wartość w osobnej linii' + setting_repository_log_display_limit: Maksymalna liczba rewizji pokazywanych w logu pliku + setting_file_max_size_displayed: Maksymalny rozmiar plików tekstowych osadzanych w stronie + field_watcher: Obserwator + setting_openid: Logowanie i rejestracja przy użyciu OpenID + field_identity_url: Identyfikator OpenID (URL) + label_login_with_open_id_option: albo użyj OpenID + field_content: Treść + label_descending: MalejÄ…co + label_sort: Sortuj + label_ascending: RosnÄ…co + label_date_from_to: Od %{start} do %{end} + label_greater_or_equal: ">=" + label_less_or_equal: <= + text_wiki_page_destroy_question: Ta strona posiada podstrony (%{descendants}). Co chcesz zrobić? + text_wiki_page_reassign_children: Podepnij je do strony nadrzÄ™dnej wzglÄ™dem usuwanej + text_wiki_page_nullify_children: PrzesuÅ„ je na szczyt hierarchii + text_wiki_page_destroy_children: UsuÅ„ wszystkie podstrony + setting_password_min_length: Minimalna dÅ‚ugość hasÅ‚a + field_group_by: Grupuj wyniki wg + mail_subject_wiki_content_updated: "Strona wiki '%{id}' zostaÅ‚a uaktualniona" + label_wiki_content_added: Dodano stronÄ™ wiki + mail_subject_wiki_content_added: "Strona wiki '%{id}' zostaÅ‚a dodana" + mail_body_wiki_content_added: Strona wiki '%{id}' zostaÅ‚a dodana przez %{author}. + label_wiki_content_updated: Uaktualniono stronÄ™ wiki + mail_body_wiki_content_updated: Strona wiki '%{id}' zostaÅ‚a uaktualniona przez %{author}. + permission_add_project: Tworzenie projektu + setting_new_project_user_role_id: Rola nadawana twórcom projektów, którzy nie posiadajÄ… uprawnieÅ„ administatora + label_view_all_revisions: Pokaż wszystkie rewizje + label_tag: SÅ‚owo kluczowe + label_branch: Gałąź + error_no_tracker_in_project: Projekt nie posiada powiÄ…zanych typów zagadnieÅ„. Sprawdź ustawienia projektu. + error_no_default_issue_status: Nie zdefiniowano domyÅ›lnego statusu zagadnieÅ„. Sprawdź konfiguracjÄ™ (Przejdź do "Administracja -> Statusy zagadnieÅ„"). + text_journal_changed: "Zmieniono %{label} z %{old} na %{new}" + text_journal_set_to: "Ustawiono %{label} na %{value}" + text_journal_deleted: "UsuniÄ™to %{label} (%{old})" + label_group_plural: Grupy + label_group: Grupa + label_group_new: Nowa grupa + label_time_entry_plural: Przepracowany czas + text_journal_added: "Dodano %{label} %{value}" + field_active: Aktywne + enumeration_system_activity: Aktywność systemowa + button_copy_and_follow: Kopiuj i przejdź do kopii zagadnienia + button_duplicate: Duplikuj + button_move_and_follow: PrzenieÅ› i przejdź do zagadnienia + button_show: Pokaż + error_can_not_archive_project: Ten projekt nie może zostać zarchiwizowany + error_can_not_reopen_issue_on_closed_version: Zagadnienie przydzielone do zakoÅ„czonej wersji nie może zostać ponownie otwarte + error_issue_done_ratios_not_updated: "% wykonania zagadnienia nie zostaÅ‚ uaktualniony." + error_workflow_copy_source: ProszÄ™ wybrać źródÅ‚owy typ zagadnienia lub rolÄ™ + error_workflow_copy_target: ProszÄ™ wybrać docelowe typ(y) zagadnieÅ„ i rolÄ™(e) + field_sharing: Współdzielenie + label_api_access_key: Klucz dostÄ™pu do API + label_api_access_key_created_on: Klucz dostÄ™pu do API zostaÅ‚ utworzony %{value} temu + label_close_versions: Zamknij ukoÅ„czone wersje + label_copy_same_as_target: Jak cel + label_copy_source: ŹródÅ‚o + label_copy_target: Cel + label_display_used_statuses_only: WyÅ›wietlaj tylko statusy używane przez ten typ zagadnienia + label_feeds_access_key: Klucz dostÄ™pu do kanaÅ‚u Atom + label_missing_api_access_key: Brakuje klucza dostÄ™pu do API + label_missing_feeds_access_key: Brakuje klucza dostÄ™pu do kanaÅ‚u Atom + label_revision_id: Rewizja %{value} + label_subproject_new: Nowy podprojekt + label_update_issue_done_ratios: Uaktualnij % wykonania + label_user_anonymous: Anonimowy + label_version_sharing_descendants: Z podprojektami + label_version_sharing_hierarchy: Z hierarchiÄ… projektów + label_version_sharing_none: Brak współdzielenia + label_version_sharing_system: Ze wszystkimi projektami + label_version_sharing_tree: Z drzewem projektów + notice_api_access_key_reseted: Twój klucz dostÄ™pu do API zostaÅ‚ zresetowany. + notice_issue_done_ratios_updated: Uaktualnienie % wykonania zakoÅ„czone sukcesem. + permission_add_subprojects: Tworzenie podprojektów + permission_delete_issue_watchers: UsuÅ„ obserwatorów + permission_view_issues: PrzeglÄ…danie zagadnieÅ„ + setting_default_projects_modules: DomyÅ›lnie włączone moduÅ‚y dla nowo tworzonych projektów + setting_gravatar_default: DomyÅ›lny obraz Gravatar + setting_issue_done_ratio: Obliczaj postÄ™p realizacji zagadnieÅ„ za pomocÄ… + setting_issue_done_ratio_issue_field: "% Wykonania zagadnienia" + setting_issue_done_ratio_issue_status: Statusu zagadnienia + setting_mail_handler_body_delimiters: Przycinaj e-maile po jednej z tych linii + setting_rest_api_enabled: Uaktywnij usÅ‚ugÄ™ sieciowÄ… REST + setting_start_of_week: Pierwszy dzieÅ„ tygodnia + text_line_separated: Dozwolone jest wiele wartoÅ›ci (każda wartość w osobnej linii). + text_own_membership_delete_confirmation: |- + Masz zamiar usunąć niektóre lub wszystkie swoje uprawnienia. Po wykonaniu tej czynnoÅ›ci możesz utracić możliwoÅ›ci edycji tego projektu. + Czy na pewno chcesz kontynuować? + version_status_closed: zamkniÄ™ta + version_status_locked: zablokowana + version_status_open: otwarta + + label_board_sticky: Przyklejona + label_board_locked: ZamkniÄ™ta + permission_export_wiki_pages: Eksport stron wiki + permission_manage_project_activities: ZarzÄ…dzanie aktywnoÅ›ciami projektu + setting_cache_formatted_text: Buforuj sformatowany tekst + error_unable_delete_issue_status: Nie można usunąć statusu zagadnienia + label_profile: Profil + permission_manage_subtasks: ZarzÄ…dzanie podzagadnieniami + field_parent_issue: Zagadnienie nadrzÄ™dne + label_subtask_plural: Podzagadnienia + label_project_copy_notifications: WyÅ›lij powiadomienia e-mailowe przy kopiowaniu projektu + error_can_not_delete_custom_field: Nie można usunąć tego pola + error_unable_to_connect: Nie można połączyć (%{value}) + error_can_not_remove_role: Ta rola przypisana jest niektórym użytkownikom i nie może zostać usuniÄ™ta. + error_can_not_delete_tracker: Ten typ przypisany jest do części zagadnieÅ„ i nie może zostać usuniÄ™ty. + field_principal: PrzeÅ‚ożony + label_my_page_block: Elementy + notice_failed_to_save_members: "Nie można zapisać uczestników: %{errors}." + text_zoom_out: Zmniejsz + text_zoom_in: PowiÄ™ksz + notice_unable_delete_time_entry: Nie można usunąć wpisu z dziennika. + label_overall_spent_time: Przepracowany czas + field_time_entries: Dziennik + project_module_gantt: Diagram Gantta + project_module_calendar: Kalendarz + button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" + field_text: Text field + label_user_mail_option_only_owner: Only for things I am the owner of + setting_default_notification_option: Default notification option + label_user_mail_option_only_my_events: Only for things I watch or I'm involved in + label_user_mail_option_only_assigned: Only for things I am assigned to + label_user_mail_option_none: "Tylko to, co obserwujÄ™ lub w czym biorÄ™ udziaÅ‚" + field_member_of_group: Assignee's group + field_assigned_to_role: Assignee's role + notice_not_authorized_archived_project: The project you're trying to access has been archived. + label_principal_search: "Szukaj użytkownika lub grupy:" + label_user_search: "Szukaj użytkownika:" + field_visible: Visible + setting_commit_logtime_activity_id: Activity for logged time + text_time_logged_by_changeset: Applied in changeset %{value}. + setting_commit_logtime_enabled: Enable time logging + notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) + setting_gantt_items_limit: Maximum number of items displayed on the gantt chart + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. + label_my_queries: My custom queries + text_journal_changed_no_detail: "%{label} updated" + label_news_comment_added: Comment added to a news + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + label_bulk_edit_selected_time_entries: Bulk edit selected time entries + text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? + label_role_anonymous: Anonymous + label_role_non_member: Non member + label_issue_note_added: Note added + label_issue_status_updated: Status updated + label_issue_priority_updated: Priority updated + label_issues_visibility_own: Issues created by or assigned to the user + field_issues_visibility: Issues visibility + label_issues_visibility_all: All issues + permission_set_own_issues_private: Set own issues public or private + field_is_private: Private + permission_set_issues_private: Set issues public or private + label_issues_visibility_public: All non private issues + text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). + field_commit_logs_encoding: Kodowanie komentarzy zatwierdzeÅ„ + field_scm_path_encoding: Path encoding + text_scm_path_encoding_note: "Default: UTF-8" + field_path_to_repository: Path to repository + field_root_directory: Root directory + field_cvs_module: Module + field_cvsroot: CVSROOT + text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) + text_scm_command: Command + text_scm_command_version: Version + label_git_report_last_commit: Report last commit for files and directories + notice_issue_successful_create: Issue %{id} created. + label_between: between + setting_issue_group_assignment: Allow issue assignment to groups + label_diff: diff + text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) + description_query_sort_criteria_direction: Sort direction + description_project_scope: Search scope + description_filter: Filter + description_user_mail_notification: Mail notification settings + description_date_from: Enter start date + description_message_content: Message content + description_available_columns: Available Columns + description_date_range_interval: Choose range by selecting start and end date + description_issue_category_reassign: Choose issue category + description_search: Searchfield + description_notes: Notes + description_date_range_list: Choose range from list + description_choose_project: Projects + description_date_to: Enter end date + description_query_sort_criteria_attribute: Sort attribute + description_wiki_subpages_reassign: Choose new parent page + description_selected_columns: Selected Columns + label_parent_revision: Parent + label_child_revision: Child + error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. + setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues + button_edit_section: Edit this section + setting_repositories_encodings: Attachments and repositories encodings + description_all_columns: All Columns + button_export: Export + label_export_options: "%{export_format} export options" + error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) + notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." + label_x_issues: + zero: 0 zagadnienie + one: 1 zagadnienie + other: "%{count} zagadnienia" + label_repository_new: New repository + field_repository_is_default: Main repository + label_copy_attachments: Copy attachments + label_item_position: "%{position}/%{count}" + label_completed_versions: Completed versions + text_project_identifier_info: 'Dozwolone maÅ‚e litery (a-z), liczby i myÅ›lniki.
    Raz zapisany, identyfikator nie może być zmieniony.' + field_multiple: Multiple values + setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed + text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes + text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) + notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. + text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} + permission_manage_related_issues: Manage related issues + field_auth_source_ldap_filter: LDAP filter + label_search_for_watchers: Search for watchers to add + notice_account_deleted: Your account has been permanently deleted. + setting_unsubscribe: Allow users to delete their own account + button_delete_my_account: Delete my account + text_account_destroy_confirmation: |- + Are you sure you want to proceed? + Your account will be permanently deleted, with no way to reactivate it. + error_session_expired: Your session has expired. Please login again. + text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." + setting_session_lifetime: Session maximum lifetime + setting_session_timeout: Session inactivity timeout + label_session_expiration: Session expiration + permission_close_project: Close / reopen the project + label_show_closed_projects: View closed projects + button_close: Close + button_reopen: Reopen + project_status_active: active + project_status_closed: closed + project_status_archived: archived + text_project_closed: This project is closed and read-only. + notice_user_successful_create: User %{id} created. + field_core_fields: Standard fields + field_timeout: Timeout (in seconds) + setting_thumbnails_enabled: Display attachment thumbnails + setting_thumbnails_size: Thumbnails size (in pixels) + label_status_transitions: Status transitions + label_fields_permissions: Fields permissions + label_readonly: Read-only + label_required: Required + text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. + field_board_parent: Parent forum + label_attribute_of_project: Project's %{name} + label_attribute_of_author: Author's %{name} + label_attribute_of_assigned_to: Assignee's %{name} + label_attribute_of_fixed_version: Target version's %{name} + label_copy_subtasks: Copy subtasks + label_copied_to: copied to + label_copied_from: copied from + label_any_issues_in_project: any issues in project + label_any_issues_not_in_project: any issues not in project + field_private_notes: Private notes + permission_view_private_notes: View private notes + permission_set_notes_private: Set notes as private + label_no_issues_in_project: no issues in project + label_any: wszystko + label_last_n_weeks: last %{count} weeks + setting_cross_project_subtasks: Allow cross-project subtasks + label_cross_project_descendants: Z podprojektami + label_cross_project_tree: Z drzewem projektów + label_cross_project_hierarchy: Z hierarchią projektów + label_cross_project_system: Ze wszystkimi projektami + button_hide: Hide + setting_non_working_week_days: Non-working days + label_in_the_next_days: in the next + label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Ogółem + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/5d/5d3d445e6a2c90ebc1a3730a84d68683aaccf4dc.svn-base --- a/.svn/pristine/5d/5d3d445e6a2c90ebc1a3730a84d68683aaccf4dc.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4 +0,0 @@ -require File.expand_path('../../../test_helper', __FILE__) - -class IssueMovesHelperTest < ActionView::TestCase -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/5d/5d49f8300d8c88036656ba3ee811b034c32e1e91.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/5d/5d49f8300d8c88036656ba3ee811b034c32e1e91.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,23 @@ +module Redmine + module Scm + class Base + class << self + + def all + @scms || [] + end + + # Add a new SCM adapter and repository + def add(scm_name) + @scms ||= [] + @scms << scm_name + end + + # Remove a SCM adapter from Redmine's list of supported scms + def delete(scm_name) + @scms.delete(scm_name) + end + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/5d/5d94857704d99231f1b0ea1e206666d924acf9ab.svn-base --- a/.svn/pristine/5d/5d94857704d99231f1b0ea1e206666d924acf9ab.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,284 +0,0 @@ -module CodeRay - - # = PluginHost - # - # A simple subclass/subfolder plugin system. - # - # Example: - # class Generators - # extend PluginHost - # plugin_path 'app/generators' - # end - # - # class Generator - # extend Plugin - # PLUGIN_HOST = Generators - # end - # - # class FancyGenerator < Generator - # register_for :fancy - # end - # - # Generators[:fancy] #-> FancyGenerator - # # or - # CodeRay.require_plugin 'Generators/fancy' - # # or - # Generators::Fancy - module PluginHost - - # Raised if Encoders::[] fails because: - # * a file could not be found - # * the requested Plugin is not registered - PluginNotFound = Class.new LoadError - HostNotFound = Class.new LoadError - - PLUGIN_HOSTS = [] - PLUGIN_HOSTS_BY_ID = {} # dummy hash - - # Loads all plugins using list and load. - def load_all - for plugin in list - load plugin - end - end - - # Returns the Plugin for +id+. - # - # Example: - # yaml_plugin = MyPluginHost[:yaml] - def [] id, *args, &blk - plugin = validate_id(id) - begin - plugin = plugin_hash.[] plugin, *args, &blk - end while plugin.is_a? Symbol - plugin - end - - alias load [] - - # Tries to +load+ the missing plugin by translating +const+ to the - # underscore form (eg. LinesOfCode becomes lines_of_code). - def const_missing const - id = const.to_s. - gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). - gsub(/([a-z\d])([A-Z])/,'\1_\2'). - downcase - load id - end - - class << self - - # Adds the module/class to the PLUGIN_HOSTS list. - def extended mod - PLUGIN_HOSTS << mod - end - - end - - # The path where the plugins can be found. - def plugin_path *args - unless args.empty? - @plugin_path = File.expand_path File.join(*args) - end - @plugin_path ||= '' - end - - # Map a plugin_id to another. - # - # Usage: Put this in a file plugin_path/_map.rb. - # - # class MyColorHost < PluginHost - # map :navy => :dark_blue, - # :maroon => :brown, - # :luna => :moon - # end - def map hash - for from, to in hash - from = validate_id from - to = validate_id to - plugin_hash[from] = to unless plugin_hash.has_key? from - end - end - - # Define the default plugin to use when no plugin is found - # for a given id, or return the default plugin. - # - # See also map. - # - # class MyColorHost < PluginHost - # map :navy => :dark_blue - # default :gray - # end - # - # MyColorHost.default # loads and returns the Gray plugin - def default id = nil - if id - id = validate_id id - raise "The default plugin can't be named \"default\"." if id == :default - plugin_hash[:default] = id - else - load :default - end - end - - # Every plugin must register itself for +id+ by calling register_for, - # which calls this method. - # - # See Plugin#register_for. - def register plugin, id - plugin_hash[validate_id(id)] = plugin - end - - # A Hash of plugion_id => Plugin pairs. - def plugin_hash - @plugin_hash ||= make_plugin_hash - end - - # Returns an array of all .rb files in the plugin path. - # - # The extension .rb is not included. - def list - Dir[path_to('*')].select do |file| - File.basename(file)[/^(?!_)\w+\.rb$/] - end.map do |file| - File.basename(file, '.rb').to_sym - end - end - - # Returns an array of all Plugins. - # - # Note: This loads all plugins using load_all. - def all_plugins - load_all - plugin_hash.values.grep(Class) - end - - # Loads the map file (see map). - # - # This is done automatically when plugin_path is called. - def load_plugin_map - mapfile = path_to '_map' - @plugin_map_loaded = true - if File.exist? mapfile - require mapfile - true - else - false - end - end - - protected - - # Return a plugin hash that automatically loads plugins. - def make_plugin_hash - @plugin_map_loaded ||= false - Hash.new do |h, plugin_id| - id = validate_id(plugin_id) - path = path_to id - begin - raise LoadError, "#{path} not found" unless File.exist? path - require path - rescue LoadError => boom - if @plugin_map_loaded - if h.has_key?(:default) - warn '%p could not load plugin %p; falling back to %p' % [self, id, h[:default]] - h[:default] - else - raise PluginNotFound, '%p could not load plugin %p: %s' % [self, id, boom] - end - else - load_plugin_map - h[plugin_id] - end - else - # Plugin should have registered by now - if h.has_key? id - h[id] - else - raise PluginNotFound, "No #{self.name} plugin for #{id.inspect} found in #{path}." - end - end - end - end - - # Returns the expected path to the plugin file for the given id. - def path_to plugin_id - File.join plugin_path, "#{plugin_id}.rb" - end - - # Converts +id+ to a Symbol if it is a String, - # or returns +id+ if it already is a Symbol. - # - # Raises +ArgumentError+ for all other objects, or if the - # given String includes non-alphanumeric characters (\W). - def validate_id id - if id.is_a? Symbol or id.nil? - id - elsif id.is_a? String - if id[/\w+/] == id - id.downcase.to_sym - else - raise ArgumentError, "Invalid id given: #{id}" - end - else - raise ArgumentError, "String or Symbol expected, but #{id.class} given." - end - end - - end - - - # = Plugin - # - # Plugins have to include this module. - # - # IMPORTANT: Use extend for this module. - # - # See CodeRay::PluginHost for examples. - module Plugin - - attr_reader :plugin_id - - # Register this class for the given +id+. - # - # Example: - # class MyPlugin < PluginHost::BaseClass - # register_for :my_id - # ... - # end - # - # See PluginHost.register. - def register_for id - @plugin_id = id - plugin_host.register self, id - end - - # Returns the title of the plugin, or sets it to the - # optional argument +title+. - def title title = nil - if title - @title = title.to_s - else - @title ||= name[/([^:]+)$/, 1] - end - end - - # The PluginHost for this Plugin class. - def plugin_host host = nil - if host.is_a? PluginHost - const_set :PLUGIN_HOST, host - end - self::PLUGIN_HOST - end - - def aliases - plugin_host.load_plugin_map - plugin_host.plugin_hash.inject [] do |aliases, (key, _)| - aliases << key if plugin_host[key] == self - aliases - end - end - - end - -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/5d/5da0b4c4bee024389ce63f0775bf42e759cc86ef.svn-base --- a/.svn/pristine/5d/5da0b4c4bee024389ce63f0775bf42e759cc86ef.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,52 +0,0 @@ - -require File.expand_path('../../../../../../test_helper', __FILE__) - -class FilesystemAdapterTest < ActiveSupport::TestCase - REPOSITORY_PATH = Rails.root.join('tmp/test/filesystem_repository').to_s - - if File.directory?(REPOSITORY_PATH) - def setup - @adapter = Redmine::Scm::Adapters::FilesystemAdapter.new(REPOSITORY_PATH) - end - - def test_entries - assert_equal 3, @adapter.entries.size - assert_equal ["dir", "japanese", "test"], @adapter.entries.collect(&:name) - assert_equal ["dir", "japanese", "test"], @adapter.entries(nil).collect(&:name) - assert_equal ["dir", "japanese", "test"], @adapter.entries("/").collect(&:name) - ["dir", "/dir", "/dir/", "dir/"].each do |path| - assert_equal ["subdir", "dirfile"], @adapter.entries(path).collect(&:name) - end - # If y try to use "..", the path is ignored - ["/../","dir/../", "..", "../", "/..", "dir/.."].each do |path| - assert_equal ["dir", "japanese", "test"], @adapter.entries(path).collect(&:name), - ".. must be ignored in path argument" - end - end - - def test_cat - assert_equal "TEST CAT\n", @adapter.cat("test") - assert_equal "TEST CAT\n", @adapter.cat("/test") - # Revision number is ignored - assert_equal "TEST CAT\n", @adapter.cat("/test", 1) - end - - def test_path_encoding_default_utf8 - adpt1 = Redmine::Scm::Adapters::FilesystemAdapter.new( - REPOSITORY_PATH - ) - assert_equal "UTF-8", adpt1.path_encoding - adpt2 = Redmine::Scm::Adapters::FilesystemAdapter.new( - REPOSITORY_PATH, - nil, - nil, - nil, - "" - ) - assert_equal "UTF-8", adpt2.path_encoding - end - else - puts "Filesystem test repository NOT FOUND. Skipping unit tests !!! See doc/RUNNING_TESTS." - def test_fake; assert true end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/5d/5db9ffd3614f105788069a6feb5a6c5de41466cf.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/5d/5db9ffd3614f105788069a6feb5a6c5de41466cf.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,476 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class ChangesetTest < ActiveSupport::TestCase + fixtures :projects, :repositories, + :issues, :issue_statuses, :issue_categories, + :changesets, :changes, + :enumerations, + :custom_fields, :custom_values, + :users, :members, :member_roles, :trackers, + :enabled_modules, :roles + + def test_ref_keywords_any + ActionMailer::Base.deliveries.clear + Setting.commit_fix_status_id = IssueStatus.find( + :first, :conditions => ["is_closed = ?", true]).id + Setting.commit_fix_done_ratio = '90' + Setting.commit_ref_keywords = '*' + Setting.commit_fix_keywords = 'fixes , closes' + + c = Changeset.new(:repository => Project.find(1).repository, + :committed_on => Time.now, + :comments => 'New commit (#2). Fixes #1', + :revision => '12345') + assert c.save + assert_equal [1, 2], c.issue_ids.sort + fixed = Issue.find(1) + assert fixed.closed? + assert_equal 90, fixed.done_ratio + assert_equal 1, ActionMailer::Base.deliveries.size + end + + def test_ref_keywords + Setting.commit_ref_keywords = 'refs' + Setting.commit_fix_keywords = '' + c = Changeset.new(:repository => Project.find(1).repository, + :committed_on => Time.now, + :comments => 'Ignores #2. Refs #1', + :revision => '12345') + assert c.save + assert_equal [1], c.issue_ids.sort + end + + def test_ref_keywords_any_only + Setting.commit_ref_keywords = '*' + Setting.commit_fix_keywords = '' + c = Changeset.new(:repository => Project.find(1).repository, + :committed_on => Time.now, + :comments => 'Ignores #2. Refs #1', + :revision => '12345') + assert c.save + assert_equal [1, 2], c.issue_ids.sort + end + + def test_ref_keywords_any_with_timelog + Setting.commit_ref_keywords = '*' + Setting.commit_logtime_enabled = '1' + + { + '2' => 2.0, + '2h' => 2.0, + '2hours' => 2.0, + '15m' => 0.25, + '15min' => 0.25, + '3h15' => 3.25, + '3h15m' => 3.25, + '3h15min' => 3.25, + '3:15' => 3.25, + '3.25' => 3.25, + '3.25h' => 3.25, + '3,25' => 3.25, + '3,25h' => 3.25, + }.each do |syntax, expected_hours| + c = Changeset.new(:repository => Project.find(1).repository, + :committed_on => 24.hours.ago, + :comments => "Worked on this issue #1 @#{syntax}", + :revision => '520', + :user => User.find(2)) + assert_difference 'TimeEntry.count' do + c.scan_comment_for_issue_ids + end + assert_equal [1], c.issue_ids.sort + + time = TimeEntry.first(:order => 'id desc') + assert_equal 1, time.issue_id + assert_equal 1, time.project_id + assert_equal 2, time.user_id + assert_equal expected_hours, time.hours, + "@#{syntax} should be logged as #{expected_hours} hours but was #{time.hours}" + assert_equal Date.yesterday, time.spent_on + assert time.activity.is_default? + assert time.comments.include?('r520'), + "r520 was expected in time_entry comments: #{time.comments}" + end + end + + def test_ref_keywords_closing_with_timelog + Setting.commit_fix_status_id = IssueStatus.find( + :first, :conditions => ["is_closed = ?", true]).id + Setting.commit_ref_keywords = '*' + Setting.commit_fix_keywords = 'fixes , closes' + Setting.commit_logtime_enabled = '1' + + c = Changeset.new(:repository => Project.find(1).repository, + :committed_on => Time.now, + :comments => 'This is a comment. Fixes #1 @4.5, #2 @1', + :user => User.find(2)) + assert_difference 'TimeEntry.count', 2 do + c.scan_comment_for_issue_ids + end + + assert_equal [1, 2], c.issue_ids.sort + assert Issue.find(1).closed? + assert Issue.find(2).closed? + + times = TimeEntry.all(:order => 'id desc', :limit => 2) + assert_equal [1, 2], times.collect(&:issue_id).sort + end + + def test_ref_keywords_any_line_start + Setting.commit_ref_keywords = '*' + c = Changeset.new(:repository => Project.find(1).repository, + :committed_on => Time.now, + :comments => '#1 is the reason of this commit', + :revision => '12345') + assert c.save + assert_equal [1], c.issue_ids.sort + end + + def test_ref_keywords_allow_brackets_around_a_issue_number + Setting.commit_ref_keywords = '*' + c = Changeset.new(:repository => Project.find(1).repository, + :committed_on => Time.now, + :comments => '[#1] Worked on this issue', + :revision => '12345') + assert c.save + assert_equal [1], c.issue_ids.sort + end + + def test_ref_keywords_allow_brackets_around_multiple_issue_numbers + Setting.commit_ref_keywords = '*' + c = Changeset.new(:repository => Project.find(1).repository, + :committed_on => Time.now, + :comments => '[#1 #2, #3] Worked on these', + :revision => '12345') + assert c.save + assert_equal [1,2,3], c.issue_ids.sort + end + + def test_commit_referencing_a_subproject_issue + c = Changeset.new(:repository => Project.find(1).repository, + :committed_on => Time.now, + :comments => 'refs #5, a subproject issue', + :revision => '12345') + assert c.save + assert_equal [5], c.issue_ids.sort + assert c.issues.first.project != c.project + end + + def test_commit_closing_a_subproject_issue + with_settings :commit_fix_status_id => 5, :commit_fix_keywords => 'closes', + :default_language => 'en' do + issue = Issue.find(5) + assert !issue.closed? + assert_difference 'Journal.count' do + c = Changeset.new(:repository => Project.find(1).repository, + :committed_on => Time.now, + :comments => 'closes #5, a subproject issue', + :revision => '12345') + assert c.save + end + assert issue.reload.closed? + journal = Journal.first(:order => 'id DESC') + assert_equal issue, journal.issue + assert_include "Applied in changeset ecookbook:r12345.", journal.notes + end + end + + def test_commit_referencing_a_parent_project_issue + # repository of child project + r = Repository::Subversion.create!( + :project => Project.find(3), + :url => 'svn://localhost/test') + c = Changeset.new(:repository => r, + :committed_on => Time.now, + :comments => 'refs #2, an issue of a parent project', + :revision => '12345') + assert c.save + assert_equal [2], c.issue_ids.sort + assert c.issues.first.project != c.project + end + + def test_commit_referencing_a_project_with_commit_cross_project_ref_disabled + r = Repository::Subversion.create!( + :project => Project.find(3), + :url => 'svn://localhost/test') + + with_settings :commit_cross_project_ref => '0' do + c = Changeset.new(:repository => r, + :committed_on => Time.now, + :comments => 'refs #4, an issue of a different project', + :revision => '12345') + assert c.save + assert_equal [], c.issue_ids + end + end + + def test_commit_referencing_a_project_with_commit_cross_project_ref_enabled + r = Repository::Subversion.create!( + :project => Project.find(3), + :url => 'svn://localhost/test') + + with_settings :commit_cross_project_ref => '1' do + c = Changeset.new(:repository => r, + :committed_on => Time.now, + :comments => 'refs #4, an issue of a different project', + :revision => '12345') + assert c.save + assert_equal [4], c.issue_ids + end + end + + def test_text_tag_revision + c = Changeset.new(:revision => '520') + assert_equal 'r520', c.text_tag + end + + def test_text_tag_revision_with_same_project + c = Changeset.new(:revision => '520', :repository => Project.find(1).repository) + assert_equal 'r520', c.text_tag(Project.find(1)) + end + + def test_text_tag_revision_with_different_project + c = Changeset.new(:revision => '520', :repository => Project.find(1).repository) + assert_equal 'ecookbook:r520', c.text_tag(Project.find(2)) + end + + def test_text_tag_revision_with_repository_identifier + r = Repository::Subversion.create!( + :project_id => 1, + :url => 'svn://localhost/test', + :identifier => 'documents') + + c = Changeset.new(:revision => '520', :repository => r) + assert_equal 'documents|r520', c.text_tag + assert_equal 'ecookbook:documents|r520', c.text_tag(Project.find(2)) + end + + def test_text_tag_hash + c = Changeset.new( + :scmid => '7234cb2750b63f47bff735edc50a1c0a433c2518', + :revision => '7234cb2750b63f47bff735edc50a1c0a433c2518') + assert_equal 'commit:7234cb2750b63f47bff735edc50a1c0a433c2518', c.text_tag + end + + def test_text_tag_hash_with_same_project + c = Changeset.new(:revision => '7234cb27', :scmid => '7234cb27', :repository => Project.find(1).repository) + assert_equal 'commit:7234cb27', c.text_tag(Project.find(1)) + end + + def test_text_tag_hash_with_different_project + c = Changeset.new(:revision => '7234cb27', :scmid => '7234cb27', :repository => Project.find(1).repository) + assert_equal 'ecookbook:commit:7234cb27', c.text_tag(Project.find(2)) + end + + def test_text_tag_hash_all_number + c = Changeset.new(:scmid => '0123456789', :revision => '0123456789') + assert_equal 'commit:0123456789', c.text_tag + end + + def test_previous + changeset = Changeset.find_by_revision('3') + assert_equal Changeset.find_by_revision('2'), changeset.previous + end + + def test_previous_nil + changeset = Changeset.find_by_revision('1') + assert_nil changeset.previous + end + + def test_next + changeset = Changeset.find_by_revision('2') + assert_equal Changeset.find_by_revision('3'), changeset.next + end + + def test_next_nil + changeset = Changeset.find_by_revision('10') + assert_nil changeset.next + end + + def test_comments_should_be_converted_to_utf8 + proj = Project.find(3) + # str = File.read("#{RAILS_ROOT}/test/fixtures/encoding/iso-8859-1.txt") + str = "Texte encod\xe9 en ISO-8859-1." + str.force_encoding("ASCII-8BIT") if str.respond_to?(:force_encoding) + r = Repository::Bazaar.create!( + :project => proj, + :url => '/tmp/test/bazaar', + :log_encoding => 'ISO-8859-1' ) + assert r + c = Changeset.new(:repository => r, + :committed_on => Time.now, + :revision => '123', + :scmid => '12345', + :comments => str) + assert( c.save ) + str_utf8 = "Texte encod\xc3\xa9 en ISO-8859-1." + str_utf8.force_encoding("UTF-8") if str_utf8.respond_to?(:force_encoding) + assert_equal str_utf8, c.comments + end + + def test_invalid_utf8_sequences_in_comments_should_be_replaced_latin1 + proj = Project.find(3) + # str = File.read("#{RAILS_ROOT}/test/fixtures/encoding/iso-8859-1.txt") + str1 = "Texte encod\xe9 en ISO-8859-1." + str2 = "\xe9a\xe9b\xe9c\xe9d\xe9e test" + str1.force_encoding("UTF-8") if str1.respond_to?(:force_encoding) + str2.force_encoding("ASCII-8BIT") if str2.respond_to?(:force_encoding) + r = Repository::Bazaar.create!( + :project => proj, + :url => '/tmp/test/bazaar', + :log_encoding => 'UTF-8' ) + assert r + c = Changeset.new(:repository => r, + :committed_on => Time.now, + :revision => '123', + :scmid => '12345', + :comments => str1, + :committer => str2) + assert( c.save ) + assert_equal "Texte encod? en ISO-8859-1.", c.comments + assert_equal "?a?b?c?d?e test", c.committer + end + + def test_invalid_utf8_sequences_in_comments_should_be_replaced_ja_jis + proj = Project.find(3) + str = "test\xb5\xfetest\xb5\xfe" + if str.respond_to?(:force_encoding) + str.force_encoding('ASCII-8BIT') + end + r = Repository::Bazaar.create!( + :project => proj, + :url => '/tmp/test/bazaar', + :log_encoding => 'ISO-2022-JP' ) + assert r + c = Changeset.new(:repository => r, + :committed_on => Time.now, + :revision => '123', + :scmid => '12345', + :comments => str) + assert( c.save ) + assert_equal "test??test??", c.comments + end + + def test_comments_should_be_converted_all_latin1_to_utf8 + s1 = "\xC2\x80" + s2 = "\xc3\x82\xc2\x80" + s4 = s2.dup + if s1.respond_to?(:force_encoding) + s3 = s1.dup + s1.force_encoding('ASCII-8BIT') + s2.force_encoding('ASCII-8BIT') + s3.force_encoding('ISO-8859-1') + s4.force_encoding('UTF-8') + assert_equal s3.encode('UTF-8'), s4 + end + proj = Project.find(3) + r = Repository::Bazaar.create!( + :project => proj, + :url => '/tmp/test/bazaar', + :log_encoding => 'ISO-8859-1' ) + assert r + c = Changeset.new(:repository => r, + :committed_on => Time.now, + :revision => '123', + :scmid => '12345', + :comments => s1) + assert( c.save ) + assert_equal s4, c.comments + end + + def test_invalid_utf8_sequences_in_paths_should_be_replaced + proj = Project.find(3) + str1 = "Texte encod\xe9 en ISO-8859-1" + str2 = "\xe9a\xe9b\xe9c\xe9d\xe9e test" + str1.force_encoding("UTF-8") if str1.respond_to?(:force_encoding) + str2.force_encoding("ASCII-8BIT") if str2.respond_to?(:force_encoding) + r = Repository::Bazaar.create!( + :project => proj, + :url => '/tmp/test/bazaar', + :log_encoding => 'UTF-8' ) + assert r + cs = Changeset.new( + :repository => r, + :committed_on => Time.now, + :revision => '123', + :scmid => '12345', + :comments => "test") + assert(cs.save) + ch = Change.new( + :changeset => cs, + :action => "A", + :path => str1, + :from_path => str2, + :from_revision => "345") + assert(ch.save) + assert_equal "Texte encod? en ISO-8859-1", ch.path + assert_equal "?a?b?c?d?e test", ch.from_path + end + + def test_comments_nil + proj = Project.find(3) + r = Repository::Bazaar.create!( + :project => proj, + :url => '/tmp/test/bazaar', + :log_encoding => 'ISO-8859-1' ) + assert r + c = Changeset.new(:repository => r, + :committed_on => Time.now, + :revision => '123', + :scmid => '12345', + :comments => nil, + :committer => nil) + assert( c.save ) + assert_equal "", c.comments + assert_equal nil, c.committer + if c.comments.respond_to?(:force_encoding) + assert_equal "UTF-8", c.comments.encoding.to_s + end + end + + def test_comments_empty + proj = Project.find(3) + r = Repository::Bazaar.create!( + :project => proj, + :url => '/tmp/test/bazaar', + :log_encoding => 'ISO-8859-1' ) + assert r + c = Changeset.new(:repository => r, + :committed_on => Time.now, + :revision => '123', + :scmid => '12345', + :comments => "", + :committer => "") + assert( c.save ) + assert_equal "", c.comments + assert_equal "", c.committer + if c.comments.respond_to?(:force_encoding) + assert_equal "UTF-8", c.comments.encoding.to_s + assert_equal "UTF-8", c.committer.encoding.to_s + end + end + + def test_identifier + c = Changeset.find_by_revision('1') + assert_equal c.revision, c.identifier + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/5d/5dc200a9880b49ee8adf93752f95bd14fc23a33e.svn-base --- a/.svn/pristine/5d/5dc200a9880b49ee8adf93752f95bd14fc23a33e.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,98 +0,0 @@ -# Generates a migration which migrates all plugins to their latest versions -# within the database. -class PluginMigrationGenerator < Rails::Generator::Base - - # 255 characters max for Windows NTFS (http://en.wikipedia.org/wiki/Filename) - # minus 14 for timestamp, minus some extra chars for dot, underscore, file - # extension. So let's have 230. - MAX_FILENAME_LENGTH = 230 - - def initialize(runtime_args, runtime_options={}) - super - @options = {:assigns => {}} - ensure_schema_table_exists - get_plugins_to_migrate(runtime_args) - - if @plugins_to_migrate.empty? - puts "All plugins are migrated to their latest versions" - exit(0) - end - - @options[:migration_file_name] = build_migration_name - @options[:assigns][:class_name] = build_migration_name.classify - end - - def manifest - record do |m| - m.migration_template 'plugin_migration.erb', 'db/migrate', @options - end - end - - protected - - # Create the schema table if it doesn't already exist. - def ensure_schema_table_exists - ActiveRecord::Base.connection.initialize_schema_migrations_table - end - - # Determine all the plugins which have migrations that aren't present - # according to the plugin schema information from the database. - def get_plugins_to_migrate(plugin_names) - - # First, grab all the plugins which exist and have migrations - @plugins_to_migrate = if plugin_names.empty? - Engines.plugins - else - plugin_names.map do |name| - Engines.plugins[name] ? Engines.plugins[name] : raise("Cannot find the plugin '#{name}'") - end - end - - @plugins_to_migrate.reject! { |p| !p.respond_to?(:latest_migration) || p.latest_migration.nil? } - - # Then find the current versions from the database - @current_versions = {} - @plugins_to_migrate.each do |plugin| - @current_versions[plugin.name] = Engines::Plugin::Migrator.current_version(plugin) - end - - # Then find the latest versions from their migration directories - @new_versions = {} - @plugins_to_migrate.each do |plugin| - @new_versions[plugin.name] = plugin.latest_migration - end - - # Remove any plugins that don't need migration - @plugins_to_migrate.map { |p| p.name }.each do |name| - @plugins_to_migrate.delete(Engines.plugins[name]) if @current_versions[name] == @new_versions[name] - end - - @options[:assigns][:plugins] = @plugins_to_migrate - @options[:assigns][:new_versions] = @new_versions - @options[:assigns][:current_versions] = @current_versions - end - - # Returns a migration name. If the descriptive migration name based on the - # plugin names involved is shorter than 230 characters that one will be - # used. Otherwise a shorter name will be returned. - def build_migration_name - descriptive_migration_name.tap do |name| - name.replace short_migration_name if name.length > MAX_FILENAME_LENGTH - end - end - - # Construct a unique migration name based on the plugins involved and the - # versions they should reach after this migration is run. The name constructed - # needs to be lowercase - def descriptive_migration_name - @plugins_to_migrate.map do |plugin| - "#{plugin.name}_to_version_#{@new_versions[plugin.name]}" - end.join("_and_").downcase - end - - # Short migration name that will be used if the descriptive_migration_name - # exceeds 230 characters - def short_migration_name - 'plugin_migrations' - end -end \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/5e/5e3393ea8f73093b28938371fb5146fc323458f9.svn-base --- a/.svn/pristine/5e/5e3393ea8f73093b28938371fb5146fc323458f9.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,17 +0,0 @@ -begin - require 'rails/version' - unless Rails::VERSION::MAJOR >= 2 && Rails::VERSION::MINOR >= 3 && Rails::VERSION::TINY >= 2 - raise "This version of the engines plugin requires Rails 2.3.2 or later!" - end -end - -require File.join(File.dirname(__FILE__), 'lib/engines') - -# initialize Rails::Configuration with our own default values to spare users -# some hassle with the installation and keep the environment cleaner - -{ :default_plugin_locators => (defined?(Gem) ? [Rails::Plugin::GemLocator] : []).push(Engines::Plugin::FileSystemLocator), - :default_plugin_loader => Engines::Plugin::Loader, - :default_plugins => [:engines, :all] }.each do |name, default| - Rails::Configuration.send(:define_method, name) { default } -end \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/5e/5e37d42ab73a7b90b18f47c2e9486d6d1d7f855f.svn-base --- a/.svn/pristine/5e/5e37d42ab73a7b90b18f47c2e9486d6d1d7f855f.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,16 +0,0 @@ -

    <%=l(:label_issue_category)%>: <%=h @category.name %>

    - -<% form_tag(issue_category_path(@category), :method => :delete) do %> -
    -

    <%= l(:text_issue_category_destroy_question, @issue_count) %>

    -


    -<% if @categories.size > 0 %> -: -<%= label_tag "reassign_to_id", l(:description_issue_category_reassign), :class => "hidden-for-sighted" %> -<%= select_tag 'reassign_to_id', options_from_collection_for_select(@categories, 'id', 'name') %>

    -<% end %> -
    - -<%= submit_tag l(:button_apply) %> -<%= link_to l(:button_cancel), :controller => 'projects', :action => 'settings', :id => @project, :tab => 'categories' %> -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/5e/5e390d048451969a2cf8ae5ceb1da6dd876fe459.svn-base --- a/.svn/pristine/5e/5e390d048451969a2cf8ae5ceb1da6dd876fe459.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,15 +0,0 @@ -<% if @entry && @entry.kind == 'file' %> - -

    -<%= link_to_if action_name != 'changes', l(:label_history), {:action => 'changes', :id => @project, :path => to_path_param(@path), :rev => @rev } %> | -<% 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 %> -

    - -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/5e/5e76196c9e35c9b7b34f45d88fc793cafc843bca.svn-base --- a/.svn/pristine/5e/5e76196c9e35c9b7b34f45d88fc793cafc843bca.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,18 +0,0 @@ -api.array :versions, api_meta(:total_count => @versions.size) do - @versions.each do |version| - api.version do - api.id version.id - api.project(:id => version.project_id, :name => version.project.name) unless version.project.nil? - - api.name version.name - api.description version.description - api.status version.status - api.due_date version.effective_date - - render_api_custom_values version.custom_field_values, api - - api.created_on version.created_on - api.updated_on version.updated_on - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/5e/5eb189ca68bafa164c9eaf8f70c4f655a815135e.svn-base --- a/.svn/pristine/5e/5eb189ca68bafa164c9eaf8f70c4f655a815135e.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,77 +0,0 @@ -module CodeRay - - # = WordList - # - # A Hash subclass designed for mapping word lists to token types. - # - # Copyright (c) 2006-2011 by murphy (Kornelius Kalnbach) - # - # License:: LGPL / ask the author - # Version:: 2.0 (2011-05-08) - # - # A WordList is a Hash with some additional features. - # It is intended to be used for keyword recognition. - # - # WordList is optimized to be used in Scanners, - # typically to decide whether a given ident is a special token. - # - # For case insensitive words use WordList::CaseIgnoring. - # - # Example: - # - # # define word arrays - # RESERVED_WORDS = %w[ - # asm break case continue default do else - # ] - # - # PREDEFINED_TYPES = %w[ - # int long short char void - # ] - # - # # make a WordList - # IDENT_KIND = WordList.new(:ident). - # add(RESERVED_WORDS, :reserved). - # add(PREDEFINED_TYPES, :predefined_type) - # - # ... - # - # def scan_tokens tokens, options - # ... - # - # elsif scan(/[A-Za-z_][A-Za-z_0-9]*/) - # # use it - # kind = IDENT_KIND[match] - # ... - class WordList < Hash - - # Create a new WordList with +default+ as default value. - def initialize default = false - super default - end - - # Add words to the list and associate them with +value+. - # - # Returns +self+, so you can concat add calls. - def add words, value = true - words.each { |word| self[word] = value } - self - end - - end - - - # A CaseIgnoring WordList is like a WordList, only that - # keys are compared case-insensitively (normalizing keys using +downcase+). - class WordList::CaseIgnoring < WordList - - def [] key - super key.downcase - end - - def []= key, value - super key.downcase, value - end - - end - -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/5f/5f0a13fce17c6abc15fce65d4826f03678f86c58.svn-base --- a/.svn/pristine/5f/5f0a13fce17c6abc15fce65d4826f03678f86c58.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,87 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class AuthSourcesController < ApplicationController - layout 'admin' - - before_filter :require_admin - - # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html) - verify :method => :post, :only => [ :destroy, :create, :update ], - :redirect_to => { :template => :index } - - def index - @auth_source_pages, @auth_sources = paginate auth_source_class.name.tableize, :per_page => 10 - render "auth_sources/index" - end - - def new - @auth_source = auth_source_class.new - render 'auth_sources/new' - end - - def create - @auth_source = auth_source_class.new(params[:auth_source]) - if @auth_source.save - flash[:notice] = l(:notice_successful_create) - redirect_to :action => 'index' - else - render 'auth_sources/new' - end - end - - def edit - @auth_source = AuthSource.find(params[:id]) - render 'auth_sources/edit' - end - - def update - @auth_source = AuthSource.find(params[:id]) - if @auth_source.update_attributes(params[:auth_source]) - flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'index' - else - render 'auth_sources/edit' - end - end - - def test_connection - @auth_method = AuthSource.find(params[:id]) - begin - @auth_method.test_connection - flash[:notice] = l(:notice_successful_connection) - rescue => text - flash[:error] = l(:error_unable_to_connect, text.message) - end - redirect_to :action => 'index' - end - - def destroy - @auth_source = AuthSource.find(params[:id]) - unless @auth_source.users.find(:first) - @auth_source.destroy - flash[:notice] = l(:notice_successful_delete) - end - redirect_to :action => 'index' - end - - protected - - def auth_source_class - AuthSource - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/5f/5f0eae515ed184a3480763e5de7b865ac6400930.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/5f/5f0eae515ed184a3480763e5de7b865ac6400930.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,163 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class WikiContentTest < ActiveSupport::TestCase + fixtures :projects, :enabled_modules, + :users, :members, :member_roles, :roles, + :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions + + def setup + @wiki = Wiki.find(1) + @page = @wiki.pages.first + end + + def test_create + page = WikiPage.new(:wiki => @wiki, :title => "Page") + page.content = WikiContent.new(:text => "Content text", :author => User.find(1), :comments => "My comment") + assert page.save + page.reload + + content = page.content + assert_kind_of WikiContent, content + assert_equal 1, content.version + assert_equal 1, content.versions.length + assert_equal "Content text", content.text + assert_equal "My comment", content.comments + assert_equal User.find(1), content.author + assert_equal content.text, content.versions.last.text + end + + def test_create_should_send_email_notification + ActionMailer::Base.deliveries.clear + page = WikiPage.new(:wiki => @wiki, :title => "A new page") + page.content = WikiContent.new(:text => "Content text", :author => User.find(1), :comments => "My comment") + + with_settings :notified_events => %w(wiki_content_added) do + assert page.save + end + + assert_equal 1, ActionMailer::Base.deliveries.size + end + + def test_update_should_be_versioned + content = @page.content + version_count = content.version + content.text = "My new content" + assert_difference 'WikiContent::Version.count' do + assert content.save + end + content.reload + assert_equal version_count+1, content.version + assert_equal version_count+1, content.versions.length + + version = WikiContent::Version.first(:order => 'id DESC') + assert_equal @page.id, version.page_id + assert_equal '', version.compression + assert_equal "My new content", version.data + assert_equal "My new content", version.text + end + + def test_update_with_gzipped_history + with_settings :wiki_compression => 'gzip' do + content = @page.content + content.text = "My new content" + assert_difference 'WikiContent::Version.count' do + assert content.save + end + end + + version = WikiContent::Version.first(:order => 'id DESC') + assert_equal @page.id, version.page_id + assert_equal 'gzip', version.compression + assert_not_equal "My new content", version.data + assert_equal "My new content", version.text + end + + def test_update_should_send_email_notification + ActionMailer::Base.deliveries.clear + content = @page.content + content.text = "My new content" + + with_settings :notified_events => %w(wiki_content_updated) do + assert content.save + end + + assert_equal 1, ActionMailer::Base.deliveries.size + end + + def test_fetch_history + assert !@page.content.versions.empty? + @page.content.versions.each do |version| + assert_kind_of String, version.text + end + end + + def test_large_text_should_not_be_truncated_to_64k + page = WikiPage.new(:wiki => @wiki, :title => "Big page") + page.content = WikiContent.new(:text => "a" * 500.kilobyte, :author => User.find(1)) + assert page.save + page.reload + assert_equal 500.kilobyte, page.content.text.size + end + + def test_current_version + content = WikiContent.find(11) + assert_equal true, content.current_version? + assert_equal true, content.versions.first(:order => 'version DESC').current_version? + assert_equal false, content.versions.first(:order => 'version ASC').current_version? + end + + def test_previous_for_first_version_should_return_nil + content = WikiContent::Version.find_by_page_id_and_version(1, 1) + assert_nil content.previous + end + + def test_previous_for_version_should_return_previous_version + content = WikiContent::Version.find_by_page_id_and_version(1, 3) + assert_not_nil content.previous + assert_equal 2, content.previous.version + end + + def test_previous_for_version_with_gap_should_return_previous_available_version + WikiContent::Version.find_by_page_id_and_version(1, 2).destroy + + content = WikiContent::Version.find_by_page_id_and_version(1, 3) + assert_not_nil content.previous + assert_equal 1, content.previous.version + end + + def test_next_for_last_version_should_return_nil + content = WikiContent::Version.find_by_page_id_and_version(1, 3) + assert_nil content.next + end + + def test_next_for_version_should_return_next_version + content = WikiContent::Version.find_by_page_id_and_version(1, 1) + assert_not_nil content.next + assert_equal 2, content.next.version + end + + def test_next_for_version_with_gap_should_return_next_available_version + WikiContent::Version.find_by_page_id_and_version(1, 2).destroy + + content = WikiContent::Version.find_by_page_id_and_version(1, 1) + assert_not_nil content.next + assert_equal 3, content.next.version + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/5f/5f34122110535f3ec5de210020321433576b63f3.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/5f/5f34122110535f3ec5de210020321433576b63f3.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,185 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 DefaultData + class DataAlreadyLoaded < Exception; end + + module Loader + include Redmine::I18n + + class << self + # Returns true if no data is already loaded in the database + # otherwise false + def no_data? + !Role.where(:builtin => 0).exists? && + !Tracker.exists? && + !IssueStatus.exists? && + !Enumeration.exists? + end + + # Loads the default data + # Raises a RecordNotSaved exception if something goes wrong + def load(lang=nil) + raise DataAlreadyLoaded.new("Some configuration data is already loaded.") unless no_data? + set_language_if_valid(lang) + + Role.transaction do + # Roles + manager = Role.create! :name => l(:default_role_manager), + :issues_visibility => 'all', + :position => 1 + manager.permissions = manager.setable_permissions.collect {|p| p.name} + manager.save! + + developer = Role.create! :name => l(:default_role_developer), + :position => 2, + :permissions => [:manage_versions, + :manage_categories, + :view_issues, + :add_issues, + :edit_issues, + :view_private_notes, + :set_notes_private, + :manage_issue_relations, + :manage_subtasks, + :add_issue_notes, + :save_queries, + :view_gantt, + :view_calendar, + :log_time, + :view_time_entries, + :comment_news, + :view_documents, + :view_wiki_pages, + :view_wiki_edits, + :edit_wiki_pages, + :delete_wiki_pages, + :add_messages, + :edit_own_messages, + :view_files, + :manage_files, + :browse_repository, + :view_changesets, + :commit_access, + :manage_related_issues] + + reporter = Role.create! :name => l(:default_role_reporter), + :position => 3, + :permissions => [:view_issues, + :add_issues, + :add_issue_notes, + :save_queries, + :view_gantt, + :view_calendar, + :log_time, + :view_time_entries, + :comment_news, + :view_documents, + :view_wiki_pages, + :view_wiki_edits, + :add_messages, + :edit_own_messages, + :view_files, + :browse_repository, + :view_changesets] + + Role.non_member.update_attribute :permissions, [:view_issues, + :add_issues, + :add_issue_notes, + :save_queries, + :view_gantt, + :view_calendar, + :view_time_entries, + :comment_news, + :view_documents, + :view_wiki_pages, + :view_wiki_edits, + :add_messages, + :view_files, + :browse_repository, + :view_changesets] + + Role.anonymous.update_attribute :permissions, [:view_issues, + :view_gantt, + :view_calendar, + :view_time_entries, + :view_documents, + :view_wiki_pages, + :view_wiki_edits, + :view_files, + :browse_repository, + :view_changesets] + + # Trackers + Tracker.create!(:name => l(:default_tracker_bug), :is_in_chlog => true, :is_in_roadmap => false, :position => 1) + Tracker.create!(:name => l(:default_tracker_feature), :is_in_chlog => true, :is_in_roadmap => true, :position => 2) + Tracker.create!(:name => l(:default_tracker_support), :is_in_chlog => false, :is_in_roadmap => false, :position => 3) + + # Issue statuses + new = IssueStatus.create!(:name => l(:default_issue_status_new), :is_closed => false, :is_default => true, :position => 1) + in_progress = IssueStatus.create!(:name => l(:default_issue_status_in_progress), :is_closed => false, :is_default => false, :position => 2) + resolved = IssueStatus.create!(:name => l(:default_issue_status_resolved), :is_closed => false, :is_default => false, :position => 3) + feedback = IssueStatus.create!(:name => l(:default_issue_status_feedback), :is_closed => false, :is_default => false, :position => 4) + closed = IssueStatus.create!(:name => l(:default_issue_status_closed), :is_closed => true, :is_default => false, :position => 5) + rejected = IssueStatus.create!(:name => l(:default_issue_status_rejected), :is_closed => true, :is_default => false, :position => 6) + + # Workflow + Tracker.all.each { |t| + IssueStatus.all.each { |os| + IssueStatus.all.each { |ns| + WorkflowTransition.create!(:tracker_id => t.id, :role_id => manager.id, :old_status_id => os.id, :new_status_id => ns.id) unless os == ns + } + } + } + + Tracker.all.each { |t| + [new, in_progress, resolved, feedback].each { |os| + [in_progress, resolved, feedback, closed].each { |ns| + WorkflowTransition.create!(:tracker_id => t.id, :role_id => developer.id, :old_status_id => os.id, :new_status_id => ns.id) unless os == ns + } + } + } + + Tracker.all.each { |t| + [new, in_progress, resolved, feedback].each { |os| + [closed].each { |ns| + WorkflowTransition.create!(:tracker_id => t.id, :role_id => reporter.id, :old_status_id => os.id, :new_status_id => ns.id) unless os == ns + } + } + WorkflowTransition.create!(:tracker_id => t.id, :role_id => reporter.id, :old_status_id => resolved.id, :new_status_id => feedback.id) + } + + # Enumerations + IssuePriority.create!(:name => l(:default_priority_low), :position => 1) + IssuePriority.create!(:name => l(:default_priority_normal), :position => 2, :is_default => true) + IssuePriority.create!(:name => l(:default_priority_high), :position => 3) + IssuePriority.create!(:name => l(:default_priority_urgent), :position => 4) + IssuePriority.create!(:name => l(:default_priority_immediate), :position => 5) + + DocumentCategory.create!(:name => l(:default_doc_category_user), :position => 1) + DocumentCategory.create!(:name => l(:default_doc_category_tech), :position => 2) + + TimeEntryActivity.create!(:name => l(:default_activity_design), :position => 1) + TimeEntryActivity.create!(:name => l(:default_activity_development), :position => 2) + end + true + end + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/5f/5f8859116475dd06123c2a90756b26148236e7e2.svn-base --- a/.svn/pristine/5f/5f8859116475dd06123c2a90756b26148236e7e2.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ -# The PluginList class is an array, enhanced to allow access to loaded plugins -# by name, and iteration over loaded plugins in order of priority. This array is used -# by Engines::RailsExtensions::RailsInitializer to create the Engines.plugins array. -# -# Each loaded plugin has a corresponding Plugin instance within this array, and -# the order the plugins were loaded is reflected in the entries in this array. -# -# For more information, see the Rails module. -module Engines - class Plugin - class List < Array - # Finds plugins with the set with the given name (accepts Strings or Symbols), or - # index. So, Engines.plugins[0] returns the first-loaded Plugin, and Engines.plugins[:engines] - # returns the Plugin instance for the engines plugin itself. - def [](name_or_index) - if name_or_index.is_a?(Fixnum) - super - else - self.find { |plugin| plugin.name.to_s == name_or_index.to_s } - end - end - - # Go through each plugin, highest priority first (last loaded first). Effectively, - # this is like Engines.plugins.reverse - def by_precedence - reverse - end - end - end -end \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/5f/5f91c8ec766f59461497fadde2dab5afeb5e2ac6.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/5f/5f91c8ec766f59461497fadde2dab5afeb5e2ac6.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,31 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../../test_helper', __FILE__) + +class Redmine::NotifiableTest < ActiveSupport::TestCase + def setup + end + + def test_all + assert_equal 12, Redmine::Notifiable.all.length + + %w(issue_added issue_updated issue_note_added issue_status_updated issue_priority_updated news_added news_comment_added document_added file_added message_posted wiki_content_added wiki_content_updated).each do |notifiable| + assert Redmine::Notifiable.all.collect(&:name).include?(notifiable), "missing #{notifiable}" + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/5f/5ffda40b0da33ea2cbd4a1b56f89be0bef6f6786.svn-base --- a/.svn/pristine/5f/5ffda40b0da33ea2cbd4a1b56f89be0bef6f6786.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ -
    -<%= link_to_if_authorized l(:label_query_new), new_project_query_path(:project_id => @project), :class => 'icon icon-add' %> -
    - -

    <%= l(:label_query_plural) %>

    - -<% if @queries.empty? %> -

    <%=l(:label_no_data)%>

    -<% else %> - - <% @queries.each do |query| %> - - - - - <% end %> -
    - <%= link_to h(query.name), :controller => 'issues', :action => 'index', :project_id => @project, :query_id => query %> - - - <% if query.editable_by?(User.current) %> - <%= link_to l(:button_edit), edit_query_path(query), :class => 'icon icon-edit' %> - <%= link_to l(:button_delete), query_path(query), :confirm => l(:text_are_you_sure), :method => :delete, :class => 'icon icon-del' %> - - <% end %> -
    -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/60/6089c129623045cd6c157b0be5cbb46ac9a89d00.svn-base --- a/.svn/pristine/60/6089c129623045cd6c157b0be5cbb46ac9a89d00.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,10 +0,0 @@ -if @journal.frozen? - # journal was destroyed - page.remove "change-#{@journal.id}" -else - page.replace "journal-#{@journal.id}-notes", render_notes(@journal.issue, @journal, :reply_links => authorize_for('issues', 'edit')) - 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 -r 0a574315af3e -r 4f746d8966dd .svn/pristine/60/60a7eaf3b71f4ea5a952f91807abdd540583522f.svn-base --- a/.svn/pristine/60/60a7eaf3b71f4ea5a952f91807abdd540583522f.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,205 +0,0 @@ -# $Id: pdu.rb 126 2006-05-31 15:55:16Z blackhedd $ -# -# LDAP PDU support classes -# -# -#---------------------------------------------------------------------------- -# -# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved. -# -# Gmail: garbagecat10 -# -# 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 St, Fifth Floor, Boston, MA 02110-1301 USA -# -#--------------------------------------------------------------------------- -# - - - -module Net - - -class LdapPduError < Exception; end - - -class LdapPdu - - BindResult = 1 - SearchReturnedData = 4 - SearchResult = 5 - ModifyResponse = 7 - AddResponse = 9 - DeleteResponse = 11 - ModifyRDNResponse = 13 - SearchResultReferral = 19 - - attr_reader :msg_id, :app_tag - attr_reader :search_dn, :search_attributes, :search_entry - attr_reader :search_referrals - - # - # initialize - # An LDAP PDU always looks like a BerSequence with - # at least two elements: an integer (message-id number), and - # an application-specific sequence. - # Some LDAPv3 packets also include an optional - # third element, which is a sequence of "controls" - # (See RFC 2251, section 4.1.12). - # The application-specific tag in the sequence tells - # us what kind of packet it is, and each kind has its - # own format, defined in RFC-1777. - # Observe that many clients (such as ldapsearch) - # do not necessarily enforce the expected application - # tags on received protocol packets. This implementation - # does interpret the RFC strictly in this regard, and - # it remains to be seen whether there are servers out - # there that will not work well with our approach. - # - # Added a controls-processor to SearchResult. - # Didn't add it everywhere because it just _feels_ - # like it will need to be refactored. - # - def initialize ber_object - begin - @msg_id = ber_object[0].to_i - @app_tag = ber_object[1].ber_identifier - 0x60 - rescue - # any error becomes a data-format error - raise LdapPduError.new( "ldap-pdu format error" ) - end - - case @app_tag - when BindResult - parse_ldap_result ber_object[1] - when SearchReturnedData - parse_search_return ber_object[1] - when SearchResultReferral - parse_search_referral ber_object[1] - when SearchResult - parse_ldap_result ber_object[1] - parse_controls(ber_object[2]) if ber_object[2] - when ModifyResponse - parse_ldap_result ber_object[1] - when AddResponse - parse_ldap_result ber_object[1] - when DeleteResponse - parse_ldap_result ber_object[1] - when ModifyRDNResponse - parse_ldap_result ber_object[1] - else - raise LdapPduError.new( "unknown pdu-type: #{@app_tag}" ) - end - end - - # - # result_code - # This returns an LDAP result code taken from the PDU, - # but it will be nil if there wasn't a result code. - # That can easily happen depending on the type of packet. - # - def result_code code = :resultCode - @ldap_result and @ldap_result[code] - end - - # Return RFC-2251 Controls if any. - # Messy. Does this functionality belong somewhere else? - def result_controls - @ldap_controls || [] - end - - - # - # parse_ldap_result - # - def parse_ldap_result sequence - sequence.length >= 3 or raise LdapPduError - @ldap_result = {:resultCode => sequence[0], :matchedDN => sequence[1], :errorMessage => sequence[2]} - end - private :parse_ldap_result - - # - # parse_search_return - # Definition from RFC 1777 (we're handling application-4 here) - # - # Search Response ::= - # CHOICE { - # entry [APPLICATION 4] SEQUENCE { - # objectName LDAPDN, - # attributes SEQUENCE OF SEQUENCE { - # AttributeType, - # SET OF AttributeValue - # } - # }, - # resultCode [APPLICATION 5] LDAPResult - # } - # - # We concoct a search response that is a hash of the returned attribute values. - # NOW OBSERVE CAREFULLY: WE ARE DOWNCASING THE RETURNED ATTRIBUTE NAMES. - # This is to make them more predictable for user programs, but it - # may not be a good idea. Maybe this should be configurable. - # ALTERNATE IMPLEMENTATION: In addition to @search_dn and @search_attributes, - # we also return @search_entry, which is an LDAP::Entry object. - # If that works out well, then we'll remove the first two. - # - # Provisionally removed obsolete search_attributes and search_dn, 04May06. - # - def parse_search_return sequence - sequence.length >= 2 or raise LdapPduError - @search_entry = LDAP::Entry.new( sequence[0] ) - #@search_dn = sequence[0] - #@search_attributes = {} - sequence[1].each {|seq| - @search_entry[seq[0]] = seq[1] - #@search_attributes[seq[0].downcase.intern] = seq[1] - } - end - - # - # A search referral is a sequence of one or more LDAP URIs. - # Any number of search-referral replies can be returned by the server, interspersed - # with normal replies in any order. - # Until I can think of a better way to do this, we'll return the referrals as an array. - # It'll be up to higher-level handlers to expose something reasonable to the client. - def parse_search_referral uris - @search_referrals = uris - end - - - # Per RFC 2251, an LDAP "control" is a sequence of tuples, each consisting - # of an OID, a boolean criticality flag defaulting FALSE, and an OPTIONAL - # Octet String. If only two fields are given, the second one may be - # either criticality or data, since criticality has a default value. - # Someday we may want to come back here and add support for some of - # more-widely used controls. RFC-2696 is a good example. - # - def parse_controls sequence - @ldap_controls = sequence.map do |control| - o = OpenStruct.new - o.oid,o.criticality,o.value = control[0],control[1],control[2] - if o.criticality and o.criticality.is_a?(String) - o.value = o.criticality - o.criticality = false - end - o - end - end - private :parse_controls - - -end - - -end # module Net - diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/60/60c7c698ddb29ef3812a4b75182c078fbc9cc19b.svn-base --- a/.svn/pristine/60/60c7c698ddb29ef3812a4b75182c078fbc9cc19b.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,72 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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 Helpers - class Diff - include ERB::Util - include ActionView::Helpers::TagHelper - include ActionView::Helpers::TextHelper - attr_reader :diff, :words - - def initialize(content_to, content_from) - @words = content_to.to_s.split(/(\s+)/) - @words = @words.select {|word| word != ' '} - words_from = content_from.to_s.split(/(\s+)/) - words_from = words_from.select {|word| word != ' '} - @diff = words_from.diff @words - end - - def to_html - words = self.words.collect{|word| h(word)} - words_add = 0 - words_del = 0 - dels = 0 - del_off = 0 - diff.diffs.each do |diff| - add_at = nil - add_to = nil - del_at = nil - deleted = "" - diff.each do |change| - pos = change[1] - if change[0] == "+" - add_at = pos + dels unless add_at - add_to = pos + dels - words_add += 1 - else - del_at = pos unless del_at - deleted << ' ' + h(change[2]) - words_del += 1 - end - end - if add_at - words[add_at] = '' + words[add_at] - words[add_to] = words[add_to] + '' - end - if del_at - words.insert del_at - del_off + dels + words_add, '' + deleted + '' - dels += 1 - del_off += words_del - words_del = 0 - end - end - words.join(' ').html_safe - end - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/60/60f6cd76a2cf878fbe57eea952f6c764270ac27b.svn-base --- a/.svn/pristine/60/60f6cd76a2cf878fbe57eea952f6c764270ac27b.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,127 +0,0 @@ -// ** I18N - -// Calendar FI language -// Author: Antti Perkiömäki -// 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 -("Sunnuntai", - "Maanantai", - "Tiistai", - "Keskiviikko", - "Torstai", - "Perjantai", - "Lauantai", - "Sunnuntai"); - -// 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 -("Su", - "Ma", - "Ti", - "Ke", - "To", - "Pe", - "La", - "Su"); - -// 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 -("Tammikuu", - "Helmikuu", - "Maaliskuu", - "Huhtikuu", - "Toukokuu", - "Kesäkuu", - "Heinäkuu", - "Elokuu", - "Syyskuu", - "Lokakuu", - "Marraskuu", - "Joulukuu"); - -// short month names -Calendar._SMN = new Array -("Tammi", - "Helmi", - "Maalis", - "Huhti", - "Touko", - "Kesä", - "Heinä", - "Elo", - "Syys", - "Loka", - "Marras", - "Dec"); - -// tooltips -Calendar._TT = {}; -Calendar._TT["INFO"] = "Tietoa kalenterista"; - -Calendar._TT["ABOUT"] = -"DHTML Date/Time Selector\n" + -"(c) dynarch.com 2002-2005 / Tekijä: Mihai Bazon\n" + // don't translate this this ;-) -"Viimeisin versio: http://www.dynarch.com/projects/calendar/\n" + -"Jaettu GNU LGPL alaisena. Katso lisätiedot http://gnu.org/licenses/lgpl.html" + -"\n\n" + -"Päivä valitsin:\n" + -"- Käytä \xab, \xbb painikkeita valitaksesi vuoden\n" + -"- Käytä " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " painikkeita valitaksesi kuukauden\n" + -"- Pidä alhaalla hiiren painiketta missä tahansa yllämainituissa painikkeissa valitaksesi nopeammin."; -Calendar._TT["ABOUT_TIME"] = "\n\n" + -"Ajan valinta:\n" + -"- Paina mitä tahansa ajan osaa kasvattaaksesi sitä\n" + -"- tai Vaihtonäppäin-paina laskeaksesi sitä\n" + -"- tai paina ja raahaa valitaksesi nopeammin."; - -Calendar._TT["PREV_YEAR"] = "Edellinen vuosi (valikko tulee painaessa)"; -Calendar._TT["PREV_MONTH"] = "Edellinen kuukausi (valikko tulee painaessa)"; -Calendar._TT["GO_TODAY"] = "Siirry Tänään"; -Calendar._TT["NEXT_MONTH"] = "Seuraava kuukausi (valikko tulee painaessa)"; -Calendar._TT["NEXT_YEAR"] = "Seuraava vuosi (valikko tulee painaessa)"; -Calendar._TT["SEL_DATE"] = "Valitse päivä"; -Calendar._TT["DRAG_TO_MOVE"] = "Rahaa siirtääksesi"; -Calendar._TT["PART_TODAY"] = " (tänään)"; - -// 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"] = "Näytä %s ensin"; - -// This may be locale-dependent. It specifies the week-end days, as an array -// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 -// means Monday, etc. -Calendar._TT["WEEKEND"] = "6,0"; - -Calendar._TT["CLOSE"] = "Sulje"; -Calendar._TT["TODAY"] = "Tänään"; -Calendar._TT["TIME_PART"] = "(Vaihtonäppäin-)Paina tai raahaa vaihtaaksesi arvoa"; - -// date formats -Calendar._TT["DEF_DATE_FORMAT"] = "%d.%m.%Y"; -Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; - -Calendar._TT["WK"] = "vko"; -Calendar._TT["TIME"] = "Aika:"; diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/60/60fb53c461a41fb9a73cca759c107e76107c0242.svn-base --- a/.svn/pristine/60/60fb53c461a41fb9a73cca759c107e76107c0242.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,176 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class Role < ActiveRecord::Base - # Built-in roles - BUILTIN_NON_MEMBER = 1 - BUILTIN_ANONYMOUS = 2 - - ISSUES_VISIBILITY_OPTIONS = [ - ['all', :label_issues_visibility_all], - ['default', :label_issues_visibility_public], - ['own', :label_issues_visibility_own] - ] - - named_scope :givable, { :conditions => "builtin = 0", :order => 'position' } - named_scope :builtin, lambda { |*args| - compare = 'not' if args.first == true - { :conditions => "#{compare} builtin = 0" } - } - - before_destroy :check_deletable - has_many :workflows, :dependent => :delete_all do - def copy(source_role) - Workflow.copy(nil, source_role, nil, proxy_owner) - end - end - - has_many :member_roles, :dependent => :destroy - has_many :members, :through => :member_roles - acts_as_list - - serialize :permissions, Array - attr_protected :builtin - - validates_presence_of :name - validates_uniqueness_of :name - validates_length_of :name, :maximum => 30 - validates_inclusion_of :issues_visibility, - :in => ISSUES_VISIBILITY_OPTIONS.collect(&:first), - :if => lambda {|role| role.respond_to?(:issues_visibility)} - - def permissions - read_attribute(:permissions) || [] - end - - def permissions=(perms) - perms = perms.collect {|p| p.to_sym unless p.blank? }.compact.uniq if perms - write_attribute(:permissions, perms) - end - - def add_permission!(*perms) - self.permissions = [] unless permissions.is_a?(Array) - - permissions_will_change! - perms.each do |p| - p = p.to_sym - permissions << p unless permissions.include?(p) - end - save! - end - - def remove_permission!(*perms) - return unless permissions.is_a?(Array) - permissions_will_change! - perms.each { |p| permissions.delete(p.to_sym) } - save! - end - - # Returns true if the role has the given permission - def has_permission?(perm) - !permissions.nil? && permissions.include?(perm.to_sym) - end - - def <=>(role) - role ? position <=> role.position : -1 - end - - def to_s - name - end - - def name - case builtin - when 1; l(:label_role_non_member, :default => read_attribute(:name)) - when 2; l(:label_role_anonymous, :default => read_attribute(:name)) - else; read_attribute(:name) - end - end - - # Return true if the role is a builtin role - def builtin? - self.builtin != 0 - end - - # Return true if the role is a project member role - def member? - !self.builtin? - end - - # Return true if role is allowed to do the specified action - # action can be: - # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit') - # * a permission Symbol (eg. :edit_project) - def allowed_to?(action) - if action.is_a? Hash - allowed_actions.include? "#{action[:controller]}/#{action[:action]}" - else - allowed_permissions.include? action - end - end - - # Return all the permissions that can be given to the role - def setable_permissions - setable_permissions = Redmine::AccessControl.permissions - Redmine::AccessControl.public_permissions - setable_permissions -= Redmine::AccessControl.members_only_permissions if self.builtin == BUILTIN_NON_MEMBER - setable_permissions -= Redmine::AccessControl.loggedin_only_permissions if self.builtin == BUILTIN_ANONYMOUS - setable_permissions - end - - # Find all the roles that can be given to a project member - def self.find_all_givable - find(:all, :conditions => {:builtin => 0}, :order => 'position') - end - - # Return the builtin 'non member' role. If the role doesn't exist, - # it will be created on the fly. - def self.non_member - find_or_create_system_role(BUILTIN_NON_MEMBER, 'Non member') - end - - # Return the builtin 'anonymous' role. If the role doesn't exist, - # it will be created on the fly. - def self.anonymous - find_or_create_system_role(BUILTIN_ANONYMOUS, 'Anonymous') - end - -private - - def allowed_permissions - @allowed_permissions ||= permissions + Redmine::AccessControl.public_permissions.collect {|p| p.name} - end - - def allowed_actions - @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten - end - - def check_deletable - raise "Can't delete role" if members.any? - raise "Can't delete builtin role" if builtin? - end - - def self.find_or_create_system_role(builtin, name) - role = first(:conditions => {:builtin => builtin}) - if role.nil? - role = create(:name => name, :position => 0) do |r| - r.builtin = builtin - end - raise "Unable to create the #{name} role." if role.new_record? - end - role - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/61/610feb39e0b4d5a86f5c3afb7419c2dc54df3346.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/61/610feb39e0b4d5a86f5c3afb7419c2dc54df3346.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,83 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class AdminController < ApplicationController + layout 'admin' + menu_item :projects, :only => :projects + menu_item :plugins, :only => :plugins + menu_item :info, :only => :info + + before_filter :require_admin + helper :sort + include SortHelper + + def index + @no_configuration_data = Redmine::DefaultData::Loader::no_data? + end + + def projects + @status = params[:status] || 1 + + scope = Project.status(@status).order('lft') + scope = scope.like(params[:name]) if params[:name].present? + @projects = scope.all + + 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 + if request.post? + begin + Redmine::DefaultData::Loader::load(params[:lang]) + flash[:notice] = l(:notice_default_data_loaded) + rescue Exception => e + flash[:error] = l(:error_can_t_load_default_data, e.message) + end + end + redirect_to admin_path + end + + def test_email + raise_delivery_errors = ActionMailer::Base.raise_delivery_errors + # Force ActionMailer to raise delivery errors so we can catch it + ActionMailer::Base.raise_delivery_errors = true + begin + @test = Mailer.test_email(User.current).deliver + flash[:notice] = l(:notice_email_sent, User.current.mail) + rescue Exception => e + flash[:error] = l(:notice_email_error, e.message) + end + ActionMailer::Base.raise_delivery_errors = raise_delivery_errors + redirect_to settings_path(:tab => 'notifications') + end + + def info + @db_adapter_name = ActiveRecord::Base.connection.adapter_name + @checklist = [ + [:text_default_administrator_account_changed, User.default_admin_account_changed?], + [:text_file_repository_writable, File.writable?(Attachment.storage_path)], + [:text_plugin_assets_writable, File.writable?(Redmine::Plugin.public_directory)], + [:text_rmagick_available, Object.const_defined?(:Magick)] + ] + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/61/61309acbc5f96ce770001ad1501caa18b7f75258.svn-base --- a/.svn/pristine/61/61309acbc5f96ce770001ad1501caa18b7f75258.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,176 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) - -class TimeEntryTest < ActiveSupport::TestCase - fixtures :issues, :projects, :users, :time_entries, - :members, :roles, :member_roles, :auth_sources, - :trackers, :issue_statuses, - :projects_trackers, - :journals, :journal_details, - :issue_categories, :enumerations, - :groups_users, - :enabled_modules, - :workflows - - def test_hours_format - 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, - "45m" => 0.75, - "45 m" => 0.75, - "3h15" => 3.25, - "3h 15" => 3.25, - "3 h 15" => 3.25, - "3 h 15m" => 3.25, - "3 h 15 m" => 3.25, - "3 hours" => 3.0, - "12min" => 0.2, - } - - assertions.each do |k, v| - t = TimeEntry.new(:hours => k) - assert_equal v, t.hours, "Converting #{k} failed:" - end - end - - def test_hours_should_default_to_nil - assert_nil TimeEntry.new.hours - end - - def test_spent_on_with_blank - c = TimeEntry.new - c.spent_on = '' - assert_nil c.spent_on - end - - def test_spent_on_with_nil - c = TimeEntry.new - c.spent_on = nil - assert_nil c.spent_on - end - - def test_spent_on_with_string - c = TimeEntry.new - c.spent_on = "2011-01-14" - assert_equal Date.parse("2011-01-14"), c.spent_on - end - - def test_spent_on_with_invalid_string - c = TimeEntry.new - c.spent_on = "foo" - assert_nil c.spent_on - end - - def test_spent_on_with_date - c = TimeEntry.new - c.spent_on = Date.today - assert_equal Date.today, c.spent_on - end - - def test_spent_on_with_time - c = TimeEntry.new - c.spent_on = Time.now - assert_equal Date.today, c.spent_on - end - - def test_validate_time_entry - anon = User.anonymous - project = Project.find(1) - issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => anon.id, :status_id => 1, - :priority => IssuePriority.all.first, :subject => 'test_create', - :description => 'IssueTest#test_create', :estimated_hours => '1:30') - assert issue.save - activity = TimeEntryActivity.find_by_name('Design') - te = TimeEntry.create(:spent_on => '2010-01-01', - :hours => 100000, - :issue => issue, - :project => project, - :user => anon, - :activity => activity) - assert_equal 1, te.errors.count - end - - def test_set_project_if_nil - anon = User.anonymous - project = Project.find(1) - issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => anon.id, :status_id => 1, - :priority => IssuePriority.all.first, :subject => 'test_create', - :description => 'IssueTest#test_create', :estimated_hours => '1:30') - assert issue.save - activity = TimeEntryActivity.find_by_name('Design') - te = TimeEntry.create(:spent_on => '2010-01-01', - :hours => 10, - :issue => issue, - :user => anon, - :activity => activity) - assert_equal project.id, te.project.id - end - - context "#earilest_date_for_project" do - setup do - User.current = nil - @public_project = Project.generate!(:is_public => true) - @issue = Issue.generate_for_project!(@public_project) - TimeEntry.generate!(:spent_on => '2010-01-01', - :issue => @issue, - :project => @public_project) - end - - context "without a project" do - should "return the lowest spent_on value that is visible to the current user" do - assert_equal "2007-03-12", TimeEntry.earilest_date_for_project.to_s - end - end - - context "with a project" do - should "return the lowest spent_on value that is visible to the current user for that project and it's subprojects only" do - assert_equal "2010-01-01", TimeEntry.earilest_date_for_project(@public_project).to_s - end - end - - end - - context "#latest_date_for_project" do - setup do - User.current = nil - @public_project = Project.generate!(:is_public => true) - @issue = Issue.generate_for_project!(@public_project) - TimeEntry.generate!(:spent_on => '2010-01-01', - :issue => @issue, - :project => @public_project) - end - - context "without a project" do - should "return the highest spent_on value that is visible to the current user" do - assert_equal "2010-01-01", TimeEntry.latest_date_for_project.to_s - end - end - - context "with a project" do - should "return the highest spent_on value that is visible to the current user for that project and it's subprojects only" do - project = Project.find(1) - assert_equal "2007-04-22", TimeEntry.latest_date_for_project(project).to_s - end - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/61/61668dfcefb644406f6147ff8d4029159a7244df.svn-base --- a/.svn/pristine/61/61668dfcefb644406f6147ff8d4029159a7244df.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,23 +0,0 @@ -<% form_tag({:action => 'edit', :tab => 'authentication'}) do %> - -
    -

    <%= setting_check_box :login_required %>

    - -

    <%= setting_select :autologin, [[l(:label_disabled), 0]] + [1, 7, 30, 365].collect{|days| [l('datetime.distance_in_words.x_days', :count => days), days.to_s]} %>

    - -

    <%= setting_select :self_registration, [[l(:label_disabled), "0"], - [l(:label_registration_activation_by_email), "1"], - [l(:label_registration_manual_activation), "2"], - [l(:label_registration_automatic_activation), "3"]] %>

    - -

    <%= setting_text_field :password_min_length, :size => 6 %>

    - -

    <%= setting_check_box :lost_password, :label => :label_password_lost %>

    - -

    <%= setting_check_box :openid, :disabled => !Object.const_defined?(:OpenID) %>

    - -

    <%= setting_check_box :rest_api_enabled %>

    -
    - -<%= submit_tag l(:button_save) %> -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/61/61a91d37bba4f376ceeb50ad99e25e5c58c85000.svn-base --- a/.svn/pristine/61/61a91d37bba4f376ceeb50ad99e25e5c58c85000.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,29 +0,0 @@ -require File.dirname(__FILE__) + '/../test_helper' - -class ExceptionNotificationCompatibilityTest < ActionController::TestCase - ExceptionNotifier.exception_recipients = %w(joe@schmoe.com bill@schmoe.com) - class SimpleController < ApplicationController - include ExceptionNotifiable - local_addresses.clear - consider_all_requests_local = false - def index - begin - raise "Fail!" - rescue Exception => e - rescue_action_in_public(e) - end - end - end - - def setup - @controller = SimpleController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - end - - def test_should_work - assert_nothing_raised do - get :index - end - end -end \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/61/61b65742b8b81ec430e2510c699672b59ba4470d.svn-base --- a/.svn/pristine/61/61b65742b8b81ec430e2510c699672b59ba4470d.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,129 +0,0 @@ -// ** I18N - -// Calendar EN language -// Author: Mihai Bazon, -// 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. - -// Translator: David Duret, from previous french version - -// full day names -Calendar._DN = new Array -("Dimanche", - "Lundi", - "Mardi", - "Mercredi", - "Jeudi", - "Vendredi", - "Samedi", - "Dimanche"); - -// 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 -("Dim", - "Lun", - "Mar", - "Mer", - "Jeu", - "Ven", - "Sam", - "Dim"); - -// 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 -("Janvier", - "Février", - "Mars", - "Avril", - "Mai", - "Juin", - "Juillet", - "Août", - "Septembre", - "Octobre", - "Novembre", - "Décembre"); - -// short month names -Calendar._SMN = new Array -("Jan", - "Fev", - "Mar", - "Avr", - "Mai", - "Juin", - "Juil", - "Aout", - "Sep", - "Oct", - "Nov", - "Dec"); - -// tooltips -Calendar._TT = {}; -Calendar._TT["INFO"] = "A propos du calendrier"; - -Calendar._TT["ABOUT"] = -"DHTML Date/Heure Selecteur\n" + -"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) -"Pour la derniere version visitez : http://www.dynarch.com/projects/calendar/\n" + -"Distribué par GNU LGPL. Voir http://gnu.org/licenses/lgpl.html pour les details." + -"\n\n" + -"Selection de la date :\n" + -"- Utiliser les bouttons \xab, \xbb pour selectionner l\'annee\n" + -"- Utiliser les bouttons " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " pour selectionner les mois\n" + -"- Garder la souris sur n'importe quels boutons pour une selection plus rapide"; -Calendar._TT["ABOUT_TIME"] = "\n\n" + -"Selection de l\'heure :\n" + -"- Cliquer sur heures ou minutes pour incrementer\n" + -"- ou Maj-clic pour decrementer\n" + -"- ou clic et glisser-deplacer pour une selection plus rapide"; - -Calendar._TT["PREV_YEAR"] = "Année préc. (maintenir pour menu)"; -Calendar._TT["PREV_MONTH"] = "Mois préc. (maintenir pour menu)"; -Calendar._TT["GO_TODAY"] = "Atteindre la date du jour"; -Calendar._TT["NEXT_MONTH"] = "Mois suiv. (maintenir pour menu)"; -Calendar._TT["NEXT_YEAR"] = "Année suiv. (maintenir pour menu)"; -Calendar._TT["SEL_DATE"] = "Sélectionner une date"; -Calendar._TT["DRAG_TO_MOVE"] = "Déplacer"; -Calendar._TT["PART_TODAY"] = " (Aujourd'hui)"; - -// 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"] = "Afficher %s en premier"; - -// 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"] = "Fermer"; -Calendar._TT["TODAY"] = "Aujourd'hui"; -Calendar._TT["TIME_PART"] = "(Maj-)Clic ou glisser pour modifier la valeur"; - -// date formats -Calendar._TT["DEF_DATE_FORMAT"] = "%d/%m/%Y"; -Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; - -Calendar._TT["WK"] = "Sem."; -Calendar._TT["TIME"] = "Heure :"; diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/61/61e50703af02347b6cb421c714576454eb248e4a.svn-base --- a/.svn/pristine/61/61e50703af02347b6cb421c714576454eb248e4a.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,82 +0,0 @@ -require File.expand_path('../../../../../../test_helper', __FILE__) -begin - require 'mocha' - - class CvsAdapterTest < ActiveSupport::TestCase - REPOSITORY_PATH = Rails.root.join('tmp/test/cvs_repository').to_s - REPOSITORY_PATH.gsub!(/\//, "\\") if Redmine::Platform.mswin? - MODULE_NAME = 'test' - - if File.directory?(REPOSITORY_PATH) - def setup - @adapter = Redmine::Scm::Adapters::CvsAdapter.new(MODULE_NAME, REPOSITORY_PATH) - end - - def test_scm_version - to_test = { "\nConcurrent Versions System (CVS) 1.12.13 (client/server)\n" => [1,12,13], - "\r\n1.12.12\r\n1.12.11" => [1,12,12], - "1.12.11\r\n1.12.10\r\n" => [1,12,11]} - to_test.each do |s, v| - test_scm_version_for(s, v) - end - end - - def test_revisions_all - cnt = 0 - @adapter.revisions('', nil, nil, :log_encoding => 'UTF-8') do |revision| - cnt += 1 - end - assert_equal 16, cnt - end - - def test_revisions_from_rev3 - rev3_committed_on = Time.gm(2007, 12, 13, 16, 27, 22) - cnt = 0 - @adapter.revisions('', rev3_committed_on, nil, :log_encoding => 'UTF-8') do |revision| - cnt += 1 - end - assert_equal 4, cnt - end - - def test_entries_rev3 - rev3_committed_on = Time.gm(2007, 12, 13, 16, 27, 22) - entries = @adapter.entries('sources', rev3_committed_on) - assert_equal 2, entries.size - assert_equal entries[0].name, "watchers_controller.rb" - assert_equal entries[0].lastrev.time, Time.gm(2007, 12, 13, 16, 27, 22) - end - - def test_path_encoding_default_utf8 - adpt1 = Redmine::Scm::Adapters::CvsAdapter.new( - MODULE_NAME, - REPOSITORY_PATH - ) - assert_equal "UTF-8", adpt1.path_encoding - adpt2 = Redmine::Scm::Adapters::CvsAdapter.new( - MODULE_NAME, - REPOSITORY_PATH, - nil, - nil, - "" - ) - assert_equal "UTF-8", adpt2.path_encoding - end - - private - - def test_scm_version_for(scm_command_version, version) - @adapter.class.expects(:scm_version_from_command_line).returns(scm_command_version) - assert_equal version, @adapter.class.scm_command_version - end - else - puts "Cvs test repository NOT FOUND. Skipping unit tests !!!" - def test_fake; assert true end - end - end - -rescue LoadError - class CvsMochaFake < ActiveSupport::TestCase - def test_fake; assert(false, "Requires mocha to run those tests") end - end -end - diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/61/61f5175c584e56d932e6fd6922f31fce1b57acd4.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/61/61f5175c584e56d932e6fd6922f31fce1b57acd4.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,88 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class IssueRelationsController < ApplicationController + before_filter :find_issue, :find_project_from_association, :authorize, :only => [:index, :create] + before_filter :find_relation, :except => [:index, :create] + + accept_api_auth :index, :show, :create, :destroy + + def index + @relations = @issue.relations + + respond_to do |format| + format.html { render :nothing => true } + format.api + end + end + + def show + raise Unauthorized unless @relation.visible? + + respond_to do |format| + format.html { render :nothing => true } + format.api + end + end + + def create + @relation = IssueRelation.new(params[:relation]) + @relation.issue_from = @issue + if params[:relation] && m = params[:relation][:issue_to_id].to_s.strip.match(/^#?(\d+)$/) + @relation.issue_to = Issue.visible.find_by_id(m[1].to_i) + end + saved = @relation.save + + respond_to do |format| + format.html { redirect_to issue_path(@issue) } + format.js { + @relations = @issue.reload.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? } + } + format.api { + if saved + render :action => 'show', :status => :created, :location => relation_url(@relation) + else + render_validation_errors(@relation) + end + } + end + end + + def destroy + raise Unauthorized unless @relation.deletable? + @relation.destroy + + respond_to do |format| + format.html { redirect_to issue_path(@relation.issue_from) } + format.js + format.api { render_api_ok } + end + end + +private + def find_issue + @issue = @object = Issue.find(params[:issue_id]) + rescue ActiveRecord::RecordNotFound + render_404 + end + + def find_relation + @relation = IssueRelation.find(params[:id]) + rescue ActiveRecord::RecordNotFound + render_404 + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/62/62244ea8823525f3feb5603a1babb7b2ffdb7ec8.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/62/62244ea8823525f3feb5603a1babb7b2ffdb7ec8.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,22 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 IssueObserver < ActiveRecord::Observer + def after_create(issue) + Mailer.issue_add(issue).deliver if Setting.notified_events.include?('issue_added') + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/62/62a5609a8ce41f5e32619c5cb46089fa3e9c24f7.svn-base --- a/.svn/pristine/62/62a5609a8ce41f5e32619c5cb46089fa3e9c24f7.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,127 +0,0 @@ -// ** I18N - -// Calendar Chinese language -// Author: Andy Wu, -// 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 -("星期日", - "星期一", - "星期二", - "星期三", - "星期四", - "星期五", - "星期六", - "星期日"); - -// 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 = 0; - -// full month names -Calendar._MN = new Array -("1月", - "2月", - "3月", - "4月", - "5月", - "6月", - "7月", - "8月", - "9月", - "10月", - "11月", - "12月"); - -// short month names -Calendar._SMN = new Array -("1月", - "2月", - "3月", - "4月", - "5月", - "6月", - "7月", - "8月", - "9月", - "10月", - "11月", - "12月"); - -// tooltips -Calendar._TT = {}; -Calendar._TT["INFO"] = "关于日历"; - -Calendar._TT["ABOUT"] = -"DHTML 日期/æ—¶é—´ 选择器\n" + -"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) -"最新版本请访问: http://www.dynarch.com/projects/calendar/\n" + -"éµå¾ª GNU LGPL å‘布。详情请查阅 http://gnu.org/licenses/lgpl.html " + -"\n\n" + -"日期选择:\n" + -"- 使用 \xab,\xbb 按钮选择年\n" + -"- 使用 " + String.fromCharCode(0x2039) + "," + String.fromCharCode(0x203a) + " 按钮选择月\n" + -"- 在上述按钮上按ä½ä¸æ”¾å¯ä»¥å¿«é€Ÿé€‰æ‹©"; -Calendar._TT["ABOUT_TIME"] = "\n\n" + -"时间选择:\n" + -"- 点击时间的任æ„部分æ¥å¢žåŠ \n" + -"- Shift加点击æ¥å‡å°‘\n" + -"- ç‚¹å‡»åŽæ‹–动进行快速选择"; - -Calendar._TT["PREV_YEAR"] = "上年(按ä½ä¸æ”¾æ˜¾ç¤ºèœå•)"; -Calendar._TT["PREV_MONTH"] = "上月(按ä½ä¸æ”¾æ˜¾ç¤ºèœå•)"; -Calendar._TT["GO_TODAY"] = "回到今天"; -Calendar._TT["NEXT_MONTH"] = "下月(按ä½ä¸æ”¾æ˜¾ç¤ºèœå•)"; -Calendar._TT["NEXT_YEAR"] = "下年(按ä½ä¸æ”¾æ˜¾ç¤ºèœå•)"; -Calendar._TT["SEL_DATE"] = "选择日期"; -Calendar._TT["DRAG_TO_MOVE"] = "拖动"; -Calendar._TT["PART_TODAY"] = " (今日)"; - -// the following is to inform that "%s" is to be the first day of week -// %s will be replaced with the day name. -Calendar._TT["DAY_FIRST"] = "一周开始于 %s"; - -// This may be locale-dependent. It specifies the week-end days, as an array -// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 -// means Monday, etc. -Calendar._TT["WEEKEND"] = "0,6"; - -Calendar._TT["CLOSE"] = "关闭"; -Calendar._TT["TODAY"] = "今天"; -Calendar._TT["TIME_PART"] = "Shift加点击或者拖动æ¥å˜æ›´"; - -// date formats -Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; -Calendar._TT["TT_DATE_FORMAT"] = "星期%a %b%eæ—¥"; - -Calendar._TT["WK"] = "周"; -Calendar._TT["TIME"] = "时间:"; diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/62/62c1677139f8e602a41e89d444679c525b9002b9.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/62/62c1677139f8e602a41e89d444679c525b9002b9.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,604 @@ +# -*- coding: utf-8 -*- +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class TimelogControllerTest < ActionController::TestCase + fixtures :projects, :enabled_modules, :roles, :members, + :member_roles, :issues, :time_entries, :users, + :trackers, :enumerations, :issue_statuses, + :custom_fields, :custom_values, + :projects_trackers, :custom_fields_trackers, + :custom_fields_projects + + include Redmine::I18n + + def test_new_with_project_id + @request.session[:user_id] = 3 + get :new, :project_id => 1 + assert_response :success + assert_template 'new' + assert_select 'select[name=?]', 'time_entry[project_id]', 0 + assert_select 'input[name=?][value=1][type=hidden]', 'time_entry[project_id]' + end + + def test_new_with_issue_id + @request.session[:user_id] = 3 + get :new, :issue_id => 2 + assert_response :success + assert_template 'new' + assert_select 'select[name=?]', 'time_entry[project_id]', 0 + assert_select 'input[name=?][value=1][type=hidden]', 'time_entry[project_id]' + end + + def test_new_without_project + @request.session[:user_id] = 3 + get :new + assert_response :success + assert_template 'new' + assert_select 'select[name=?]', 'time_entry[project_id]' + assert_select 'input[name=?]', 'time_entry[project_id]', 0 + end + + def test_new_without_project_should_prefill_the_form + @request.session[:user_id] = 3 + get :new, :time_entry => {:project_id => '1'} + assert_response :success + assert_template 'new' + assert_select 'select[name=?]', 'time_entry[project_id]' do + assert_select 'option[value=1][selected=selected]' + end + assert_select 'input[name=?]', 'time_entry[project_id]', 0 + end + + def test_new_without_project_should_deny_without_permission + Role.all.each {|role| role.remove_permission! :log_time} + @request.session[:user_id] = 3 + + get :new + assert_response 403 + end + + def test_new_should_select_default_activity + @request.session[:user_id] = 3 + get :new, :project_id => 1 + assert_response :success + assert_select 'select[name=?]', 'time_entry[activity_id]' do + assert_select 'option[selected=selected]', :text => 'Development' + end + end + + def test_new_should_only_show_active_time_entry_activities + @request.session[:user_id] = 3 + get :new, :project_id => 1 + assert_response :success + assert_no_tag 'option', :content => 'Inactive Activity' + 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/time_entries/2' } + end + + def test_get_edit_with_an_existing_time_entry_with_inactive_activity + te = TimeEntry.find(1) + te.activity = TimeEntryActivity.find_by_name("Inactive Activity") + te.save! + + @request.session[:user_id] = 1 + get :edit, :project_id => 1, :id => 1 + assert_response :success + assert_template 'edit' + # Blank option since nothing is pre-selected + assert_tag :tag => 'option', :content => '--- Please select ---' + end + + def test_post_create + # TODO: should POST to issues’ time log instead of project. change form + # and routing + @request.session[:user_id] = 3 + post :create, :project_id => 1, + :time_entry => {:comments => 'Some work on TimelogControllerTest', + # Not the default activity + :activity_id => '11', + :spent_on => '2008-03-14', + :issue_id => '1', + :hours => '7.3'} + assert_redirected_to :action => 'index', :project_id => 'ecookbook' + + i = Issue.find(1) + t = TimeEntry.find_by_comments('Some work on TimelogControllerTest') + assert_not_nil t + assert_equal 11, t.activity_id + assert_equal 7.3, t.hours + assert_equal 3, t.user_id + assert_equal i, t.issue + assert_equal i.project, t.project + end + + def test_post_create_with_blank_issue + # TODO: should POST to issues’ time log instead of project. change form + # and routing + @request.session[:user_id] = 3 + post :create, :project_id => 1, + :time_entry => {:comments => 'Some work on TimelogControllerTest', + # Not the default activity + :activity_id => '11', + :issue_id => '', + :spent_on => '2008-03-14', + :hours => '7.3'} + assert_redirected_to :action => 'index', :project_id => 'ecookbook' + + t = TimeEntry.find_by_comments('Some work on TimelogControllerTest') + assert_not_nil t + assert_equal 11, t.activity_id + assert_equal 7.3, t.hours + assert_equal 3, t.user_id + end + + def test_create_and_continue + @request.session[:user_id] = 2 + post :create, :project_id => 1, + :time_entry => {:activity_id => '11', + :issue_id => '', + :spent_on => '2008-03-14', + :hours => '7.3'}, + :continue => '1' + assert_redirected_to '/projects/ecookbook/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=' + end + + def test_create_and_continue_with_issue_id + @request.session[:user_id] = 2 + post :create, :project_id => 1, + :time_entry => {:activity_id => '11', + :issue_id => '1', + :spent_on => '2008-03-14', + :hours => '7.3'}, + :continue => '1' + assert_redirected_to '/projects/ecookbook/issues/1/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=1' + end + + def test_create_and_continue_without_project + @request.session[:user_id] = 2 + post :create, :time_entry => {:project_id => '1', + :activity_id => '11', + :issue_id => '', + :spent_on => '2008-03-14', + :hours => '7.3'}, + :continue => '1' + + assert_redirected_to '/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=&time_entry%5Bproject_id%5D=1' + end + + def test_create_without_log_time_permission_should_be_denied + @request.session[:user_id] = 2 + Role.find_by_name('Manager').remove_permission! :log_time + post :create, :project_id => 1, + :time_entry => {:activity_id => '11', + :issue_id => '', + :spent_on => '2008-03-14', + :hours => '7.3'} + + assert_response 403 + end + + def test_create_with_failure + @request.session[:user_id] = 2 + post :create, :project_id => 1, + :time_entry => {:activity_id => '', + :issue_id => '', + :spent_on => '2008-03-14', + :hours => '7.3'} + + assert_response :success + assert_template 'new' + end + + def test_create_without_project + @request.session[:user_id] = 2 + assert_difference 'TimeEntry.count' do + post :create, :time_entry => {:project_id => '1', + :activity_id => '11', + :issue_id => '', + :spent_on => '2008-03-14', + :hours => '7.3'} + end + + assert_redirected_to '/projects/ecookbook/time_entries' + time_entry = TimeEntry.first(:order => 'id DESC') + assert_equal 1, time_entry.project_id + end + + def test_create_without_project_should_fail_with_issue_not_inside_project + @request.session[:user_id] = 2 + assert_no_difference 'TimeEntry.count' do + post :create, :time_entry => {:project_id => '1', + :activity_id => '11', + :issue_id => '5', + :spent_on => '2008-03-14', + :hours => '7.3'} + end + + assert_response :success + assert assigns(:time_entry).errors[:issue_id].present? + end + + def test_create_without_project_should_deny_without_permission + @request.session[:user_id] = 2 + Project.find(3).disable_module!(:time_tracking) + + assert_no_difference 'TimeEntry.count' do + post :create, :time_entry => {:project_id => '3', + :activity_id => '11', + :issue_id => '', + :spent_on => '2008-03-14', + :hours => '7.3'} + end + + assert_response 403 + end + + def test_create_without_project_with_failure + @request.session[:user_id] = 2 + assert_no_difference 'TimeEntry.count' do + post :create, :time_entry => {:project_id => '1', + :activity_id => '11', + :issue_id => '', + :spent_on => '2008-03-14', + :hours => ''} + end + + assert_response :success + assert_tag 'select', :attributes => {:name => 'time_entry[project_id]'}, + :child => {:tag => 'option', :attributes => {:value => '1', :selected => 'selected'}} + end + + def test_update + entry = TimeEntry.find(1) + assert_equal 1, entry.issue_id + assert_equal 2, entry.user_id + + @request.session[:user_id] = 1 + put :update, :id => 1, + :time_entry => {:issue_id => '2', + :hours => '8'} + assert_redirected_to :action => 'index', :project_id => 'ecookbook' + entry.reload + + assert_equal 8, entry.hours + assert_equal 2, entry.issue_id + assert_equal 2, entry.user_id + end + + def test_get_bulk_edit + @request.session[:user_id] = 2 + get :bulk_edit, :ids => [1, 2] + assert_response :success + assert_template 'bulk_edit' + + assert_select 'ul#bulk-selection' do + assert_select 'li', 2 + assert_select 'li a', :text => '03/23/2007 - eCookbook: 4.25 hours' + end + + assert_select 'form#bulk_edit_form[action=?]', '/time_entries/bulk_update' do + # System wide custom field + assert_select 'select[name=?]', 'time_entry[custom_field_values][10]' + + # Activities + assert_select 'select[name=?]', 'time_entry[activity_id]' do + assert_select 'option[value=]', :text => '(No change)' + assert_select 'option[value=9]', :text => 'Design' + end + end + end + + def test_get_bulk_edit_on_different_projects + @request.session[:user_id] = 2 + get :bulk_edit, :ids => [1, 2, 6] + assert_response :success + assert_template 'bulk_edit' + end + + def test_bulk_update + @request.session[:user_id] = 2 + # update time entry activity + post :bulk_update, :ids => [1, 2], :time_entry => { :activity_id => 9} + + assert_response 302 + # check that the issues were updated + assert_equal [9, 9], TimeEntry.find_all_by_id([1, 2]).collect {|i| i.activity_id} + end + + def test_bulk_update_with_failure + @request.session[:user_id] = 2 + post :bulk_update, :ids => [1, 2], :time_entry => { :hours => 'A'} + + assert_response 302 + assert_match /Failed to save 2 time entrie/, flash[:error] + end + + def test_bulk_update_on_different_projects + @request.session[:user_id] = 2 + # makes user a manager on the other project + Member.create!(:user_id => 2, :project_id => 3, :role_ids => [1]) + + # update time entry activity + post :bulk_update, :ids => [1, 2, 4], :time_entry => { :activity_id => 9 } + + assert_response 302 + # check that the issues were updated + assert_equal [9, 9, 9], TimeEntry.find_all_by_id([1, 2, 4]).collect {|i| i.activity_id} + end + + def test_bulk_update_on_different_projects_without_rights + @request.session[:user_id] = 3 + user = User.find(3) + action = { :controller => "timelog", :action => "bulk_update" } + assert user.allowed_to?(action, TimeEntry.find(1).project) + assert ! user.allowed_to?(action, TimeEntry.find(5).project) + post :bulk_update, :ids => [1, 5], :time_entry => { :activity_id => 9 } + assert_response 403 + end + + def test_bulk_update_custom_field + @request.session[:user_id] = 2 + post :bulk_update, :ids => [1, 2], :time_entry => { :custom_field_values => {'10' => '0'} } + + assert_response 302 + assert_equal ["0", "0"], TimeEntry.find_all_by_id([1, 2]).collect {|i| i.custom_value_for(10).value} + end + + def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter + @request.session[:user_id] = 2 + post :bulk_update, :ids => [1,2], :back_url => '/time_entries' + + assert_response :redirect + assert_redirected_to '/time_entries' + end + + def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host + @request.session[:user_id] = 2 + post :bulk_update, :ids => [1,2], :back_url => 'http://google.com' + + assert_response :redirect + assert_redirected_to :controller => 'timelog', :action => 'index', :project_id => Project.find(1).identifier + end + + def test_post_bulk_update_without_edit_permission_should_be_denied + @request.session[:user_id] = 2 + Role.find_by_name('Manager').remove_permission! :edit_time_entries + post :bulk_update, :ids => [1,2] + + assert_response 403 + end + + def test_destroy + @request.session[:user_id] = 2 + delete :destroy, :id => 1 + assert_redirected_to :action => 'index', :project_id => 'ecookbook' + assert_equal I18n.t(:notice_successful_delete), flash[:notice] + assert_nil TimeEntry.find_by_id(1) + end + + def test_destroy_should_fail + # simulate that this fails (e.g. due to a plugin), see #5700 + TimeEntry.any_instance.expects(:destroy).returns(false) + + @request.session[:user_id] = 2 + delete :destroy, :id => 1 + assert_redirected_to :action => 'index', :project_id => 'ecookbook' + assert_equal I18n.t(:notice_unable_delete_time_entry), flash[:error] + assert_not_nil TimeEntry.find_by_id(1) + end + + def test_index_all_projects + get :index + assert_response :success + assert_template 'index' + assert_not_nil assigns(:total_hours) + assert_equal "162.90", "%.2f" % assigns(:total_hours) + assert_tag :form, + :attributes => {:action => "/time_entries", :id => 'query_form'} + end + + def test_index_all_projects_should_show_log_time_link + @request.session[:user_id] = 2 + get :index + assert_response :success + assert_template 'index' + assert_tag 'a', :attributes => {:href => '/time_entries/new'}, :content => /Log time/ + end + + def test_index_at_project_level + get :index, :project_id => 'ecookbook' + assert_response :success + assert_template 'index' + assert_not_nil assigns(:entries) + assert_equal 4, assigns(:entries).size + # project and subproject + assert_equal [1, 3], assigns(:entries).collect(&:project_id).uniq.sort + assert_not_nil assigns(:total_hours) + assert_equal "162.90", "%.2f" % assigns(:total_hours) + assert_tag :form, + :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'} + end + + def test_index_at_project_level_with_date_range + get :index, :project_id => 'ecookbook', + :f => ['spent_on'], + :op => {'spent_on' => '><'}, + :v => {'spent_on' => ['2007-03-20', '2007-04-30']} + assert_response :success + assert_template 'index' + assert_not_nil assigns(:entries) + assert_equal 3, assigns(:entries).size + assert_not_nil assigns(:total_hours) + assert_equal "12.90", "%.2f" % assigns(:total_hours) + assert_tag :form, + :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'} + end + + def test_index_at_project_level_with_date_range_using_from_and_to_params + get :index, :project_id => 'ecookbook', :from => '2007-03-20', :to => '2007-04-30' + assert_response :success + assert_template 'index' + assert_not_nil assigns(:entries) + assert_equal 3, assigns(:entries).size + assert_not_nil assigns(:total_hours) + assert_equal "12.90", "%.2f" % assigns(:total_hours) + assert_tag :form, + :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'} + end + + def test_index_at_project_level_with_period + get :index, :project_id => 'ecookbook', + :f => ['spent_on'], + :op => {'spent_on' => '>t-'}, + :v => {'spent_on' => ['7']} + assert_response :success + assert_template 'index' + assert_not_nil assigns(:entries) + assert_not_nil assigns(:total_hours) + assert_tag :form, + :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'} + end + + def test_index_at_issue_level + get :index, :issue_id => 1 + assert_response :success + assert_template 'index' + assert_not_nil assigns(:entries) + assert_equal 2, assigns(:entries).size + assert_not_nil assigns(:total_hours) + assert_equal 154.25, assigns(:total_hours) + # display all time + assert_nil assigns(:from) + assert_nil assigns(:to) + # TODO: remove /projects/:project_id/issues/:issue_id/time_entries routes + # to use /issues/:issue_id/time_entries + assert_tag :form, + :attributes => {:action => "/projects/ecookbook/issues/1/time_entries", :id => 'query_form'} + end + + def test_index_should_sort_by_spent_on_and_created_on + t1 = TimeEntry.create!(:user => User.find(1), :project => Project.find(1), :hours => 1, :spent_on => '2012-06-16', :created_on => '2012-06-16 20:00:00', :activity_id => 10) + t2 = TimeEntry.create!(:user => User.find(1), :project => Project.find(1), :hours => 1, :spent_on => '2012-06-16', :created_on => '2012-06-16 20:05:00', :activity_id => 10) + t3 = TimeEntry.create!(:user => User.find(1), :project => Project.find(1), :hours => 1, :spent_on => '2012-06-15', :created_on => '2012-06-16 20:10:00', :activity_id => 10) + + get :index, :project_id => 1, + :f => ['spent_on'], + :op => {'spent_on' => '><'}, + :v => {'spent_on' => ['2012-06-15', '2012-06-16']} + assert_response :success + assert_equal [t2, t1, t3], assigns(:entries) + + get :index, :project_id => 1, + :f => ['spent_on'], + :op => {'spent_on' => '><'}, + :v => {'spent_on' => ['2012-06-15', '2012-06-16']}, + :sort => 'spent_on' + assert_response :success + assert_equal [t3, t1, t2], assigns(:entries) + end + + def test_index_with_filter_on_issue_custom_field + issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {2 => 'filter_on_issue_custom_field'}) + entry = TimeEntry.generate!(:issue => issue, :hours => 2.5) + + get :index, :f => ['issue.cf_2'], :op => {'issue.cf_2' => '='}, :v => {'issue.cf_2' => ['filter_on_issue_custom_field']} + assert_response :success + assert_equal [entry], assigns(:entries) + end + + def test_index_with_issue_custom_field_column + issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {2 => 'filter_on_issue_custom_field'}) + entry = TimeEntry.generate!(:issue => issue, :hours => 2.5) + + get :index, :c => %w(project spent_on issue comments hours issue.cf_2) + assert_response :success + assert_include :'issue.cf_2', assigns(:query).column_names + assert_select 'td.issue_cf_2', :text => 'filter_on_issue_custom_field' + end + + def test_index_atom_feed + get :index, :project_id => 1, :format => 'atom' + assert_response :success + assert_equal 'application/atom+xml', @response.content_type + assert_not_nil assigns(:items) + assert assigns(:items).first.is_a?(TimeEntry) + end + + def test_index_at_project_level_should_include_csv_export_dialog + get :index, :project_id => 'ecookbook', + :f => ['spent_on'], + :op => {'spent_on' => '>='}, + :v => {'spent_on' => ['2007-04-01']}, + :c => ['spent_on', 'user'] + assert_response :success + + assert_select '#csv-export-options' do + assert_select 'form[action=?][method=get]', '/projects/ecookbook/time_entries.csv' do + # filter + assert_select 'input[name=?][value=?]', 'f[]', 'spent_on' + assert_select 'input[name=?][value=?]', 'op[spent_on]', '>=' + assert_select 'input[name=?][value=?]', 'v[spent_on][]', '2007-04-01' + # columns + assert_select 'input[name=?][value=?]', 'c[]', 'spent_on' + assert_select 'input[name=?][value=?]', 'c[]', 'user' + assert_select 'input[name=?]', 'c[]', 2 + end + end + end + + def test_index_cross_project_should_include_csv_export_dialog + get :index + assert_response :success + + assert_select '#csv-export-options' do + assert_select 'form[action=?][method=get]', '/time_entries.csv' + end + end + + def test_index_at_issue_level_should_include_csv_export_dialog + get :index, :project_id => 'ecookbook', :issue_id => 3 + assert_response :success + + assert_select '#csv-export-options' do + assert_select 'form[action=?][method=get]', '/projects/ecookbook/issues/3/time_entries.csv' + end + end + + def test_index_csv_all_projects + Setting.date_format = '%m/%d/%Y' + get :index, :format => 'csv' + assert_response :success + assert_equal 'text/csv; header=present', response.content_type + end + + def test_index_csv + Setting.date_format = '%m/%d/%Y' + get :index, :project_id => 1, :format => 'csv' + assert_response :success + assert_equal 'text/csv; header=present', response.content_type + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/62/62d25b06b15c0b260279da71be5d88bfab79f61e.svn-base --- a/.svn/pristine/62/62d25b06b15c0b260279da71be5d88bfab79f61e.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,131 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) - -class WikiPageTest < ActiveSupport::TestCase - fixtures :projects, :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions - - def setup - @wiki = Wiki.find(1) - @page = @wiki.pages.first - end - - def test_create - page = WikiPage.new(:wiki => @wiki) - assert !page.save - assert_equal 1, page.errors.count - - page.title = "Page" - assert page.save - page.reload - assert !page.protected? - - @wiki.reload - assert @wiki.pages.include?(page) - end - - def test_sidebar_should_be_protected_by_default - page = @wiki.find_or_new_page('sidebar') - assert page.new_record? - assert page.protected? - end - - def test_find_or_new_page - page = @wiki.find_or_new_page("CookBook documentation") - assert_kind_of WikiPage, page - assert !page.new_record? - - page = @wiki.find_or_new_page("Non existing page") - assert_kind_of WikiPage, page - assert page.new_record? - end - - def test_parent_title - page = WikiPage.find_by_title('Another_page') - assert_nil page.parent_title - - page = WikiPage.find_by_title('Page_with_an_inline_image') - assert_equal 'CookBook documentation', page.parent_title - end - - def test_assign_parent - page = WikiPage.find_by_title('Another_page') - page.parent_title = 'CookBook documentation' - assert page.save - page.reload - assert_equal WikiPage.find_by_title('CookBook_documentation'), page.parent - end - - def test_unassign_parent - page = WikiPage.find_by_title('Page_with_an_inline_image') - page.parent_title = '' - assert page.save - page.reload - assert_nil page.parent - end - - def test_parent_validation - page = WikiPage.find_by_title('CookBook_documentation') - - # A page that doesn't exist - page.parent_title = 'Unknown title' - assert !page.save - assert_equal I18n.translate('activerecord.errors.messages.invalid'), page.errors.on(:parent_title) - # A child page - page.parent_title = 'Page_with_an_inline_image' - assert !page.save - assert_equal I18n.translate('activerecord.errors.messages.circular_dependency'), page.errors.on(:parent_title) - # The page itself - page.parent_title = 'CookBook_documentation' - assert !page.save - assert_equal I18n.translate('activerecord.errors.messages.circular_dependency'), page.errors.on(:parent_title) - - page.parent_title = 'Another_page' - assert page.save - end - - def test_destroy - page = WikiPage.find(1) - page.destroy - assert_nil WikiPage.find_by_id(1) - # make sure that page content and its history are deleted - assert WikiContent.find_all_by_page_id(1).empty? - assert WikiContent.versioned_class.find_all_by_page_id(1).empty? - end - - def test_destroy_should_not_nullify_children - page = WikiPage.find(2) - child_ids = page.child_ids - assert child_ids.any? - page.destroy - assert_nil WikiPage.find_by_id(2) - - children = WikiPage.find_all_by_id(child_ids) - assert_equal child_ids.size, children.size - children.each do |child| - assert_nil child.parent_id - end - end - - def test_updated_on_eager_load - page = WikiPage.with_updated_on.first - assert page.is_a?(WikiPage) - assert_not_nil page.read_attribute(:updated_on) - assert_equal page.content.updated_on, page.updated_on - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/62/62ecd1bfcad4ed987069f0252b9c65f6abdbb461.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/62/62ecd1bfcad4ed987069f0252b9c65f6abdbb461.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,72 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class UserPreferenceTest < ActiveSupport::TestCase + fixtures :users, :user_preferences + + def test_create + user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo") + user.login = "newuser" + user.password, user.password_confirmation = "password", "password" + assert user.save + + assert_kind_of UserPreference, user.pref + assert_kind_of Hash, user.pref.others + assert user.pref.save + end + + def test_update + user = User.find(1) + assert_equal true, user.pref.hide_mail + user.pref['preftest'] = 'value' + assert user.pref.save + + user.reload + assert_equal 'value', user.pref['preftest'] + end + + def test_others_hash + user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo") + user.login = "newuser" + user.password, user.password_confirmation = "password", "password" + assert user.save + assert_nil user.preference + up = UserPreference.new(:user => user) + assert_kind_of Hash, up.others + up.others = nil + assert_nil up.others + assert up.save + assert_kind_of Hash, up.others + end + + def test_reading_value_from_nil_others_hash + up = UserPreference.new(:user => User.new) + up.others = nil + assert_nil up.others + assert_nil up[:foo] + end + + def test_writing_value_to_nil_others_hash + up = UserPreference.new(:user => User.new) + up.others = nil + assert_nil up.others + up[:foo] = 'bar' + assert_equal 'bar', up[:foo] + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/63/6337da68aa661918822db9c4ab113e8ae3671adf.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/63/6337da68aa661918822db9c4ab113e8ae3671adf.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,846 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class Redmine::ApiTest::IssuesTest < Redmine::ApiTest::Base + fixtures :projects, + :users, + :roles, + :members, + :member_roles, + :issues, + :issue_statuses, + :issue_relations, + :versions, + :trackers, + :projects_trackers, + :issue_categories, + :enabled_modules, + :enumerations, + :attachments, + :workflows, + :custom_fields, + :custom_values, + :custom_fields_projects, + :custom_fields_trackers, + :time_entries, + :journals, + :journal_details, + :queries, + :attachments + + def setup + Setting.rest_api_enabled = '1' + end + + context "/issues" do + # Use a private project to make sure auth is really working and not just + # only showing public issues. + should_allow_api_authentication(:get, "/projects/private-child/issues.xml") + + should "contain metadata" do + get '/issues.xml' + + assert_tag :tag => 'issues', + :attributes => { + :type => 'array', + :total_count => assigns(:issue_count), + :limit => 25, + :offset => 0 + } + end + + context "with offset and limit" do + should "use the params" do + get '/issues.xml?offset=2&limit=3' + + assert_equal 3, assigns(:limit) + assert_equal 2, assigns(:offset) + assert_tag :tag => 'issues', :children => {:count => 3, :only => {:tag => 'issue'}} + end + end + + context "with nometa param" do + should "not contain metadata" do + get '/issues.xml?nometa=1' + + assert_tag :tag => 'issues', + :attributes => { + :type => 'array', + :total_count => nil, + :limit => nil, + :offset => nil + } + end + end + + context "with nometa header" do + should "not contain metadata" do + get '/issues.xml', {}, {'X-Redmine-Nometa' => '1'} + + assert_tag :tag => 'issues', + :attributes => { + :type => 'array', + :total_count => nil, + :limit => nil, + :offset => nil + } + end + end + + context "with relations" do + should "display relations" do + get '/issues.xml?include=relations' + + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_tag 'relations', + :parent => {:tag => 'issue', :child => {:tag => 'id', :content => '3'}}, + :children => {:count => 1}, + :child => { + :tag => 'relation', + :attributes => {:id => '2', :issue_id => '2', :issue_to_id => '3', + :relation_type => 'relates'} + } + assert_tag 'relations', + :parent => {:tag => 'issue', :child => {:tag => 'id', :content => '1'}}, + :children => {:count => 0} + end + end + + context "with invalid query params" do + should "return errors" do + get '/issues.xml', {:f => ['start_date'], :op => {:start_date => '='}} + + assert_response :unprocessable_entity + assert_equal 'application/xml', @response.content_type + assert_tag 'errors', :child => {:tag => 'error', :content => "Start date can't be blank"} + end + end + + context "with custom field filter" do + should "show only issues with the custom field value" do + get '/issues.xml', + {:set_filter => 1, :f => ['cf_1'], :op => {:cf_1 => '='}, + :v => {:cf_1 => ['MySQL']}} + expected_ids = Issue.visible.all( + :include => :custom_values, + :conditions => {:custom_values => {:custom_field_id => 1, :value => 'MySQL'}}).map(&:id) + assert_select 'issues > issue > id', :count => expected_ids.count do |ids| + ids.each { |id| assert expected_ids.delete(id.children.first.content.to_i) } + end + end + end + + context "with custom field filter (shorthand method)" do + should "show only issues with the custom field value" do + get '/issues.xml', { :cf_1 => 'MySQL' } + + expected_ids = Issue.visible.all( + :include => :custom_values, + :conditions => {:custom_values => {:custom_field_id => 1, :value => 'MySQL'}}).map(&:id) + + assert_select 'issues > issue > id', :count => expected_ids.count do |ids| + ids.each { |id| assert expected_ids.delete(id.children.first.content.to_i) } + end + end + end + end + + context "/index.json" do + should_allow_api_authentication(:get, "/projects/private-child/issues.json") + end + + context "/index.xml with filter" do + should "show only issues with the status_id" do + get '/issues.xml?status_id=5' + + expected_ids = Issue.visible.all(:conditions => {:status_id => 5}).map(&:id) + + assert_select 'issues > issue > id', :count => expected_ids.count do |ids| + ids.each { |id| assert expected_ids.delete(id.children.first.content.to_i) } + end + end + end + + context "/index.json with filter" do + should "show only issues with the status_id" do + get '/issues.json?status_id=5' + + json = ActiveSupport::JSON.decode(response.body) + status_ids_used = json['issues'].collect {|j| j['status']['id'] } + assert_equal 3, status_ids_used.length + assert status_ids_used.all? {|id| id == 5 } + end + + end + + # Issue 6 is on a private project + context "/issues/6.xml" do + should_allow_api_authentication(:get, "/issues/6.xml") + end + + context "/issues/6.json" do + should_allow_api_authentication(:get, "/issues/6.json") + end + + context "GET /issues/:id" do + context "with journals" do + context ".xml" do + should "display journals" do + get '/issues/1.xml?include=journals' + + assert_tag :tag => 'issue', + :child => { + :tag => 'journals', + :attributes => { :type => 'array' }, + :child => { + :tag => 'journal', + :attributes => { :id => '1'}, + :child => { + :tag => 'details', + :attributes => { :type => 'array' }, + :child => { + :tag => 'detail', + :attributes => { :name => 'status_id' }, + :child => { + :tag => 'old_value', + :content => '1', + :sibling => { + :tag => 'new_value', + :content => '2' + } + } + } + } + } + } + end + end + end + + context "with custom fields" do + context ".xml" do + should "display custom fields" do + get '/issues/3.xml' + + assert_tag :tag => 'issue', + :child => { + :tag => 'custom_fields', + :attributes => { :type => 'array' }, + :child => { + :tag => 'custom_field', + :attributes => { :id => '1'}, + :child => { + :tag => 'value', + :content => 'MySQL' + } + } + } + + assert_nothing_raised do + Hash.from_xml(response.body).to_xml + end + end + end + end + + context "with multi custom fields" do + setup do + field = CustomField.find(1) + field.update_attribute :multiple, true + issue = Issue.find(3) + issue.custom_field_values = {1 => ['MySQL', 'Oracle']} + issue.save! + end + + context ".xml" do + should "display custom fields" do + get '/issues/3.xml' + assert_response :success + assert_tag :tag => 'issue', + :child => { + :tag => 'custom_fields', + :attributes => { :type => 'array' }, + :child => { + :tag => 'custom_field', + :attributes => { :id => '1'}, + :child => { + :tag => 'value', + :attributes => { :type => 'array' }, + :children => { :count => 2 } + } + } + } + + xml = Hash.from_xml(response.body) + custom_fields = xml['issue']['custom_fields'] + assert_kind_of Array, custom_fields + field = custom_fields.detect {|f| f['id'] == '1'} + assert_kind_of Hash, field + assert_equal ['MySQL', 'Oracle'], field['value'].sort + end + end + + context ".json" do + should "display custom fields" do + get '/issues/3.json' + assert_response :success + json = ActiveSupport::JSON.decode(response.body) + custom_fields = json['issue']['custom_fields'] + assert_kind_of Array, custom_fields + field = custom_fields.detect {|f| f['id'] == 1} + assert_kind_of Hash, field + assert_equal ['MySQL', 'Oracle'], field['value'].sort + end + end + end + + context "with empty value for multi custom field" do + setup do + field = CustomField.find(1) + field.update_attribute :multiple, true + issue = Issue.find(3) + issue.custom_field_values = {1 => ['']} + issue.save! + end + + context ".xml" do + should "display custom fields" do + get '/issues/3.xml' + assert_response :success + assert_tag :tag => 'issue', + :child => { + :tag => 'custom_fields', + :attributes => { :type => 'array' }, + :child => { + :tag => 'custom_field', + :attributes => { :id => '1'}, + :child => { + :tag => 'value', + :attributes => { :type => 'array' }, + :children => { :count => 0 } + } + } + } + + xml = Hash.from_xml(response.body) + custom_fields = xml['issue']['custom_fields'] + assert_kind_of Array, custom_fields + field = custom_fields.detect {|f| f['id'] == '1'} + assert_kind_of Hash, field + assert_equal [], field['value'] + end + end + + context ".json" do + should "display custom fields" do + get '/issues/3.json' + assert_response :success + json = ActiveSupport::JSON.decode(response.body) + custom_fields = json['issue']['custom_fields'] + assert_kind_of Array, custom_fields + field = custom_fields.detect {|f| f['id'] == 1} + assert_kind_of Hash, field + assert_equal [], field['value'].sort + end + end + end + + context "with attachments" do + context ".xml" do + should "display attachments" do + get '/issues/3.xml?include=attachments' + + assert_tag :tag => 'issue', + :child => { + :tag => 'attachments', + :children => {:count => 5}, + :child => { + :tag => 'attachment', + :child => { + :tag => 'filename', + :content => 'source.rb', + :sibling => { + :tag => 'content_url', + :content => 'http://www.example.com/attachments/download/4/source.rb' + } + } + } + } + end + end + end + + context "with subtasks" do + setup do + @c1 = Issue.create!( + :status_id => 1, :subject => "child c1", + :tracker_id => 1, :project_id => 1, :author_id => 1, + :parent_issue_id => 1 + ) + @c2 = Issue.create!( + :status_id => 1, :subject => "child c2", + :tracker_id => 1, :project_id => 1, :author_id => 1, + :parent_issue_id => 1 + ) + @c3 = Issue.create!( + :status_id => 1, :subject => "child c3", + :tracker_id => 1, :project_id => 1, :author_id => 1, + :parent_issue_id => @c1.id + ) + end + + context ".xml" do + should "display children" do + get '/issues/1.xml?include=children' + + assert_tag :tag => 'issue', + :child => { + :tag => 'children', + :children => {:count => 2}, + :child => { + :tag => 'issue', + :attributes => {:id => @c1.id.to_s}, + :child => { + :tag => 'subject', + :content => 'child c1', + :sibling => { + :tag => 'children', + :children => {:count => 1}, + :child => { + :tag => 'issue', + :attributes => {:id => @c3.id.to_s} + } + } + } + } + } + end + + context ".json" do + should "display children" do + get '/issues/1.json?include=children' + + json = ActiveSupport::JSON.decode(response.body) + assert_equal([ + { + 'id' => @c1.id, 'subject' => 'child c1', 'tracker' => {'id' => 1, 'name' => 'Bug'}, + 'children' => [{'id' => @c3.id, 'subject' => 'child c3', + 'tracker' => {'id' => 1, 'name' => 'Bug'} }] + }, + { 'id' => @c2.id, 'subject' => 'child c2', 'tracker' => {'id' => 1, 'name' => 'Bug'} } + ], + json['issue']['children']) + end + end + end + end + end + + test "GET /issues/:id.xml?include=watchers should include watchers" do + Watcher.create!(:user_id => 3, :watchable => Issue.find(1)) + + get '/issues/1.xml?include=watchers', {}, credentials('jsmith') + + assert_response :ok + assert_equal 'application/xml', response.content_type + assert_select 'issue' do + assert_select 'watchers', Issue.find(1).watchers.count + assert_select 'watchers' do + assert_select 'user[id=3]' + end + end + end + + context "POST /issues.xml" do + should_allow_api_authentication( + :post, + '/issues.xml', + {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}}, + {:success_code => :created} + ) + should "create an issue with the attributes" do + assert_difference('Issue.count') do + post '/issues.xml', + {:issue => {:project_id => 1, :subject => 'API test', + :tracker_id => 2, :status_id => 3}}, credentials('jsmith') + end + issue = Issue.first(:order => 'id DESC') + assert_equal 1, issue.project_id + assert_equal 2, issue.tracker_id + assert_equal 3, issue.status_id + assert_equal 'API test', issue.subject + + assert_response :created + assert_equal 'application/xml', @response.content_type + assert_tag 'issue', :child => {:tag => 'id', :content => issue.id.to_s} + end + end + + test "POST /issues.xml with watcher_user_ids should create issue with watchers" do + assert_difference('Issue.count') do + post '/issues.xml', + {:issue => {:project_id => 1, :subject => 'Watchers', + :tracker_id => 2, :status_id => 3, :watcher_user_ids => [3, 1]}}, credentials('jsmith') + assert_response :created + end + issue = Issue.order('id desc').first + assert_equal 2, issue.watchers.size + assert_equal [1, 3], issue.watcher_user_ids.sort + end + + context "POST /issues.xml with failure" do + should "have an errors tag" do + assert_no_difference('Issue.count') do + post '/issues.xml', {:issue => {:project_id => 1}}, credentials('jsmith') + end + + assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"} + end + end + + context "POST /issues.json" do + should_allow_api_authentication(:post, + '/issues.json', + {:issue => {:project_id => 1, :subject => 'API test', + :tracker_id => 2, :status_id => 3}}, + {:success_code => :created}) + + should "create an issue with the attributes" do + assert_difference('Issue.count') do + post '/issues.json', + {:issue => {:project_id => 1, :subject => 'API test', + :tracker_id => 2, :status_id => 3}}, + credentials('jsmith') + end + + issue = Issue.first(:order => 'id DESC') + assert_equal 1, issue.project_id + assert_equal 2, issue.tracker_id + assert_equal 3, issue.status_id + assert_equal 'API test', issue.subject + end + + end + + context "POST /issues.json with failure" do + should "have an errors element" do + assert_no_difference('Issue.count') do + post '/issues.json', {:issue => {:project_id => 1}}, credentials('jsmith') + end + + json = ActiveSupport::JSON.decode(response.body) + assert json['errors'].include?("Subject can't be blank") + end + end + + # Issue 6 is on a private project + context "PUT /issues/6.xml" do + setup do + @parameters = {:issue => {:subject => 'API update', :notes => 'A new note'}} + end + + should_allow_api_authentication(:put, + '/issues/6.xml', + {:issue => {:subject => 'API update', :notes => 'A new note'}}, + {:success_code => :ok}) + + should "not create a new issue" do + assert_no_difference('Issue.count') do + put '/issues/6.xml', @parameters, credentials('jsmith') + end + end + + should "create a new journal" do + assert_difference('Journal.count') do + put '/issues/6.xml', @parameters, credentials('jsmith') + end + end + + should "add the note to the journal" do + put '/issues/6.xml', @parameters, credentials('jsmith') + + journal = Journal.last + assert_equal "A new note", journal.notes + end + + should "update the issue" do + put '/issues/6.xml', @parameters, credentials('jsmith') + + issue = Issue.find(6) + assert_equal "API update", issue.subject + end + + end + + context "PUT /issues/3.xml with custom fields" do + setup do + @parameters = { + :issue => {:custom_fields => [{'id' => '1', 'value' => 'PostgreSQL' }, + {'id' => '2', 'value' => '150'}]} + } + end + + should "update custom fields" do + assert_no_difference('Issue.count') do + put '/issues/3.xml', @parameters, credentials('jsmith') + end + + issue = Issue.find(3) + assert_equal '150', issue.custom_value_for(2).value + assert_equal 'PostgreSQL', issue.custom_value_for(1).value + end + end + + context "PUT /issues/3.xml with multi custom fields" do + setup do + field = CustomField.find(1) + field.update_attribute :multiple, true + @parameters = { + :issue => {:custom_fields => [{'id' => '1', 'value' => ['MySQL', 'PostgreSQL'] }, + {'id' => '2', 'value' => '150'}]} + } + end + + should "update custom fields" do + assert_no_difference('Issue.count') do + put '/issues/3.xml', @parameters, credentials('jsmith') + end + + issue = Issue.find(3) + assert_equal '150', issue.custom_value_for(2).value + assert_equal ['MySQL', 'PostgreSQL'], issue.custom_field_value(1).sort + end + end + + context "PUT /issues/3.xml with project change" do + setup do + @parameters = {:issue => {:project_id => 2, :subject => 'Project changed'}} + end + + should "update project" do + assert_no_difference('Issue.count') do + put '/issues/3.xml', @parameters, credentials('jsmith') + end + + issue = Issue.find(3) + assert_equal 2, issue.project_id + assert_equal 'Project changed', issue.subject + end + end + + context "PUT /issues/6.xml with failed update" do + setup do + @parameters = {:issue => {:subject => ''}} + end + + should "not create a new issue" do + assert_no_difference('Issue.count') do + put '/issues/6.xml', @parameters, credentials('jsmith') + end + end + + should "not create a new journal" do + assert_no_difference('Journal.count') do + put '/issues/6.xml', @parameters, credentials('jsmith') + end + end + + should "have an errors tag" do + put '/issues/6.xml', @parameters, credentials('jsmith') + + assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"} + end + end + + context "PUT /issues/6.json" do + setup do + @parameters = {:issue => {:subject => 'API update', :notes => 'A new note'}} + end + + should_allow_api_authentication(:put, + '/issues/6.json', + {:issue => {:subject => 'API update', :notes => 'A new note'}}, + {:success_code => :ok}) + + should "update the issue" do + assert_no_difference('Issue.count') do + assert_difference('Journal.count') do + put '/issues/6.json', @parameters, credentials('jsmith') + + assert_response :ok + assert_equal '', response.body + end + end + + issue = Issue.find(6) + assert_equal "API update", issue.subject + journal = Journal.last + assert_equal "A new note", journal.notes + end + end + + context "PUT /issues/6.json with failed update" do + should "return errors" do + assert_no_difference('Issue.count') do + assert_no_difference('Journal.count') do + put '/issues/6.json', {:issue => {:subject => ''}}, credentials('jsmith') + + assert_response :unprocessable_entity + end + end + + json = ActiveSupport::JSON.decode(response.body) + assert json['errors'].include?("Subject can't be blank") + end + end + + context "DELETE /issues/1.xml" do + should_allow_api_authentication(:delete, + '/issues/6.xml', + {}, + {:success_code => :ok}) + + should "delete the issue" do + assert_difference('Issue.count', -1) do + delete '/issues/6.xml', {}, credentials('jsmith') + + assert_response :ok + assert_equal '', response.body + end + + assert_nil Issue.find_by_id(6) + end + end + + context "DELETE /issues/1.json" do + should_allow_api_authentication(:delete, + '/issues/6.json', + {}, + {:success_code => :ok}) + + should "delete the issue" do + assert_difference('Issue.count', -1) do + delete '/issues/6.json', {}, credentials('jsmith') + + assert_response :ok + assert_equal '', response.body + end + + assert_nil Issue.find_by_id(6) + end + end + + test "POST /issues/:id/watchers.xml should add watcher" do + assert_difference 'Watcher.count' do + post '/issues/1/watchers.xml', {:user_id => 3}, credentials('jsmith') + + assert_response :ok + assert_equal '', response.body + end + watcher = Watcher.order('id desc').first + assert_equal Issue.find(1), watcher.watchable + assert_equal User.find(3), watcher.user + end + + test "DELETE /issues/:id/watchers/:user_id.xml should remove watcher" do + Watcher.create!(:user_id => 3, :watchable => Issue.find(1)) + + assert_difference 'Watcher.count', -1 do + delete '/issues/1/watchers/3.xml', {}, credentials('jsmith') + + assert_response :ok + assert_equal '', response.body + end + assert_equal false, Issue.find(1).watched_by?(User.find(3)) + end + + def test_create_issue_with_uploaded_file + set_tmp_attachments_directory + # upload the file + assert_difference 'Attachment.count' do + post '/uploads.xml', 'test_create_with_upload', + {"CONTENT_TYPE" => 'application/octet-stream'}.merge(credentials('jsmith')) + assert_response :created + end + xml = Hash.from_xml(response.body) + token = xml['upload']['token'] + attachment = Attachment.first(:order => 'id DESC') + + # create the issue with the upload's token + assert_difference 'Issue.count' do + post '/issues.xml', + {:issue => {:project_id => 1, :subject => 'Uploaded file', + :uploads => [{:token => token, :filename => 'test.txt', + :content_type => 'text/plain'}]}}, + credentials('jsmith') + assert_response :created + end + issue = Issue.first(:order => 'id DESC') + assert_equal 1, issue.attachments.count + assert_equal attachment, issue.attachments.first + + attachment.reload + assert_equal 'test.txt', attachment.filename + assert_equal 'text/plain', attachment.content_type + assert_equal 'test_create_with_upload'.size, attachment.filesize + assert_equal 2, attachment.author_id + + # get the issue with its attachments + get "/issues/#{issue.id}.xml", :include => 'attachments' + assert_response :success + xml = Hash.from_xml(response.body) + attachments = xml['issue']['attachments'] + assert_kind_of Array, attachments + assert_equal 1, attachments.size + url = attachments.first['content_url'] + assert_not_nil url + + # download the attachment + get url + assert_response :success + end + + def test_update_issue_with_uploaded_file + set_tmp_attachments_directory + # upload the file + assert_difference 'Attachment.count' do + post '/uploads.xml', 'test_upload_with_upload', + {"CONTENT_TYPE" => 'application/octet-stream'}.merge(credentials('jsmith')) + assert_response :created + end + xml = Hash.from_xml(response.body) + token = xml['upload']['token'] + attachment = Attachment.first(:order => 'id DESC') + + # update the issue with the upload's token + assert_difference 'Journal.count' do + put '/issues/1.xml', + {:issue => {:notes => 'Attachment added', + :uploads => [{:token => token, :filename => 'test.txt', + :content_type => 'text/plain'}]}}, + credentials('jsmith') + assert_response :ok + assert_equal '', @response.body + end + + issue = Issue.find(1) + assert_include attachment, issue.attachments + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/63/634fa24a02c5bb6db30eef5565a1a7bdf8877051.svn-base --- a/.svn/pristine/63/634fa24a02c5bb6db30eef5565a1a7bdf8877051.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,202 +0,0 @@ -# redMine - project management software -# Copyright (C) 2006-2007 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - - -# DO NOT MODIFY THIS FILE !!! -# Settings can be defined through the application in Admin -> Settings - -app_title: - default: Redmine -app_subtitle: - default: Project management -welcome_text: - default: -login_required: - default: 0 -self_registration: - default: '2' -lost_password: - default: 1 -password_min_length: - format: int - default: 4 -attachment_max_size: - format: int - default: 5120 -issues_export_limit: - format: int - default: 500 -activity_days_default: - format: int - default: 30 -per_page_options: - default: '25,50,100' -mail_from: - default: redmine@example.net -bcc_recipients: - default: 1 -plain_text_mail: - default: 0 -text_formatting: - default: textile -cache_formatted_text: - default: 0 -wiki_compression: - default: "" -default_language: - default: en -host_name: - default: localhost:3000 -protocol: - default: http -feeds_limit: - format: int - default: 15 -gantt_items_limit: - format: int - default: 500 -# Maximum size of files that can be displayed -# inline through the file viewer (in KB) -file_max_size_displayed: - format: int - default: 512 -diff_max_lines_displayed: - format: int - default: 1500 -enabled_scm: - serialized: true - default: - - Subversion - - Darcs - - Mercurial - - Cvs - - Bazaar - - Git -autofetch_changesets: - default: 1 -sys_api_enabled: - default: 0 -sys_api_key: - default: '' -commit_ref_keywords: - default: 'refs,references,IssueID' -commit_fix_keywords: - default: 'fixes,closes' -commit_fix_status_id: - format: int - default: 0 -commit_fix_done_ratio: - default: 100 -commit_logtime_enabled: - default: 0 -commit_logtime_activity_id: - format: int - default: 0 -# autologin duration in days -# 0 means autologin is disabled -autologin: - format: int - default: 0 -# date format -date_format: - default: '' -time_format: - default: '' -user_format: - default: :firstname_lastname - format: symbol -cross_project_issue_relations: - default: 0 -issue_group_assignment: - default: 0 -default_issue_start_date_to_creation_date: - default: 1 -notified_events: - serialized: true - default: - - issue_added - - issue_updated -mail_handler_body_delimiters: - default: '' -mail_handler_api_enabled: - default: 0 -mail_handler_api_key: - default: -issue_list_default_columns: - serialized: true - default: - - tracker - - status - - priority - - subject - - assigned_to - - updated_on -display_subprojects_issues: - default: 1 -issue_done_ratio: - default: 'issue_field' -default_projects_public: - default: 1 -default_projects_modules: - serialized: true - default: - - issue_tracking - - time_tracking - - news - - documents - - files - - wiki - - repository - - boards - - calendar - - gantt -# Role given to a non-admin user who creates a project -new_project_user_role_id: - format: int - default: '' -sequential_project_identifiers: - default: 0 -# encodings used to convert repository files content to UTF-8 -# multiple values accepted, comma separated -repositories_encodings: - default: '' -# 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 -gravatar_default: - default: '' -start_of_week: - default: '' -rest_api_enabled: - default: 0 -default_notification_option: - default: 'only_my_events' -emails_header: - default: '' diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/63/6363e04d893f5e743cf860de53b9bf91e0dfa927.svn-base --- a/.svn/pristine/63/6363e04d893f5e743cf860de53b9bf91e0dfa927.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,15 +0,0 @@ -<%= call_hook(:view_repositories_show_contextual, { :repository => @repository, :project => @project }) %> - -
    - <%= render :partial => 'navigation' %> -
    - -

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

    - -

    <%= render :partial => 'link_to_functions' %>

    - -<%= render :partial => 'common/file', :locals => {:filename => @path, :content => @content} %> - -<% content_for :header_tags do %> -<%= stylesheet_link_tag "scm" %> -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/63/636943525457e5c4d4d0c3e654c86a8a51ed59f5.svn-base --- a/.svn/pristine/63/636943525457e5c4d4d0c3e654c86a8a51ed59f5.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,159 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../../../test_helper', __FILE__) - -class Redmine::I18nTest < ActiveSupport::TestCase - include Redmine::I18n - include ActionView::Helpers::NumberHelper - - def setup - @hook_module = Redmine::Hook - end - - def test_date_format_default - set_language_if_valid 'en' - today = Date.today - Setting.date_format = '' - assert_equal I18n.l(today), format_date(today) - end - - def test_date_format - set_language_if_valid 'en' - today = Date.today - Setting.date_format = '%d %m %Y' - assert_equal today.strftime('%d %m %Y'), format_date(today) - end - - def test_date_and_time_for_each_language - Setting.date_format = '' - valid_languages.each do |lang| - set_language_if_valid lang - assert_nothing_raised "#{lang} failure" do - format_date(Date.today) - format_time(Time.now) - format_time(Time.now, false) - assert_not_equal 'default', ::I18n.l(Date.today, :format => :default), - "date.formats.default missing in #{lang}" - assert_not_equal 'time', ::I18n.l(Time.now, :format => :time), - "time.formats.time missing in #{lang}" - end - assert l('date.day_names').is_a?(Array) - assert_equal 7, l('date.day_names').size - - assert l('date.month_names').is_a?(Array) - assert_equal 13, l('date.month_names').size - end - end - - def test_time_format - set_language_if_valid 'en' - now = Time.parse('2011-02-20 15:45:22') - with_settings :time_format => '%H:%M' do - with_settings :date_format => '' do - assert_equal '02/20/2011 15:45', format_time(now) - assert_equal '15:45', format_time(now, false) - end - with_settings :date_format => '%Y-%m-%d' do - assert_equal '2011-02-20 15:45', format_time(now) - assert_equal '15:45', format_time(now, false) - end - end - end - - def test_time_format_default - set_language_if_valid 'en' - now = Time.parse('2011-02-20 15:45:22') - with_settings :time_format => '' do - with_settings :date_format => '' do - assert_equal '02/20/2011 03:45 pm', format_time(now) - assert_equal '03:45 pm', format_time(now, false) - end - with_settings :date_format => '%Y-%m-%d' do - assert_equal '2011-02-20 03:45 pm', format_time(now) - assert_equal '03:45 pm', format_time(now, false) - end - end - end - - def test_time_format - set_language_if_valid 'en' - now = Time.now - Setting.date_format = '%d %m %Y' - Setting.time_format = '%H %M' - assert_equal now.strftime('%d %m %Y %H %M'), format_time(now) - assert_equal now.strftime('%H %M'), format_time(now, false) - end - - def test_utc_time_format - set_language_if_valid 'en' - now = Time.now - Setting.date_format = '%d %m %Y' - Setting.time_format = '%H %M' - assert_equal now.strftime('%d %m %Y %H %M'), format_time(now.utc) - assert_equal now.strftime('%H %M'), format_time(now.utc, false) - end - - def test_number_to_human_size_for_each_language - valid_languages.each do |lang| - set_language_if_valid lang - assert_nothing_raised "#{lang} failure" do - number_to_human_size(1024*1024*4) - end - end - end - - def test_valid_languages - assert valid_languages.is_a?(Array) - assert valid_languages.first.is_a?(Symbol) - end - - def test_valid_language - to_test = {'fr' => :fr, - 'Fr' => :fr, - 'zh' => :zh, - 'zh-tw' => :"zh-TW", - 'zh-TW' => :"zh-TW", - 'zh-ZZ' => nil } - to_test.each {|lang, expected| assert_equal expected, find_language(lang)} - end - - def test_fallback - ::I18n.backend.store_translations(:en, {:untranslated => "Untranslated string"}) - ::I18n.locale = 'en' - assert_equal "Untranslated string", l(:untranslated) - ::I18n.locale = 'fr' - assert_equal "Untranslated string", l(:untranslated) - - ::I18n.backend.store_translations(:fr, {:untranslated => "Pas de traduction"}) - ::I18n.locale = 'en' - assert_equal "Untranslated string", l(:untranslated) - ::I18n.locale = 'fr' - assert_equal "Pas de traduction", l(:untranslated) - end - - def test_utf8 - set_language_if_valid 'ja' - str_ja_yes = "\xe3\x81\xaf\xe3\x81\x84" - i18n_ja_yes = l(:general_text_Yes) - if str_ja_yes.respond_to?(:force_encoding) - str_ja_yes.force_encoding('UTF-8') - assert_equal "UTF-8", i18n_ja_yes.encoding.to_s - end - assert_equal str_ja_yes, i18n_ja_yes - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/63/638ef428af64a93a5a54376e8e444c15761d3973.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/63/638ef428af64a93a5a54376e8e444c15761d3973.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,57 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class CommentTest < ActiveSupport::TestCase + fixtures :users, :news, :comments, :projects, :enabled_modules + + def setup + @jsmith = User.find(2) + @news = News.find(1) + end + + def test_create + comment = Comment.new(:commented => @news, :author => @jsmith, :comments => "my comment") + assert comment.save + @news.reload + assert_equal 2, @news.comments_count + end + + def test_create_should_send_notification + Watcher.create!(:watchable => @news, :user => @jsmith) + + with_settings :notified_events => %w(news_comment_added) do + assert_difference 'ActionMailer::Base.deliveries.size' do + Comment.create!(:commented => @news, :author => @jsmith, :comments => "my comment") + end + end + end + + def test_validate + comment = Comment.new(:commented => @news) + assert !comment.save + assert_equal 2, comment.errors.count + end + + def test_destroy + comment = Comment.find(1) + assert comment.destroy + @news.reload + assert_equal 0, @news.comments_count + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/63/63ab165c5d9aefdb49df1a8bc5d36c75c1c75e7e.svn-base --- a/.svn/pristine/63/63ab165c5d9aefdb49df1a8bc5d36c75c1c75e7e.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,151 +0,0 @@ -require File.expand_path('../../test_helper', __FILE__) - -class ContextMenusControllerTest < ActionController::TestCase - fixtures :projects, - :trackers, - :projects_trackers, - :roles, - :member_roles, - :members, - :auth_sources, - :enabled_modules, - :workflows, - :journals, :journal_details, - :versions, - :issues, :issue_statuses, :issue_categories, - :users, - :enumerations - - def test_context_menu_one_issue - @request.session[:user_id] = 2 - get :issues, :ids => [1] - assert_response :success - assert_template 'context_menu' - assert_tag :tag => 'a', :content => 'Edit', - :attributes => { :href => '/issues/1/edit', - :class => 'icon-edit' } - assert_tag :tag => 'a', :content => 'Closed', - :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&issue%5Bstatus_id%5D=5', - :class => '' } - assert_tag :tag => 'a', :content => 'Immediate', - :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&issue%5Bpriority_id%5D=8', - :class => '' } - assert_no_tag :tag => 'a', :content => 'Inactive Priority' - # Versions - assert_tag :tag => 'a', :content => '2.0', - :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&issue%5Bfixed_version_id%5D=3', - :class => '' } - assert_tag :tag => 'a', :content => 'eCookbook Subproject 1 - 2.0', - :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&issue%5Bfixed_version_id%5D=4', - :class => '' } - - assert_tag :tag => 'a', :content => 'Dave Lopper', - :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&issue%5Bassigned_to_id%5D=3', - :class => '' } - assert_tag :tag => 'a', :content => 'Duplicate', - :attributes => { :href => '/projects/ecookbook/issues/1/copy', - :class => 'icon-duplicate' } - assert_tag :tag => 'a', :content => 'Copy', - :attributes => { :href => '/issues/move/new?copy_options%5Bcopy%5D=t&ids%5B%5D=1', - :class => 'icon-copy' } - assert_tag :tag => 'a', :content => 'Move', - :attributes => { :href => '/issues/move/new?ids%5B%5D=1', - :class => 'icon-move' } - assert_tag :tag => 'a', :content => 'Delete', - :attributes => { :href => '/issues/destroy?ids%5B%5D=1', - :class => 'icon-del' } - end - - def test_context_menu_one_issue_by_anonymous - get :issues, :ids => [1] - assert_response :success - assert_template 'context_menu' - assert_tag :tag => 'a', :content => 'Delete', - :attributes => { :href => '#', - :class => 'icon-del disabled' } - end - - def test_context_menu_multiple_issues_of_same_project - @request.session[:user_id] = 2 - get :issues, :ids => [1, 2] - assert_response :success - assert_template 'context_menu' - assert_not_nil assigns(:issues) - assert_equal [1, 2], assigns(:issues).map(&:id).sort - - ids = assigns(:issues).map(&:id).map {|i| "ids%5B%5D=#{i}"}.join('&') - assert_tag :tag => 'a', :content => 'Edit', - :attributes => { :href => "/issues/bulk_edit?#{ids}", - :class => 'icon-edit' } - assert_tag :tag => 'a', :content => 'Closed', - :attributes => { :href => "/issues/bulk_edit?#{ids}&issue%5Bstatus_id%5D=5", - :class => '' } - assert_tag :tag => 'a', :content => 'Immediate', - :attributes => { :href => "/issues/bulk_edit?#{ids}&issue%5Bpriority_id%5D=8", - :class => '' } - assert_tag :tag => 'a', :content => 'Dave Lopper', - :attributes => { :href => "/issues/bulk_edit?#{ids}&issue%5Bassigned_to_id%5D=3", - :class => '' } - assert_tag :tag => 'a', :content => 'Copy', - :attributes => { :href => "/issues/move/new?copy_options%5Bcopy%5D=t&#{ids}", - :class => 'icon-copy' } - assert_tag :tag => 'a', :content => 'Move', - :attributes => { :href => "/issues/move/new?#{ids}", - :class => 'icon-move' } - assert_tag :tag => 'a', :content => 'Delete', - :attributes => { :href => "/issues/destroy?#{ids}", - :class => 'icon-del' } - end - - def test_context_menu_multiple_issues_of_different_projects - @request.session[:user_id] = 2 - get :issues, :ids => [1, 2, 6] - assert_response :success - assert_template 'context_menu' - assert_not_nil assigns(:issues) - assert_equal [1, 2, 6], assigns(:issues).map(&:id).sort - - ids = assigns(:issues).map(&:id).map {|i| "ids%5B%5D=#{i}"}.join('&') - assert_tag :tag => 'a', :content => 'Edit', - :attributes => { :href => "/issues/bulk_edit?#{ids}", - :class => 'icon-edit' } - assert_tag :tag => 'a', :content => 'Closed', - :attributes => { :href => "/issues/bulk_edit?#{ids}&issue%5Bstatus_id%5D=5", - :class => '' } - assert_tag :tag => 'a', :content => 'Immediate', - :attributes => { :href => "/issues/bulk_edit?#{ids}&issue%5Bpriority_id%5D=8", - :class => '' } - assert_tag :tag => 'a', :content => 'John Smith', - :attributes => { :href => "/issues/bulk_edit?#{ids}&issue%5Bassigned_to_id%5D=2", - :class => '' } - assert_tag :tag => 'a', :content => 'Delete', - :attributes => { :href => "/issues/destroy?#{ids}", - :class => 'icon-del' } - end - - def test_context_menu_issue_visibility - get :issues, :ids => [1, 4] - assert_response :success - assert_template 'context_menu' - assert_equal [1], assigns(:issues).collect(&:id) - end - - def test_time_entries_context_menu - @request.session[:user_id] = 2 - get :time_entries, :ids => [1, 2] - assert_response :success - assert_template 'time_entries' - assert_tag 'a', :content => 'Edit' - assert_no_tag 'a', :content => 'Edit', :attributes => {:class => /disabled/} - end - - def test_time_entries_context_menu_without_edit_permission - @request.session[:user_id] = 2 - Role.find_by_name('Manager').remove_permission! :edit_time_entries - - get :time_entries, :ids => [1, 2] - assert_response :success - assert_template 'time_entries' - assert_tag 'a', :content => 'Edit', :attributes => {:class => /disabled/} - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/63/63f5d9f908a9cb71c1fdf7287c1a8cd3db047d81.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/63/63f5d9f908a9cb71c1fdf7287c1a8cd3db047d81.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,211 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class TrackersControllerTest < ActionController::TestCase + fixtures :trackers, :projects, :projects_trackers, :users, :issues, :custom_fields + + def setup + User.current = nil + @request.session[:user_id] = 1 # admin + end + + def test_index + get :index + assert_response :success + assert_template 'index' + end + + def test_index_by_anonymous_should_redirect_to_login_form + @request.session[:user_id] = nil + get :index + assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Ftrackers' + end + + def test_index_by_user_should_respond_with_406 + @request.session[:user_id] = 2 + get :index + assert_response 406 + end + + def test_new + get :new + assert_response :success + assert_template 'new' + end + + def test_create + assert_difference 'Tracker.count' do + post :create, :tracker => { :name => 'New tracker', :project_ids => ['1', '', ''], :custom_field_ids => ['1', '6', ''] } + end + assert_redirected_to :action => 'index' + tracker = Tracker.first(:order => 'id DESC') + assert_equal 'New tracker', tracker.name + assert_equal [1], tracker.project_ids.sort + assert_equal Tracker::CORE_FIELDS, tracker.core_fields + assert_equal [1, 6], tracker.custom_field_ids.sort + assert_equal 0, tracker.workflow_rules.count + end + + def create_with_disabled_core_fields + assert_difference 'Tracker.count' do + post :create, :tracker => { :name => 'New tracker', :core_fields => ['assigned_to_id', 'fixed_version_id', ''] } + end + assert_redirected_to :action => 'index' + tracker = Tracker.first(:order => 'id DESC') + assert_equal 'New tracker', tracker.name + assert_equal %w(assigned_to_id fixed_version_id), tracker.core_fields + end + + def test_create_new_with_workflow_copy + assert_difference 'Tracker.count' do + post :create, :tracker => { :name => 'New tracker' }, :copy_workflow_from => 1 + end + assert_redirected_to :action => 'index' + tracker = Tracker.find_by_name('New tracker') + assert_equal 0, tracker.projects.count + assert_equal Tracker.find(1).workflow_rules.count, tracker.workflow_rules.count + end + + def test_create_with_failure + assert_no_difference 'Tracker.count' do + post :create, :tracker => { :name => '', :project_ids => ['1', '', ''], :custom_field_ids => ['1', '6', ''] } + end + assert_response :success + assert_template 'new' + assert_error_tag :content => /name can't be blank/i + end + + def test_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_edit_should_check_core_fields + tracker = Tracker.find(1) + tracker.core_fields = %w(assigned_to_id fixed_version_id) + tracker.save! + + get :edit, :id => 1 + assert_response :success + assert_template 'edit' + + assert_select 'input[name=?][value=assigned_to_id][checked=checked]', 'tracker[core_fields][]' + assert_select 'input[name=?][value=fixed_version_id][checked=checked]', 'tracker[core_fields][]' + + assert_select 'input[name=?][value=category_id]', 'tracker[core_fields][]' + assert_select 'input[name=?][value=category_id][checked=checked]', 'tracker[core_fields][]', 0 + + assert_select 'input[name=?][value=][type=hidden]', 'tracker[core_fields][]' + end + + def test_update + put :update, :id => 1, :tracker => { :name => 'Renamed', + :project_ids => ['1', '2', ''] } + assert_redirected_to :action => 'index' + assert_equal [1, 2], Tracker.find(1).project_ids.sort + end + + def test_update_without_projects + put :update, :id => 1, :tracker => { :name => 'Renamed', + :project_ids => [''] } + assert_redirected_to :action => 'index' + assert Tracker.find(1).project_ids.empty? + end + + def test_update_without_core_fields + put :update, :id => 1, :tracker => { :name => 'Renamed', :core_fields => [''] } + assert_redirected_to :action => 'index' + assert Tracker.find(1).core_fields.empty? + end + + def test_update_with_failure + put :update, :id => 1, :tracker => { :name => '' } + assert_response :success + assert_template 'edit' + assert_error_tag :content => /name can't be blank/i + end + + def test_move_lower + tracker = Tracker.find_by_position(1) + put :update, :id => 1, :tracker => { :move_to => 'lower' } + assert_equal 2, tracker.reload.position + end + + def test_destroy + tracker = Tracker.create!(:name => 'Destroyable') + assert_difference 'Tracker.count', -1 do + delete :destroy, :id => tracker.id + end + assert_redirected_to :action => 'index' + assert_nil flash[:error] + end + + def test_destroy_tracker_in_use + assert_no_difference 'Tracker.count' do + delete :destroy, :id => 1 + end + assert_redirected_to :action => 'index' + assert_not_nil flash[:error] + end + + def test_get_fields + get :fields + assert_response :success + assert_template 'fields' + + assert_select 'form' do + assert_select 'input[type=checkbox][name=?][value=assigned_to_id]', 'trackers[1][core_fields][]' + assert_select 'input[type=checkbox][name=?][value=2]', 'trackers[1][custom_field_ids][]' + + assert_select 'input[type=hidden][name=?][value=]', 'trackers[1][core_fields][]' + assert_select 'input[type=hidden][name=?][value=]', 'trackers[1][custom_field_ids][]' + end + end + + def test_post_fields + post :fields, :trackers => { + '1' => {'core_fields' => ['assigned_to_id', 'due_date', ''], 'custom_field_ids' => ['1', '2']}, + '2' => {'core_fields' => [''], 'custom_field_ids' => ['']} + } + assert_redirected_to '/trackers/fields' + + tracker = Tracker.find(1) + assert_equal %w(assigned_to_id due_date), tracker.core_fields + assert_equal [1, 2], tracker.custom_field_ids.sort + + tracker = Tracker.find(2) + assert_equal [], tracker.core_fields + assert_equal [], tracker.custom_field_ids.sort + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/64/6402c0873c916f8ca24a20adde0b93d3110a6089.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/64/6402c0873c916f8ca24a20adde0b93d3110a6089.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,118 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# FileSystem adapter +# File written by Paul Rivier, at Demotera. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require 'redmine/scm/adapters/abstract_adapter' +require 'find' + +module Redmine + module Scm + module Adapters + class FilesystemAdapter < AbstractAdapter + + class << self + def client_available + true + end + end + + def initialize(url, root_url=nil, login=nil, password=nil, + path_encoding=nil) + @url = with_trailling_slash(url) + @path_encoding = path_encoding.blank? ? 'UTF-8' : path_encoding + end + + def path_encoding + @path_encoding + end + + def format_path_ends(path, leading=true, trailling=true) + path = leading ? with_leading_slash(path) : + without_leading_slash(path) + trailling ? with_trailling_slash(path) : + without_trailling_slash(path) + end + + def info + info = Info.new({:root_url => target(), + :lastrev => nil + }) + info + rescue CommandFailed + return nil + end + + def entries(path="", identifier=nil, options={}) + entries = Entries.new + trgt_utf8 = target(path) + trgt = scm_iconv(@path_encoding, 'UTF-8', trgt_utf8) + Dir.new(trgt).each do |e1| + e_utf8 = scm_iconv('UTF-8', @path_encoding, e1) + next if e_utf8.blank? + relative_path_utf8 = format_path_ends( + (format_path_ends(path,false,true) + e_utf8),false,false) + t1_utf8 = target(relative_path_utf8) + t1 = scm_iconv(@path_encoding, 'UTF-8', t1_utf8) + relative_path = scm_iconv(@path_encoding, 'UTF-8', relative_path_utf8) + e1 = scm_iconv(@path_encoding, 'UTF-8', e_utf8) + if File.exist?(t1) and # paranoid test + %w{file directory}.include?(File.ftype(t1)) and # avoid special types + not File.basename(e1).match(/^\.+$/) # avoid . and .. + p1 = File.readable?(t1) ? relative_path : "" + utf_8_path = scm_iconv('UTF-8', @path_encoding, p1) + entries << + Entry.new({ :name => scm_iconv('UTF-8', @path_encoding, File.basename(e1)), + # below : list unreadable files, but dont link them. + :path => utf_8_path, + :kind => (File.directory?(t1) ? 'dir' : 'file'), + :size => (File.directory?(t1) ? nil : [File.size(t1)].pack('l').unpack('L').first), + :lastrev => + Revision.new({:time => (File.mtime(t1)) }) + }) + end + end + entries.sort_by_name + rescue => err + logger.error "scm: filesystem: error: #{err.message}" + raise CommandFailed.new(err.message) + end + + def cat(path, identifier=nil) + p = scm_iconv(@path_encoding, 'UTF-8', target(path)) + File.new(p, "rb").read + rescue => err + logger.error "scm: filesystem: error: #{err.message}" + raise CommandFailed.new(err.message) + end + + private + + # AbstractAdapter::target is implicitly made to quote paths. + # Here we do not shell-out, so we do not want quotes. + def target(path=nil) + # Prevent the use of .. + if path and !path.match(/(^|\/)\.\.(\/|$)/) + return "#{self.url}#{without_leading_slash(path)}" + end + return self.url + end + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/64/64036c3be2c819ccb0429f69ed2813c1f8f00a12.svn-base --- a/.svn/pristine/64/64036c3be2c819ccb0429f69ed2813c1f8f00a12.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,250 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) -require 'queries_controller' - -# Re-raise errors caught by the controller. -class QueriesController; def rescue_action(e) raise e end; end - -class QueriesControllerTest < ActionController::TestCase - fixtures :projects, :users, :members, :member_roles, :roles, :trackers, :issue_statuses, :issue_categories, :enumerations, :issues, :custom_fields, :custom_values, :queries - - def setup - @controller = QueriesController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - User.current = nil - end - - def test_get_new_project_query - @request.session[:user_id] = 2 - get :new, :project_id => 1 - assert_response :success - assert_template 'new' - assert_tag :tag => 'input', :attributes => { :type => 'checkbox', - :name => 'query[is_public]', - :checked => nil } - assert_tag :tag => 'input', :attributes => { :type => 'checkbox', - :name => 'query_is_for_all', - :checked => nil, - :disabled => nil } - end - - def test_get_new_global_query - @request.session[:user_id] = 2 - get :new - assert_response :success - assert_template 'new' - assert_no_tag :tag => 'input', :attributes => { :type => 'checkbox', - :name => 'query[is_public]' } - assert_tag :tag => 'input', :attributes => { :type => 'checkbox', - :name => 'query_is_for_all', - :checked => 'checked', - :disabled => nil } - end - - def test_new_project_public_query - @request.session[:user_id] = 2 - post :create, - :project_id => 'ecookbook', - :default_columns => '1', - :f => ["status_id", "assigned_to_id"], - :op => {"assigned_to_id" => "=", "status_id" => "o"}, - :v => { "assigned_to_id" => ["1"], "status_id" => ["1"]}, - :query => {"name" => "test_new_project_public_query", "is_public" => "1"} - - q = Query.find_by_name('test_new_project_public_query') - assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook', :query_id => q - assert q.is_public? - assert q.has_default_columns? - assert q.valid? - end - - def test_new_project_private_query - @request.session[:user_id] = 3 - post :create, - :project_id => 'ecookbook', - :default_columns => '1', - :fields => ["status_id", "assigned_to_id"], - :operators => {"assigned_to_id" => "=", "status_id" => "o"}, - :values => { "assigned_to_id" => ["1"], "status_id" => ["1"]}, - :query => {"name" => "test_new_project_private_query", "is_public" => "1"} - - q = Query.find_by_name('test_new_project_private_query') - assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook', :query_id => q - assert !q.is_public? - assert q.has_default_columns? - assert q.valid? - end - - def test_new_global_private_query_with_custom_columns - @request.session[:user_id] = 3 - post :create, - :fields => ["status_id", "assigned_to_id"], - :operators => {"assigned_to_id" => "=", "status_id" => "o"}, - :values => { "assigned_to_id" => ["me"], "status_id" => ["1"]}, - :query => {"name" => "test_new_global_private_query", "is_public" => "1"}, - :c => ["", "tracker", "subject", "priority", "category"] - - q = Query.find_by_name('test_new_global_private_query') - assert_redirected_to :controller => 'issues', :action => 'index', :project_id => nil, :query_id => q - assert !q.is_public? - assert !q.has_default_columns? - assert_equal [:tracker, :subject, :priority, :category], q.columns.collect {|c| c.name} - assert q.valid? - end - - def test_new_global_query_with_custom_filters - @request.session[:user_id] = 3 - post :create, - :fields => ["assigned_to_id"], - :operators => {"assigned_to_id" => "="}, - :values => { "assigned_to_id" => ["me"]}, - :query => {"name" => "test_new_global_query"} - - q = Query.find_by_name('test_new_global_query') - assert_redirected_to :controller => 'issues', :action => 'index', :project_id => nil, :query_id => q - assert !q.has_filter?(:status_id) - assert_equal ['assigned_to_id'], q.filters.keys - assert q.valid? - end - - def test_new_with_sort - @request.session[:user_id] = 1 - post :create, - :default_columns => '1', - :operators => {"status_id" => "o"}, - :values => {"status_id" => ["1"]}, - :query => {:name => "test_new_with_sort", - :is_public => "1", - :sort_criteria => {"0" => ["due_date", "desc"], "1" => ["tracker", ""]}} - - query = Query.find_by_name("test_new_with_sort") - assert_not_nil query - assert_equal [['due_date', 'desc'], ['tracker', 'asc']], query.sort_criteria - end - - def test_get_edit_global_public_query - @request.session[:user_id] = 1 - get :edit, :id => 4 - assert_response :success - assert_template 'edit' - assert_tag :tag => 'input', :attributes => { :type => 'checkbox', - :name => 'query[is_public]', - :checked => 'checked' } - assert_tag :tag => 'input', :attributes => { :type => 'checkbox', - :name => 'query_is_for_all', - :checked => 'checked', - :disabled => 'disabled' } - end - - def test_edit_global_public_query - @request.session[:user_id] = 1 - put :update, - :id => 4, - :default_columns => '1', - :fields => ["status_id", "assigned_to_id"], - :operators => {"assigned_to_id" => "=", "status_id" => "o"}, - :values => { "assigned_to_id" => ["1"], "status_id" => ["1"]}, - :query => {"name" => "test_edit_global_public_query", "is_public" => "1"} - - assert_redirected_to :controller => 'issues', :action => 'index', :query_id => 4 - q = Query.find_by_name('test_edit_global_public_query') - assert q.is_public? - assert q.has_default_columns? - assert q.valid? - end - - def test_get_edit_global_private_query - @request.session[:user_id] = 3 - get :edit, :id => 3 - assert_response :success - assert_template 'edit' - assert_no_tag :tag => 'input', :attributes => { :type => 'checkbox', - :name => 'query[is_public]' } - assert_tag :tag => 'input', :attributes => { :type => 'checkbox', - :name => 'query_is_for_all', - :checked => 'checked', - :disabled => 'disabled' } - end - - def test_edit_global_private_query - @request.session[:user_id] = 3 - put :update, - :id => 3, - :default_columns => '1', - :fields => ["status_id", "assigned_to_id"], - :operators => {"assigned_to_id" => "=", "status_id" => "o"}, - :values => { "assigned_to_id" => ["me"], "status_id" => ["1"]}, - :query => {"name" => "test_edit_global_private_query", "is_public" => "1"} - - assert_redirected_to :controller => 'issues', :action => 'index', :query_id => 3 - q = Query.find_by_name('test_edit_global_private_query') - assert !q.is_public? - assert q.has_default_columns? - assert q.valid? - end - - def test_get_edit_project_private_query - @request.session[:user_id] = 3 - get :edit, :id => 2 - assert_response :success - assert_template 'edit' - assert_no_tag :tag => 'input', :attributes => { :type => 'checkbox', - :name => 'query[is_public]' } - assert_tag :tag => 'input', :attributes => { :type => 'checkbox', - :name => 'query_is_for_all', - :checked => nil, - :disabled => nil } - end - - def test_get_edit_project_public_query - @request.session[:user_id] = 2 - get :edit, :id => 1 - assert_response :success - assert_template 'edit' - assert_tag :tag => 'input', :attributes => { :type => 'checkbox', - :name => 'query[is_public]', - :checked => 'checked' - } - assert_tag :tag => 'input', :attributes => { :type => 'checkbox', - :name => 'query_is_for_all', - :checked => nil, - :disabled => 'disabled' } - end - - def test_get_edit_sort_criteria - @request.session[:user_id] = 1 - get :edit, :id => 5 - assert_response :success - assert_template 'edit' - assert_tag :tag => 'select', :attributes => { :name => 'query[sort_criteria][0][]' }, - :child => { :tag => 'option', :attributes => { :value => 'priority', - :selected => 'selected' } } - assert_tag :tag => 'select', :attributes => { :name => 'query[sort_criteria][0][]' }, - :child => { :tag => 'option', :attributes => { :value => 'desc', - :selected => 'selected' } } - end - - def test_destroy - @request.session[:user_id] = 2 - delete :destroy, :id => 1 - assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook', :set_filter => 1, :query_id => nil - assert_nil Query.find_by_id(1) - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/64/642c69f33e7614a977979878c1ccc6e65c0c0226.svn-base --- a/.svn/pristine/64/642c69f33e7614a977979878c1ccc6e65c0c0226.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +0,0 @@ -#!/usr/bin/env ruby -require File.expand_path('../../config/boot', __FILE__) -require 'commands/breakpointer' diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/64/643e94db4aed32471bbf1bd1d5bd4f887d610b4b.svn-base --- a/.svn/pristine/64/643e94db4aed32471bbf1bd1d5bd4f887d610b4b.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,100 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class AttachmentsController < ApplicationController - before_filter :find_project - before_filter :file_readable, :read_authorize, :except => :destroy - before_filter :delete_authorize, :only => :destroy - - accept_api_auth :show, :download - - def show - respond_to do |format| - format.html { - if @attachment.is_diff? - @diff = File.new(@attachment.diskfile, "rb").read - @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline' - @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type) - # Save diff type as user preference - if User.current.logged? && @diff_type != User.current.pref[:diff_type] - User.current.pref[:diff_type] = @diff_type - User.current.preference.save - end - render :action => 'diff' - elsif @attachment.is_text? && @attachment.filesize <= Setting.file_max_size_displayed.to_i.kilobyte - @content = File.new(@attachment.diskfile, "rb").read - render :action => 'file' - else - download - end - } - format.api - end - end - - def download - 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 => detect_content_type(@attachment), - :disposition => (@attachment.image? ? 'inline' : 'attachment') - - end - - verify :method => :delete, :only => :destroy - 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 - rescue ActiveRecord::RecordNotFound - render_404 - end - - # Checks that the file exists and is readable - def file_readable - @attachment.readable? ? true : render_404 - end - - def read_authorize - @attachment.visible? ? true : deny_access - end - - def delete_authorize - @attachment.deletable? ? true : deny_access - end - - def detect_content_type(attachment) - content_type = attachment.content_type - if content_type.blank? - content_type = Redmine::MimeType.of(attachment.filename) - end - content_type.to_s - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/64/6495ba1b375d76ae48a1a29718388eabb1d9a8c0.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/64/6495ba1b375d76ae48a1a29718388eabb1d9a8c0.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,49 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class CustomValue < ActiveRecord::Base + belongs_to :custom_field + belongs_to :customized, :polymorphic => true + + def initialize(attributes=nil, *args) + super + if new_record? && custom_field && (customized_type.blank? || (customized && customized.new_record?)) + self.value ||= custom_field.default_value + end + end + + # Returns true if the boolean custom value is true + def true? + self.value == '1' + end + + def editable? + custom_field.editable? + end + + def visible? + custom_field.visible? + end + + def required? + custom_field.is_required? + end + + def to_s + value.to_s + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/64/64d46ee6e8251803e7c04c919d09da2321bf22b4.svn-base --- a/.svn/pristine/64/64d46ee6e8251803e7c04c919d09da2321bf22b4.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +0,0 @@ -class AppAndPluginLibModel < ActiveRecord::Base - def self.report_location; TestHelper::report_location(__FILE__); end -end \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/64/64d9599d84742c6f276c43c19ddf01ad011835e1.svn-base --- a/.svn/pristine/64/64d9599d84742c6f276c43c19ddf01ad011835e1.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,127 +0,0 @@ -// ** I18N - -// Calendar RU language -// Translation: Sly Golovanov, http://golovanov.net, -// 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 -("воÑкреÑенье", - "понедельник", - "вторник", - "Ñреда", - "четверг", - "пÑтница", - "Ñуббота", - "воÑкреÑенье"); - -// 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" + -"Как выбрать дату:\n" + -"- При помощи кнопок \xab, \xbb можно выбрать год\n" + -"- При помощи кнопок " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " можно выбрать меÑÑц\n" + -"- Подержите Ñти кнопки нажатыми, чтобы поÑвилоÑÑŒ меню быÑтрого выбора."; -Calendar._TT["ABOUT_TIME"] = "\n\n" + -"Как выбрать времÑ:\n" + -"- При клике на чаÑах или минутах они увеличиваютÑÑ\n" + -"- при клике Ñ Ð½Ð°Ð¶Ð°Ñ‚Ð¾Ð¹ клавишей Shift они уменьшаютÑÑ\n" + -"- еÑли нажать и двигать мышкой влево/вправо, они будут менÑтьÑÑ Ð±Ñ‹Ñтрее."; - -Calendar._TT["PREV_YEAR"] = "Ðа год назад (удерживать Ð´Ð»Ñ Ð¼ÐµÐ½ÑŽ)"; -Calendar._TT["PREV_MONTH"] = "Ðа меÑÑц назад (удерживать Ð´Ð»Ñ Ð¼ÐµÐ½ÑŽ)"; -Calendar._TT["GO_TODAY"] = "СегоднÑ"; -Calendar._TT["NEXT_MONTH"] = "Ðа меÑÑц вперед (удерживать Ð´Ð»Ñ Ð¼ÐµÐ½ÑŽ)"; -Calendar._TT["NEXT_YEAR"] = "Ðа год вперед (удерживать Ð´Ð»Ñ Ð¼ÐµÐ½ÑŽ)"; -Calendar._TT["SEL_DATE"] = "Выберите дату"; -Calendar._TT["DRAG_TO_MOVE"] = "ПеретаÑкивайте мышкой"; -Calendar._TT["PART_TODAY"] = " (ÑегоднÑ)"; - -// the following is to inform that "%s" is to be the first day of week -// %s will be replaced with the day name. -Calendar._TT["DAY_FIRST"] = "Первый день недели будет %s"; - -// This may be locale-dependent. It specifies the week-end days, as an array -// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 -// means Monday, etc. -Calendar._TT["WEEKEND"] = "0,6"; - -Calendar._TT["CLOSE"] = "Закрыть"; -Calendar._TT["TODAY"] = "СегоднÑ"; -Calendar._TT["TIME_PART"] = "(Shift-)клик или нажать и двигать"; - -// date formats -Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; -Calendar._TT["TT_DATE_FORMAT"] = "%e %b, %a"; - -Calendar._TT["WK"] = "нед"; -Calendar._TT["TIME"] = "ВремÑ:"; diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/64/64e2a2f022fb062bdaa6b6061eb9a2636bb458a2.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/64/64e2a2f022fb062bdaa6b6061eb9a2636bb458a2.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,75 @@ +<%= error_messages_for 'member' %> +<% roles = Role.find_all_givable + members = @project.member_principals.includes(:roles, :principal).all.sort %> + +
    +<% if members.any? %> + + + + + + <%= call_hook(:view_projects_settings_members_table_header, :project => @project) %> + + + <% members.each do |member| %> + <% next if member.new_record? %> + + + + + <%= call_hook(:view_projects_settings_members_table_row, { :project => @project, :member => member}) %> + +<% end; reset_cycle %> + +
    <%= l(:label_user) %> / <%= l(:label_group) %><%= l(:label_role_plural) %>
    <%= link_to_user member.principal %> + <%=h member.roles.sort.collect(&:to_s).join(', ') %> + <%= form_for(member, {:as => :membership, :remote => true, :url => membership_path(member), + :method => :put, + :html => { :id => "member-#{member.id}-roles-form", :class => 'hol' }} + ) do |f| %> +

    <% roles.each do |role| %> +
    + <% end %>

    + <%= hidden_field_tag 'membership[role_ids][]', '' %> +

    <%= submit_tag l(:button_change), :class => "small" %> + <%= link_to_function l(:button_cancel), + "$('#member-#{member.id}-roles').show(); $('#member-#{member.id}-roles-form').hide(); return false;" + %>

    + <% end %> +
    + <%= link_to_function l(:button_edit), + "$('#member-#{member.id}-roles').hide(); $('#member-#{member.id}-roles-form').show(); return false;", + :class => 'icon icon-edit' %> + <%= delete_link membership_path(member), + :remote => true, + :data => (!User.current.admin? && member.include?(User.current) ? {:confirm => l(:text_own_membership_delete_confirmation)} : {}) if member.deletable? %> +
    +<% else %> +

    <%= l(:label_no_data) %>

    +<% end %> +
    + +
    +<% if roles.any? %> + <%= form_for(@member, {:as => :membership, :url => project_memberships_path(@project), :remote => true, :method => :post}) do |f| %> +
    <%=l(:label_member_new)%> + +

    <%= label_tag "principal_search", l(:label_principal_search) %><%= text_field_tag 'principal_search', nil %>

    + <%= javascript_tag "observeSearchfield('principal_search', null, '#{ escape_javascript autocomplete_project_memberships_path(@project, :format => 'js') }')" %> + +
    + <%= render_principals_for_new_members(@project) %> +
    + +

    <%= l(:label_role_plural) %>: + <% roles.each do |role| %> + + <% end %>

    + +

    <%= submit_tag l(:button_add), :id => 'member-add-submit' %>

    +
    + <% end %> +<% end %> +
    diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/65/6522becd35e6716c94ac60c2f3bd1780740bea87.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/65/6522becd35e6716c94ac60c2f3bd1780740bea87.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,62 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class DocumentTest < ActiveSupport::TestCase + fixtures :projects, :enumerations, :documents, :attachments, + :enabled_modules, + :users, :members, :member_roles, :roles, + :groups_users + + 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_should_send_email_notification + ActionMailer::Base.deliveries.clear + + with_settings :notified_events => %w(document_added) do + doc = Document.new(:project => Project.find(1), :title => 'New document', :category => Enumeration.find_by_name('User documentation')) + assert doc.save + end + assert_equal 1, ActionMailer::Base.deliveries.size + 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 + + def test_updated_on_with_attachments + d = Document.find(1) + assert d.attachments.any? + assert_equal d.attachments.map(&:created_on).max, d.updated_on + end + + def test_updated_on_without_attachments + d = Document.find(2) + assert d.attachments.empty? + assert_equal d.created_on, d.updated_on + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/65/652cee5115cd618a1d7b1b071076d6439b22f7ec.svn-base --- a/.svn/pristine/65/652cee5115cd618a1d7b1b071076d6439b22f7ec.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,60 +0,0 @@ -class ContextMenusController < ApplicationController - helper :watchers - helper :issues - - def issues - @issues = Issue.visible.all(:conditions => {:id => params[:ids]}, :include => :project) - - if (@issues.size == 1) - @issue = @issues.first - @allowed_statuses = @issue.new_statuses_allowed_to(User.current) - else - @allowed_statuses = @issues.map do |i| - i.new_statuses_allowed_to(User.current) - end.inject do |memo,s| - memo & s - end - end - @projects = @issues.collect(&:project).compact.uniq - @project = @projects.first if @projects.size == 1 - - @can = {:edit => User.current.allowed_to?(:edit_issues, @projects), - :log_time => (@project && User.current.allowed_to?(:log_time, @project)), - :update => (User.current.allowed_to?(:edit_issues, @projects) || (User.current.allowed_to?(:change_status, @projects) && !@allowed_statuses.blank?)), - :move => (@project && User.current.allowed_to?(:move_issues, @project)), - :copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)), - :delete => User.current.allowed_to?(:delete_issues, @projects) - } - if @project - if @issue - @assignables = @issue.assignable_users - else - @assignables = @project.assignable_users - end - @trackers = @project.trackers - else - #when multiple projects, we only keep the intersection of each set - @assignables = @projects.map(&:assignable_users).inject{|memo,a| memo & a} - @trackers = @projects.map(&:trackers).inject{|memo,t| memo & t} - end - - @priorities = IssuePriority.active.reverse - @statuses = IssueStatus.find(:all, :order => 'position') - @back = back_url - - render :layout => false - end - - def time_entries - @time_entries = TimeEntry.all( - :conditions => {:id => params[:ids]}, :include => :project) - @projects = @time_entries.collect(&:project).compact.uniq - @project = @projects.first if @projects.size == 1 - @activities = TimeEntryActivity.shared.active - @can = {:edit => User.current.allowed_to?(:edit_time_entries, @projects), - :delete => User.current.allowed_to?(:edit_time_entries, @projects) - } - @back = back_url - render :layout => false - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/65/6548ced1be2ac2971e9540ff4fe8979b3f03573e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/65/6548ced1be2ac2971e9540ff4fe8979b3f03573e.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,80 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class RoutingIssueStatusesTest < ActionController::IntegrationTest + def test_issue_statuses + assert_routing( + { :method => 'get', :path => "/issue_statuses" }, + { :controller => 'issue_statuses', :action => 'index' } + ) + assert_routing( + { :method => 'get', :path => "/issue_statuses.xml" }, + { :controller => 'issue_statuses', :action => 'index', :format => 'xml' } + ) + assert_routing( + { :method => 'post', :path => "/issue_statuses" }, + { :controller => 'issue_statuses', :action => 'create' } + ) + assert_routing( + { :method => 'post', :path => "/issue_statuses.xml" }, + { :controller => 'issue_statuses', :action => 'create', :format => 'xml' } + ) + assert_routing( + { :method => 'get', :path => "/issue_statuses/new" }, + { :controller => 'issue_statuses', :action => 'new' } + ) + assert_routing( + { :method => 'get', :path => "/issue_statuses/new.xml" }, + { :controller => 'issue_statuses', :action => 'new', :format => 'xml' } + ) + assert_routing( + { :method => 'get', :path => "/issue_statuses/1/edit" }, + { :controller => 'issue_statuses', :action => 'edit', :id => '1' } + ) + assert_routing( + { :method => 'put', :path => "/issue_statuses/1" }, + { :controller => 'issue_statuses', :action => 'update', + :id => '1' } + ) + assert_routing( + { :method => 'put', :path => "/issue_statuses/1.xml" }, + { :controller => 'issue_statuses', :action => 'update', + :format => 'xml', :id => '1' } + ) + assert_routing( + { :method => 'delete', :path => "/issue_statuses/1" }, + { :controller => 'issue_statuses', :action => 'destroy', + :id => '1' } + ) + assert_routing( + { :method => 'delete', :path => "/issue_statuses/1.xml" }, + { :controller => 'issue_statuses', :action => 'destroy', + :format => 'xml', :id => '1' } + ) + assert_routing( + { :method => 'post', :path => "/issue_statuses/update_issue_done_ratio" }, + { :controller => 'issue_statuses', :action => 'update_issue_done_ratio' } + ) + assert_routing( + { :method => 'post', :path => "/issue_statuses/update_issue_done_ratio.xml" }, + { :controller => 'issue_statuses', :action => 'update_issue_done_ratio', + :format => 'xml' } + ) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/65/65538df31e36234b3f7b2e1412df21f4d984c90c.svn-base --- a/.svn/pristine/65/65538df31e36234b3f7b2e1412df21f4d984c90c.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,10 +0,0 @@ -

    <%= l(:label_settings) %>: <%=h @plugin.name %>

    - -
    -<% form_tag({:action => 'plugin'}) do %> -
    -<%= render :partial => @partial, :locals => {:settings => @settings}%> -
    -<%= submit_tag l(:button_apply) %> -<% end %> -
    diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/65/65761dc96875890d24f44eea0c8e51f6640664b9.svn-base --- a/.svn/pristine/65/65761dc96875890d24f44eea0c8e51f6640664b9.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,173 +0,0 @@ -var commits = chunk.commits, - comms = {}, - pixelsX = [], - pixelsY = [], - mmax = Math.max, - max_rdmid = 0, - max_space = 0, - parents = {}; -for (var i = 0, ii = commits.length; i < ii; i++) { - for (var j = 0, jj = commits[i].parents.length; j < jj; j++) { - parents[commits[i].parents[j][0]] = true; - } - max_rdmid = Math.max(max_rdmid, commits[i].rdmid); - max_space = Math.max(max_space, commits[i].space); -} - -for (i = 0; i < ii; i++) { - if (commits[i].scmid in parents) { - commits[i].isParent = true; - } - comms[commits[i].scmid] = commits[i]; -} -var colors = ["#000"]; -for (var k = 0; k < max_space; k++) { - colors.push(Raphael.getColor()); -} - -function branchGraph(holder) { - var xstep = 20; - var ystep = $$('tr.changeset')[0].getHeight(); - var ch, cw; - cw = max_space * xstep + xstep; - ch = max_rdmid * ystep + ystep; - var r = Raphael("holder", cw, ch), - top = r.set(); - var cuday = 0, cumonth = ""; - - for (i = 0; i < ii; i++) { - var x, y; - y = 10 + ystep *(max_rdmid - commits[i].rdmid); - x = 3 + xstep * commits[i].space; - var stroke = "none"; - r.circle(x, y, 3).attr({fill: colors[commits[i].space], stroke: stroke}); - if (commits[i].refs != null && commits[i].refs != "") { - var longrefs = commits[i].refs - var shortrefs = commits[i].refs; - if (shortrefs.length > 15) { - shortrefs = shortrefs.substr(0,13) + "..."; - } - var t = r.text(x+5,y+5,shortrefs).attr({font: "12px Fontin-Sans, Arial", fill: "#666", - title: longrefs, cursor: "pointer", rotation: "0"}); - - var textbox = t.getBBox(); - t.translate(textbox.width / 2, textbox.height / -3); - } - for (var j = 0, jj = commits[i].parents.length; j < jj; j++) { - var c = comms[commits[i].parents[j][0]]; - var p,arrow; - if (c) { - var cy, cx; - cy = 10 + ystep * (max_rdmid - c.rdmid), - cx = 3 + xstep * c.space; - - if (c.space == commits[i].space) { - p = r.path("M" + x + "," + y + "L" + cx + "," + cy); - } else { - p = r.path(["M", x, y, "C",x,y,x, y+(cy-y)/2,x+(cx-x)/2, y+(cy-y)/2, - "C", x+(cx-x)/2,y+(cy-y)/2, cx, cy-(cy-y)/2, cx, cy]); - } - } else { - p = r.path("M" + x + "," + y + "L" + x + "," + ch); - } - p.attr({stroke: colors[commits[i].space], "stroke-width": 1.5}); - } - (function (c, x, y) { - top.push(r.circle(x, y, 10).attr({fill: "#000", opacity: 0, - cursor: "pointer", href: commits[i].href}) - .hover(function () {}, function () {}) - ); - }(commits[i], x, y)); - } - top.toFront(); - var hw = holder.offsetWidth, - hh = holder.offsetHeight, - drag, - dragger = function (e) { - if (drag) { - e = e || window.event; - holder.scrollLeft = drag.sl - (e.clientX - drag.x); - holder.scrollTop = drag.st - (e.clientY - drag.y); - } - }; - holder.onmousedown = function (e) { - e = e || window.event; - drag = {x: e.clientX, y: e.clientY, st: holder.scrollTop, sl: holder.scrollLeft}; - document.onmousemove = dragger; - }; - document.onmouseup = function () { - drag = false; - document.onmousemove = null; - }; - holder.scrollLeft = cw; -}; - -Raphael.fn.popupit = function (x, y, set, dir, size) { - dir = dir == null ? 2 : dir; - size = size || 5; - x = Math.round(x); - y = Math.round(y); - var bb = set.getBBox(), - w = Math.round(bb.width / 2), - h = Math.round(bb.height / 2), - dx = [0, w + size * 2, 0, -w - size * 2], - dy = [-h * 2 - size * 3, -h - size, 0, -h - size], - p = ["M", x - dx[dir], y - dy[dir], "l", -size, (dir == 2) * -size, -mmax(w - size, 0), - 0, "a", size, size, 0, 0, 1, -size, -size, - "l", 0, -mmax(h - size, 0), (dir == 3) * -size, -size, (dir == 3) * size, -size, 0, - -mmax(h - size, 0), "a", size, size, 0, 0, 1, size, -size, - "l", mmax(w - size, 0), 0, size, !dir * -size, size, !dir * size, mmax(w - size, 0), - 0, "a", size, size, 0, 0, 1, size, size, - "l", 0, mmax(h - size, 0), (dir == 1) * size, size, (dir == 1) * -size, size, 0, - mmax(h - size, 0), "a", size, size, 0, 0, 1, -size, size, - "l", -mmax(w - size, 0), 0, "z"].join(","), - xy = [{x: x, y: y + size * 2 + h}, - {x: x - size * 2 - w, y: y}, - {x: x, y: y - size * 2 - h}, - {x: x + size * 2 + w, y: y}] - [dir]; - set.translate(xy.x - w - bb.x, xy.y - h - bb.y); - return this.set(this.path(p).attr({fill: "#234", stroke: "none"}) - .insertBefore(set.node ? set : set[0]), set); -}; - -Raphael.fn.popup = function (x, y, text, dir, size) { - dir = dir == null ? 2 : dir > 3 ? 3 : dir; - size = size || 5; - text = text || "$9.99"; - var res = this.set(), - d = 3; - res.push(this.path().attr({fill: "#000", stroke: "#000"})); - res.push(this.text(x, y, text).attr(this.g.txtattr).attr({fill: "#fff", "font-family": "Helvetica, Arial"})); - res.update = function (X, Y, withAnimation) { - X = X || x; - Y = Y || y; - var bb = this[1].getBBox(), - w = bb.width / 2, - h = bb.height / 2, - dx = [0, w + size * 2, 0, -w - size * 2], - dy = [-h * 2 - size * 3, -h - size, 0, -h - size], - p = ["M", X - dx[dir], Y - dy[dir], "l", -size, (dir == 2) * -size, - -mmax(w - size, 0), 0, "a", size, size, 0, 0, 1, -size, -size, - "l", 0, -mmax(h - size, 0), (dir == 3) * -size, -size, (dir == 3) * size, -size, - 0, -mmax(h - size, 0), "a", size, size, 0, 0, 1, size, -size, - "l", mmax(w - size, 0), 0, size, !dir * -size, size, !dir * size, mmax(w - size, 0), - 0, "a", size, size, 0, 0, 1, size, size, - "l", 0, mmax(h - size, 0), (dir == 1) * size, size, (dir == 1) * -size, size, 0, - mmax(h - size, 0), "a", size, size, 0, 0, 1, -size, size, - "l", -mmax(w - size, 0), 0, "z"].join(","), - xy = [{x: X, y: Y + size * 2 + h}, - {x: X - size * 2 - w, y: Y}, - {x: X, y: Y - size * 2 - h}, - {x: X + size * 2 + w, y: Y}] - [dir]; - xy.path = p; - if (withAnimation) { - this.animate(xy, 500, ">"); - } else { - this.attr(xy); - } - return this; - }; - return res.update(x, y); -}; diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/65/657bd14b118043b9ebda19a4d6192af1ad0ce4ac.svn-base --- a/.svn/pristine/65/657bd14b118043b9ebda19a4d6192af1ad0ce4ac.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,60 +0,0 @@ -Return-Path: -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" -To: -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. - ---- This line starts with a delimiter and should not be stripped - -This paragraph is before delimiters. - -BREAK - -This paragraph is between delimiters. - ---- - -This paragraph is after the delimiter so it shouldn't appear. - -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. - -Project: onlinestore -Status: Resolved -due date: 2010-12-31 -Start Date:2010-01-01 -Assigned to: John Smith -fixed version: alpha -estimated hours: 2.5 -done ratio: 30 - diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/65/659cc1b107566502a54373a80e2f28e678539ea0.svn-base --- a/.svn/pristine/65/659cc1b107566502a54373a80e2f28e678539ea0.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,140 +0,0 @@ -module CodeRay -module Scanners - - # Scanner for YAML. - # - # Based on the YAML scanner from Syntax by Jamis Buck. - class YAML < Scanner - - register_for :yaml - file_extension 'yml' - - KINDS_NOT_LOC = :all - - protected - - def scan_tokens encoder, options - - state = :initial - key_indent = string_indent = 0 - - until eos? - - key_indent = nil if bol? - - if match = scan(/ +[\t ]*/) - encoder.text_token match, :space - - elsif match = scan(/\n+/) - encoder.text_token match, :space - state = :initial if match.index(?\n) - - elsif match = scan(/#.*/) - encoder.text_token match, :comment - - elsif bol? and case - when match = scan(/---|\.\.\./) - encoder.begin_group :head - encoder.text_token match, :head - encoder.end_group :head - next - when match = scan(/%.*/) - encoder.text_token match, :doctype - next - end - - elsif state == :value and case - when !check(/(?:"[^"]*")(?=: |:$)/) && match = scan(/"/) - encoder.begin_group :string - encoder.text_token match, :delimiter - encoder.text_token match, :content if match = scan(/ [^"\\]* (?: \\. [^"\\]* )* /mx) - encoder.text_token match, :delimiter if match = scan(/"/) - encoder.end_group :string - next - when match = scan(/[|>][-+]?/) - encoder.begin_group :string - encoder.text_token match, :delimiter - string_indent = key_indent || column(pos - match.size) - 1 - encoder.text_token matched, :content if scan(/(?:\n+ {#{string_indent + 1}}.*)+/) - encoder.end_group :string - next - when match = scan(/(?![!"*&]).+?(?=$|\s+#)/) - encoder.begin_group :string - encoder.text_token match, :content - string_indent = key_indent || column(pos - match.size) - 1 - encoder.text_token matched, :content if scan(/(?:\n+ {#{string_indent + 1}}.*)+/) - encoder.end_group :string - next - end - - elsif case - when match = scan(/[-:](?= |$)/) - state = :value if state == :colon && (match == ':' || match == '-') - state = :value if state == :initial && match == '-' - encoder.text_token match, :operator - next - when match = scan(/[,{}\[\]]/) - encoder.text_token match, :operator - next - when state == :initial && match = scan(/[\w.() ]*\S(?= *:(?: |$))/) - encoder.text_token match, :key - key_indent = column(pos - match.size) - 1 - state = :colon - next - when match = scan(/(?:"[^"\n]*"|'[^'\n]*')(?= *:(?: |$))/) - encoder.begin_group :key - encoder.text_token match[0,1], :delimiter - encoder.text_token match[1..-2], :content - encoder.text_token match[-1,1], :delimiter - encoder.end_group :key - key_indent = column(pos - match.size) - 1 - state = :colon - next - when match = scan(/(![\w\/]+)(:([\w:]+))?/) - encoder.text_token self[1], :type - if self[2] - encoder.text_token ':', :operator - encoder.text_token self[3], :class - end - next - when match = scan(/&\S+/) - encoder.text_token match, :variable - next - when match = scan(/\*\w+/) - encoder.text_token match, :global_variable - next - when match = scan(/< false + assert_nil IssuePriority.default + end + + def test_should_be_an_enumeration + assert IssuePriority.ancestors.include?(Enumeration) + end + + def test_objects_count + # low priority + assert_equal 6, IssuePriority.find(4).objects_count + # urgent + assert_equal 0, IssuePriority.find(7).objects_count + end + + def test_option_name + assert_equal :enumeration_issue_priorities, IssuePriority.new.option_name + end + + def test_should_be_created_at_last_position + IssuePriority.delete_all + + priorities = [1, 2, 3].map {|i| IssuePriority.create!(:name => "P#{i}")} + assert_equal [1, 2, 3], priorities.map(&:position) + end + + def test_reset_positions_in_list_should_set_sequential_positions + IssuePriority.delete_all + + priorities = [1, 2, 3].map {|i| IssuePriority.create!(:name => "P#{i}")} + priorities[0].update_attribute :position, 4 + priorities[1].update_attribute :position, 2 + priorities[2].update_attribute :position, 7 + assert_equal [4, 2, 7], priorities.map(&:reload).map(&:position) + + priorities[0].reset_positions_in_list + assert_equal [2, 1, 3], priorities.map(&:reload).map(&:position) + end + + def test_moving_in_list_should_reset_positions + priority = IssuePriority.first + priority.expects(:reset_positions_in_list).once + priority.move_to = 'higher' + end + + def test_clear_position_names_should_set_position_names_to_nil + IssuePriority.clear_position_names + assert IssuePriority.all.all? {|priority| priority.position_name.nil?} + end + + def test_compute_position_names_with_default_priority + IssuePriority.clear_position_names + + IssuePriority.compute_position_names + assert_equal %w(lowest default high3 high2 highest), IssuePriority.active.all.sort.map(&:position_name) + end + + def test_compute_position_names_without_default_priority_should_split_priorities + IssuePriority.clear_position_names + IssuePriority.update_all :is_default => false + + IssuePriority.compute_position_names + assert_equal %w(lowest low2 default high2 highest), IssuePriority.active.all.sort.map(&:position_name) + end + + def test_adding_a_priority_should_update_position_names + priority = IssuePriority.create!(:name => 'New') + assert_equal %w(lowest default high4 high3 high2 highest), IssuePriority.active.all.sort.map(&:position_name) + end + + def test_destroying_a_priority_should_update_position_names + IssuePriority.find_by_position_name('highest').destroy + assert_equal %w(lowest default high2 highest), IssuePriority.active.all.sort.map(&:position_name) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/65/65c4d0cb8c8282f190e809331350172b32319080.svn-base --- a/.svn/pristine/65/65c4d0cb8c8282f190e809331350172b32319080.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,55 +0,0 @@ -# General Apache options - - AddHandler fastcgi-script .fcgi - - - AddHandler fcgid-script .fcgi - - - AddHandler cgi-script .cgi - -Options +FollowSymLinks +ExecCGI - -# If you don't want Rails to look in certain directories, -# use the following rewrite rules so that Apache won't rewrite certain requests -# -# Example: -# RewriteCond %{REQUEST_URI} ^/notrails.* -# RewriteRule .* - [L] - -# Redirect all requests not available on the filesystem to Rails -# By default the cgi dispatcher is used which is very slow -# -# For better performance replace the dispatcher with the fastcgi one -# -# Example: -# RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] -RewriteEngine On - -# If your Rails application is accessed via an Alias directive, -# then you MUST also set the RewriteBase in this htaccess file. -# -# Example: -# Alias /myrailsapp /path/to/myrailsapp/public -# RewriteBase /myrailsapp - -RewriteRule ^$ index.html [QSA] -RewriteRule ^([^.]+)$ $1.html [QSA] -RewriteCond %{REQUEST_FILENAME} !-f - - RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] - - - RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] - - - RewriteRule ^(.*)$ dispatch.cgi [QSA,L] - - -# In case Rails experiences terminal errors -# Instead of displaying this message you can supply a file here which will be rendered instead -# -# Example: -# ErrorDocument 500 /500.html - -ErrorDocument 500 "

    Application error

    Rails application failed to start properly" \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/65/65c994bd2135199487251ba6af321a01cf90b1ea.svn-base Binary file .svn/pristine/65/65c994bd2135199487251ba6af321a01cf90b1ea.svn-base has changed diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/65/65e9a6c068b35b0498ff2c94d67db3241b91f5be.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/65/65e9a6c068b35b0498ff2c94d67db3241b91f5be.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,126 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class Redmine::ApiTest::IssueCategoriesTest < Redmine::ApiTest::Base + fixtures :projects, :users, :issue_categories, :issues, + :roles, + :member_roles, + :members, + :enabled_modules + + def setup + Setting.rest_api_enabled = '1' + end + + context "GET /projects/:project_id/issue_categories.xml" do + should "return issue categories" do + get '/projects/1/issue_categories.xml', {}, credentials('jsmith') + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_tag :tag => 'issue_categories', + :child => {:tag => 'issue_category', :child => {:tag => 'id', :content => '2'}} + end + end + + context "GET /issue_categories/2.xml" do + should "return requested issue category" do + get '/issue_categories/2.xml', {}, credentials('jsmith') + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_tag :tag => 'issue_category', + :child => {:tag => 'id', :content => '2'} + end + end + + context "POST /projects/:project_id/issue_categories.xml" do + should "return create issue category" do + assert_difference 'IssueCategory.count' do + post '/projects/1/issue_categories.xml', {:issue_category => {:name => 'API'}}, credentials('jsmith') + end + assert_response :created + assert_equal 'application/xml', @response.content_type + + category = IssueCategory.first(:order => 'id DESC') + assert_equal 'API', category.name + assert_equal 1, category.project_id + end + + context "with invalid parameters" do + should "return errors" do + assert_no_difference 'IssueCategory.count' do + post '/projects/1/issue_categories.xml', {:issue_category => {:name => ''}}, credentials('jsmith') + end + assert_response :unprocessable_entity + assert_equal 'application/xml', @response.content_type + + assert_tag 'errors', :child => {:tag => 'error', :content => "Name can't be blank"} + end + end + end + + context "PUT /issue_categories/2.xml" do + context "with valid parameters" do + should "update issue category" do + assert_no_difference 'IssueCategory.count' do + put '/issue_categories/2.xml', {:issue_category => {:name => 'API Update'}}, credentials('jsmith') + end + assert_response :ok + assert_equal '', @response.body + assert_equal 'API Update', IssueCategory.find(2).name + end + end + + context "with invalid parameters" do + should "return errors" do + assert_no_difference 'IssueCategory.count' do + put '/issue_categories/2.xml', {:issue_category => {:name => ''}}, credentials('jsmith') + end + assert_response :unprocessable_entity + assert_equal 'application/xml', @response.content_type + + assert_tag 'errors', :child => {:tag => 'error', :content => "Name can't be blank"} + end + end + end + + context "DELETE /issue_categories/1.xml" do + should "destroy issue categories" do + assert_difference 'IssueCategory.count', -1 do + delete '/issue_categories/1.xml', {}, credentials('jsmith') + end + assert_response :ok + assert_equal '', @response.body + assert_nil IssueCategory.find_by_id(1) + end + + should "reassign issues with :reassign_to_id param" do + issue_count = Issue.count(:conditions => {:category_id => 1}) + assert issue_count > 0 + + assert_difference 'IssueCategory.count', -1 do + assert_difference 'Issue.count(:conditions => {:category_id => 2})', 3 do + delete '/issue_categories/1.xml', {:reassign_to_id => 2}, credentials('jsmith') + end + end + assert_response :ok + assert_equal '', @response.body + assert_nil IssueCategory.find_by_id(1) + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/65/65ef890cea4ede6b19d9b1202dd3caee109782b0.svn-base --- a/.svn/pristine/65/65ef890cea4ede6b19d9b1202dd3caee109782b0.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ -<%= l(:text_issue_updated, :id => "##{@issue.id}", :author => h(@journal.user)) %> - -
      -<% for detail in @journal.details %> -
    • <%= show_detail(detail, true) %>
    • -<% end %> -
    - -<%= textilizable(@journal, :notes, :only_path => false) %> -
    -<%= render :partial => "issue.html.erb", :locals => { :issue => @issue, :issue_url => @issue_url } %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/66/66131eee4b40e2c9d9cbb2ef1cd7414a3df88d2c.svn-base --- a/.svn/pristine/66/66131eee4b40e2c9d9cbb2ef1cd7414a3df88d2c.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,36 +0,0 @@ -module Engines - module Assets - class << self - @@readme = %{Files in this directory are automatically generated from your plugins. -They are copied from the 'assets' directories of each plugin into this directory -each time Rails starts (script/server, script/console... and so on). -Any edits you make will NOT persist across the next server restart; instead you -should edit the files within the /assets/ directory itself.} - - # Ensure that the plugin asset subdirectory of RAILS_ROOT/public exists, and - # that we've added a little warning message to instruct developers not to mess with - # the files inside, since they're automatically generated. - def initialize_base_public_directory - dir = Engines.public_directory - unless File.exist?(dir) - FileUtils.mkdir_p(dir) - end - readme = File.join(dir, "README") - File.open(readme, 'w') { |f| f.puts @@readme } unless File.exist?(readme) - end - - # Replicates the subdirectories under the plugins's +assets+ (or +public+) - # directory into the corresponding public directory. See also - # Plugin#public_directory for more. - def mirror_files_for(plugin) - return if plugin.public_directory.nil? - begin - Engines.mirror_files_from(plugin.public_directory, File.join(Engines.public_directory, plugin.name)) - rescue Exception => e - Engines.logger.warn "WARNING: Couldn't create the public file structure for plugin '#{plugin.name}'; Error follows:" - Engines.logger.warn e - end - end - end - end -end \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/66/662e86180d02fd07ea0b83b2c6615af46feab399.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/66/662e86180d02fd07ea0b83b2c6615af46feab399.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,105 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class IssueStatus < ActiveRecord::Base + before_destroy :check_integrity + has_many :workflows, :class_name => 'WorkflowTransition', :foreign_key => "old_status_id" + acts_as_list + + before_destroy :delete_workflow_rules + after_save :update_default + + validates_presence_of :name + validates_uniqueness_of :name + validates_length_of :name, :maximum => 30 + validates_inclusion_of :default_done_ratio, :in => 0..100, :allow_nil => true + + scope :sorted, lambda { order("#{table_name}.position ASC") } + scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)} + + def update_default + IssueStatus.update_all({:is_default => false}, ['id <> ?', id]) if self.is_default? + end + + # Returns the default status for new issues + def self.default + where(:is_default => true).first + end + + # Update all the +Issues+ setting their done_ratio to the value of their +IssueStatus+ + def self.update_issue_done_ratios + if Issue.use_status_for_done_ratio? + IssueStatus.where("default_done_ratio >= 0").all.each do |status| + Issue.update_all({:done_ratio => status.default_done_ratio}, {:status_id => status.id}) + end + end + + return Issue.use_status_for_done_ratio? + end + + # Returns an array of all statuses the given role can switch to + # Uses association cache when called more than one time + def new_statuses_allowed_to(roles, tracker, author=false, assignee=false) + if roles && tracker + role_ids = roles.collect(&:id) + transitions = workflows.select do |w| + role_ids.include?(w.role_id) && + w.tracker_id == tracker.id && + ((!w.author && !w.assignee) || (author && w.author) || (assignee && w.assignee)) + end + transitions.map(&:new_status).compact.sort + else + [] + end + end + + # Same thing as above but uses a database query + # More efficient than the previous method if called just once + def find_new_statuses_allowed_to(roles, tracker, author=false, assignee=false) + if roles.present? && tracker + conditions = "(author = :false AND assignee = :false)" + conditions << " OR author = :true" if author + conditions << " OR assignee = :true" if assignee + + workflows. + includes(:new_status). + where(["role_id IN (:role_ids) AND tracker_id = :tracker_id AND (#{conditions})", + {:role_ids => roles.collect(&:id), :tracker_id => tracker.id, :true => true, :false => false} + ]).all. + map(&:new_status).compact.sort + else + [] + end + end + + def <=>(status) + position <=> status.position + end + + def to_s; name end + + private + + def check_integrity + raise "Can't delete status" if Issue.where(:status_id => id).any? + end + + # Deletes associated workflows + def delete_workflow_rules + WorkflowRule.delete_all(["old_status_id = :id OR new_status_id = :id", {:id => id}]) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/66/665335962ecb9e0672e62f281339799d0339e4f1.svn-base --- a/.svn/pristine/66/665335962ecb9e0672e62f281339799d0339e4f1.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +0,0 @@ -#!/usr/bin/env ruby -require File.expand_path('../../config/boot', __FILE__) -require 'commands/plugin' diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/66/66aa08562a330b659bb4aa3903977c6e536a5787.svn-base --- a/.svn/pristine/66/66aa08562a330b659bb4aa3903977c6e536a5787.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,52 +0,0 @@ -#context-menu { position: absolute; z-index: 40; font-size: 0.9em;} - -#context-menu ul, #context-menu li, #context-menu a { - display:block; - margin:0; - padding:0; - border:0; -} - -#context-menu ul { - width:150px; - border-top:1px solid #ddd; - border-left:1px solid #ddd; - border-bottom:1px solid #777; - border-right:1px solid #777; - background:white; - list-style:none; -} - -#context-menu li { - position:relative; - padding:1px; - z-index:39; - border:1px solid white; -} -#context-menu li.folder ul { position:absolute; left:168px; /* IE6 */ top:-2px; max-height:300px; overflow:hidden; overflow-y: auto; } -#context-menu li.folder>ul { left:148px; } - -#context-menu.reverse-y li.folder>ul { top:auto; bottom:0; } -#context-menu.reverse-x li.folder ul { left:auto; right:168px; /* IE6 */ } -#context-menu.reverse-x li.folder>ul { right:148px; } - -#context-menu a { - text-decoration:none !important; - background-repeat: no-repeat; - background-position: 1px 50%; - padding: 1px 0px 1px 20px; - width:100%; /* IE */ -} -#context-menu li>a { width:auto; } /* others */ -#context-menu a.disabled, #context-menu a.disabled:hover {color: #ccc;} -#context-menu li a.submenu { background:url("../images/bullet_arrow_right.png") right no-repeat; } -#context-menu li:hover { border:1px solid gray; background-color:#eee; } -#context-menu a:hover {color:#2A5685;} -#context-menu li.folder:hover { z-index:40; } -#context-menu ul ul, #context-menu li:hover ul ul { display:none; } -#context-menu li:hover ul, #context-menu li:hover li:hover ul { display:block; } - -/* selected element */ -.context-menu-selection { background-color:#507AAA !important; color:#f8f8f8 !important; } -.context-menu-selection a, .context-menu-selection a:hover { color:#f8f8f8 !important; } -.context-menu-selection:hover { background-color:#507AAA !important; color:#f8f8f8 !important; } diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/66/66ab27fbec26a34302ffce7f06e525b4e0ebdfde.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/66/66ab27fbec26a34302ffce7f06e525b4e0ebdfde.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,15 @@ +class AddCustomFieldsPosition < ActiveRecord::Migration + def self.up + add_column(:custom_fields, :position, :integer, :default => 1) + CustomField.all.group_by(&:type).each do |t, fields| + fields.each_with_index do |field, i| + # do not call model callbacks + CustomField.update_all "position = #{i+1}", {:id => field.id} + end + end + end + + def self.down + remove_column :custom_fields, :position + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/66/66ab50d76f6f50210c09955e4a16c4d364df47ff.svn-base --- a/.svn/pristine/66/66ab50d76f6f50210c09955e4a16c4d364df47ff.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,111 +0,0 @@ -module CodeRay -module Encoders - - load :filter - - # A Filter that selects tokens based on their token kind. - # - # == Options - # - # === :exclude - # - # One or many symbols (in an Array) which shall be excluded. - # - # Default: [] - # - # === :include - # - # One or many symbols (in an array) which shall be included. - # - # Default: :all, which means all tokens are included. - # - # Exclusion wins over inclusion. - # - # See also: CommentFilter - class TokenKindFilter < Filter - - register_for :token_kind_filter - - DEFAULT_OPTIONS = { - :exclude => [], - :include => :all - } - - protected - def setup options - super - - @group_excluded = false - @exclude = options[:exclude] - @exclude = Array(@exclude) unless @exclude == :all - @include = options[:include] - @include = Array(@include) unless @include == :all - end - - def include_text_token? text, kind - include_group? kind - end - - def include_group? kind - (@include == :all || @include.include?(kind)) && - !(@exclude == :all || @exclude.include?(kind)) - end - - public - - # Add the token to the output stream if +kind+ matches the conditions. - def text_token text, kind - super if !@group_excluded && include_text_token?(text, kind) - end - - # Add the token group to the output stream if +kind+ matches the - # conditions. - # - # If it does not, all tokens inside the group are excluded from the - # stream, even if their kinds match. - def begin_group kind - if @group_excluded - @group_excluded += 1 - elsif include_group? kind - super - else - @group_excluded = 1 - end - end - - # See +begin_group+. - def begin_line kind - if @group_excluded - @group_excluded += 1 - elsif include_group? kind - super - else - @group_excluded = 1 - end - end - - # Take care of re-enabling the delegation of tokens to the output stream - # if an exluded group has ended. - def end_group kind - if @group_excluded - @group_excluded -= 1 - @group_excluded = false if @group_excluded.zero? - else - super - end - end - - # See +end_group+. - def end_line kind - if @group_excluded - @group_excluded -= 1 - @group_excluded = false if @group_excluded.zero? - else - super - end - end - - end - -end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/66/66dfb65ecc4431408ec461d6daa3720b2fb4b2a0.svn-base --- a/.svn/pristine/66/66dfb65ecc4431408ec461d6daa3720b2fb4b2a0.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1008 +0,0 @@ -ar: - # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl) - direction: rtl - date: - formats: - # Use the strftime parameters for formats. - # When no format has been given, it uses default. - # You can provide other formats here if you like! - default: "%m/%d/%Y" - short: "%b %d" - long: "%B %d, %Y" - - day_names: [الاحد, الاثنين, الثلاثاء, الاربعاء, الخميس, الجمعة, السبت] - abbr_day_names: [أح, اث, Ø«, ار, Ø®, ج, س] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, كانون الثاني, شباط, آذار, نيسان, أيار, حزيران, تموز, آب, أيلول, تشرين الأول, تشرين الثاني, كانون الأول] - abbr_month_names: [~, كانون الثاني, شباط, آذار, نيسان, أيار, حزيران, تموز, آب, أيلول, تشرين الأول, تشرين الثاني, كانون الأول] - # Used in date_select and datime_select. - order: - - :السنة - - :الشهر - - :اليوم - - time: - formats: - default: "%m/%d/%Y %I:%M %p" - time: "%I:%M %p" - short: "%d %b %H:%M" - long: "%B %d, %Y %H:%M" - am: "صباحا" - pm: "مساءا" - - datetime: - distance_in_words: - half_a_minute: "نص٠دقيقة" - less_than_x_seconds: - one: "أقل من ثانية" - other: "ثواني %{count}أقل من " - x_seconds: - one: "ثانية" - other: "%{count}ثواني " - less_than_x_minutes: - one: "أقل من دقيقة" - other: "دقائق%{count}أقل من " - x_minutes: - one: "دقيقة" - other: "%{count} دقائق" - about_x_hours: - one: "حوالي ساعة" - other: "ساعات %{count}حوالي " - x_days: - one: "يوم" - other: "%{count} أيام" - about_x_months: - one: "حوالي شهر" - other: "أشهر %{count} حوالي" - x_months: - one: "شهر" - other: "%{count} أشهر" - about_x_years: - one: "حوالي سنة" - other: "سنوات %{count}حوالي " - over_x_years: - one: "اكثر من سنة" - other: "سنوات %{count}أكثر من " - almost_x_years: - one: "تقريبا سنة" - other: "سنوات %{count} نقريبا" - number: - format: - separator: "." - delimiter: "" - precision: 3 - - human: - format: - delimiter: "" - precision: 1 - storage_units: - format: "%n %u" - units: - byte: - one: "Byte" - other: "Bytes" - kb: "kB" - mb: "MB" - gb: "GB" - tb: "TB" - -# Used in array.to_sentence. - support: - array: - sentence_connector: "Ùˆ" - skip_last_comma: خطأ - - activerecord: - errors: - template: - header: - one: " %{model} خطأ يمنع تخزين" - other: " %{model} يمنع تخزين%{count}خطأ رقم " - messages: - inclusion: "غير مدرجة على القائمة" - exclusion: "محجوز" - invalid: "غير صالح" - confirmation: "غير متطابق" - accepted: "مقبولة" - empty: "لا يمكن ان تكون ÙØ§Ø±ØºØ©" - blank: "لا يمكن ان تكون ÙØ§Ø±ØºØ©" - too_long: " %{count}طويلة جدا، الحد الاقصى هو )" - too_short: " %{count}قصيرة جدا، الحد الادنى هو)" - wrong_length: " %{count}خطأ ÙÙŠ الطول، يجب ان يكون )" - taken: "لقد اتخذت سابقا" - not_a_number: "ليس رقما" - not_a_date: "ليس تاريخا صالحا" - greater_than: "%{count}يجب ان تكون اكثر من " - greater_than_or_equal_to: "%{count}يجب ان تكون اكثر من او تساوي" - equal_to: "%{count}يجب ان تساوي" - less_than: " %{count}يجب ان تكون اقل من" - less_than_or_equal_to: " %{count}يجب ان تكون اقل من او تساوي" - odd: "must be odd" - even: "must be even" - greater_than_start_date: "يجب ان تكون اكثر من تاريخ البداية" - not_same_project: "لا ينتمي الى Ù†ÙØ³ المشروع" - circular_dependency: "هذه العلاقة سو٠تخلق علاقة تبعية دائرية" - cant_link_an_issue_with_a_descendant: "لا يمكن ان تكون المشكلة مرتبطة بواحدة من المهام Ø§Ù„ÙØ±Ø¹ÙŠØ©" - - actionview_instancetag_blank_option: الرجاء التحديد - - general_text_No: 'لا' - general_text_Yes: 'نعم' - general_text_no: 'لا' - general_text_yes: 'نعم' - general_lang_name: 'Arabic (عربي)' - general_csv_separator: ',' - general_csv_decimal_separator: '.' - general_csv_encoding: ISO-8859-1 - general_pdf_encoding: UTF-8 - general_first_day_of_week: '7' - - notice_account_updated: لقد تم تجديد الحساب بنجاح. - notice_account_invalid_creditentials: اسم المستخدم او كلمة المرور غير صحيحة - notice_account_password_updated: لقد تم تجديد كلمة المرور بنجاح. - notice_account_wrong_password: كلمة المرور غير صحيحة - notice_account_register_done: لقد تم انشاء حسابك بنجاح، الرجاء تأكيد الطلب من البريد الالكتروني - notice_account_unknown_email: مستخدم غير معروÙ. - notice_can_t_change_password: هذا الحساب يستخدم جهاز خارجي غير مصرح به لا يمكن تغير كلمة المرور - notice_account_lost_email_sent: لقد تم ارسال رسالة على بريدك بالتعليمات اللازمة لتغير كلمة المرور - notice_account_activated: لقد تم ØªÙØ¹ÙŠÙ„ حسابك، يمكنك الدخول الان - notice_successful_create: لقد تم الانشاء بنجاح - notice_successful_update: لقد تم التحديث بنجاح - notice_successful_delete: لقد تم الحذ٠بنجاح - notice_successful_connection: لقد تم الربط بنجاح - notice_file_not_found: Ø§Ù„ØµÙØ­Ø© التي تحاول الدخول اليها غير موجوده او تم حذÙها - notice_locking_conflict: تم تحديث البيانات عن طريق مستخدم آخر. - notice_not_authorized: غير مصرح لك الدخول الى هذه المنطقة. - notice_not_authorized_archived_project: المشروع الذي تحاول الدخول اليه تم Ø§Ø±Ø´ÙØªÙ‡ - notice_email_sent: "%{value}تم ارسال رسالة الى " - notice_email_error: " (%{value})لقد حدث خطأ ما اثناء ارسال الرسالة الى " - notice_feeds_access_key_reseted: كلمة الدخول RSSلقد تم تعديل . - notice_api_access_key_reseted: كلمة الدخولAPIلقد تم تعديل . - notice_failed_to_save_issues: "ÙØ´Ù„ ÙÙŠ Ø­ÙØ¸ الملÙ" - notice_failed_to_save_members: "ÙØ´Ù„ ÙÙŠ Ø­ÙØ¸ الاعضاء: %{errors}." - notice_no_issue_selected: "لم يتم تحديد شيء، الرجاء تحديد المسألة التي تريد" - notice_account_pending: "لقد تم انشاء حسابك، الرجاء الانتظار حتى تتم المواÙقة" - notice_default_data_loaded: تم تحميل التكوين Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠ Ø¨Ù†Ø¬Ø§Ø­ - notice_unable_delete_version: غير قادر على مسح النسخة. - notice_unable_delete_time_entry: غير قادر على مسح وقت الدخول. - notice_issue_done_ratios_updated: لقد تم تحديث النسب. - notice_gantt_chart_truncated: " (%{max})لقد تم اقتطاع الرسم البياني لانه تجاوز الاحد الاقصى لعدد العناصر المسموح عرضها " - notice_issue_successful_create: "%{id}لقد تم انشاء " - - - error_can_t_load_default_data: "لم يتم تحميل التكوين Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠ ÙƒØ§Ù…Ù„Ø§ %{value}" - error_scm_not_found: "لم يتم العثور على ادخال ÙÙŠ المستودع" - error_scm_command_failed: "حدث خطأ عند محاولة الوصول الى المستودع: %{value}" - error_scm_annotate: "الادخال غير موجود." - error_scm_annotate_big_text_file: "لا يمكن Ø­ÙØ¸ الادخال لانه تجاوز الحد الاقصى لحجم الملÙ." - error_issue_not_found_in_project: 'لم يتم العثور على المخرج او انه ينتمي الى مشروع اخر' - error_no_tracker_in_project: 'لا يوجد متتبع لهذا المشروع، الرجاء التحقق من إعدادات المشروع. ' - error_no_default_issue_status: 'لم يتم التعر٠على اي وضع Ø§ÙØªØ±Ø§Ø¶ÙŠØŒ الرجاء التحقق من التكوين الخاص بك (اذهب الى إدارة-إصدار الحالات)' - error_can_not_delete_custom_field: غير قادر على حذ٠الحقل المظلل - error_can_not_delete_tracker: "هذا المتتبع يحتوي على مسائل نشطة ولا يمكن حذÙÙ‡" - error_can_not_remove_role: "هذا الدور قيد الاستخدام، لا يمكن حذÙÙ‡" - error_can_not_reopen_issue_on_closed_version: 'لا يمكن إعادة ÙØªØ­ قضية معينه لاصدار مقÙÙ„' - error_can_not_archive_project: لا يمكن Ø§Ø±Ø´ÙØ© هذا المشروع - error_issue_done_ratios_not_updated: "لم يتم تحديث النسب" - error_workflow_copy_source: 'الرجاء اختيار المتتبع او الادوار' - error_workflow_copy_target: 'الرجاء اختيار هد٠المتتبع او هد٠الادوار' - error_unable_delete_issue_status: 'غير قادر على حذ٠حالة القضية' - error_unable_to_connect: "تعذر الاتصال(%{value})" - error_attachment_too_big: " (%{max_size})لا يمكن تحميل هذا Ø§Ù„Ù…Ù„ÙØŒ لقد تجاوز الحد الاقصى المسموح به " - warning_attachments_not_saved: "%{count}تعذر Ø­ÙØ¸ الملÙ" - - mail_subject_lost_password: " %{value}كلمة المرور الخاصة بك " - mail_body_lost_password: 'لتغير كلمة المرور، انقر على الروابط التالية:' - mail_subject_register: " %{value}ØªÙØ¹ÙŠÙ„ حسابك " - mail_body_register: 'Ù„ØªÙØ¹ÙŠÙ„ حسابك، انقر على الروابط التالية:' - mail_body_account_information_external: " %{value}اصبح بامكانك استخدام حسابك للدخول" - mail_body_account_information: معلومات حسابك - mail_subject_account_activation_request: "%{value}طلب ØªÙØ¹ÙŠÙ„ الحساب " - mail_body_account_activation_request: " (%{value})تم تسجيل حساب جديد، بانتظار المواÙقة:" - mail_subject_reminder: "%{count}تم تأجيل المهام التالية " - mail_body_reminder: "%{count}يجب ان تقوم بتسليم المهام التالية :" - mail_subject_wiki_content_added: "'%{id}' تم Ø§Ø¶Ø§ÙØ© ØµÙØ­Ø© ويكي" - mail_body_wiki_content_added: "The '%{id}' تم Ø§Ø¶Ø§ÙØ© ØµÙØ­Ø© ويكي من قبل %{author}." - mail_subject_wiki_content_updated: "'%{id}' تم تحديث ØµÙØ­Ø© ويكي" - mail_body_wiki_content_updated: "The '%{id}'تم تحديث ØµÙØ­Ø© ويكي من قبل %{author}." - - gui_validation_error: خطأ - gui_validation_error_plural: "%{count}أخطاء" - - 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: التعبير العادي - field_min_length: الحد الادنى للطول - field_max_length: الحد الاعلى للطول - field_value: القيمة - field_category: Ø§Ù„ÙØ¦Ø© - field_title: العنوان - field_project: المشروع - field_issue: القضية - field_status: الحالة - field_notes: ملاحظات - field_is_closed: القضية مغلقة - field_is_default: القيمة Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠØ© - field_tracker: المتتبع - field_subject: الموضوع - field_due_date: تاريخ الاستحقاق - field_assigned_to: المحال اليه - field_priority: الأولوية - field_fixed_version: الاصدار المستهد٠- field_user: المستخدم - field_principal: الرئيسي - field_role: دور - field_homepage: Ø§Ù„ØµÙØ­Ø© الرئيسية - field_is_public: عام - field_parent: مشروع ÙØ±Ø¹ÙŠ Ù…Ù† - field_is_in_roadmap: القضايا المعروضة ÙÙŠ خارطة الطريق - field_login: تسجيل الدخول - field_mail_notification: ملاحظات على البريد الالكتروني - field_admin: المدير - field_last_login_on: اخر اتصال - field_language: لغة - field_effective_date: تاريخ - field_password: كلمة المرور - field_new_password: كلمة المرور الجديدة - field_password_confirmation: تأكيد - field_version: إصدار - field_type: نوع - field_host: المضي٠- field_port: Ø§Ù„Ù…Ù†ÙØ° - field_account: الحساب - field_base_dn: DN قاعدة - field_attr_login: سمة الدخول - field_attr_firstname: سمة الاسم الاول - field_attr_lastname: سمة الاسم الاخير - field_attr_mail: سمة البريد الالكتروني - field_onthefly: إنشاء حساب مستخدم على تحرك - field_start_date: تاريخ البدية - field_done_ratio: "% تم" - field_auth_source: وضع المصادقة - field_hide_mail: Ø¥Ø®ÙØ§Ø¡ بريدي الإلكتروني - field_comments: تعليق - field_url: رابط - field_start_page: ØµÙØ­Ø© البداية - field_subproject: المشروع Ø§Ù„ÙØ±Ø¹ÙŠ - field_hours: ساعات - field_activity: النشاط - field_spent_on: تاريخ - field_identifier: المعر٠- field_is_filter: استخدم كتصÙية - field_issue_to: القضايا المتصلة - field_delay: تأخير - field_assignable: يمكن ان تستند القضايا الى هذا الدور - field_redirect_existing_links: إعادة توجيه الروابط الموجودة - field_estimated_hours: الوقت المتوقع - field_column_names: أعمدة - field_time_entries: وقت الدخول - field_time_zone: المنطقة الزمنية - field_searchable: يمكن البحث Ùيه - field_default_value: القيمة Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠØ© - field_comments_sorting: اعرض التعليقات - field_parent_title: ØµÙØ­Ø© الوالدين - field_editable: يمكن اعادة تحريره - field_watcher: مراقب - field_identity_url: Ø§ÙØªØ­ الرابط الخاص بالهوية الشخصية - field_content: المحتويات - field_group_by: مجموعة النتائج عن طريق - field_sharing: مشاركة - field_parent_issue: مهمة الوالدين - field_member_of_group: "مجموعة المحال" - field_assigned_to_role: "دور المحال" - field_text: حقل نصي - field_visible: غير مرئي - field_warn_on_leaving_unsaved: "الرجاء التحذير عند مغادرة ØµÙØ­Ø© والنص غير محÙوظ" - field_issues_visibility: القضايا المرئية - field_is_private: خاص - field_commit_logs_encoding: رسائل الترميز - field_scm_path_encoding: ترميز المسار - field_path_to_repository: مسار المستودع - field_root_directory: دليل الجذر - field_cvsroot: CVSجذر - field_cvs_module: وحدة - - setting_app_title: عنوان التطبيق - setting_app_subtitle: العنوان Ø§Ù„ÙØ±Ø¹ÙŠ Ù„Ù„ØªØ·Ø¨ÙŠÙ‚ - setting_welcome_text: نص الترحيب - setting_default_language: اللغة Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠØ© - setting_login_required: مطلوب المصادقة - setting_self_registration: التسجيل الذاتي - setting_attachment_max_size: الحد الاقصى Ù„Ù„Ù…Ù„ÙØ§Øª المرÙقة - setting_issues_export_limit: الحد الاقصى لقضايا التصدير - setting_mail_from: انبعاثات عنوان بريدك - setting_bcc_recipients: مستلمين النسخ المخÙية (bcc) - setting_plain_text_mail: نص عادي (no HTML) - setting_host_name: اسم ومسار المستخدم - setting_text_formatting: تنسيق النص - setting_wiki_compression: ضغط تاريخ الويكي - setting_feeds_limit: Atom feeds الحد الاقصى لعدد البنود ÙÙŠ - setting_default_projects_public: المشاريع الجديده متاحة للجميع Ø§ÙØªØ±Ø§Ø¶ÙŠØ§ - setting_autofetch_changesets: الإحضار التلقائي - setting_sys_api_enabled: من ادارة المستودع WS تمكين - setting_commit_ref_keywords: مرجعية الكلمات Ø§Ù„Ù…ÙØªØ§Ø­ÙŠØ© - setting_commit_fix_keywords: تصحيح الكلمات Ø§Ù„Ù…ÙØªØ§Ø­ÙŠØ© - setting_autologin: الدخول التلقائي - setting_date_format: تنسيق التاريخ - setting_time_format: تنسيق الوقت - setting_cross_project_issue_relations: السماح بادارج القضايا ÙÙŠ هذا المشروع - setting_issue_list_default_columns: الاعمدة Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠØ© المعروضة ÙÙŠ قائمة القضية - setting_repositories_encodings: ترميز المرÙقات والمستودعات - setting_emails_header: رأس رسائل البريد الإلكتروني - setting_emails_footer: ذيل رسائل البريد الإلكتروني - setting_protocol: بروتوكول - setting_per_page_options: الكائنات لكل خيارات Ø§Ù„ØµÙØ­Ø© - setting_user_format: تنسيق عرض المستخدم - setting_activity_days_default: الايام المعروضة على نشاط المشروع - setting_display_subprojects_issues: عرض القضايا Ø§Ù„ÙØ±Ø¹ÙŠØ© للمشارع الرئيسية بشكل Ø§ÙØªØ±Ø§Ø¶ÙŠ - setting_enabled_scm: SCM تمكين - setting_mail_handler_body_delimiters: "اقتطاع رسائل البريد الإلكتروني بعد هذه الخطوط" - setting_mail_handler_api_enabled: للرسائل الواردةWS تمكين - setting_mail_handler_api_key: API Ù…ÙØªØ§Ø­ - setting_sequential_project_identifiers: انشاء Ù…Ø¹Ø±ÙØ§Øª المشروع المتسلسلة - setting_gravatar_enabled: كأيقونة مستخدمGravatar استخدام - setting_gravatar_default: Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠØ©Gravatar صورة - setting_diff_max_lines_displayed: الحد الاقصى لعدد الخطوط - setting_file_max_size_displayed: الحد الأقصى لحجم النص المعروض على Ø§Ù„Ù…Ù„ÙØ§Øª المرÙقة - setting_repository_log_display_limit: الحد الاقصى لعدد التنقيحات المعروضة على مل٠السجل - setting_openid: السماح بدخول اسم المستخدم Ø§Ù„Ù…ÙØªÙˆØ­ والتسجيل - setting_password_min_length: الحد الادني لطول كلمة المرور - setting_new_project_user_role_id: الدور المسند الى المستخدم غير المسؤول الذي يقوم بإنشاء المشروع - setting_default_projects_modules: تمكين الوحدات النمطية للمشاريع الجديدة بشكل Ø§ÙØªØ±Ø§Ø¶ÙŠ - setting_issue_done_ratio: حساب نسبة القضية المنتهية - setting_issue_done_ratio_issue_field: استخدم حقل القضية - setting_issue_done_ratio_issue_status: استخدم وضع القضية - setting_start_of_week: بدأ التقويم - setting_rest_api_enabled: تمكين باقي خدمات الويب - setting_cache_formatted_text: النص المسبق تنسيقه ÙÙŠ ذاكرة التخزين المؤقت - setting_default_notification_option: خيار الاعلام Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠ - setting_commit_logtime_enabled: تميكن وقت الدخول - setting_commit_logtime_activity_id: النشاط ÙÙŠ وقت الدخول - setting_gantt_items_limit: الحد الاقصى لعدد العناصر المعروضة على المخطط - setting_issue_group_assignment: السماح للإحالة الى المجموعات - setting_default_issue_start_date_to_creation_date: استخدام التاريخ الحالي كتاريخ بدأ للقضايا الجديدة - - permission_add_project: إنشاء مشروع - permission_add_subprojects: إنشاء مشاريع ÙØ±Ø¹ÙŠØ© - permission_edit_project: تعديل مشروع - permission_select_project_modules: تحديد شكل المشروع - permission_manage_members: إدارة الاعضاء - permission_manage_project_activities: ادارة اصدارات المشروع - permission_manage_versions: ادارة الاصدارات - permission_manage_categories: ادارة انواع القضايا - permission_view_issues: عرض القضايا - permission_add_issues: Ø§Ø¶Ø§ÙØ© القضايا - permission_edit_issues: تعديل القضايا - permission_manage_issue_relations: ادارة علاقات القضايا - permission_set_issues_private: تعين قضايا عامة او خاصة - permission_set_own_issues_private: تعين القضايا الخاصة بك كقضايا عامة او خاصة - 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_delete_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: ادارة ويكي - 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: حماية ØµÙØ­Ø§Øª ويكي - 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: حذ٠الرسائل الخاصة - permission_export_wiki_pages: تصدير ØµÙØ­Ø§Øª ويكي - permission_manage_subtasks: ادارة المهام Ø§Ù„ÙØ±Ø¹ÙŠØ© - - project_module_issue_tracking: تعقب القضايا - project_module_time_tracking: التعقب الزمني - project_module_news: الاخبار - project_module_documents: المستندات - project_module_files: Ø§Ù„Ù…Ù„ÙØ§Øª - project_module_wiki: ويكي - project_module_repository: المستودع - project_module_boards: المنتديات - project_module_calendar: التقويم - project_module_gantt: جانت - - label_user: المستخدم - label_user_plural: المستخدمين - label_user_new: مستخدم جديد - label_user_anonymous: مجهول الهوية - label_project: مشروع - label_project_new: مشروع جديد - label_project_plural: مشاريع - label_x_projects: - zero: لا يوجد مشاريع - one: مشروع واحد - other: "%{count} مشاريع" - label_project_all: كل المشاريع - label_project_latest: احدث المشاريع - label_issue: قضية - label_issue_new: قضية جديدة - label_issue_plural: قضايا - label_issue_view_all: عرض كل القضايا - label_issues_by: " %{value}القضية لصحابها" - label_issue_added: تم Ø§Ø¶Ø§ÙØ© القضية - label_issue_updated: تم تحديث القضية - label_issue_note_added: تم Ø§Ø¶Ø§ÙØ© الملاحظة - label_issue_status_updated: تم تحديث الحالة - label_issue_priority_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_role_anonymous: مجهول الهوية - label_role_non_member: ليس عضو - label_member: عضو - label_member_new: عضو جديد - label_member_plural: اعضاء - label_tracker: المتتبع - label_tracker_plural: المتتبعين - label_tracker_new: متتبع جديد - label_workflow: سير العمل - label_issue_status: وضع القضية - label_issue_status_plural: اوضاع القضية - label_issue_status_new: وضع جديد - label_issue_category: نوع القضية - label_issue_category_plural: انواع القضايا - label_issue_category_new: نوع جديد - label_custom_field: تخصيص حقل - label_custom_field_plural: تخصيص حقول - label_custom_field_new: حقل مخصص جديد - label_enumerations: التعدادات - label_enumeration_new: قيمة جديدة - label_information: معلومة - label_information_plural: معلومات - label_please_login: برجى تسجيل الدخول - label_register: تسجيل - label_login_with_open_id_option: او الدخول بهوية Ù…ÙØªÙˆØ­Ø© - label_password_lost: Ùقدت كلمة السر - label_home: Ø§Ù„ØµÙØ­Ø© الرئيسية - label_my_page: Ø§Ù„ØµÙØ­Ø© الخاصة بي - label_my_account: حسابي - label_my_projects: مشاريعي الخاصة - label_my_page_block: حجب ØµÙØ­ØªÙŠ Ø§Ù„Ø®Ø§ØµØ© - label_administration: الإدارة - label_login: تسجيل الدخول - label_logout: تسجيل الخروج - label_help: مساعدة - label_reported_issues: أبلغ القضايا - label_assigned_to_me_issues: المسائل المعنية إلى - label_last_login: آخر اتصال - label_registered_on: مسجل على - label_activity: النشاط - label_overall_activity: النشاط العام - label_user_activity: "قيمة النشاط" - label_new: جديدة - label_logged_as: تم تسجيل دخولك - label_environment: البيئة - label_authentication: المصادقة - label_auth_source: وضع المصادقة - label_auth_source_new: وضع مصادقة جديدة - label_auth_source_plural: أوضاع المصادقة - label_subproject_plural: مشاريع ÙØ±Ø¹ÙŠØ© - label_subproject_new: مشروع ÙØ±Ø¹ÙŠ Ø¬Ø¯ÙŠØ¯ - label_and_its_subprojects: "قيمةالمشاريع Ø§Ù„ÙØ±Ø¹ÙŠØ© الخاصة بك" - label_min_max_length: الحد الاقصى والادنى للطول - label_list: قائمة - label_date: تاريخ - label_integer: عدد صحيح - label_float: تعويم - label_boolean: منطقية - label_string: النص - label_text: نص طويل - label_attribute: سمة - label_attribute_plural: السمات - label_download: "تحميل" - label_download_plural: "تحميل" - 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_news_comment_added: Ø¥Ø¶Ø§ÙØ© التعليقات على أخبار - label_settings: إعدادات - label_overview: لمحة عامة - label_version: الإصدار - label_version_new: الإصدار الجديد - label_version_plural: الإصدارات - label_close_versions: أكملت إغلاق الإصدارات - 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_x_open_issues_abbr_on_total: - zero: 0 Ù…ÙØªÙˆØ­ / %{total} - one: 1 Ù…ÙØªÙˆØ­ / %{total} - other: "%{count} Ù…ÙØªÙˆØ­ / %{total}" - label_x_open_issues_abbr: - zero: 0 Ù…ÙØªÙˆØ­ - one: 1 مقتوح - other: "%{count} Ù…ÙØªÙˆØ­" - label_x_closed_issues_abbr: - zero: 0 مغلق - one: 1 مغلق - other: "%{count} مغلق" - 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: جانت - label_internal: الداخلية - label_last_changes: "آخر التغييرات %{count}" - label_change_view_all: عرض ÙƒØ§ÙØ© التغييرات - label_personalize_page: تخصيص هذه Ø§Ù„ØµÙØ­Ø© - label_comment: تعليق - label_comment_plural: تعليقات - label_x_comments: - zero: لا يوجد تعليقات - one: تعليق واحد - other: "%{count} تعليقات" - label_comment_add: Ø¥Ø¶Ø§ÙØ© تعليق - label_comment_added: تم Ø¥Ø¶Ø§ÙØ© التعليق - label_comment_delete: حذ٠التعليقات - label_query: استعلام مخصص - label_query_plural: استعلامات مخصصة - label_query_new: استعلام جديد - label_my_queries: استعلاماتي المخصصة - label_filter_add: Ø¥Ø¶Ø§ÙØ© عامل تصÙية - label_filter_plural: عوامل التصÙية - label_equals: يساوي - label_not_equals: لا يساوي - label_in_less_than: ÙÙŠ أقل من - label_in_more_than: ÙÙŠ أكثر من - label_greater_or_equal: '>=' - label_less_or_equal: '< =' - label_between: بين - label_in: ÙÙŠ - label_today: اليوم - label_all_time: كل الوقت - label_yesterday: بالأمس - label_this_week: هذا الأسبوع - label_last_week: الأسبوع الماضي - label_last_n_days: "ايام %{count} اخر" - label_this_month: هذا الشهر - label_last_month: الشهر الماضي - label_this_year: هذا العام - label_date_range: نطاق التاريخ - label_less_than_ago: أقل من قبل أيام - label_more_than_ago: أكثر من قبل أيام - label_ago: منذ أيام - label_contains: يحتوي على - label_not_contains: لا يحتوي على - label_day_plural: أيام - label_repository: المستودع - label_repository_plural: المستودعات - label_browse: ØªØµÙØ­ - label_modification: "%{count} تغير" - label_modification_plural: "%{count}تغيرات " - label_branch: ÙØ±Ø¹ - label_tag: ربط - label_revision: مراجعة - label_revision_plural: تنقيحات - label_revision_id: " %{value}مراجعة" - label_associated_revisions: التنقيحات المرتبطة - label_added: Ø¥Ø¶Ø§ÙØ© - label_modified: تعديل - label_copied: نسخ - label_renamed: إعادة تسمية - label_deleted: حذ٠- label_latest_revision: آخر تنقيح - label_latest_revision_plural: أحدث المراجعات - label_view_revisions: عرض التنقيحات - label_view_all_revisions: عرض ÙƒØ§ÙØ© المراجعات - label_max_size: الحد الأقصى للحجم - label_sort_highest: التحرك إلى أعلى - label_sort_higher: تحريك لأعلى - label_sort_lower: تحريك لأسÙÙ„ - label_sort_lowest: الانتقال إلى أسÙÙ„ - label_roadmap: خارطة الطريق - label_roadmap_due_in: " %{value}تستحق ÙÙŠ " - label_roadmap_overdue: "%{value}تأخير" - 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: الÙهرس حسب العنوان - label_index_by_date: الÙهرس حسب التاريخ - label_current_version: الإصدار الحالي - label_preview: معاينة - label_feed_plural: موجز ويب - label_changes_details: ØªÙØ§ØµÙŠÙ„ جميع التغييرات - label_issue_tracking: تعقب القضايا - label_spent_time: أمضى بعض الوقت - label_overall_spent_time: الوقت الذي تم Ø§Ù†ÙØ§Ù‚Ù‡ كاملا - label_f_hour: "%{value} ساعة" - label_f_hour_plural: "%{value} ساعات" - label_time_tracking: تعقب الوقت - label_change_plural: التغييرات - label_statistics: إحصاءات - label_commits_per_month: يثبت ÙÙŠ الشهر - label_commits_per_author: يثبت لكل مؤل٠- label_diff: Ø§Ù„Ø§Ø®ØªÙ„Ø§ÙØ§Øª - label_view_diff: عرض Ø§Ù„Ø§Ø®ØªÙ„Ø§ÙØ§Øª - label_diff_inline: مضمنة - label_diff_side_by_side: جنبا إلى جنب - label_options: خيارات - label_copy_workflow_from: نسخ سير العمل من - label_permissions_report: تقرير أذونات - label_watched_issues: شاهد القضايا - label_related_issues: القضايا ذات الصلة - label_applied_status: تطبيق مركز - label_loading: تحميل... - label_relation_new: علاقة جديدة - label_relation_delete: حذ٠العلاقة - label_relates_to: ذات الصلة إلى - label_duplicates: التكرارات - label_duplicated_by: ازدواج - label_blocks: حظر - label_blocked_by: حظر بواسطة - label_precedes: يسبق - label_follows: يتبع - label_end_to_start: نهاية لبدء - label_end_to_end: نهاية إلى نهاية - label_start_to_start: بدء إلى بدء - label_start_to_end: بداية لنهاية - label_stay_logged_in: تسجيل الدخول ÙÙŠ - label_disabled: تعطيل - label_show_completed_versions: أكملت إظهار إصدارات - label_me: لي - label_board: المنتدى - label_board_new: منتدى جديد - label_board_plural: المنتديات - label_board_locked: تأمين - label_board_sticky: لزجة - label_topic_plural: المواضيع - label_message_plural: رسائل - label_message_last: آخر رسالة - label_message_new: رسالة جديدة - label_message_posted: تم Ø§Ø¶Ø§ÙØ© الرسالة - label_reply_plural: الردود - label_send_information: إرسال معلومات الحساب للمستخدم - label_year: سنة - label_month: شهر - label_week: أسبوع - label_date_from: من - label_date_to: إلى - label_language_based: استناداً إلى لغة المستخدم - label_sort_by: " %{value}الترتيب حسب " - label_send_test_email: ارسل رسالة الكترونية كاختبار - label_feeds_access_key: RSS Ù…ÙØªØ§Ø­ دخول - label_missing_feeds_access_key: Ù…ÙقودRSS Ù…ÙØªØ§Ø­ دخول - label_feeds_access_key_created_on: "RSS تم انشاء Ù…ÙØªØ§Ø­ %{value} منذ" - label_module_plural: الوحدات النمطية - label_added_time_by: " تم Ø§Ø¶Ø§ÙØªÙ‡ من قبل%{author} %{age} منذ" - label_updated_time_by: " تم تحديثه من قبل%{author} %{age} منذ" - label_updated_time: "تم التحديث %{value} منذ" - label_jump_to_a_project: الانتقال إلى مشروع... - label_file_plural: Ø§Ù„Ù…Ù„ÙØ§Øª - label_changeset_plural: اعدادات التغير - label_default_columns: الاعمدة Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠØ© - label_no_change_option: (أي تغيير) - label_bulk_edit_selected_issues: تحرير القضايا المظللة - label_bulk_edit_selected_time_entries: تعديل كل الإدخالات ÙÙŠ كل الاوقات - 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_option_only_my_events: "السماح لي Ùقط بمشاهدة الاحداث الخاصة" - label_user_mail_option_only_assigned: "Ùقط الخيارات التي تم تعيينها" - label_user_mail_option_only_owner: "Ùقط للخيارات التي املكها" - label_user_mail_no_self_notified: "لا تريد اعلامك بالتغيرات التي تجريها Ø¨Ù†ÙØ³Ùƒ" - label_registration_activation_by_email: حساب التنشيط عبر البريد الإلكتروني - label_registration_manual_activation: تنشيط الحساب اليدوي - label_registration_automatic_activation: تنشيط الحساب التلقائي - label_display_per_page: "لكل ØµÙØ­Ø©: %{value}" - label_age: العمر - label_change_properties: تغيير الخصائص - label_general: عامة - label_more: أكثر - label_scm: scm - label_plugins: Ø§Ù„Ø¥Ø¶Ø§ÙØ§Øª - label_ldap_authentication: مصادقة LDAP - label_downloads_abbr: D/L - label_optional_description: وص٠اختياري - label_add_another_file: Ø¥Ø¶Ø§ÙØ© مل٠آخر - label_preferences: ØªÙØ¶ÙŠÙ„ات - label_chronological_order: ÙÙŠ ترتيب زمني - label_reverse_chronological_order: ÙÙŠ ترتيب زمني عكسي - label_planning: التخطيط - label_incoming_emails: رسائل البريد الإلكتروني الوارد - label_generate_key: إنشاء Ù…ÙØªØ§Ø­ - label_issue_watchers: المراقبون - label_example: مثال - label_display: العرض - label_sort: ÙØ±Ø² - label_ascending: تصاعدي - label_descending: تنازلي - label_date_from_to: من %{start} الى %{end} - label_wiki_content_added: Ø¥Ø¶Ø§ÙØ© ØµÙØ­Ø© ويكي - label_wiki_content_updated: تحديث ØµÙØ­Ø© ويكي - label_group: مجموعة - label_group_plural: المجموعات - label_group_new: مجموعة جديدة - label_time_entry_plural: أمضى بعض الوقت - label_version_sharing_none: لم يشارك - label_version_sharing_descendants: يشارك - label_version_sharing_hierarchy: مع التسلسل الهرمي للمشروع - label_version_sharing_tree: مع شجرة المشروع - label_version_sharing_system: مع جميع المشاريع - label_update_issue_done_ratios: تحديث قضيةالنسب - label_copy_source: مصدر - label_copy_target: الهد٠- label_copy_same_as_target: Ù†ÙØ³ الهد٠- label_display_used_statuses_only: عرض الحالات المستخدمة من قبل هذا "تعقب" Ùقط - label_api_access_key: Ù…ÙØªØ§Ø­ الوصول إلى API - label_missing_api_access_key: API لم يتم الحصول على Ù…ÙØªØ§Ø­ الوصول - label_api_access_key_created_on: " API إنشاء Ù…ÙØªØ§Ø­ الوصول إلى" - label_profile: المل٠الشخصي - label_subtask_plural: المهام Ø§Ù„ÙØ±Ø¹ÙŠØ© - label_project_copy_notifications: إرسال إشعار الى البريد الإلكتروني عند نسخ المشروع - label_principal_search: "البحث عن مستخدم أو مجموعة:" - label_user_search: "البحث عن المستخدم:" - label_additional_workflow_transitions_for_author: الانتقالات الإضاÙية المسموح بها عند المستخدم صاحب البلاغ - label_additional_workflow_transitions_for_assignee: الانتقالات الإضاÙية المسموح بها عند المستخدم المحال إليه - label_issues_visibility_all: جميع القضايا - label_issues_visibility_public: جميع القضايا الخاصة - label_issues_visibility_own: القضايا التي أنشأها المستخدم - label_git_report_last_commit: اعتماد التقرير الأخير Ù„Ù„Ù…Ù„ÙØ§Øª والدلائل - label_parent_revision: الوالدين - label_child_revision: الطÙÙ„ - label_export_options: "%{export_format} خيارات التصدير" - - button_login: دخول - button_submit: تثبيت - button_save: Ø­ÙØ¸ - button_check_all: نحديد الكل - button_uncheck_all: عدم تحديد الكل - button_collapse_all: تقليص الكل - button_expand_all: عرض الكل - button_delete: حذ٠- button_create: انشاء - button_create_and_continue: انشاء واستمرار - button_test: اختبار - button_edit: تعديل - button_edit_associated_wikipage: "تغير ØµÙØ­Ø© ويكي: %{page_title}" - button_add: Ø§Ø¶Ø§ÙØ© - button_change: تغير - button_apply: تطبيق - button_clear: واضح - button_lock: Ù‚ÙÙ„ - button_unlock: الغاء القÙÙ„ - button_download: تنزيل - button_list: قائمة - button_view: عرض - button_move: تحرك - button_move_and_follow: تحرك واتبع - button_back: رجوع - button_cancel: إلغاء - button_activate: تنشيط - button_sort: ترتيب - button_log_time: وقت الدخول - button_rollback: الرجوع الى هذا الاصدار - button_watch: يشاهد - button_unwatch: إلغاء المشاهدة - button_reply: رد - button_archive: الارشي٠- button_unarchive: إلغاء Ø§Ù„Ø§Ø±Ø´ÙØ© - button_reset: إعادة - button_rename: إعادة التسمية - button_change_password: تغير كلمة المرور - button_copy: نسخ - button_copy_and_follow: نسخ واتباع - button_annotate: تعليق - button_update: تحديث - button_configure: تكوين - button_quote: يقتبس - button_duplicate: يضاع٠- button_show: يظهر - button_edit_section: يعدل هذا الجزء - button_export: يستورد - - status_active: نشيط - status_registered: مسجل - status_locked: مقÙÙ„ - - version_status_open: Ù…ÙØªÙˆØ­ - version_status_locked: مقÙÙ„ - version_status_closed: مغلق - - field_active: ÙØ¹Ø§Ù„ - - text_select_mail_notifications: حدد الامور التي يجب ابلاغك بها عن طريق البريد الالكتروني - text_regexp_info: مثال. ^[A-Z0-9]+$ - text_min_max_length_info: الحد الاقصى والادني لطول المعلومات - text_project_destroy_confirmation: هل أنت متأكد من أنك تريد حذ٠هذا المشروع والبيانات ذات الصلة؟ - text_subprojects_destroy_warning: "subproject(s): سيتم حذ٠أيضا." - text_workflow_edit: حدد دوراً وتعقب لتحرير سير العمل - text_are_you_sure: هل أنت متأكد؟ - text_are_you_sure_with_children: "حذ٠الموضوع وجميع المسائل المتعلقة بالطÙل؟" - text_journal_changed: "%{label} تغير %{old} الى %{new}" - text_journal_changed_no_detail: "%{label} تم التحديث" - text_journal_set_to: "%{label} تغير الى %{value}" - text_journal_deleted: "%{label} تم الحذ٠(%{old})" - text_journal_added: "%{label} %{value} تم Ø§Ù„Ø§Ø¶Ø§ÙØ©" - text_tip_issue_begin_day: قضية بدأت اليوم - text_tip_issue_end_day: قضية انتهت اليوم - text_tip_issue_begin_end_day: قضية بدأت وانتهت اليوم - text_caracters_maximum: "%{count} الحد الاقصى." - text_caracters_minimum: "الحد الادنى %{count}" - text_length_between: "الطول %{min} بين %{max} رمز" - text_tracker_no_workflow: لم يتم تحديد سير العمل لهذا المتتبع - text_unallowed_characters: رموز غير مسموحة - text_comma_separated: مسموح رموز متنوعة ÙŠÙØµÙ„ها ÙØ§ØµÙ„Ø© . - text_line_separated: مسموح رموز متنوعة ÙŠÙØµÙ„ها سطور - text_issues_ref_in_commit_messages: الرجوع واصلاح القضايا ÙÙŠ رسائل المشتكين - text_issue_added: "القضية %{id} تم ابلاغها عن طريق %{author}." - text_issue_updated: "القضية %{id} تم تحديثها عن طريق %{author}." - text_wiki_destroy_confirmation: هل انت متأكد من رغبتك ÙÙŠ حذ٠هذا الويكي ومحتوياته؟ - text_issue_category_destroy_question: "بعض القضايا (%{count}) مرتبطة بهذه Ø§Ù„ÙØ¦Ø©ØŒ ماذا تريد ان ØªÙØ¹Ù„ بها؟" - text_issue_category_destroy_assignments: Ø­Ø°Ù Ø§Ù„ÙØ¦Ø© - text_issue_category_reassign_to: اعادة تثبيت البنود ÙÙŠ Ø§Ù„ÙØ¦Ø© - text_user_mail_option: "بالنسبة للمشاريع غير المحددة، سو٠يتم ابلاغك عن المشاريع التي تشاهدها او تشارك بها Ùقط!" - text_no_configuration_data: "الادوار والمتتبع وحالات القضية ومخطط سير العمل لم يتم تحديد وضعها Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠ Ø¨Ø¹Ø¯. " - text_load_default_configuration: احمل الاعدادات Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠØ© - text_status_changed_by_changeset: " طبق التغيرات المعينة على %{value}." - text_time_logged_by_changeset: "تم تطبيق التغيرات المعينة على %{value}." - text_issues_destroy_confirmation: هل انت متأكد من حذ٠البنود المظللة؟' - text_issues_destroy_descendants_confirmation: "سو٠يؤدي هذا الى حذ٠%{count} المهام Ø§Ù„ÙØ±Ø¹ÙŠØ© ايضا." - text_time_entries_destroy_confirmation: "هل انت متأكد من رغبتك ÙÙŠ حذ٠الادخالات الزمنية المحددة؟" - text_select_project_modules: قم بتحديد الوضع المناسب لهذا المشروع:' - text_default_administrator_account_changed: تم تعديل الاعدادات Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠØ© لحساب المدير - text_file_repository_writable: المرÙقات قابلة للكتابة - text_plugin_assets_writable: الدليل المساعد قابل للكتابة - text_destroy_time_entries_question: " ساعة على القضية التي تود حذÙها، ماذا تريد ان ØªÙØ¹Ù„ØŸ %{hours} تم تثبيت" - text_destroy_time_entries: قم بحذ٠الساعات المسجلة - text_assign_time_entries_to_project: ثبت الساعات المسجلة على التقرير - text_reassign_time_entries: 'اعادة تثبيت الساعات المسجلة لهذه القضية:' - text_user_wrote: "%{value} كتب:" - text_enumeration_destroy_question: "%{count} الكائنات المعنية لهذه القيمة" - text_enumeration_category_reassign_to: اعادة تثبيت الكائنات التالية لهذه القيمة:' - text_email_delivery_not_configured: "لم يتم تسليم البريد الالكتروني" - text_diff_truncated: '... لقد تم اقتطلع هذا الجزء لانه تجاوز الحد الاقصى المسموح بعرضه' - text_custom_field_possible_values_info: 'سطر لكل قيمة' - text_wiki_page_nullify_children: "Ø§Ù„Ø§Ø­ØªÙØ§Ø¸ Ø¨ØµÙØ­Ø§Øª الطÙÙ„ ÙƒØµÙØ­Ø§Øª جذر" - text_wiki_page_destroy_children: "Ø­Ø°Ù ØµÙØ­Ø§Øª الطÙÙ„ وجميع أولادهم" - text_wiki_page_reassign_children: "إعادة تعيين ØµÙØ­Ø§Øª تابعة لهذه Ø§Ù„ØµÙØ­Ø© الأصلية" - text_own_membership_delete_confirmation: "انت على وشك إزالة بعض أو ÙƒØ§ÙØ© الأذونات الخاصة بك، لن تكون قادراً على تحرير هذا المشروع بعد ذلك. هل أنت متأكد من أنك تريد المتابعة؟" - text_zoom_in: تصغير - text_zoom_out: تكبير - text_warn_on_leaving_unsaved: "Ø§Ù„ØµÙØ­Ø© تحتوي على نص غير مخزن، سو٠يÙقد النص اذا تم الخروج من Ø§Ù„ØµÙØ­Ø©." - text_scm_path_encoding_note: "Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠ: UTF-8" - text_git_repository_note: مستودع ÙØ§Ø±Øº ومحلي - text_mercurial_repository_note: مستودع محلي - text_scm_command: امر - text_scm_command_version: اصدار - text_scm_config: الرجاء اعادة تشغيل التطبيق - text_scm_command_not_available: الامر غير Ù…ØªÙˆÙØ±ØŒ الرجاء التحقق من لوحة التحكم - - default_role_manager: مدير - default_role_developer: مطور - default_role_reporter: مراسل - default_tracker_bug: الشوائب - default_tracker_feature: خاصية - default_tracker_support: دعم - default_issue_status_new: جديد - default_issue_status_in_progress: جاري التحميل - default_issue_status_resolved: الحل - default_issue_status_feedback: التغذية الراجعة - default_issue_status_closed: مغلق - default_issue_status_rejected: مرÙوض - default_doc_category_user: مستندات المستخدم - default_doc_category_tech: المستندات التقنية - default_priority_low: قليل - default_priority_normal: عادي - default_priority_high: عالي - default_priority_urgent: طارئ - default_priority_immediate: مباشرة - default_activity_design: تصميم - default_activity_development: تطوير - - enumeration_issue_priorities: الاولويات - enumeration_doc_categories: تصني٠المستندات - enumeration_activities: الانشطة - enumeration_system_activity: نشاط النظام - description_filter: Ùلترة - description_search: حقل البحث - description_choose_project: مشاريع - description_project_scope: مجال البحث - description_notes: ملاحظات - description_message_content: محتويات الرسالة - description_query_sort_criteria_attribute: نوع الترتيب - description_query_sort_criteria_direction: اتجاه الترتيب - description_user_mail_notification: إعدادات البريد الالكتروني - description_available_columns: الاعمدة Ø§Ù„Ù…ØªÙˆÙØ±Ø© - description_selected_columns: الاعمدة المحددة - description_all_columns: كل الاعمدة - description_issue_category_reassign: اختر التصني٠- description_wiki_subpages_reassign: اختر ØµÙØ­Ø© جديدة - description_date_range_list: اختر المجال من القائمة - description_date_range_interval: اختر المدة عن طريق اختيار تاريخ البداية والنهاية - description_date_from: ادخل تاريخ البداية - description_date_to: ادخل تاريخ الانتهاء - text_rmagick_available: RMagick available (optional) - text_wiki_page_destroy_question: This page has %{descendants} child page(s) and descendant(s). What do you want to do? - text_project_identifier_info: Only lower case letters (a-z), numbers and dashes are allowed.
    Once saved, the identifier cannot be changed. - text_repository_usernames_mapping: |- - Select or update the Redmine user mapped to each username found in the repository log. - Users with the same Redmine and repository username or email are automatically mapped. diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/67/674b8036a925ccf2c9ef3d3610ab9b8e2eef3746.svn-base --- a/.svn/pristine/67/674b8036a925ccf2c9ef3d3610ab9b8e2eef3746.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -def deprecated_task(name, new_name) - task name=>new_name do - $stderr.puts "\nNote: The rake task #{name} has been deprecated, please use the replacement version #{new_name}" - end -end - -deprecated_task :load_default_data, "redmine:load_default_data" -deprecated_task :migrate_from_mantis, "redmine:migrate_from_mantis" -deprecated_task :migrate_from_trac, "redmine:migrate_from_trac" diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/67/67867a3abe62489d5d689d70198440d4c835b00f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/67/67867a3abe62489d5d689d70198440d4c835b00f.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,1090 @@ +# Serbian translations for Redmine +# by Vladimir Medarović (vlada@medarovic.com) +sr: + direction: ltr + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%d.%m.%Y." + short: "%e %b" + long: "%B %e, %Y" + + day_names: [недеља, понедељак, уторак, Ñреда, четвртак, петак, Ñубота] + abbr_day_names: [нед, пон, уто, Ñре, чет, пет, Ñуб] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, јануар, фебруар, март, април, мај, јун, јул, авгуÑÑ‚, Ñептембар, октобар, новембар, децембар] + abbr_month_names: [~, јан, феб, мар, апр, мај, јун, јул, авг, Ñеп, окт, нов, дец] + # Used in date_select and datime_select. + order: + - :day + - :month + - :year + + time: + formats: + default: "%d.%m.%Y. у %H:%M" + time: "%H:%M" + short: "%d. %b у %H:%M" + long: "%d. %B %Y у %H:%M" + am: "am" + pm: "pm" + + datetime: + distance_in_words: + half_a_minute: "пола минута" + less_than_x_seconds: + one: "мање од једне Ñекунде" + other: "мање од %{count} Ñек." + x_seconds: + one: "једна Ñекунда" + other: "%{count} Ñек." + less_than_x_minutes: + one: "мање од минута" + other: "мање од %{count} мин." + x_minutes: + one: "један минут" + other: "%{count} мин." + about_x_hours: + one: "приближно један Ñат" + other: "приближно %{count} Ñати" + x_hours: + one: "1 Ñат" + other: "%{count} Ñати" + x_days: + one: "један дан" + other: "%{count} дана" + about_x_months: + one: "приближно један меÑец" + other: "приближно %{count} меÑеци" + x_months: + one: "један меÑец" + other: "%{count} меÑеци" + about_x_years: + one: "приближно годину дана" + other: "приближно %{count} год." + over_x_years: + one: "преко годину дана" + other: "преко %{count} год." + almost_x_years: + one: "Ñкоро годину дана" + other: "Ñкоро %{count} год." + + number: + format: + separator: "," + delimiter: "" + precision: 3 + human: + format: + delimiter: "" + precision: 3 + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + + +# Used in array.to_sentence. + support: + array: + sentence_connector: "и" + skip_last_comma: false + + activerecord: + errors: + template: + header: + one: "1 error prohibited this %{model} from being saved" + other: "%{count} errors prohibited this %{model} from being saved" + messages: + inclusion: "није укључен у ÑпиÑак" + exclusion: "је резервиÑан" + invalid: "је неиÑправан" + confirmation: "потврда не одговара" + accepted: "мора бити прихваћен" + empty: "не може бити празно" + blank: "не може бити празно" + too_long: "је предугачка (макÑимум знакова је %{count})" + too_short: "је прекратка (минимум знакова је %{count})" + wrong_length: "је погрешне дужине (број знакова мора бити %{count})" + taken: "је већ у употреби" + not_a_number: "није број" + not_a_date: "није иÑправан датум" + greater_than: "мора бити већи од %{count}" + greater_than_or_equal_to: "мора бити већи или једнак %{count}" + equal_to: "мора бити једнак %{count}" + less_than: "мора бити мањи од %{count}" + less_than_or_equal_to: "мора бити мањи или једнак %{count}" + odd: "мора бити паран" + even: "мора бити непаран" + greater_than_start_date: "мора бити већи од почетног датума" + not_same_project: "не припада иÑтом пројекту" + circular_dependency: "Ова веза ће Ñтворити кружну референцу" + cant_link_an_issue_with_a_descendant: "Проблем не може бити повезан Ñа једним од Ñвојих подзадатака" + + actionview_instancetag_blank_option: Молим одаберите + + general_text_No: 'Ðе' + general_text_Yes: 'Да' + general_text_no: 'не' + general_text_yes: 'да' + general_lang_name: 'Serbian Cyrillic (СрпÑки)' + general_csv_separator: ',' + general_csv_decimal_separator: '.' + general_csv_encoding: UTF-8 + general_pdf_encoding: UTF-8 + general_first_day_of_week: '1' + + notice_account_updated: Ðалог је уÑпешно ажуриран. + notice_account_invalid_creditentials: ÐеиÑправно кориÑничко име или лозинка. + notice_account_password_updated: Лозинка је уÑпешно ажурирана. + notice_account_wrong_password: Погрешна лозинка + notice_account_register_done: КориÑнички налог је уÑпешно креиран. Кликните на линк који Ñте добили у е-поруци за активацију. + notice_account_unknown_email: Ðепознат кориÑник. + notice_can_t_change_password: Овај кориÑнички налог за потврду идентитета кориÑти Ñпољни извор. Ðемогуће је променити лозинку. + notice_account_lost_email_sent: ПоÑлата вам је е-порука Ñа упутÑтвом за избор нове лозинке + notice_account_activated: Ваш кориÑнички налог је активиран. Сада Ñе можете пријавити. + notice_successful_create: УÑпешно креирање. + notice_successful_update: УÑпешно ажурирање. + notice_successful_delete: УÑпешно бриÑање. + notice_successful_connection: УÑпешно повезивање. + notice_file_not_found: Страна којој желите приÑтупити не поÑтоји или је уклоњена. + notice_locking_conflict: Податак је ажуриран од Ñтране другог кориÑника. + notice_not_authorized: ÐиÑте овлашћени за приÑтуп овој Ñтрани. + notice_email_sent: "E-порука је поÑлата на %{value}" + notice_email_error: "Догодила Ñе грешка приликом Ñлања е-поруке (%{value})" + notice_feeds_access_key_reseted: Ваш RSS приÑтупни кључ је поништен. + notice_api_access_key_reseted: Ваш API приÑтупни кључ је поништен. + notice_failed_to_save_issues: "ÐеуÑпешно Ñнимање %{count} проблема од %{total} одабраних: %{ids}." + notice_failed_to_save_members: "ÐеуÑпешно Ñнимање члана(ова): %{errors}." + notice_no_issue_selected: "Ðи један проблем није одабран! Молимо, одаберите проблем који желите да мењате." + notice_account_pending: "Ваш налог је креиран и чека на одобрење админиÑтратора." + notice_default_data_loaded: Подразумевано конфигуриÑање је уÑпешно учитано. + notice_unable_delete_version: Верзију је немогуће избриÑати. + notice_unable_delete_time_entry: Ставку евиденције времена је немогуће избриÑати. + notice_issue_done_ratios_updated: ÐžÐ´Ð½Ð¾Ñ Ñ€ÐµÑˆÐµÐ½Ð¸Ñ… проблема је ажуриран. + + error_can_t_load_default_data: "Подразумевано конфигуриÑање је немогуће учитати: %{value}" + error_scm_not_found: "Ставка или иÑправка ниÑу пронађене у Ñпремишту." + error_scm_command_failed: "Грешка Ñе јавила приликом покушаја приÑтупа Ñпремишту: %{value}" + error_scm_annotate: "Ставка не поÑтоји или не може бити означена." + error_issue_not_found_in_project: 'Проблем није пронађен или не припада овом пројекту.' + error_no_tracker_in_project: 'Ðи једно праћење није повезано Ñа овим пројектом. Молимо проверите подешавања пројекта.' + error_no_default_issue_status: 'Подразумевани ÑÑ‚Ð°Ñ‚ÑƒÑ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð° није дефиниÑан. Молимо проверите ваше конфигуриÑање (идите на "ÐдминиÑтрација -> СтатуÑи проблема").' + error_can_not_delete_custom_field: Ðемогуће је избриÑати прилагођено поље + error_can_not_delete_tracker: "Ово праћење Ñадржи проблеме и не може бити обриÑано." + error_can_not_remove_role: "Ова улога је у употреби и не може бити обриÑана." + error_can_not_reopen_issue_on_closed_version: 'Проблем додељен затвореној верзији не може бити поново отворен' + error_can_not_archive_project: Овај пројекат Ñе не може архивирати + error_issue_done_ratios_not_updated: "ÐžÐ´Ð½Ð¾Ñ Ñ€ÐµÑˆÐµÐ½Ð¸Ñ… проблема није ажуриран." + error_workflow_copy_source: 'Молимо одаберите изворно праћење или улогу' + error_workflow_copy_target: 'Молимо одаберите одредишно праћење и улогу' + error_unable_delete_issue_status: 'Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð° је немогуће обриÑати' + error_unable_to_connect: "Повезивање Ñа (%{value}) је немогуће" + warning_attachments_not_saved: "%{count} датотека не може бити Ñнимљена." + + mail_subject_lost_password: "Ваша %{value} лозинка" + mail_body_lost_password: 'За промену ваше лозинке, кликните на Ñледећи линк:' + mail_subject_register: "Ðктивација вашег %{value} налога" + mail_body_register: 'За активацију вашег налога, кликните на Ñледећи линк:' + mail_body_account_information_external: "Ваш налог %{value} можете кориÑтити за пријаву." + mail_body_account_information: Информације о вашем налогу + mail_subject_account_activation_request: "Захтев за активацију налога %{value}" + mail_body_account_activation_request: "Ðови кориÑник (%{value}) је региÑтрован. Ðалог чека на ваше одобрење:" + mail_subject_reminder: "%{count} проблема доÑпева наредних %{days} дана" + mail_body_reminder: "%{count} проблема додељених вама доÑпева у наредних %{days} дана:" + mail_subject_wiki_content_added: "Wiki Ñтраница '%{id}' је додата" + mail_body_wiki_content_added: "%{author} је додао wiki Ñтраницу '%{id}'." + mail_subject_wiki_content_updated: "Wiki Ñтраница '%{id}' је ажурирана" + mail_body_wiki_content_updated: "%{author} је ажурирао wiki Ñтраницу '%{id}'." + + + 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: Регуларан израз + field_min_length: Минимална дужина + field_max_length: МакÑимална дужина + field_value: ВредноÑÑ‚ + field_category: Категорија + field_title: ÐаÑлов + field_project: Пројекат + field_issue: Проблем + field_status: Ð¡Ñ‚Ð°Ñ‚ÑƒÑ + field_notes: Белешке + field_is_closed: Затворен проблем + field_is_default: Подразумевана вредноÑÑ‚ + field_tracker: Праћење + field_subject: Предмет + field_due_date: Крајњи рок + field_assigned_to: Додељено + field_priority: Приоритет + field_fixed_version: Одредишна верзија + field_user: КориÑник + field_principal: Главни + field_role: Улога + field_homepage: Почетна Ñтраница + field_is_public: Јавно објављивање + field_parent: Потпројекат од + field_is_in_roadmap: Проблеми приказани у плану рада + field_login: КориÑничко име + field_mail_notification: Обавештења путем е-поште + field_admin: ÐдминиÑтратор + field_last_login_on: ПоÑледње повезивање + field_language: Језик + field_effective_date: Датум + field_password: Лозинка + field_new_password: Ðова лозинка + field_password_confirmation: Потврда лозинке + field_version: Верзија + field_type: Тип + field_host: Главни рачунар + field_port: Порт + field_account: КориÑнички налог + field_base_dn: Базни DN + field_attr_login: Ðтрибут пријављивања + field_attr_firstname: Ðтрибут имена + field_attr_lastname: Ðтрибут презимена + field_attr_mail: Ðтрибут е-адреÑе + field_onthefly: Креирање кориÑника у току рада + field_start_date: Почетак + field_done_ratio: "% урађено" + field_auth_source: Режим потврде идентитета + field_hide_mail: Сакриј моју е-адреÑу + field_comments: Коментар + field_url: URL + field_start_page: Почетна Ñтраница + field_subproject: Потпројекат + field_hours: Ñати + field_activity: ÐктивноÑÑ‚ + field_spent_on: Датум + field_identifier: Идентификатор + field_is_filter: Употреби као филтер + field_issue_to: Сродни проблеми + field_delay: Кашњење + field_assignable: Проблем може бити додељен овој улози + field_redirect_existing_links: ПреуÑмери поÑтојеће везе + field_estimated_hours: Протекло време + field_column_names: Колоне + field_time_zone: ВременÑка зона + field_searchable: Може да Ñе претражује + field_default_value: Подразумевана вредноÑÑ‚ + field_comments_sorting: Прикажи коментаре + field_parent_title: Матична Ñтраница + field_editable: Изменљиво + field_watcher: ПоÑматрач + field_identity_url: OpenID URL + field_content: Садржај + field_group_by: ГрупиÑање резултата по + field_sharing: Дељење + field_parent_issue: Матични задатак + + setting_app_title: ÐаÑлов апликације + setting_app_subtitle: ПоднаÑлов апликације + setting_welcome_text: ТекÑÑ‚ добродошлице + setting_default_language: Подразумевани језик + setting_login_required: Обавезна потврда идентитета + setting_self_registration: СаморегиÑтрација + setting_attachment_max_size: МакÑ. величина приложене датотеке + setting_issues_export_limit: Ограничење извоза „проблема“ + setting_mail_from: Е-адреÑа пошиљаоца + setting_bcc_recipients: Примаоци „Bcc“ копије + setting_plain_text_mail: Порука Ñа чиÑтим текÑтом (без HTML-а) + setting_host_name: Путања и назив главног рачунара + setting_text_formatting: Обликовање текÑта + setting_wiki_compression: КомпреÑија Wiki иÑторије + setting_feeds_limit: Ограничење Ñадржаја извора веÑти + setting_default_projects_public: Подразумева Ñе јавно приказивање нових пројеката + setting_autofetch_changesets: Извршавање аутоматÑког преузимања + setting_sys_api_enabled: Омогућавање WS за управљање Ñпремиштем + setting_commit_ref_keywords: Референцирање кључних речи + setting_commit_fix_keywords: Поправљање кључних речи + setting_autologin: ÐутоматÑка пријава + setting_date_format: Формат датума + setting_time_format: Формат времена + setting_cross_project_issue_relations: Дозволи повезивање проблема из унакрÑних пројеката + setting_issue_list_default_columns: Подразумеване колоне приказане на ÑпиÑку проблема + setting_emails_footer: Подножје Ñтранице е-поруке + setting_protocol: Протокол + setting_per_page_options: Опције приказа објеката по Ñтраници + setting_user_format: Формат приказа кориÑника + setting_activity_days_default: Број дана приказаних на пројектној активноÑти + setting_display_subprojects_issues: Приказуј проблеме из потпројеката на главном пројекту, уколико није другачије наведено + setting_enabled_scm: Омогућавање SCM + setting_mail_handler_body_delimiters: "Скраћивање е-поруке након једне од ових линија" + setting_mail_handler_api_enabled: Омогућавање WS долазне е-поруке + setting_mail_handler_api_key: API кључ + setting_sequential_project_identifiers: ГенериÑање Ñеквенцијалног имена пројекта + setting_gravatar_enabled: КориÑти Gravatar кориÑничке иконе + setting_gravatar_default: Подразумевана Gravatar Ñлика + setting_diff_max_lines_displayed: МакÑ. број приказаних различитих линија + setting_file_max_size_displayed: МакÑ. величина текÑÑ‚. датотека приказаних уметнуто + setting_repository_log_display_limit: МакÑ. број ревизија приказаних у датотеци за евиденцију + setting_openid: Дозволи OpenID пријаву и региÑтрацију + setting_password_min_length: Минимална дужина лозинке + setting_new_project_user_role_id: Креатору пројекта (који није админиÑтратор) додељује је улога + setting_default_projects_modules: Подразумевано омогућени модули за нове пројекте + setting_issue_done_ratio: Израчунај Ð¾Ð´Ð½Ð¾Ñ Ñ€ÐµÑˆÐµÐ½Ð¸Ñ… проблема + setting_issue_done_ratio_issue_field: кориÑтећи поље проблема + setting_issue_done_ratio_issue_status: кориÑтећи ÑÑ‚Ð°Ñ‚ÑƒÑ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð° + setting_start_of_week: Први дан у Ñедмици + setting_rest_api_enabled: Омогући REST web уÑлуге + setting_cache_formatted_text: Кеширање обрађеног текÑта + + permission_add_project: Креирање пројекта + permission_add_subprojects: Креирање потпојекта + permission_edit_project: Измена пројеката + permission_select_project_modules: Одабирање модула пројекта + permission_manage_members: Управљање члановима + permission_manage_project_activities: Управљање пројектним активноÑтима + permission_manage_versions: Управљање верзијама + permission_manage_categories: Управљање категоријама проблема + permission_view_issues: Преглед проблема + 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_delete_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_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: БриÑање ÑопÑтвених порука + permission_export_wiki_pages: Извоз wiki Ñтраница + permission_manage_subtasks: Управљање подзадацима + + project_module_issue_tracking: Праћење проблема + project_module_time_tracking: Праћење времена + project_module_news: ВеÑти + project_module_documents: Документи + project_module_files: Датотеке + project_module_wiki: Wiki + project_module_repository: Спремиште + project_module_boards: Форуми + + label_user: КориÑник + label_user_plural: КориÑници + label_user_new: Ðови кориÑник + label_user_anonymous: Ðнониман + label_project: Пројекат + label_project_new: Ðови пројекат + label_project_plural: Пројекти + label_x_projects: + zero: нема пројеката + one: један пројекат + other: "%{count} пројеката" + label_project_all: Сви пројекти + label_project_latest: ПоÑледњи пројекти + label_issue: Проблем + label_issue_new: Ðови проблем + label_issue_plural: Проблеми + label_issue_view_all: Приказ Ñвих проблема + label_issues_by: "Проблеми (%{value})" + label_issue_added: Проблем је додат + label_issue_updated: Проблем је ажуриран + label_document: Документ + label_document_new: Ðови документ + label_document_plural: Документи + label_document_added: Документ је додат + label_role: Улога + label_role_plural: Улоге + label_role_new: Ðова улога + label_role_and_permissions: Улоге и дозволе + label_member: Члан + label_member_new: Ðови члан + label_member_plural: Чланови + label_tracker: Праћење + label_tracker_plural: Праћења + label_tracker_new: Ðово праћење + label_workflow: Ток поÑла + label_issue_status: Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð° + label_issue_status_plural: СтатуÑи проблема + label_issue_status_new: Ðови ÑÑ‚Ð°Ñ‚ÑƒÑ + label_issue_category: Категорија проблема + label_issue_category_plural: Категорије проблема + label_issue_category_new: Ðова категорија + label_custom_field: Прилагођено поље + label_custom_field_plural: Прилагођена поља + label_custom_field_new: Ðово прилагођено поље + label_enumerations: Ðабројива лиÑта + label_enumeration_new: Ðова вредноÑÑ‚ + label_information: Информација + label_information_plural: Информације + label_please_login: Молимо, пријавите Ñе + label_register: РегиÑтрација + label_login_with_open_id_option: или пријава Ñа OpenID + label_password_lost: Изгубљена лозинка + label_home: Почетак + label_my_page: Моја Ñтраница + label_my_account: Мој налог + label_my_projects: Моји пројекти + label_my_page_block: My page block + label_administration: ÐдминиÑтрација + label_login: Пријава + label_logout: Одјава + label_help: Помоћ + label_reported_issues: Пријављени проблеми + label_assigned_to_me_issues: Проблеми додељени мени + label_last_login: ПоÑледње повезивање + label_registered_on: РегиÑтрован + label_activity: ÐктивноÑÑ‚ + label_overall_activity: Целокупна активноÑÑ‚ + label_user_activity: "ÐктивноÑÑ‚ кориÑника %{value}" + label_new: Ðово + label_logged_as: Пријављени Ñте као + label_environment: Окружење + label_authentication: Потврда идентитета + label_auth_source: Режим потврде идентитета + label_auth_source_new: Ðови режим потврде идентитета + label_auth_source_plural: Режими потврде идентитета + label_subproject_plural: Потпројекти + label_subproject_new: Ðови потпројекат + label_and_its_subprojects: "%{value} и његови потпројекти" + label_min_max_length: Мин. - МакÑ. дужина + label_list: СпиÑак + label_date: Датум + label_integer: Цео број + label_float: Са покретним зарезом + label_boolean: Логички оператор + label_string: ТекÑÑ‚ + label_text: Дуги текÑÑ‚ + label_attribute: ОÑобина + label_attribute_plural: ОÑобине + 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_settings: Подешавања + label_overview: Преглед + label_version: Верзија + label_version_new: Ðова верзија + label_version_plural: Верзије + label_close_versions: Затвори завршене верзије + 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_x_open_issues_abbr_on_total: + zero: 0 отворених / %{total} + one: 1 отворен / %{total} + other: "%{count} отворених / %{total}" + label_x_open_issues_abbr: + zero: 0 отворених + one: 1 отворен + other: "%{count} отворених" + label_x_closed_issues_abbr: + zero: 0 затворених + one: 1 затворен + other: "%{count} затворених" + 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: Гантов дијаграм + label_internal: Унутрашњи + label_last_changes: "поÑледњих %{count} промена" + label_change_view_all: Прикажи Ñве промене + label_personalize_page: ПерÑонализуј ову Ñтрану + label_comment: Коментар + label_comment_plural: Коментари + label_x_comments: + zero: без коментара + one: један коментар + other: "%{count} коментара" + label_comment_add: Додај коментар + label_comment_added: Коментар додат + label_comment_delete: Обриши коментаре + label_query: Прилагођен упит + label_query_plural: Прилагођени упити + label_query_new: Ðови упит + label_filter_add: Додавање филтера + label_filter_plural: Филтери + label_equals: је + label_not_equals: није + label_in_less_than: мање од + label_in_more_than: више од + label_greater_or_equal: '>=' + label_less_or_equal: '<=' + label_in: у + label_today: Ð´Ð°Ð½Ð°Ñ + label_all_time: Ñве време + label_yesterday: јуче + label_this_week: ове Ñедмице + label_last_week: поÑледње Ñедмице + label_last_n_days: "поÑледњих %{count} дана" + label_this_month: овог меÑеца + label_last_month: поÑледњег меÑеца + label_this_year: ове године + label_date_range: ВременÑки период + label_less_than_ago: пре мање од неколико дана + label_more_than_ago: пре више од неколико дана + label_ago: пре неколико дана + label_contains: Ñадржи + label_not_contains: не Ñадржи + label_day_plural: дана + label_repository: Спремиште + label_repository_plural: Спремишта + label_browse: Прегледање + label_branch: Грана + label_tag: Ознака + label_revision: Ревизија + label_revision_plural: Ревизије + label_revision_id: "Ревизија %{value}" + label_associated_revisions: Придружене ревизије + label_added: додато + label_modified: промењено + label_copied: копирано + label_renamed: преименовано + label_deleted: избриÑано + label_latest_revision: ПоÑледња ревизија + label_latest_revision_plural: ПоÑледње ревизије + label_view_revisions: Преглед ревизија + label_view_all_revisions: Преглед Ñвих ревизија + label_max_size: МакÑимална величина + label_sort_highest: Премештање на врх + label_sort_higher: Премештање на горе + label_sort_lower: Премештање на доле + label_sort_lowest: Премештање на дно + label_roadmap: План рада + label_roadmap_due_in: "ДоÑпева %{value}" + label_roadmap_overdue: "%{value} најкаÑније" + label_roadmap_no_issues: Ðема проблема за ову верзију + label_search: Претрага + label_result_plural: Резултати + label_all_words: Све речи + label_wiki: Wiki + label_wiki_edit: Wiki измена + label_wiki_edit_plural: Wiki измене + label_wiki_page: Wiki Ñтраница + label_wiki_page_plural: Wiki Ñтранице + label_index_by_title: ИндекÑирање по наÑлову + label_index_by_date: ИндекÑирање по датуму + label_current_version: Тренутна верзија + label_preview: Преглед + label_feed_plural: Извори веÑти + label_changes_details: Детаљи Ñвих промена + label_issue_tracking: Праћење проблема + label_spent_time: Утрошено време + label_overall_spent_time: Целокупно утрошено време + label_f_hour: "%{value} Ñат" + label_f_hour_plural: "%{value} Ñати" + label_time_tracking: Праћење времена + label_change_plural: Промене + label_statistics: СтатиÑтика + label_commits_per_month: Извршења меÑечно + label_commits_per_author: Извршења по аутору + label_view_diff: Погледај разлике + label_diff_inline: унутра + label_diff_side_by_side: упоредо + label_options: Опције + label_copy_workflow_from: Копирање тока поÑла од + label_permissions_report: Извештај о дозволама + label_watched_issues: ПоÑматрани проблеми + label_related_issues: Сродни проблеми + label_applied_status: Примењени ÑтатуÑи + label_loading: Учитавање... + label_relation_new: Ðова релација + label_relation_delete: БриÑање релације + label_relates_to: Ñродних Ñа + label_duplicates: дуплираних + label_duplicated_by: дуплираних од + label_blocks: одбијених + label_blocked_by: одбијених од + label_precedes: претходи + label_follows: праћених + label_end_to_start: од краја до почетка + label_end_to_end: од краја до краја + label_start_to_start: од почетка до почетка + label_start_to_end: од почетка до краја + label_stay_logged_in: ОÑтаните пријављени + label_disabled: онемогућено + label_show_completed_versions: Приказивање завршене верзије + label_me: мени + label_board: Форум + label_board_new: Ðови форум + label_board_plural: Форуми + label_board_locked: Закључана + label_board_sticky: Лепљива + label_topic_plural: Теме + label_message_plural: Поруке + label_message_last: ПоÑледња порука + label_message_new: Ðова порука + label_message_posted: Порука је додата + label_reply_plural: Одговори + label_send_information: Пошаљи кориÑнику детаље налога + label_year: Година + label_month: МеÑец + label_week: Седмица + label_date_from: Шаље + label_date_to: Прима + label_language_based: Базирано на језику кориÑника + label_sort_by: "Сортирано по %{value}" + label_send_test_email: Слање пробне е-поруке + label_feeds_access_key: RSS приÑтупни кључ + label_missing_feeds_access_key: RSS приÑтупни кључ недоÑтаје + label_feeds_access_key_created_on: "RSS приÑтупни кључ је направљен пре %{value}" + label_module_plural: Модули + label_added_time_by: "Додао %{author} пре %{age}" + label_updated_time_by: "Ðжурирао %{author} пре %{age}" + label_updated_time: "Ðжурирано пре %{value}" + label_jump_to_a_project: Скок на пројекат... + label_file_plural: Датотеке + label_changeset_plural: Скупови промена + label_default_columns: Подразумеване колоне + label_no_change_option: (Без промена) + label_bulk_edit_selected_issues: Групна измена одабраних проблема + label_theme: Тема + label_default: Подразумевано + label_search_titles_only: Претражуј Ñамо наÑлове + label_user_mail_option_all: "За било који догађај на Ñвим мојим пројектима" + label_user_mail_option_selected: "За било који догађај на Ñамо одабраним пројектима..." + label_user_mail_no_self_notified: "Ðе желим бити обавештаван за промене које Ñам правим" + label_registration_activation_by_email: активација налога путем е-поруке + label_registration_manual_activation: ручна активација налога + label_registration_automatic_activation: аутоматÑка активација налога + label_display_per_page: "Број Ñтавки по Ñтраници: %{value}" + label_age: СтароÑÑ‚ + label_change_properties: Промени ÑвојÑтва + label_general: Општи + label_more: Више + label_scm: SCM + label_plugins: Додатне компоненте + label_ldap_authentication: LDAP потврда идентитета + label_downloads_abbr: D/L + label_optional_description: Опционо Ð¾Ð¿Ð¸Ñ + label_add_another_file: Додај још једну датотеку + label_preferences: Подешавања + label_chronological_order: по хронолошком редоÑледу + label_reverse_chronological_order: по обрнутом хронолошком редоÑледу + label_planning: Планирање + label_incoming_emails: Долазне е-поруке + label_generate_key: ГенериÑање кључа + label_issue_watchers: ПоÑматрачи + label_example: Пример + label_display: Приказ + label_sort: Сортирање + label_ascending: РаÑтући низ + label_descending: Опадајући низ + label_date_from_to: Од %{start} до %{end} + label_wiki_content_added: Wiki Ñтраница је додата + label_wiki_content_updated: Wiki Ñтраница је ажурирана + label_group: Група + label_group_plural: Групе + label_group_new: Ðова група + label_time_entry_plural: Утрошено време + label_version_sharing_none: Ðије дељено + label_version_sharing_descendants: Са потпројектима + label_version_sharing_hierarchy: Са хијерархијом пројекта + label_version_sharing_tree: Са Ñтаблом пројекта + label_version_sharing_system: Са Ñвим пројектима + label_update_issue_done_ratios: Ðжурирај Ð¾Ð´Ð½Ð¾Ñ Ñ€ÐµÑˆÐµÐ½Ð¸Ñ… проблема + label_copy_source: Извор + label_copy_target: Одредиште + label_copy_same_as_target: ИÑто као одредиште + label_display_used_statuses_only: Приказуј ÑтатуÑе коришћене Ñамо од Ñтране овог праћења + label_api_access_key: API приÑтупни кључ + label_missing_api_access_key: ÐедоÑтаје API приÑтупни кључ + label_api_access_key_created_on: "API приÑтупни кључ је креиран пре %{value}" + label_profile: Профил + label_subtask_plural: Подзадатак + label_project_copy_notifications: Пошаљи е-поруку Ñа обавештењем приликом копирања пројекта + + button_login: Пријава + button_submit: Пошаљи + button_save: Сними + button_check_all: Укључи Ñве + button_uncheck_all: ИÑкључи Ñве + button_delete: Избриши + button_create: Креирај + button_create_and_continue: Креирај и наÑтави + 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_move_and_follow: Помери и прати + button_back: Ðазад + button_cancel: Поништи + button_activate: Ðктивирај + button_sort: Сортирај + button_log_time: Евидентирај време + button_rollback: Повратак на ову верзију + button_watch: Прати + button_unwatch: Ðе прати више + button_reply: Одговори + button_archive: Ðрхивирај + button_unarchive: Врати из архиве + button_reset: Поништи + button_rename: Преименуј + button_change_password: Промени лозинку + button_copy: Копирај + button_copy_and_follow: Копирај и прати + button_annotate: Прибележи + button_update: Ðжурирај + button_configure: ПодеÑи + button_quote: Под наводницима + button_duplicate: Дуплирај + button_show: Прикажи + + status_active: активни + status_registered: региÑтровани + status_locked: закључани + + version_status_open: отворен + version_status_locked: закључан + version_status_closed: затворен + + field_active: Ðктиван + + text_select_mail_notifications: Одабери акције за које ће обавештење бити поÑлато путем е-поште. + text_regexp_info: нпр. ^[A-Z0-9]+$ + text_min_max_length_info: 0 значи без ограничења + text_project_destroy_confirmation: ЈеÑте ли Ñигурни да желите да избришете овај пројекат и Ñве припадајуће податке? + text_subprojects_destroy_warning: "Потпројекти: %{value} ће такође бити избриÑан." + text_workflow_edit: Одаберите улогу и праћење за измену тока поÑла + text_are_you_sure: ЈеÑте ли Ñигурни? + text_journal_changed: "%{label} промењен од %{old} у %{new}" + text_journal_set_to: "%{label} поÑтављен у %{value}" + text_journal_deleted: "%{label} избриÑано (%{old})" + text_journal_added: "%{label} %{value} додато" + text_tip_issue_begin_day: задатак почиње овог дана + text_tip_issue_end_day: задатак Ñе завршава овог дана + text_tip_issue_begin_end_day: задатак почиње и завршава овог дана + text_caracters_maximum: "Ðајвише %{count} знак(ова)." + text_caracters_minimum: "Број знакова мора бити најмање %{count}." + text_length_between: "Број знакова мора бити између %{min} и %{max}." + text_tracker_no_workflow: Ово праћење нема дефиниÑан ток поÑла + text_unallowed_characters: Ðедозвољени знакови + text_comma_separated: Дозвољене Ñу вишеÑтруке вредноÑти (одвојене зарезом). + text_line_separated: Дозвољене Ñу вишеÑтруке вредноÑти (један ред за Ñваку вредноÑÑ‚). + text_issues_ref_in_commit_messages: Референцирање и поправљање проблема у извршним порукама + text_issue_added: "%{author} је пријавио проблем %{id}." + text_issue_updated: "%{author} је ажурирао проблем %{id}." + text_wiki_destroy_confirmation: ЈеÑте ли Ñигурни да желите да обришете wiki и Ñав Ñадржај? + text_issue_category_destroy_question: "Ðеколико проблема (%{count}) је додељено овој категорији. Шта желите да урадите?" + text_issue_category_destroy_assignments: Уклони додељене категорије + text_issue_category_reassign_to: Додели поново проблеме овој категорији + text_user_mail_option: "За неизабране пројекте, добићете Ñамо обавештење о Ñтварима које пратите или Ñте укључени (нпр. проблеми чији Ñте ви аутор или заÑтупник)." + text_no_configuration_data: "Улоге, праћења, ÑтатуÑи проблема и тока поÑла још увек ниÑу подешени.\nПрепоручљиво је да учитате подразумевано конфигуриÑање. Измена је могућа након првог учитавања." + text_load_default_configuration: Учитај подразумевано конфигуриÑање + text_status_changed_by_changeset: "Примењено у Ñкупу Ñа променама %{value}." + text_issues_destroy_confirmation: 'ЈеÑте ли Ñигурни да желите да избришете одабране проблеме?' + text_select_project_modules: 'Одаберите модуле које желите омогућити за овај пројекат:' + text_default_administrator_account_changed: Подразумевани админиÑтраторÑки налог је промењен + text_file_repository_writable: ФаÑцикла приложених датотека је упиÑива + text_plugin_assets_writable: ФаÑцикла елемената додатних компоненти је упиÑива + text_rmagick_available: RMagick је доÑтупан (опционо) + text_destroy_time_entries_question: "%{hours} Ñати је пријављено за овај проблем који желите избриÑати. Шта желите да урадите?" + text_destroy_time_entries: Избриши пријављене Ñате + text_assign_time_entries_to_project: Додели пријављене Ñате пројекту + text_reassign_time_entries: 'Додели поново пријављене Ñате овом проблему:' + text_user_wrote: "%{value} је напиÑао:" + text_enumeration_destroy_question: "%{count} објекат(а) је додељено овој вредноÑти." + text_enumeration_category_reassign_to: 'Додели их поново овој вредноÑти:' + text_email_delivery_not_configured: "ИÑпорука е-порука није конфигуриÑана и обавештења Ñу онемогућена.\nПодеÑите ваш SMTP Ñервер у config/configuration.yml и покрените поново апликацију за њихово омогућавање." + text_repository_usernames_mapping: "Одаберите или ажурирајте Redmine кориÑнике мапирањем Ñваког кориÑничког имена пронађеног у евиденцији Ñпремишта.\nКориÑници Ñа иÑтим Redmine именом и именом Ñпремишта или е-адреÑом Ñу аутоматÑки мапирани." + text_diff_truncated: '... Ова разлика је иÑечена јер је доÑтигнута макÑимална величина приказа.' + text_custom_field_possible_values_info: 'Један ред за Ñваку вредноÑÑ‚' + text_wiki_page_destroy_question: "Ова Ñтраница има %{descendants} подређених Ñтраница и подÑтраница. Шта желите да урадите?" + text_wiki_page_nullify_children: "Задржи подређене Ñтранице као корене Ñтранице" + text_wiki_page_destroy_children: "Избриши подређене Ñтранице и Ñве њихове подÑтранице" + text_wiki_page_reassign_children: "Додели поново подређене Ñтранице овој матичној Ñтраници" + text_own_membership_delete_confirmation: "Ðакон уклањања појединих или Ñвих ваших дозвола нећете више моћи да уређујете овај пројекат.\nЖелите ли да наÑтавите?" + text_zoom_in: Увећај + text_zoom_out: Умањи + + default_role_manager: Менаџер + default_role_developer: Програмер + default_role_reporter: Извештач + default_tracker_bug: Грешка + default_tracker_feature: ФункционалноÑÑ‚ + default_tracker_support: Подршка + default_issue_status_new: Ðово + default_issue_status_in_progress: У току + default_issue_status_resolved: Решено + default_issue_status_feedback: Повратна информација + default_issue_status_closed: Затворено + default_issue_status_rejected: Одбијено + default_doc_category_user: КориÑничка документација + default_doc_category_tech: Техничка документација + default_priority_low: Ðизак + default_priority_normal: Ðормалан + default_priority_high: ВиÑок + default_priority_urgent: Хитно + default_priority_immediate: ÐепоÑредно + default_activity_design: Дизајн + default_activity_development: Развој + + enumeration_issue_priorities: Приоритети проблема + enumeration_doc_categories: Категорије документа + enumeration_activities: ÐктивноÑти (праћење времена) + enumeration_system_activity: СиÑтемÑка активноÑÑ‚ + + field_time_entries: Време евиденције + project_module_gantt: Гантов дијаграм + project_module_calendar: Календар + + button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" + field_text: Text field + label_user_mail_option_only_owner: Only for things I am the owner of + setting_default_notification_option: Default notification option + label_user_mail_option_only_my_events: Only for things I watch or I'm involved in + label_user_mail_option_only_assigned: Only for things I am assigned to + label_user_mail_option_none: No events + field_member_of_group: Assignee's group + field_assigned_to_role: Assignee's role + notice_not_authorized_archived_project: The project you're trying to access has been archived. + label_principal_search: "Search for user or group:" + label_user_search: "Search for user:" + field_visible: Visible + setting_commit_logtime_activity_id: Activity for logged time + text_time_logged_by_changeset: Applied in changeset %{value}. + setting_commit_logtime_enabled: Enable time logging + notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) + setting_gantt_items_limit: Maximum number of items displayed on the gantt chart + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. + label_my_queries: My custom queries + text_journal_changed_no_detail: "%{label} updated" + label_news_comment_added: Comment added to a news + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + label_bulk_edit_selected_time_entries: Bulk edit selected time entries + text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? + label_role_anonymous: Anonymous + label_role_non_member: Non member + label_issue_note_added: Note added + label_issue_status_updated: Status updated + label_issue_priority_updated: Priority updated + label_issues_visibility_own: Issues created by or assigned to the user + field_issues_visibility: Issues visibility + label_issues_visibility_all: All issues + permission_set_own_issues_private: Set own issues public or private + field_is_private: Private + permission_set_issues_private: Set issues public or private + label_issues_visibility_public: All non private issues + text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). + field_commit_logs_encoding: Кодирање извршних порука + field_scm_path_encoding: Path encoding + text_scm_path_encoding_note: "Default: UTF-8" + field_path_to_repository: Path to repository + field_root_directory: Root directory + field_cvs_module: Module + field_cvsroot: CVSROOT + text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) + text_scm_command: Command + text_scm_command_version: Version + label_git_report_last_commit: Report last commit for files and directories + notice_issue_successful_create: Issue %{id} created. + label_between: between + setting_issue_group_assignment: Allow issue assignment to groups + label_diff: diff + text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) + description_query_sort_criteria_direction: Sort direction + description_project_scope: Search scope + description_filter: Filter + description_user_mail_notification: Mail notification settings + description_date_from: Enter start date + description_message_content: Message content + description_available_columns: Available Columns + description_date_range_interval: Choose range by selecting start and end date + description_issue_category_reassign: Choose issue category + description_search: Searchfield + description_notes: Notes + description_date_range_list: Choose range from list + description_choose_project: Projects + description_date_to: Enter end date + description_query_sort_criteria_attribute: Sort attribute + description_wiki_subpages_reassign: Choose new parent page + description_selected_columns: Selected Columns + label_parent_revision: Parent + label_child_revision: Child + error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. + setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues + button_edit_section: Edit this section + setting_repositories_encodings: Attachments and repositories encodings + description_all_columns: All Columns + button_export: Export + label_export_options: "%{export_format} export options" + error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) + notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." + label_x_issues: + zero: 0 Проблем + one: 1 Проблем + other: "%{count} Проблеми" + label_repository_new: New repository + field_repository_is_default: Main repository + label_copy_attachments: Copy attachments + label_item_position: "%{position}/%{count}" + label_completed_versions: Completed versions + text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. + field_multiple: Multiple values + setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed + text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes + text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) + notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. + text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} + permission_manage_related_issues: Manage related issues + field_auth_source_ldap_filter: LDAP filter + label_search_for_watchers: Search for watchers to add + notice_account_deleted: Your account has been permanently deleted. + setting_unsubscribe: Allow users to delete their own account + button_delete_my_account: Delete my account + text_account_destroy_confirmation: |- + Are you sure you want to proceed? + Your account will be permanently deleted, with no way to reactivate it. + error_session_expired: Your session has expired. Please login again. + text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." + setting_session_lifetime: Session maximum lifetime + setting_session_timeout: Session inactivity timeout + label_session_expiration: Session expiration + permission_close_project: Close / reopen the project + label_show_closed_projects: View closed projects + button_close: Close + button_reopen: Reopen + project_status_active: active + project_status_closed: closed + project_status_archived: archived + text_project_closed: This project is closed and read-only. + notice_user_successful_create: User %{id} created. + field_core_fields: Standard fields + field_timeout: Timeout (in seconds) + setting_thumbnails_enabled: Display attachment thumbnails + setting_thumbnails_size: Thumbnails size (in pixels) + label_status_transitions: Status transitions + label_fields_permissions: Fields permissions + label_readonly: Read-only + label_required: Required + text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. + field_board_parent: Parent forum + label_attribute_of_project: Project's %{name} + label_attribute_of_author: Author's %{name} + label_attribute_of_assigned_to: Assignee's %{name} + label_attribute_of_fixed_version: Target version's %{name} + label_copy_subtasks: Copy subtasks + label_copied_to: copied to + label_copied_from: copied from + label_any_issues_in_project: any issues in project + label_any_issues_not_in_project: any issues not in project + field_private_notes: Private notes + permission_view_private_notes: View private notes + permission_set_notes_private: Set notes as private + label_no_issues_in_project: no issues in project + label_any: Ñви + label_last_n_weeks: last %{count} weeks + setting_cross_project_subtasks: Allow cross-project subtasks + label_cross_project_descendants: Са потпројектима + label_cross_project_tree: Са Ñтаблом пројекта + label_cross_project_hierarchy: Са хијерархијом пројекта + label_cross_project_system: Са Ñвим пројектима + button_hide: Hide + setting_non_working_week_days: Non-working days + label_in_the_next_days: in the next + label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Укупно + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/67/67aa4777ed1332a8a87bb44f4455573440b70d60.svn-base --- a/.svn/pristine/67/67aa4777ed1332a8a87bb44f4455573440b70d60.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,24 +0,0 @@ -/* ssh views */ - -CREATE OR REPLACE VIEW ssh_users as -select login as username, hashed_password as password -from users -where status = 1; - - -/* nss views */ - -CREATE OR REPLACE VIEW nss_groups AS -select identifier AS name, (id + 5000) AS gid, 'x' AS password -from projects; - -CREATE OR REPLACE VIEW nss_users AS -select login AS username, CONCAT_WS(' ', firstname, lastname) as realname, (id + 5000) AS uid, 'x' AS password -from users -where status = 1; - -CREATE OR REPLACE VIEW nss_grouplist AS -select (members.project_id + 5000) AS gid, users.login AS username -from users, members -where users.id = members.user_id -and users.status = 1; diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/67/67ef2cb1f5b1a30645948e8400fbb5f9ee122b3c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/67/67ef2cb1f5b1a30645948e8400fbb5f9ee122b3c.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,425 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class RepositoriesSubversionControllerTest < ActionController::TestCase + tests RepositoriesController + + fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules, + :repositories, :issues, :issue_statuses, :changesets, :changes, + :issue_categories, :enumerations, :custom_fields, :custom_values, :trackers + + PRJ_ID = 3 + NUM_REV = 11 + + def setup + Setting.default_language = 'en' + User.current = nil + + @project = Project.find(PRJ_ID) + @repository = Repository::Subversion.create(:project => @project, + :url => self.class.subversion_repository_url) + assert @repository + end + + if repository_configured?('subversion') + def test_new + @request.session[:user_id] = 1 + @project.repository.destroy + get :new, :project_id => 'subproject1', :repository_scm => 'Subversion' + assert_response :success + assert_template 'new' + assert_kind_of Repository::Subversion, assigns(:repository) + assert assigns(:repository).new_record? + end + + def test_show + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + get :show, :id => PRJ_ID + assert_response :success + assert_template 'show' + assert_not_nil assigns(:entries) + assert_not_nil assigns(:changesets) + + entry = assigns(:entries).detect {|e| e.name == 'subversion_test'} + assert_not_nil entry + assert_equal 'dir', entry.kind + assert_select 'tr.dir a[href=/projects/subproject1/repository/show/subversion_test]' + + assert_tag 'input', :attributes => {:name => 'rev'} + assert_tag 'a', :content => 'Statistics' + assert_tag 'a', :content => 'Atom' + assert_tag :tag => 'a', + :attributes => {:href => '/projects/subproject1/repository'}, + :content => 'root' + end + + def test_show_non_default + Repository::Subversion.create(:project => @project, + :url => self.class.subversion_repository_url, + :is_default => false, :identifier => 'svn') + + get :show, :id => PRJ_ID, :repository_id => 'svn' + assert_response :success + assert_template 'show' + assert_select 'tr.dir a[href=/projects/subproject1/repository/svn/show/subversion_test]' + # Repository menu should link to the main repo + assert_select '#main-menu a[href=/projects/subproject1/repository]' + end + + def test_browse_directory + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + get :show, :id => PRJ_ID, :path => repository_path_hash(['subversion_test'])[:param] + assert_response :success + assert_template 'show' + assert_not_nil assigns(:entries) + assert_equal [ + '[folder_with_brackets]', 'folder', '.project', + 'helloworld.c', 'textfile.txt' + ], + assigns(:entries).collect(&:name) + entry = assigns(:entries).detect {|e| e.name == 'helloworld.c'} + assert_equal 'file', entry.kind + assert_equal 'subversion_test/helloworld.c', entry.path + assert_tag :a, :content => 'helloworld.c', :attributes => { :class => /text\-x\-c/ } + end + + def test_browse_at_given_revision + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + get :show, :id => PRJ_ID, :path => repository_path_hash(['subversion_test'])[:param], + :rev => 4 + assert_response :success + assert_template 'show' + assert_not_nil assigns(:entries) + assert_equal ['folder', '.project', 'helloworld.c', 'helloworld.rb', 'textfile.txt'], + assigns(:entries).collect(&:name) + end + + def test_file_changes + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + get :changes, :id => PRJ_ID, + :path => repository_path_hash(['subversion_test', 'folder', 'helloworld.rb'])[:param] + assert_response :success + assert_template 'changes' + + changesets = assigns(:changesets) + assert_not_nil changesets + assert_equal %w(6 3 2), changesets.collect(&:revision) + + # 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_directory_changes + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + get :changes, :id => PRJ_ID, + :path => repository_path_hash(['subversion_test', 'folder'])[:param] + assert_response :success + assert_template 'changes' + + changesets = assigns(:changesets) + assert_not_nil changesets + assert_equal %w(10 9 7 6 5 2), changesets.collect(&:revision) + end + + def test_entry + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + get :entry, :id => PRJ_ID, + :path => repository_path_hash(['subversion_test', 'helloworld.c'])[:param] + assert_response :success + assert_template 'entry' + end + + def test_entry_should_send_if_too_big + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + # no files in the test repo is larger than 1KB... + with_settings :file_max_size_displayed => 0 do + get :entry, :id => PRJ_ID, + :path => repository_path_hash(['subversion_test', 'helloworld.c'])[:param] + assert_response :success + assert_equal 'attachment; filename="helloworld.c"', + @response.headers['Content-Disposition'] + end + end + + def test_entry_should_send_images_inline + get :entry, :id => PRJ_ID, + :path => repository_path_hash(['subversion_test', 'folder', 'subfolder', 'rubylogo.gif'])[:param] + assert_response :success + assert_equal 'inline; filename="rubylogo.gif"', response.headers['Content-Disposition'] + end + + def test_entry_at_given_revision + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + get :entry, :id => PRJ_ID, + :path => repository_path_hash(['subversion_test', 'helloworld.rb'])[:param], + :rev => 2 + assert_response :success + assert_template 'entry' + # this line was removed in r3 and file was moved in r6 + assert_tag :tag => 'td', :attributes => { :class => /line-code/}, + :content => /Here's the code/ + end + + def test_entry_not_found + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + get :entry, :id => PRJ_ID, + :path => repository_path_hash(['subversion_test', 'zzz.c'])[:param] + assert_tag :tag => 'p', :attributes => { :id => /errorExplanation/ }, + :content => /The entry or revision was not found in the repository/ + end + + def test_entry_download + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + get :raw, :id => PRJ_ID, + :path => repository_path_hash(['subversion_test', 'helloworld.c'])[:param] + assert_response :success + assert_equal 'attachment; filename="helloworld.c"', @response.headers['Content-Disposition'] + end + + def test_directory_entry + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + get :entry, :id => PRJ_ID, + :path => repository_path_hash(['subversion_test', 'folder'])[:param] + assert_response :success + assert_template 'show' + assert_not_nil assigns(:entry) + assert_equal 'folder', assigns(:entry).name + end + + # TODO: this test needs fixtures. + def test_revision + get :revision, :id => 1, :rev => 2 + assert_response :success + assert_template 'revision' + + assert_select 'ul' do + assert_select 'li' do + # link to the entry at rev 2 + assert_select 'a[href=?]', '/projects/ecookbook/repository/revisions/2/entry/test/some/path/in/the/repo', :text => 'repo' + # link to partial diff + assert_select 'a[href=?]', '/projects/ecookbook/repository/revisions/2/diff/test/some/path/in/the/repo' + end + end + end + + def test_invalid_revision + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + get :revision, :id => PRJ_ID, :rev => 'something_weird' + assert_response 404 + assert_error_tag :content => /was not found/ + end + + def test_invalid_revision_diff + get :diff, :id => PRJ_ID, :rev => '1', :rev_to => 'something_weird' + assert_response 404 + assert_error_tag :content => /was not found/ + end + + def test_empty_revision + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + ['', ' ', nil].each do |r| + get :revision, :id => PRJ_ID, :rev => r + assert_response 404 + assert_error_tag :content => /was not found/ + end + end + + # TODO: this test needs fixtures. + def test_revision_with_repository_pointing_to_a_subdirectory + r = Project.find(1).repository + # Changes repository url to a subdirectory + r.update_attribute :url, (r.url + '/test/some') + + get :revision, :id => 1, :rev => 2 + assert_response :success + assert_template 'revision' + + assert_select 'ul' do + assert_select 'li' do + # link to the entry at rev 2 + assert_select 'a[href=?]', '/projects/ecookbook/repository/revisions/2/entry/path/in/the/repo', :text => 'repo' + # link to partial diff + assert_select 'a[href=?]', '/projects/ecookbook/repository/revisions/2/diff/path/in/the/repo' + end + end + end + + def test_revision_diff + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + ['inline', 'sbs'].each do |dt| + get :diff, :id => PRJ_ID, :rev => 3, :type => dt + assert_response :success + assert_template 'diff' + assert_select 'h2', :text => /Revision 3/ + assert_select 'th.filename', :text => 'subversion_test/textfile.txt' + end + end + + def test_revision_diff_raw_format + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + + get :diff, :id => PRJ_ID, :rev => 3, :format => 'diff' + assert_response :success + assert_equal 'text/x-patch', @response.content_type + assert_equal 'Index: subversion_test/textfile.txt', @response.body.split(/\r?\n/).first + end + + def test_directory_diff + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + ['inline', 'sbs'].each do |dt| + get :diff, :id => PRJ_ID, :rev => 6, :rev_to => 2, + :path => repository_path_hash(['subversion_test', 'folder'])[:param], + :type => dt + assert_response :success + assert_template 'diff' + + diff = assigns(:diff) + assert_not_nil diff + # 2 files modified + assert_equal 2, Redmine::UnifiedDiff.new(diff).size + assert_tag :tag => 'h2', :content => /2:6/ + end + end + + def test_annotate + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + get :annotate, :id => PRJ_ID, + :path => repository_path_hash(['subversion_test', 'helloworld.c'])[:param] + assert_response :success + assert_template 'annotate' + + assert_select 'tr' do + assert_select 'th.line-num', :text => '1' + assert_select 'td.revision', :text => '4' + assert_select 'td.author', :text => 'jp' + assert_select 'td', :text => /stdio.h/ + end + # Same revision + assert_select 'tr' do + assert_select 'th.line-num', :text => '2' + assert_select 'td.revision', :text => '' + assert_select 'td.author', :text => '' + end + end + + def test_annotate_at_given_revision + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + get :annotate, :id => PRJ_ID, :rev => 8, + :path => repository_path_hash(['subversion_test', 'helloworld.c'])[:param] + assert_response :success + assert_template 'annotate' + assert_tag :tag => 'h2', :content => /@ 8/ + end + + def test_destroy_valid_repository + @request.session[:user_id] = 1 # admin + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + assert_equal NUM_REV, @repository.changesets.count + + assert_difference 'Repository.count', -1 do + delete :destroy, :id => @repository.id + end + assert_response 302 + @project.reload + assert_nil @project.repository + end + + def test_destroy_invalid_repository + @request.session[:user_id] = 1 # admin + @project.repository.destroy + @repository = Repository::Subversion.create!( + :project => @project, + :url => "file:///invalid") + @repository.fetch_changesets + assert_equal 0, @repository.changesets.count + + assert_difference 'Repository.count', -1 do + delete :destroy, :id => @repository.id + end + assert_response 302 + @project.reload + assert_nil @project.repository + end + else + puts "Subversion test repository NOT FOUND. Skipping functional tests !!!" + def test_fake; assert true end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/67/67f3eefb3b2c497cfa5d20b68f15bca1281bb897.svn-base --- a/.svn/pristine/67/67f3eefb3b2c497cfa5d20b68f15bca1281bb897.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,6 +0,0 @@ -<%= error_messages_for 'category' %> - -
    -

    <%= f.text_field :name, :size => 30, :required => true %>

    -

    <%= f.select :assigned_to_id, principals_options_for_select(@project.assignable_users, @category.assigned_to), :include_blank => true %>

    -
    diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/68/68223188fb3e37e391044629dba24730132e043f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/68/68223188fb3e37e391044629dba24730132e043f.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,512 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +desc 'Mantis migration script' + +require 'active_record' +require 'iconv' if RUBY_VERSION < '1.9' +require 'pp' + +namespace :redmine do +task :migrate_from_mantis => :environment do + + module MantisMigrate + + DEFAULT_STATUS = IssueStatus.default + assigned_status = IssueStatus.find_by_position(2) + resolved_status = IssueStatus.find_by_position(3) + feedback_status = IssueStatus.find_by_position(4) + closed_status = IssueStatus.where(:is_closed => true).first + STATUS_MAPPING = {10 => DEFAULT_STATUS, # new + 20 => feedback_status, # feedback + 30 => DEFAULT_STATUS, # acknowledged + 40 => DEFAULT_STATUS, # confirmed + 50 => assigned_status, # assigned + 80 => resolved_status, # resolved + 90 => closed_status # closed + } + + priorities = IssuePriority.all + DEFAULT_PRIORITY = priorities[2] + PRIORITY_MAPPING = {10 => priorities[1], # none + 20 => priorities[1], # low + 30 => priorities[2], # normal + 40 => priorities[3], # high + 50 => priorities[4], # urgent + 60 => priorities[5] # immediate + } + + TRACKER_BUG = Tracker.find_by_position(1) + TRACKER_FEATURE = Tracker.find_by_position(2) + + roles = Role.where(:builtin => 0).order('position ASC').all + manager_role = roles[0] + developer_role = roles[1] + DEFAULT_ROLE = roles.last + ROLE_MAPPING = {10 => DEFAULT_ROLE, # viewer + 25 => DEFAULT_ROLE, # reporter + 40 => DEFAULT_ROLE, # updater + 55 => developer_role, # developer + 70 => manager_role, # manager + 90 => manager_role # administrator + } + + CUSTOM_FIELD_TYPE_MAPPING = {0 => 'string', # String + 1 => 'int', # Numeric + 2 => 'int', # Float + 3 => 'list', # Enumeration + 4 => 'string', # Email + 5 => 'bool', # Checkbox + 6 => 'list', # List + 7 => 'list', # Multiselection list + 8 => 'date', # Date + } + + RELATION_TYPE_MAPPING = {1 => IssueRelation::TYPE_RELATES, # related to + 2 => IssueRelation::TYPE_RELATES, # parent of + 3 => IssueRelation::TYPE_RELATES, # child of + 0 => IssueRelation::TYPE_DUPLICATES, # duplicate of + 4 => IssueRelation::TYPE_DUPLICATES # has duplicate + } + + class MantisUser < ActiveRecord::Base + self.table_name = :mantis_user_table + + def firstname + @firstname = realname.blank? ? username : realname.split.first[0..29] + @firstname + end + + def lastname + @lastname = realname.blank? ? '-' : realname.split[1..-1].join(' ')[0..29] + @lastname = '-' if @lastname.blank? + @lastname + end + + def email + if read_attribute(:email).match(/^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i) && + !User.find_by_mail(read_attribute(:email)) + @email = read_attribute(:email) + else + @email = "#{username}@foo.bar" + end + end + + def username + read_attribute(:username)[0..29].gsub(/[^a-zA-Z0-9_\-@\.]/, '-') + end + end + + class MantisProject < ActiveRecord::Base + self.table_name = :mantis_project_table + has_many :versions, :class_name => "MantisVersion", :foreign_key => :project_id + has_many :categories, :class_name => "MantisCategory", :foreign_key => :project_id + has_many :news, :class_name => "MantisNews", :foreign_key => :project_id + has_many :members, :class_name => "MantisProjectUser", :foreign_key => :project_id + + def identifier + read_attribute(:name).gsub(/[^a-z0-9\-]+/, '-').slice(0, Project::IDENTIFIER_MAX_LENGTH) + end + end + + class MantisVersion < ActiveRecord::Base + self.table_name = :mantis_project_version_table + + def version + read_attribute(:version)[0..29] + end + + def description + read_attribute(:description)[0..254] + end + end + + class MantisCategory < ActiveRecord::Base + self.table_name = :mantis_project_category_table + end + + class MantisProjectUser < ActiveRecord::Base + self.table_name = :mantis_project_user_list_table + end + + class MantisBug < ActiveRecord::Base + self.table_name = :mantis_bug_table + belongs_to :bug_text, :class_name => "MantisBugText", :foreign_key => :bug_text_id + has_many :bug_notes, :class_name => "MantisBugNote", :foreign_key => :bug_id + has_many :bug_files, :class_name => "MantisBugFile", :foreign_key => :bug_id + has_many :bug_monitors, :class_name => "MantisBugMonitor", :foreign_key => :bug_id + end + + class MantisBugText < ActiveRecord::Base + self.table_name = :mantis_bug_text_table + + # Adds Mantis steps_to_reproduce and additional_information fields + # to description if any + def full_description + full_description = description + full_description += "\n\n*Steps to reproduce:*\n\n#{steps_to_reproduce}" unless steps_to_reproduce.blank? + full_description += "\n\n*Additional information:*\n\n#{additional_information}" unless additional_information.blank? + full_description + end + end + + class MantisBugNote < ActiveRecord::Base + self.table_name = :mantis_bugnote_table + belongs_to :bug, :class_name => "MantisBug", :foreign_key => :bug_id + belongs_to :bug_note_text, :class_name => "MantisBugNoteText", :foreign_key => :bugnote_text_id + end + + class MantisBugNoteText < ActiveRecord::Base + self.table_name = :mantis_bugnote_text_table + end + + class MantisBugFile < ActiveRecord::Base + self.table_name = :mantis_bug_file_table + + def size + filesize + end + + def original_filename + MantisMigrate.encode(filename) + end + + def content_type + file_type + end + + def read(*args) + if @read_finished + nil + else + @read_finished = true + content + end + end + end + + class MantisBugRelationship < ActiveRecord::Base + self.table_name = :mantis_bug_relationship_table + end + + class MantisBugMonitor < ActiveRecord::Base + self.table_name = :mantis_bug_monitor_table + end + + class MantisNews < ActiveRecord::Base + self.table_name = :mantis_news_table + end + + class MantisCustomField < ActiveRecord::Base + self.table_name = :mantis_custom_field_table + set_inheritance_column :none + has_many :values, :class_name => "MantisCustomFieldString", :foreign_key => :field_id + has_many :projects, :class_name => "MantisCustomFieldProject", :foreign_key => :field_id + + def format + read_attribute :type + end + + def name + read_attribute(:name)[0..29] + end + end + + class MantisCustomFieldProject < ActiveRecord::Base + self.table_name = :mantis_custom_field_project_table + end + + class MantisCustomFieldString < ActiveRecord::Base + self.table_name = :mantis_custom_field_string_table + end + + def self.migrate + + # Users + print "Migrating users" + User.delete_all "login <> 'admin'" + users_map = {} + users_migrated = 0 + MantisUser.all.each do |user| + u = User.new :firstname => encode(user.firstname), + :lastname => encode(user.lastname), + :mail => user.email, + :last_login_on => user.last_visit + u.login = user.username + u.password = 'mantis' + u.status = User::STATUS_LOCKED if user.enabled != 1 + u.admin = true if user.access_level == 90 + next unless u.save! + users_migrated += 1 + users_map[user.id] = u.id + print '.' + end + puts + + # Projects + print "Migrating projects" + Project.destroy_all + projects_map = {} + versions_map = {} + categories_map = {} + MantisProject.all.each do |project| + p = Project.new :name => encode(project.name), + :description => encode(project.description) + p.identifier = project.identifier + next unless p.save + projects_map[project.id] = p.id + p.enabled_module_names = ['issue_tracking', 'news', 'wiki'] + p.trackers << TRACKER_BUG unless p.trackers.include?(TRACKER_BUG) + p.trackers << TRACKER_FEATURE unless p.trackers.include?(TRACKER_FEATURE) + print '.' + + # Project members + project.members.each do |member| + m = Member.new :user => User.find_by_id(users_map[member.user_id]), + :roles => [ROLE_MAPPING[member.access_level] || DEFAULT_ROLE] + m.project = p + m.save + end + + # Project versions + project.versions.each do |version| + v = Version.new :name => encode(version.version), + :description => encode(version.description), + :effective_date => (version.date_order ? version.date_order.to_date : nil) + v.project = p + v.save + versions_map[version.id] = v.id + end + + # Project categories + project.categories.each do |category| + g = IssueCategory.new :name => category.category[0,30] + g.project = p + g.save + categories_map[category.category] = g.id + end + end + puts + + # Bugs + print "Migrating bugs" + Issue.destroy_all + issues_map = {} + keep_bug_ids = (Issue.count == 0) + MantisBug.find_each(:batch_size => 200) do |bug| + next unless projects_map[bug.project_id] && users_map[bug.reporter_id] + i = Issue.new :project_id => projects_map[bug.project_id], + :subject => encode(bug.summary), + :description => encode(bug.bug_text.full_description), + :priority => PRIORITY_MAPPING[bug.priority] || DEFAULT_PRIORITY, + :created_on => bug.date_submitted, + :updated_on => bug.last_updated + i.author = User.find_by_id(users_map[bug.reporter_id]) + i.category = IssueCategory.find_by_project_id_and_name(i.project_id, bug.category[0,30]) unless bug.category.blank? + i.fixed_version = Version.find_by_project_id_and_name(i.project_id, bug.fixed_in_version) unless bug.fixed_in_version.blank? + i.status = STATUS_MAPPING[bug.status] || DEFAULT_STATUS + i.tracker = (bug.severity == 10 ? TRACKER_FEATURE : TRACKER_BUG) + i.id = bug.id if keep_bug_ids + next unless i.save + issues_map[bug.id] = i.id + print '.' + STDOUT.flush + + # Assignee + # Redmine checks that the assignee is a project member + if (bug.handler_id && users_map[bug.handler_id]) + i.assigned_to = User.find_by_id(users_map[bug.handler_id]) + i.save(:validate => false) + end + + # Bug notes + bug.bug_notes.each do |note| + next unless users_map[note.reporter_id] + n = Journal.new :notes => encode(note.bug_note_text.note), + :created_on => note.date_submitted + n.user = User.find_by_id(users_map[note.reporter_id]) + n.journalized = i + n.save + end + + # Bug files + bug.bug_files.each do |file| + a = Attachment.new :created_on => file.date_added + a.file = file + a.author = User.first + a.container = i + a.save + end + + # Bug monitors + bug.bug_monitors.each do |monitor| + next unless users_map[monitor.user_id] + i.add_watcher(User.find_by_id(users_map[monitor.user_id])) + end + 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 + + # Bug relationships + print "Migrating bug relations" + MantisBugRelationship.all.each do |relation| + next unless issues_map[relation.source_bug_id] && issues_map[relation.destination_bug_id] + r = IssueRelation.new :relation_type => RELATION_TYPE_MAPPING[relation.relationship_type] + r.issue_from = Issue.find_by_id(issues_map[relation.source_bug_id]) + r.issue_to = Issue.find_by_id(issues_map[relation.destination_bug_id]) + pp r unless r.save + print '.' + STDOUT.flush + end + puts + + # News + print "Migrating news" + News.destroy_all + MantisNews.where('project_id > 0').all.each do |news| + next unless projects_map[news.project_id] + n = News.new :project_id => projects_map[news.project_id], + :title => encode(news.headline[0..59]), + :description => encode(news.body), + :created_on => news.date_posted + n.author = User.find_by_id(users_map[news.poster_id]) + n.save + print '.' + STDOUT.flush + end + puts + + # Custom fields + print "Migrating custom fields" + IssueCustomField.destroy_all + MantisCustomField.all.each do |field| + f = IssueCustomField.new :name => field.name[0..29], + :field_format => CUSTOM_FIELD_TYPE_MAPPING[field.format], + :min_length => field.length_min, + :max_length => field.length_max, + :regexp => field.valid_regexp, + :possible_values => field.possible_values.split('|'), + :is_required => field.require_report? + next unless f.save + print '.' + STDOUT.flush + # Trackers association + f.trackers = Tracker.all + + # Projects association + field.projects.each do |project| + f.projects << Project.find_by_id(projects_map[project.project_id]) if projects_map[project.project_id] + end + + # Values + field.values.each do |value| + v = CustomValue.new :custom_field_id => f.id, + :value => value.value + v.customized = Issue.find_by_id(issues_map[value.bug_id]) if issues_map[value.bug_id] + v.save + end unless f.new_record? + end + puts + + puts + puts "Users: #{users_migrated}/#{MantisUser.count}" + puts "Projects: #{Project.count}/#{MantisProject.count}" + puts "Memberships: #{Member.count}/#{MantisProjectUser.count}" + puts "Versions: #{Version.count}/#{MantisVersion.count}" + puts "Categories: #{IssueCategory.count}/#{MantisCategory.count}" + puts "Bugs: #{Issue.count}/#{MantisBug.count}" + puts "Bug notes: #{Journal.count}/#{MantisBugNote.count}" + puts "Bug files: #{Attachment.count}/#{MantisBugFile.count}" + puts "Bug relations: #{IssueRelation.count}/#{MantisBugRelationship.count}" + puts "Bug monitors: #{Watcher.count}/#{MantisBugMonitor.count}" + puts "News: #{News.count}/#{MantisNews.count}" + puts "Custom fields: #{IssueCustomField.count}/#{MantisCustomField.count}" + end + + def self.encoding(charset) + @charset = charset + end + + def self.establish_connection(params) + constants.each do |const| + klass = const_get(const) + next unless klass.respond_to? 'establish_connection' + klass.establish_connection params + end + end + + def self.encode(text) + if RUBY_VERSION < '1.9' + @ic ||= Iconv.new('UTF-8', @charset) + @ic.iconv text + else + text.to_s.force_encoding(@charset).encode('UTF-8') + end + end + end + + puts + if Redmine::DefaultData::Loader.no_data? + puts "Redmine configuration need to be loaded before importing data." + puts "Please, run this first:" + puts + puts " rake redmine:load_default_data RAILS_ENV=\"#{ENV['RAILS_ENV']}\"" + exit + end + + puts "WARNING: Your Redmine data will be deleted during this process." + print "Are you sure you want to continue ? [y/N] " + STDOUT.flush + break unless STDIN.gets.match(/^y$/i) + + # Default Mantis database settings + db_params = {:adapter => 'mysql2', + :database => 'bugtracker', + :host => 'localhost', + :username => 'root', + :password => '' } + + puts + puts "Please enter settings for your Mantis database" + [:adapter, :host, :database, :username, :password].each do |param| + print "#{param} [#{db_params[param]}]: " + value = STDIN.gets.chomp! + db_params[param] = value unless value.blank? + end + + while true + print "encoding [UTF-8]: " + STDOUT.flush + encoding = STDIN.gets.chomp! + encoding = 'UTF-8' if encoding.blank? + break if MantisMigrate.encoding encoding + puts "Invalid encoding!" + end + puts + + # Make sure bugs can refer bugs in other projects + Setting.cross_project_issue_relations = 1 if Setting.respond_to? 'cross_project_issue_relations' + + # Turn off email notifications + Setting.notified_events = [] + + MantisMigrate.establish_connection db_params + MantisMigrate.migrate +end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/68/6868bae4892efaa793d57d13538c445e84b6efeb.svn-base --- a/.svn/pristine/68/6868bae4892efaa793d57d13538c445e84b6efeb.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +0,0 @@ -<%= error_messages_for 'tracker' %> - -
    -
    - -

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

    -

    <%= f.check_box :is_in_roadmap %>

    - -<% if IssueCustomField.all.any? %> -

    - - <% IssueCustomField.all.each do |field| %> - - <% end %> -

    -<%= hidden_field_tag 'tracker[custom_field_ids][]', '' %> -<% end %> - -<% if @tracker.new_record? && @trackers.any? %> -

    -<%= select_tag(:copy_workflow_from, content_tag("option") + options_from_collection_for_select(@trackers, :id, :name)) %>

    -<% end %> - -
    -<%= submit_tag l(@tracker.new_record? ? :button_create : :button_save) %> -
    - -
    -<% if @projects.any? %> -
    <%= l(:label_project_plural) %> -<%= 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) %> -

    <%= check_all_links 'tracker_project_ids' %>

    -
    -<% end %> -
    diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/68/6879000e58347aae2d239ff118e15a1cadd798ce.svn-base --- a/.svn/pristine/68/6879000e58347aae2d239ff118e15a1cadd798ce.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1121 +0,0 @@ -# Russian localization for Ruby on Rails 2.2+ -# by Yaroslav Markin -# -# Be sure to check out "russian" gem (http://github.com/yaroslav/russian) for -# full Russian language support in Rails (month names, pluralization, etc). -# The following is an excerpt from that gem. -# -# Ð”Ð»Ñ Ð¿Ð¾Ð»Ð½Ð¾Ñ†ÐµÐ½Ð½Ð¾Ð¹ поддержки руÑÑкого Ñзыка (варианты названий меÑÑцев, -# Ð¿Ð»ÑŽÑ€Ð°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð¸ так далее) в Rails 2.2 нужно иÑпользовать gem "russian" -# (http://github.com/yaroslav/russian). Следующие данные -- выдержка их него, чтобы -# была возможноÑть минимальной локализации Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð½Ð° руÑÑкий Ñзык. - -ru: - direction: ltr - date: - formats: - default: "%d.%m.%Y" - short: "%d %b" - long: "%d %B %Y" - - day_names: [воÑкреÑенье, понедельник, вторник, Ñреда, четверг, пÑтница, Ñуббота] - standalone_day_names: [ВоÑкреÑенье, Понедельник, Вторник, Среда, Четверг, ПÑтница, Суббота] - abbr_day_names: [Ð’Ñ, Пн, Ð’Ñ‚, Ср, Чт, Пт, Сб] - - month_names: [~, ÑнварÑ, февралÑ, марта, апрелÑ, маÑ, июнÑ, июлÑ, авгуÑта, ÑентÑбрÑ, октÑбрÑ, ноÑбрÑ, декабрÑ] - # see russian gem for info on "standalone" day names - standalone_month_names: [~, Январь, Февраль, Март, Ðпрель, Май, Июнь, Июль, ÐвгуÑÑ‚, СентÑбрь, ОктÑбрь, ÐоÑбрь, Декабрь] - abbr_month_names: [~, Ñнв., февр., марта, апр., маÑ, июнÑ, июлÑ, авг., Ñент., окт., ноÑб., дек.] - standalone_abbr_month_names: [~, Ñнв., февр., март, апр., май, июнь, июль, авг., Ñент., окт., ноÑб., дек.] - - order: - - :day - - :month - - :year - - time: - formats: - default: "%a, %d %b %Y, %H:%M:%S %z" - time: "%H:%M" - short: "%d %b, %H:%M" - long: "%d %B %Y, %H:%M" - - am: "утра" - pm: "вечера" - - number: - format: - separator: "," - delimiter: " " - precision: 3 - - currency: - format: - format: "%n %u" - unit: "руб." - separator: "." - delimiter: " " - precision: 2 - - percentage: - format: - delimiter: "" - - precision: - format: - delimiter: "" - - human: - format: - delimiter: "" - precision: 2 - # Rails 2.2 - # storage_units: [байт, КБ, МБ, ГБ, ТБ] - - # Rails 2.3 - storage_units: - # Storage units output formatting. - # %u is the storage unit, %n is the number (default: 2 MB) - format: "%n %u" - units: - byte: - one: "байт" - few: "байта" - many: "байт" - other: "байта" - kb: "КБ" - mb: "МБ" - gb: "ГБ" - tb: "ТБ" - - datetime: - distance_in_words: - half_a_minute: "меньше минуты" - less_than_x_seconds: - one: "меньше %{count} Ñекунды" - few: "меньше %{count} Ñекунд" - many: "меньше %{count} Ñекунд" - other: "меньше %{count} Ñекунды" - x_seconds: - one: "%{count} Ñекунда" - few: "%{count} Ñекунды" - many: "%{count} Ñекунд" - other: "%{count} Ñекунды" - less_than_x_minutes: - one: "меньше %{count} минуты" - few: "меньше %{count} минут" - many: "меньше %{count} минут" - other: "меньше %{count} минуты" - x_minutes: - one: "%{count} минуту" - few: "%{count} минуты" - many: "%{count} минут" - other: "%{count} минуты" - about_x_hours: - one: "около %{count} чаÑа" - few: "около %{count} чаÑов" - many: "около %{count} чаÑов" - other: "около %{count} чаÑа" - x_days: - one: "%{count} день" - few: "%{count} днÑ" - many: "%{count} дней" - other: "%{count} днÑ" - about_x_months: - one: "около %{count} меÑÑца" - few: "около %{count} меÑÑцев" - many: "около %{count} меÑÑцев" - other: "около %{count} меÑÑца" - x_months: - one: "%{count} меÑÑц" - few: "%{count} меÑÑца" - many: "%{count} меÑÑцев" - other: "%{count} меÑÑца" - about_x_years: - one: "около %{count} года" - few: "около %{count} лет" - many: "около %{count} лет" - other: "около %{count} лет" - over_x_years: - one: "больше %{count} года" - few: "больше %{count} лет" - many: "больше %{count} лет" - other: "больше %{count} лет" - almost_x_years: - one: "почти 1 год" - few: "почти %{count} года" - many: "почти %{count} лет" - other: "почти %{count} года" - prompts: - year: "Год" - month: "МеÑÑц" - day: "День" - hour: "ЧаÑов" - minute: "Минут" - second: "Секунд" - - activerecord: - errors: - template: - header: - one: "%{model}: Ñохранение не удалоÑÑŒ из-за %{count} ошибки" - few: "%{model}: Ñохранение не удалоÑÑŒ из-за %{count} ошибок" - many: "%{model}: Ñохранение не удалоÑÑŒ из-за %{count} ошибок" - other: "%{model}: Ñохранение не удалоÑÑŒ из-за %{count} ошибки" - - body: "Проблемы возникли Ñо Ñледующими полÑми:" - - messages: - inclusion: "имеет непредуÑмотренное значение" - exclusion: "имеет зарезервированное значение" - invalid: "имеет неверное значение" - confirmation: "не Ñовпадает Ñ Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸ÐµÐ¼" - accepted: "нужно подтвердить" - empty: "не может быть пуÑтым" - blank: "не может быть пуÑтым" - too_long: - one: "Ñлишком большой длины (не может быть больше чем %{count} Ñимвол)" - few: "Ñлишком большой длины (не может быть больше чем %{count} Ñимвола)" - many: "Ñлишком большой длины (не может быть больше чем %{count} Ñимволов)" - other: "Ñлишком большой длины (не может быть больше чем %{count} Ñимвола)" - too_short: - one: "недоÑтаточной длины (не может быть меньше %{count} Ñимвола)" - few: "недоÑтаточной длины (не может быть меньше %{count} Ñимволов)" - many: "недоÑтаточной длины (не может быть меньше %{count} Ñимволов)" - other: "недоÑтаточной длины (не может быть меньше %{count} Ñимвола)" - wrong_length: - one: "неверной длины (может быть длиной ровно %{count} Ñимвол)" - few: "неверной длины (может быть длиной ровно %{count} Ñимвола)" - many: "неверной длины (может быть длиной ровно %{count} Ñимволов)" - other: "неверной длины (может быть длиной ровно %{count} Ñимвола)" - taken: "уже ÑущеÑтвует" - not_a_number: "не ÑвлÑетÑÑ Ñ‡Ð¸Ñлом" - greater_than: "может иметь значение большее %{count}" - greater_than_or_equal_to: "может иметь значение большее или равное %{count}" - equal_to: "может иметь лишь значение, равное %{count}" - less_than: "может иметь значение меньшее чем %{count}" - less_than_or_equal_to: "может иметь значение меньшее или равное %{count}" - odd: "может иметь лишь нечетное значение" - even: "может иметь лишь четное значение" - greater_than_start_date: "должна быть позднее даты начала" - not_same_project: "не отноÑитÑÑ Ðº одному проекту" - circular_dependency: "Ð¢Ð°ÐºÐ°Ñ ÑвÑзь приведет к цикличеÑкой завиÑимоÑти" - cant_link_an_issue_with_a_descendant: "Задача не может быть ÑвÑзана Ñо Ñвоей подзадачей" - - support: - array: - # Rails 2.2 - sentence_connector: "и" - skip_last_comma: true - - # Rails 2.3 - words_connector: ", " - two_words_connector: " и " - last_word_connector: " и " - - actionview_instancetag_blank_option: Выберите - - button_activate: Ðктивировать - button_add: Добавить - button_annotate: ÐвторÑтво - button_apply: Применить - button_archive: Ðрхивировать - button_back: Ðазад - button_cancel: Отмена - button_change_password: Изменить пароль - button_change: Изменить - button_check_all: Отметить вÑе - button_clear: ОчиÑтить - button_configure: Параметры - button_copy: Копировать - button_create: Создать - button_create_and_continue: Создать и продолжить - button_delete: Удалить - button_download: Загрузить - button_edit: Редактировать - button_edit_associated_wikipage: "Редактировать ÑвÑзанную wiki-Ñтраницу: %{page_title}" - button_list: СпиÑок - button_lock: Заблокировать - button_login: Вход - button_log_time: Затраченное Ð²Ñ€ÐµÐ¼Ñ - button_move: ПеремеÑтить - button_quote: Цитировать - button_rename: Переименовать - button_reply: Ответить - button_reset: СброÑить - button_rollback: ВернутьÑÑ Ðº данной верÑии - button_save: Сохранить - button_sort: Сортировать - button_submit: ПринÑть - button_test: Проверить - button_unarchive: Разархивировать - button_uncheck_all: ОчиÑтить - button_unlock: Разблокировать - button_unwatch: Ðе Ñледить - button_update: Обновить - button_view: ПроÑмотреть - button_watch: Следить - - default_activity_design: Проектирование - default_activity_development: Разработка - default_doc_category_tech: ТехничеÑÐºÐ°Ñ Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ñ - default_doc_category_user: ПользовательÑÐºÐ°Ñ Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ñ - default_issue_status_in_progress: Ð’ работе - default_issue_status_closed: Закрыта - default_issue_status_feedback: ÐžÐ±Ñ€Ð°Ñ‚Ð½Ð°Ñ ÑвÑзь - default_issue_status_new: ÐÐ¾Ð²Ð°Ñ - default_issue_status_rejected: Отклонена - default_issue_status_resolved: Решена - default_priority_high: Ð’Ñ‹Ñокий - default_priority_immediate: Ðемедленный - default_priority_low: Ðизкий - default_priority_normal: Ðормальный - default_priority_urgent: Срочный - default_role_developer: Разработчик - default_role_manager: Менеджер - default_role_reporter: Репортёр - default_tracker_bug: Ошибка - default_tracker_feature: Улучшение - default_tracker_support: Поддержка - - enumeration_activities: ДейÑÑ‚Ð²Ð¸Ñ (учет времени) - enumeration_doc_categories: Категории документов - enumeration_issue_priorities: Приоритеты задач - - error_can_not_remove_role: Эта роль иÑпользуетÑÑ Ð¸ не может быть удалена. - error_can_not_delete_custom_field: Ðевозможно удалить наÑтраиваемое поле - error_can_not_delete_tracker: Этот трекер Ñодержит задачи и не может быть удален. - error_can_t_load_default_data: "ÐšÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ Ð¿Ð¾ умолчанию не была загружена: %{value}" - error_issue_not_found_in_project: Задача не была найдена или не прикреплена к Ñтому проекту - error_scm_annotate: "Данные отÑутÑтвуют или не могут быть подпиÑаны." - error_scm_command_failed: "Ошибка доÑтупа к хранилищу: %{value}" - error_scm_not_found: Хранилище не Ñодержит запиÑи и/или иÑправлениÑ. - error_unable_to_connect: Ðевозможно подключитьÑÑ (%{value}) - error_unable_delete_issue_status: Ðевозможно удалить ÑÑ‚Ð°Ñ‚ÑƒÑ Ð·Ð°Ð´Ð°Ñ‡Ð¸ - - field_account: Ð£Ñ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ - field_activity: ДеÑтельноÑть - field_admin: ÐдминиÑтратор - field_assignable: Задача может быть назначена Ñтой роли - field_assigned_to: Ðазначена - field_attr_firstname: Ð˜Ð¼Ñ - field_attr_lastname: Ð¤Ð°Ð¼Ð¸Ð»Ð¸Ñ - field_attr_login: Ðтрибут Login - field_attr_mail: email - field_author: Ðвтор - field_auth_source: Режим аутентификации - field_base_dn: BaseDN - field_category: ÐšÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñ - field_column_names: Столбцы - field_comments: Комментарий - field_comments_sorting: Отображение комментариев - field_content: Content - field_created_on: Создано - field_default_value: Значение по умолчанию - field_delay: Отложить - field_description: ОпиÑание - field_done_ratio: ГотовноÑть - field_downloads: Загрузки - field_due_date: Дата Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ - field_editable: Редактируемое - field_effective_date: Дата - field_estimated_hours: Оценка времени - field_field_format: Формат - field_filename: Файл - field_filesize: Размер - field_firstname: Ð˜Ð¼Ñ - field_fixed_version: ВерÑÐ¸Ñ - field_hide_mail: Скрывать мой email - field_homepage: Ð¡Ñ‚Ð°Ñ€Ñ‚Ð¾Ð²Ð°Ñ Ñтраница - field_host: Компьютер - field_hours: чаÑ(а,ов) - field_identifier: Уникальный идентификатор - field_identity_url: OpenID URL - field_is_closed: Задача закрыта - field_is_default: Значение по умолчанию - field_is_filter: ИÑпользуетÑÑ Ð² качеÑтве фильтра - field_is_for_all: Ð”Ð»Ñ Ð²Ñех проектов - field_is_in_roadmap: Задачи, отображаемые в оперативном плане - field_is_public: ОбщедоÑтупный - field_is_required: ОбÑзательное - field_issue_to: СвÑзанные задачи - field_issue: Задача - field_language: Язык - field_last_login_on: ПоÑледнее подключение - field_lastname: Ð¤Ð°Ð¼Ð¸Ð»Ð¸Ñ - field_login: Пользователь - field_mail: Email - field_mail_notification: Ð£Ð²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð¿Ð¾ email - field_max_length: МакÑÐ¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð´Ð»Ð¸Ð½Ð° - field_min_length: ÐœÐ¸Ð½Ð¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð´Ð»Ð¸Ð½Ð° - field_name: Ð˜Ð¼Ñ - field_new_password: Ðовый пароль - field_notes: ÐŸÑ€Ð¸Ð¼ÐµÑ‡Ð°Ð½Ð¸Ñ - field_onthefly: Создание Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð½Ð° лету - field_parent_title: РодительÑÐºÐ°Ñ Ñтраница - field_parent: РодительÑкий проект - field_parent_issue: РодительÑÐºÐ°Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° - field_password_confirmation: Подтверждение - field_password: Пароль - field_port: Порт - field_possible_values: Возможные Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ - field_priority: Приоритет - field_project: Проект - field_redirect_existing_links: Перенаправить ÑущеÑтвующие ÑÑылки - field_regexp: РегулÑрное выражение - field_role: Роль - field_searchable: ДоÑтупно Ð´Ð»Ñ Ð¿Ð¾Ð¸Ñка - field_spent_on: Дата - field_start_date: Ðачата - field_start_page: Ð¡Ñ‚Ð°Ñ€Ñ‚Ð¾Ð²Ð°Ñ Ñтраница - field_status: Ð¡Ñ‚Ð°Ñ‚ÑƒÑ - field_subject: Тема - field_subproject: Подпроект - field_summary: Краткое опиÑание - field_text: ТекÑтовое поле - field_time_entries: Затраченное Ð²Ñ€ÐµÐ¼Ñ - field_time_zone: ЧаÑовой поÑÑ - field_title: Заголовок - field_tracker: Трекер - field_type: Тип - field_updated_on: Обновлено - field_url: URL - field_user: Пользователь - field_value: Значение - field_version: ВерÑÐ¸Ñ - field_watcher: Ðаблюдатель - - general_csv_decimal_separator: ',' - general_csv_encoding: UTF-8 - general_csv_separator: ';' - general_first_day_of_week: '1' - general_lang_name: 'Russian (РуÑÑкий)' - general_pdf_encoding: UTF-8 - general_text_no: 'нет' - general_text_No: 'Ðет' - general_text_yes: 'да' - general_text_Yes: 'Да' - - gui_validation_error: 1 ошибка - gui_validation_error_plural: "%{count} ошибок" - gui_validation_error_plural2: "%{count} ошибки" - gui_validation_error_plural5: "%{count} ошибок" - - label_activity: ДейÑÑ‚Ð²Ð¸Ñ - label_add_another_file: Добавить ещё один файл - label_added_time_by: "Добавил(а) %{author} %{age} назад" - label_added: добавлено - label_add_note: Добавить замечание - label_administration: ÐдминиÑтрирование - label_age: ВозраÑÑ‚ - label_ago: дней(Ñ) назад - label_all_time: вÑÑ‘ Ð²Ñ€ÐµÐ¼Ñ - label_all_words: Ð’Ñе Ñлова - label_all: вÑе - label_and_its_subprojects: "%{value} и вÑе подпроекты" - label_applied_status: Применимый ÑÑ‚Ð°Ñ‚ÑƒÑ - label_ascending: По возраÑтанию - label_assigned_to_me_issues: Мои задачи - label_associated_revisions: СвÑзанные редакции - label_attachment: Файл - label_attachment_delete: Удалить файл - label_attachment_new: Ðовый файл - label_attachment_plural: Файлы - label_attribute: Ðтрибут - label_attribute_plural: Ðтрибуты - label_authentication: ÐÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ - label_auth_source: Режим аутентификации - label_auth_source_new: Ðовый режим аутентификации - label_auth_source_plural: Режимы аутентификации - label_blocked_by: блокируетÑÑ - label_blocks: блокирует - label_board: Форум - label_board_new: Ðовый форум - label_board_plural: Форумы - label_boolean: ЛогичеÑкий - label_browse: Обзор - label_bulk_edit_selected_issues: Редактировать вÑе выбранные задачи - label_calendar: Календарь - label_calendar_filter: Ð’ÐºÐ»ÑŽÑ‡Ð°Ñ - label_calendar_no_assigned: не мои - label_change_plural: Правки - label_change_properties: Изменить ÑвойÑтва - label_change_status: Изменить ÑÑ‚Ð°Ñ‚ÑƒÑ - label_change_view_all: ПроÑмотреть вÑе Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ - label_changes_details: ПодробноÑти по вÑем изменениÑм - label_changeset_plural: Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ - label_chronological_order: Ð’ хронологичеÑком порÑдке - label_closed_issues: закрыто - label_closed_issues_plural: закрыто - label_closed_issues_plural2: закрыто - label_closed_issues_plural5: закрыто - label_comment: комментарий - label_comment_add: ОÑтавить комментарий - label_comment_added: Добавленный комментарий - label_comment_delete: Удалить комментарии - label_comment_plural: Комментарии - label_comment_plural2: ÐºÐ¾Ð¼Ð¼ÐµÐ½Ñ‚Ð°Ñ€Ð¸Ñ - label_comment_plural5: комментариев - label_commits_per_author: Изменений на Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ - label_commits_per_month: Изменений в меÑÑц - label_confirmation: Подтверждение - label_contains: Ñодержит - label_copied: Ñкопировано - label_copy_workflow_from: Скопировать поÑледовательноÑть дейÑтвий из - label_current_status: Текущий ÑÑ‚Ð°Ñ‚ÑƒÑ - label_current_version: Ð¢ÐµÐºÑƒÑ‰Ð°Ñ Ð²ÐµÑ€ÑÐ¸Ñ - label_custom_field: ÐаÑтраиваемое поле - label_custom_field_new: Ðовое наÑтраиваемое поле - label_custom_field_plural: ÐаÑтраиваемые Ð¿Ð¾Ð»Ñ - label_date_from: С - label_date_from_to: С %{start} по %{end} - label_date_range: временной интервал - label_date_to: по - label_date: Дата - label_day_plural: дней(Ñ) - label_default: По умолчанию - label_default_columns: Столбцы по умолчанию - label_deleted: удалено - label_descending: По убыванию - label_details: ПодробноÑти - label_diff_inline: в текÑте - label_diff_side_by_side: Ñ€Ñдом - label_disabled: отключено - label_display: Отображение - label_display_per_page: "Ðа Ñтраницу: %{value}" - label_document: Документ - label_document_added: Добавлен документ - label_document_new: Ðовый документ - label_document_plural: Документы - label_download: "%{count} загрузка" - label_download_plural: "%{count} Ñкачиваний" - label_download_plural2: "%{count} загрузки" - label_download_plural5: "%{count} загрузок" - label_downloads_abbr: Скачиваний - label_duplicated_by: дублируетÑÑ - label_duplicates: дублирует - label_end_to_end: Ñ ÐºÐ¾Ð½Ñ†Ð° к концу - label_end_to_start: Ñ ÐºÐ¾Ð½Ñ†Ð° к началу - label_enumeration_new: Ðовое значение - label_enumerations: СпиÑки значений - label_environment: Окружение - label_equals: ÑвлÑетÑÑ - label_example: Пример - label_export_to: ЭкÑпортировать в - label_feed_plural: RSS - label_feeds_access_key_created_on: "Ключ доÑтупа RSS Ñоздан %{value} назад" - label_f_hour: "%{value} чаÑ" - label_f_hour_plural: "%{value} чаÑов" - label_file_added: Добавлен файл - label_file_plural: Файлы - label_filter_add: Добавить фильтр - label_filter_plural: Фильтры - label_float: С плавающей точкой - label_follows: Ð¿Ñ€ÐµÐ´Ñ‹Ð´ÑƒÑ‰Ð°Ñ - label_gantt: Диаграмма Ганта - label_general: Общее - label_generate_key: Сгенерировать ключ - label_greater_or_equal: ">=" - label_help: Помощь - label_history: ИÑÑ‚Ð¾Ñ€Ð¸Ñ - label_home: ДомашнÑÑ Ñтраница - label_incoming_emails: Приём Ñообщений - label_index_by_date: ИÑÑ‚Ð¾Ñ€Ð¸Ñ Ñтраниц - label_index_by_title: Оглавление - label_information_plural: Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ - label_information: Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ - label_in_less_than: менее чем - label_in_more_than: более чем - label_integer: Целый - label_internal: Внутренний - label_in: в - label_issue: Задача - label_issue_added: Добавлена задача - label_issue_category_new: ÐÐ¾Ð²Ð°Ñ ÐºÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñ - label_issue_category_plural: Категории задачи - label_issue_category: ÐšÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñ Ð·Ð°Ð´Ð°Ñ‡Ð¸ - label_issue_new: ÐÐ¾Ð²Ð°Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° - label_issue_plural: Задачи - label_issues_by: "Сортировать по %{value}" - label_issue_status_new: Ðовый ÑÑ‚Ð°Ñ‚ÑƒÑ - label_issue_status_plural: СтатуÑÑ‹ задач - label_issue_status: Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð·Ð°Ð´Ð°Ñ‡Ð¸ - label_issue_tracking: Задачи - label_issue_updated: Обновлена задача - label_issue_view_all: ПроÑмотреть вÑе задачи - label_issue_watchers: Ðаблюдатели - label_jump_to_a_project: Перейти к проекту... - label_language_based: Ðа оÑнове Ñзыка - label_last_changes: "менее %{count} изменений" - label_last_login: ПоÑледнее подключение - label_last_month: поÑледний меÑÑц - label_last_n_days: "поÑледние %{count} дней" - label_last_week: поÑледнÑÑ Ð½ÐµÐ´ÐµÐ»Ñ - label_latest_revision: ПоÑледнÑÑ Ñ€ÐµÐ´Ð°ÐºÑ†Ð¸Ñ - label_latest_revision_plural: ПоÑледние редакции - label_ldap_authentication: ÐÐ²Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ð¸Ñ Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ LDAP - label_less_or_equal: <= - label_less_than_ago: менее, чем дней(Ñ) назад - label_list: СпиÑок - label_loading: Загрузка... - label_logged_as: Вошли как - label_login: Войти - label_login_with_open_id_option: или войти Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ OpenID - label_logout: Выйти - label_max_size: МакÑимальный размер - label_member_new: Ðовый учаÑтник - label_member: УчаÑтник - label_member_plural: УчаÑтники - label_message_last: ПоÑледнее Ñообщение - label_message_new: Ðовое Ñообщение - label_message_plural: Ð¡Ð¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ - label_message_posted: Добавлено Ñообщение - label_me: мне - label_min_max_length: ÐœÐ¸Ð½Ð¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ - макÑÐ¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð´Ð»Ð¸Ð½Ð° - label_modification: "%{count} изменение" - label_modification_plural: "%{count} изменений" - label_modification_plural2: "%{count} изменениÑ" - label_modification_plural5: "%{count} изменений" - label_modified: изменено - label_module_plural: Модули - label_months_from: меÑÑцев(ца) Ñ - label_month: МеÑÑц - label_more_than_ago: более, чем дней(Ñ) назад - label_more: Больше - label_my_account: ÐœÐ¾Ñ ÑƒÑ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ - label_my_page: ÐœÐ¾Ñ Ñтраница - label_my_page_block: Блок моей Ñтраницы - label_my_projects: Мои проекты - label_new: Ðовый - label_new_statuses_allowed: Разрешенные новые ÑтатуÑÑ‹ - label_news_added: Добавлена новоÑть - label_news_latest: ПоÑледние новоÑти - label_news_new: Добавить новоÑть - label_news_plural: ÐовоÑти - label_news_view_all: ПоÑмотреть вÑе новоÑти - label_news: ÐовоÑти - label_next: Следующее - label_nobody: никто - label_no_change_option: (Ðет изменений) - label_no_data: Ðет данных Ð´Ð»Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ - label_none: отÑутÑтвует - label_not_contains: не Ñодержит - label_not_equals: не ÑвлÑетÑÑ - label_open_issues: открыто - label_open_issues_plural: открыто - label_open_issues_plural2: открыто - label_open_issues_plural5: открыто - label_optional_description: ОпиÑание (необÑзательно) - label_options: Опции - label_overall_activity: Сводный отчет дейÑтвий - label_overview: Обзор - label_password_lost: ВоÑÑтановление Ð¿Ð°Ñ€Ð¾Ð»Ñ - label_permissions_report: Отчет по правам доÑтупа - label_permissions: Права доÑтупа - label_per_page: Ðа Ñтраницу - label_personalize_page: ПерÑонализировать данную Ñтраницу - label_planning: Планирование - label_please_login: ПожалуйÑта, войдите. - label_plugins: Модули - label_precedes: ÑÐ»ÐµÐ´ÑƒÑŽÑ‰Ð°Ñ - label_preferences: ÐŸÑ€ÐµÐ´Ð¿Ð¾Ñ‡Ñ‚ÐµÐ½Ð¸Ñ - label_preview: ПредпроÑмотр - label_previous: Предыдущее - label_profile: Профиль - label_project: Проект - label_project_all: Ð’Ñе проекты - label_project_copy_notifications: ОтправлÑть ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð¿Ð¾ Ñлектронной почте при копировании проекта - label_project_latest: ПоÑледние проекты - label_project_new: Ðовый проект - label_project_plural: Проекты - label_project_plural2: проекта - label_project_plural5: проектов - label_public_projects: Общие проекты - label_query: Сохраненный Ð·Ð°Ð¿Ñ€Ð¾Ñ - label_query_new: Ðовый Ð·Ð°Ð¿Ñ€Ð¾Ñ - label_query_plural: Сохраненные запроÑÑ‹ - label_read: Чтение... - label_register: РегиÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ - label_registered_on: ЗарегиÑтрирован(а) - label_registration_activation_by_email: Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ñ ÑƒÑ‡ÐµÑ‚Ð½Ñ‹Ñ… запиÑей по email - label_registration_automatic_activation: автоматичеÑÐºÐ°Ñ Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ñ ÑƒÑ‡ÐµÑ‚Ð½Ñ‹Ñ… запиÑей - label_registration_manual_activation: активировать учетные запиÑи вручную - label_related_issues: СвÑзанные задачи - label_relates_to: ÑвÑзана Ñ - label_relation_delete: Удалить ÑвÑзь - label_relation_new: Ðовое отношение - label_renamed: переименовано - label_reply_plural: Ответы - label_report: Отчет - label_report_plural: Отчеты - label_reported_issues: Созданные задачи - label_repository: Хранилище - label_repository_plural: Хранилища - label_result_plural: Результаты - label_reverse_chronological_order: Ð’ обратном порÑдке - label_revision: Ð ÐµÐ´Ð°ÐºÑ†Ð¸Ñ - label_revision_plural: Редакции - label_roadmap: Оперативный план - label_roadmap_due_in: "Ð’ Ñрок %{value}" - label_roadmap_no_issues: Ðет задач Ð´Ð»Ñ Ð´Ð°Ð½Ð½Ð¾Ð¹ верÑии - label_roadmap_overdue: "опоздание %{value}" - label_role: Роль - label_role_and_permissions: Роли и права доÑтупа - label_role_new: ÐÐ¾Ð²Ð°Ñ Ñ€Ð¾Ð»ÑŒ - label_role_plural: Роли - label_scm: Тип хранилища - label_search: ПоиÑк - label_search_titles_only: ИÑкать только в названиÑÑ… - label_send_information: Отправить пользователю информацию по учетной запиÑи - label_send_test_email: ПоÑлать email Ð´Ð»Ñ Ð¿Ñ€Ð¾Ð²ÐµÑ€ÐºÐ¸ - label_settings: ÐаÑтройки - label_show_completed_versions: Показывать завершенные верÑии - label_sort: Сортировать - label_sort_by: "Сортировать по %{value}" - label_sort_higher: Вверх - label_sort_highest: Ð’ начало - label_sort_lower: Вниз - label_sort_lowest: Ð’ конец - label_spent_time: Затраченное Ð²Ñ€ÐµÐ¼Ñ - label_start_to_end: Ñ Ð½Ð°Ñ‡Ð°Ð»Ð° к концу - label_start_to_start: Ñ Ð½Ð°Ñ‡Ð°Ð»Ð° к началу - label_statistics: СтатиÑтика - label_stay_logged_in: ОÑтаватьÑÑ Ð² ÑиÑтеме - label_string: ТекÑÑ‚ - label_subproject_plural: Подпроекты - label_subtask_plural: Подзадачи - label_text: Длинный текÑÑ‚ - label_theme: Тема - label_this_month: Ñтот меÑÑц - label_this_week: на Ñтой неделе - label_this_year: Ñтот год - label_time_tracking: Учет времени - label_timelog_today: РаÑход времени на ÑÐµÐ³Ð¾Ð´Ð½Ñ - label_today: ÑÐµÐ³Ð¾Ð´Ð½Ñ - label_topic_plural: Темы - label_total: Ð’Ñего - label_tracker: Трекер - label_tracker_new: Ðовый трекер - label_tracker_plural: Трекеры - label_updated_time: "Обновлено %{value} назад" - label_updated_time_by: "Обновлено %{author} %{age} назад" - label_used_by: ИÑпользуетÑÑ - label_user: Пользователь - label_user_activity: "ДейÑÑ‚Ð²Ð¸Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ %{value}" - label_user_mail_no_self_notified: "Ðе извещать об изменениÑÑ…, которые Ñ Ñделал Ñам" - label_user_mail_option_all: "О вÑех ÑобытиÑÑ… во вÑех моих проектах" - label_user_mail_option_selected: "О вÑех ÑобытиÑÑ… только в выбранном проекте..." - label_user_mail_option_only_owner: Только Ð´Ð»Ñ Ð¾Ð±ÑŠÐµÐºÑ‚Ð¾Ð², Ð´Ð»Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ñ… Ñ ÑвлÑÑŽÑÑŒ владельцем - label_user_mail_option_only_my_events: Только Ð´Ð»Ñ Ð¾Ð±ÑŠÐµÐºÑ‚Ð¾Ð², которые Ñ Ð¾Ñ‚Ñлеживаю или в которых учаÑтвую - label_user_mail_option_only_assigned: Только Ð´Ð»Ñ Ð¾Ð±ÑŠÐµÐºÑ‚Ð¾Ð², которые назначены мне - label_user_new: Ðовый пользователь - label_user_plural: Пользователи - label_version: ВерÑÐ¸Ñ - label_version_new: ÐÐ¾Ð²Ð°Ñ Ð²ÐµÑ€ÑÐ¸Ñ - label_version_plural: ВерÑии - label_view_diff: ПроÑмотреть Ð¾Ñ‚Ð»Ð¸Ñ‡Ð¸Ñ - label_view_revisions: ПроÑмотреть редакции - label_watched_issues: ОтÑлеживаемые задачи - label_week: ÐÐµÐ´ÐµÐ»Ñ - label_wiki: Wiki - label_wiki_edit: Редактирование Wiki - label_wiki_edit_plural: Wiki - label_wiki_page: Страница Wiki - label_wiki_page_plural: Страницы Wiki - label_workflow: ПоÑледовательноÑть дейÑтвий - label_x_closed_issues_abbr: - zero: "0 закрыто" - one: "1 закрыт" - few: "%{count} закрыто" - many: "%{count} закрыто" - other: "%{count} закрыто" - label_x_comments: - zero: "нет комментариев" - one: "1 комментарий" - few: "%{count} комментариÑ" - many: "%{count} комментариев" - other: "%{count} комментариев" - label_x_open_issues_abbr: - zero: "0 открыто" - one: "1 открыт" - few: "%{count} открыто" - many: "%{count} открыто" - other: "%{count} открыто" - label_x_open_issues_abbr_on_total: - zero: "0 открыто / %{total}" - one: "1 открыт / %{total}" - few: "%{count} открыто / %{total}" - many: "%{count} открыто / %{total}" - other: "%{count} открыто / %{total}" - label_x_projects: - zero: "нет проектов" - one: "1 проект" - few: "%{count} проекта" - many: "%{count} проектов" - other: "%{count} проектов" - label_year: Год - label_yesterday: вчера - - mail_body_account_activation_request: "ЗарегиÑтрирован новый пользователь (%{value}). Ð£Ñ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ ожидает Вашего утверждениÑ:" - mail_body_account_information: Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ Вашей учетной запиÑи - mail_body_account_information_external: "Ð’Ñ‹ можете иÑпользовать Вашу %{value} учетную запиÑÑŒ Ð´Ð»Ñ Ð²Ñ…Ð¾Ð´Ð°." - mail_body_lost_password: 'Ð”Ð»Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ Ð¿Ñ€Ð¾Ð¹Ð´Ð¸Ñ‚Ðµ по Ñледующей ÑÑылке:' - mail_body_register: 'Ð”Ð»Ñ Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ð¸ учетной запиÑи пройдите по Ñледующей ÑÑылке:' - mail_body_reminder: "%{count} назначенных на Ð’Ð°Ñ Ð·Ð°Ð´Ð°Ñ‡ на Ñледующие %{days} дней:" - mail_subject_account_activation_request: "Ð—Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° активацию Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð² ÑиÑтеме %{value}" - mail_subject_lost_password: "Ваш %{value} пароль" - mail_subject_register: "ÐÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ñ ÑƒÑ‡ÐµÑ‚Ð½Ð¾Ð¹ запиÑи %{value}" - mail_subject_reminder: "%{count} назначенных на Ð’Ð°Ñ Ð·Ð°Ð´Ð°Ñ‡ в ближайшие %{days} дней" - - notice_account_activated: Ваша ÑƒÑ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ активирована. Ð’Ñ‹ можете войти. - notice_account_invalid_creditentials: Ðеправильное Ð¸Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¸Ð»Ð¸ пароль - notice_account_lost_email_sent: Вам отправлено пиÑьмо Ñ Ð¸Ð½ÑтрукциÑми по выбору нового паролÑ. - notice_account_password_updated: Пароль уÑпешно обновлен. - notice_account_pending: "Ваша ÑƒÑ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ Ñоздана и ожидает Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ Ð°Ð´Ð¼Ð¸Ð½Ð¸Ñтратора." - notice_account_register_done: Ð£Ñ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ уÑпешно Ñоздана. Ð”Ð»Ñ Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ð¸ Вашей учетной запиÑи пройдите по ÑÑылке, ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ð²Ñ‹Ñлана Вам по Ñлектронной почте. - notice_account_unknown_email: ÐеизвеÑтный пользователь. - notice_account_updated: Ð£Ñ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ уÑпешно обновлена. - notice_account_wrong_password: Ðеверный пароль - notice_can_t_change_password: Ð”Ð»Ñ Ð´Ð°Ð½Ð½Ð¾Ð¹ учетной запиÑи иÑпользуетÑÑ Ð¸Ñточник внешней аутентификации. Ðевозможно изменить пароль. - notice_default_data_loaded: Была загружена ÐºÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ Ð¿Ð¾ умолчанию. - notice_email_error: "Во Ð²Ñ€ÐµÐ¼Ñ Ð¾Ñ‚Ð¿Ñ€Ð°Ð²ÐºÐ¸ пиÑьма произошла ошибка (%{value})" - notice_email_sent: "Отправлено пиÑьмо %{value}" - notice_failed_to_save_issues: "Ðе удалоÑÑŒ Ñохранить %{count} пункт(ов) из %{total} выбранных: %{ids}." - notice_failed_to_save_members: "Ðе удалоÑÑŒ Ñохранить учаÑтника(ов): %{errors}." - notice_feeds_access_key_reseted: Ваш ключ доÑтупа RSS был Ñброшен. - notice_file_not_found: Страница, на которую Ð’Ñ‹ пытаетеÑÑŒ зайти, не ÑущеÑтвует или удалена. - notice_locking_conflict: Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð° другим пользователем. - notice_no_issue_selected: "Ðе выбрано ни одной задачи! ПожалуйÑта, отметьте задачи, которые Ð’Ñ‹ хотите отредактировать." - notice_not_authorized: У Ð’Ð°Ñ Ð½ÐµÑ‚ прав Ð´Ð»Ñ Ð¿Ð¾ÑÐµÑ‰ÐµÐ½Ð¸Ñ Ð´Ð°Ð½Ð½Ð¾Ð¹ Ñтраницы. - notice_successful_connection: Подключение уÑпешно уÑтановлено. - notice_successful_create: Создание уÑпешно. - notice_successful_delete: Удаление уÑпешно. - notice_successful_update: Обновление уÑпешно. - notice_unable_delete_version: Ðевозможно удалить верÑию. - - permission_add_issues: Добавление задач - permission_add_issue_notes: Добавление примечаний - permission_add_issue_watchers: Добавление наблюдателей - permission_add_messages: Отправка Ñообщений - permission_browse_repository: ПроÑмотр хранилища - permission_comment_news: Комментирование новоÑтей - permission_commit_access: Изменение файлов в хранилище - permission_delete_issues: Удаление задач - permission_delete_messages: Удаление Ñообщений - permission_delete_own_messages: Удаление ÑобÑтвенных Ñообщений - permission_delete_wiki_pages: Удаление wiki-Ñтраниц - permission_delete_wiki_pages_attachments: Удаление прикрепленных файлов - permission_edit_issue_notes: Редактирование примечаний - permission_edit_issues: Редактирование задач - permission_edit_messages: Редактирование Ñообщений - permission_edit_own_issue_notes: Редактирование ÑобÑтвенных примечаний - permission_edit_own_messages: Редактирование ÑобÑтвенных Ñообщений - permission_edit_own_time_entries: Редактирование ÑобÑтвенного учета времени - permission_edit_project: Редактирование проектов - permission_edit_time_entries: Редактирование учета времени - permission_edit_wiki_pages: Редактирование wiki-Ñтраниц - permission_export_wiki_pages: ЭкÑпорт wiki-Ñтраниц - permission_log_time: Учет затраченного времени - permission_view_changesets: ПроÑмотр изменений хранилища - permission_view_time_entries: ПроÑмотр затраченного времени - permission_manage_project_activities: Управление типами дейÑтвий Ð´Ð»Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚Ð° - permission_manage_boards: Управление форумами - permission_manage_categories: Управление категориÑми задач - permission_manage_documents: Управление документами - permission_manage_files: Управление файлами - permission_manage_issue_relations: Управление ÑвÑзыванием задач - permission_manage_members: Управление учаÑтниками - permission_manage_news: Управление новоÑÑ‚Ñми - permission_manage_public_queries: Управление общими запроÑами - permission_manage_repository: Управление хранилищем - permission_manage_subtasks: Управление подзадачами - permission_manage_versions: Управление верÑиÑми - permission_manage_wiki: Управление Wiki - permission_move_issues: ÐŸÐµÑ€ÐµÐ½Ð¾Ñ Ð·Ð°Ð´Ð°Ñ‡ - permission_protect_wiki_pages: Блокирование wiki-Ñтраниц - permission_rename_wiki_pages: Переименование wiki-Ñтраниц - permission_save_queries: Сохранение запроÑов - permission_select_project_modules: Выбор модулей проекта - permission_view_calendar: ПроÑмотр ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€Ñ - permission_view_documents: ПроÑмотр документов - permission_view_files: ПроÑмотр файлов - permission_view_gantt: ПроÑмотр диаграммы Ганта - permission_view_issue_watchers: ПроÑмотр ÑпиÑка наблюдателей - permission_view_messages: ПроÑмотр Ñообщение - permission_view_wiki_edits: ПроÑмотр иÑтории Wiki - permission_view_wiki_pages: ПроÑмотр Wiki - - project_module_boards: Форумы - project_module_documents: Документы - project_module_files: Файлы - project_module_issue_tracking: Задачи - project_module_news: ÐовоÑти - project_module_repository: Хранилище - project_module_time_tracking: Учет времени - project_module_wiki: Wiki - project_module_gantt: Диаграмма Ганта - project_module_calendar: Календарь - - setting_activity_days_default: КоличеÑтво дней, отображаемых в ДейÑтвиÑÑ… - setting_app_subtitle: Подзаголовок Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ - setting_app_title: Ðазвание Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ - setting_attachment_max_size: МакÑимальный размер Ð²Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ - setting_autofetch_changesets: ÐвтоматичеÑки Ñледить за изменениÑми хранилища - setting_autologin: ÐвтоматичеÑкий вход - setting_bcc_recipients: ИÑпользовать Ñкрытые копии (BCC) - setting_cache_formatted_text: Кешировать форматированный текÑÑ‚ - setting_commit_fix_keywords: Ðазначение ключевых Ñлов - setting_commit_ref_keywords: Ключевые Ñлова Ð´Ð»Ñ Ð¿Ð¾Ð¸Ñка - setting_cross_project_issue_relations: Разрешить переÑечение задач по проектам - setting_date_format: Формат даты - setting_default_language: Язык по умолчанию - setting_default_notification_option: СпоÑоб Ð¾Ð¿Ð¾Ð²ÐµÑ‰ÐµÐ½Ð¸Ñ Ð¿Ð¾ умолчанию - setting_default_projects_public: Ðовые проекты ÑвлÑÑŽÑ‚ÑÑ Ð¾Ð±Ñ‰ÐµÐ´Ð¾Ñтупными - setting_diff_max_lines_displayed: МакÑимальное чиÑло Ñтрок Ð´Ð»Ñ diff - setting_display_subprojects_issues: Отображение подпроектов по умолчанию - setting_emails_footer: ПодÑтрочные Ð¿Ñ€Ð¸Ð¼ÐµÑ‡Ð°Ð½Ð¸Ñ Ð¿Ð¸Ñьма - setting_enabled_scm: Разрешенные SCM - setting_feeds_limit: Ограничение количеÑтва заголовков Ð´Ð»Ñ RSS потока - setting_file_max_size_displayed: МакÑимальный размер текÑтового файла Ð´Ð»Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ - setting_gravatar_enabled: ИÑпользовать аватар Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¸Ð· Gravatar - setting_host_name: Ð˜Ð¼Ñ ÐºÐ¾Ð¼Ð¿ÑŒÑŽÑ‚ÐµÑ€Ð° - setting_issue_list_default_columns: Столбцы, отображаемые в ÑпиÑке задач по умолчанию - setting_issues_export_limit: Ограничение по ÑкÑпортируемым задачам - setting_login_required: Ðеобходима Ð°ÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ - setting_mail_from: ИÑходÑщий email Ð°Ð´Ñ€ÐµÑ - setting_mail_handler_api_enabled: Включить веб-ÑÐµÑ€Ð²Ð¸Ñ Ð´Ð»Ñ Ð²Ñ…Ð¾Ð´Ñщих Ñообщений - setting_mail_handler_api_key: API ключ - setting_openid: Разрешить OpenID Ð´Ð»Ñ Ð²Ñ…Ð¾Ð´Ð° и региÑтрации - setting_per_page_options: КоличеÑтво запиÑей на Ñтраницу - setting_plain_text_mail: Только проÑтой текÑÑ‚ (без HTML) - setting_protocol: Протокол - setting_repository_log_display_limit: МакÑимальное количеÑтво редакций, отображаемых в журнале изменений - setting_self_registration: СаморегиÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ - setting_sequential_project_identifiers: Генерировать поÑледовательные идентификаторы проектов - setting_sys_api_enabled: Включить веб-ÑÐµÑ€Ð²Ð¸Ñ Ð´Ð»Ñ ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ñ…Ñ€Ð°Ð½Ð¸Ð»Ð¸Ñ‰ÐµÐ¼ - setting_text_formatting: Форматирование текÑта - setting_time_format: Формат времени - setting_user_format: Формат Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð¸Ð¼ÐµÐ½Ð¸ - setting_welcome_text: ТекÑÑ‚ приветÑÑ‚Ð²Ð¸Ñ - setting_wiki_compression: Сжатие иÑтории Wiki - - status_active: активен - status_locked: заблокирован - status_registered: зарегиÑтрирован - - text_are_you_sure_with_children: Удалить задачу и вÑе ее подзадачи? - text_are_you_sure: Ð’Ñ‹ уверены? - text_assign_time_entries_to_project: Прикрепить зарегиÑтрированное Ð²Ñ€ÐµÐ¼Ñ Ðº проекту - text_caracters_maximum: "МакÑимум %{count} Ñимволов(а)." - text_caracters_minimum: "Должно быть не менее %{count} Ñимволов." - text_comma_separated: ДопуÑтимы неÑколько значений (через запÑтую). - text_custom_field_possible_values_info: 'По одному значению в каждой Ñтроке' - text_default_administrator_account_changed: Ð£Ñ‡ÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ админиÑтратора по умолчанию изменена - text_destroy_time_entries_question: "Ðа Ñту задачу зарегиÑтрировано %{hours} чаÑа(ов) затраченного времени. Что Ð’Ñ‹ хотите предпринÑть?" - text_destroy_time_entries: Удалить зарегиÑтрированное Ð²Ñ€ÐµÐ¼Ñ - text_diff_truncated: '... Этот diff ограничен, так как превышает макÑимальный отображаемый размер.' - text_email_delivery_not_configured: "Параметры работы Ñ Ð¿Ð¾Ñ‡Ñ‚Ð¾Ð²Ñ‹Ð¼ Ñервером не наÑтроены и Ñ„ÑƒÐ½ÐºÑ†Ð¸Ñ ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð¿Ð¾ email не активна.\nÐаÑтроить параметры Ð´Ð»Ñ Ð’Ð°ÑˆÐµÐ³Ð¾ SMTP-Ñервера Ð’Ñ‹ можете в файле config/configuration.yml. Ð”Ð»Ñ Ð¿Ñ€Ð¸Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ð¹ перезапуÑтите приложение." - text_enumeration_category_reassign_to: 'Ðазначить им Ñледующее значение:' - text_enumeration_destroy_question: "%{count} объект(а,ов) ÑвÑзаны Ñ Ñтим значением." - text_file_repository_writable: Хранилище Ñ Ð´Ð¾Ñтупом на запиÑÑŒ - text_issue_added: "Создана Ð½Ð¾Ð²Ð°Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° %{id} (%{author})." - text_issue_category_destroy_assignments: Удалить Ð½Ð°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ ÐºÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ð¸ - text_issue_category_destroy_question: "ÐеÑколько задач (%{count}) назначено в данную категорию. Что Ð’Ñ‹ хотите предпринÑть?" - text_issue_category_reassign_to: Переназначить задачи Ð´Ð»Ñ Ð´Ð°Ð½Ð½Ð¾Ð¹ категории - text_issues_destroy_confirmation: 'Ð’Ñ‹ уверены, что хотите удалить выбранные задачи?' - text_issues_ref_in_commit_messages: СопоÑтавление и изменение ÑтатуÑа задач иÑÑ…Ð¾Ð´Ñ Ð¸Ð· текÑта Ñообщений - text_issue_updated: "Задача %{id} была обновлена (%{author})." - text_journal_changed: "Параметр %{label} изменилÑÑ Ñ %{old} на %{new}" - text_journal_deleted: "Значение %{old} параметра %{label} удалено" - text_journal_set_to: "Параметр %{label} изменилÑÑ Ð½Ð° %{value}" - text_length_between: "Длина между %{min} и %{max} Ñимволов." - text_load_default_configuration: Загрузить конфигурацию по умолчанию - text_min_max_length_info: 0 означает отÑутÑтвие ограничений - text_no_configuration_data: "Роли, трекеры, ÑтатуÑÑ‹ задач и оперативный план не были Ñконфигурированы.\nÐаÑтоÑтельно рекомендуетÑÑ Ð·Ð°Ð³Ñ€ÑƒÐ·Ð¸Ñ‚ÑŒ конфигурацию по-умолчанию. Ð’Ñ‹ Ñможете её изменить потом." - text_plugin_assets_writable: Каталог модулей доÑтупен Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñи - text_project_destroy_confirmation: Ð’Ñ‹ наÑтаиваете на удалении данного проекта и вÑей отноÑÑщейÑÑ Ðº нему информации? - text_project_identifier_info: 'ДопуÑтимы Ñтрочные буквы (a-z), цифры и дефиÑ.
    Сохраненный идентификатор не может быть изменен.' - 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: 'Выберите модули, которые будут иÑпользованы в проекте:' - text_status_changed_by_changeset: "Реализовано в %{value} редакции." - text_subprojects_destroy_warning: "Подпроекты: %{value} также будут удалены." - text_tip_issue_begin_day: дата начала задачи - text_tip_issue_begin_end_day: начало задачи и окончание ее в Ñтот же день - text_tip_issue_end_day: дата Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ Ð·Ð°Ð´Ð°Ñ‡Ð¸ - text_tracker_no_workflow: Ð”Ð»Ñ Ñтого трекера поÑледовательноÑть дейÑтвий не определена - text_unallowed_characters: Запрещенные Ñимволы - text_user_mail_option: "Ð”Ð»Ñ Ð½ÐµÐ²Ñ‹Ð±Ñ€Ð°Ð½Ð½Ñ‹Ñ… проектов, Ð’Ñ‹ будете получать ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ о том, что проÑматриваете или в чем учаÑтвуете (например, задачи, автором которых Ð’Ñ‹ ÑвлÑетеÑÑŒ, или которые Вам назначены)." - text_user_wrote: "%{value} пиÑал(а):" - text_wiki_destroy_confirmation: Ð’Ñ‹ уверены, что хотите удалить данную Wiki и вÑе ее Ñодержимое? - text_workflow_edit: Выберите роль и трекер Ð´Ð»Ñ Ñ€ÐµÐ´Ð°ÐºÑ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¿Ð¾ÑледовательноÑти ÑоÑтоÑний - - warning_attachments_not_saved: "%{count} файл(ов) невозможно Ñохранить." - text_wiki_page_destroy_question: Эта Ñтраница имеет %{descendants} дочерних Ñтраниц и их потомков. Что вы хотите предпринÑть? - text_wiki_page_reassign_children: Переопределить дочерние Ñтраницы на текущую Ñтраницу - text_wiki_page_nullify_children: Сделать дочерние Ñтраницы главными Ñтраницами - text_wiki_page_destroy_children: Удалить дочерние Ñтраницы и вÑех их потомков - setting_password_min_length: ÐœÐ¸Ð½Ð¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð´Ð»Ð¸Ð½Ð° Ð¿Ð°Ñ€Ð¾Ð»Ñ - field_group_by: Группировать результаты по - mail_subject_wiki_content_updated: "Wiki-Ñтраница '%{id}' была обновлена" - label_wiki_content_added: Добавлена wiki-Ñтраница - mail_subject_wiki_content_added: "Wiki-Ñтраница '%{id}' была добавлена" - mail_body_wiki_content_added: "%{author} добавил(а) wiki-Ñтраницу '%{id}'." - label_wiki_content_updated: Обновлена wiki-Ñтраница - mail_body_wiki_content_updated: "%{author} обновил(а) wiki-Ñтраницу '%{id}'." - permission_add_project: Создание проекта - setting_new_project_user_role_id: Роль, Ð½Ð°Ð·Ð½Ð°Ñ‡Ð°ÐµÐ¼Ð°Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»ÑŽ, Ñоздавшему проект - label_view_all_revisions: Показать вÑе ревизии - label_tag: Метка - label_branch: Ветвь - error_no_tracker_in_project: С Ñтим проектом не аÑÑоциирован ни один трекер. Проверьте наÑтройки проекта. - error_no_default_issue_status: Ðе определен ÑÑ‚Ð°Ñ‚ÑƒÑ Ð·Ð°Ð´Ð°Ñ‡ по умолчанию. Проверьте наÑтройки (Ñм. "ÐдминиÑтрирование -> СтатуÑÑ‹ задач"). - label_group_plural: Группы - label_group: Группа - label_group_new: ÐÐ¾Ð²Ð°Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð° - label_time_entry_plural: Затраченное Ð²Ñ€ÐµÐ¼Ñ - text_journal_added: "%{label} %{value} добавлен" - field_active: Ðктивно - enumeration_system_activity: СиÑтемное - permission_delete_issue_watchers: Удаление наблюдателей - version_status_closed: закрыт - version_status_locked: заблокирован - version_status_open: открыт - error_can_not_reopen_issue_on_closed_version: Задача, Ð½Ð°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ð°Ñ Ðº закрытой верÑии, не Ñможет быть открыта Ñнова - label_user_anonymous: Ðноним - button_move_and_follow: ПеремеÑтить и перейти - setting_default_projects_modules: Включенные по умолчанию модули Ð´Ð»Ñ Ð½Ð¾Ð²Ñ‹Ñ… проектов - setting_gravatar_default: Изображение Gravatar по умолчанию - field_sharing: СовмеÑтное иÑпользование - label_version_sharing_hierarchy: С иерархией проектов - label_version_sharing_system: Со вÑеми проектами - label_version_sharing_descendants: С подпроектами - label_version_sharing_tree: С деревом проектов - label_version_sharing_none: Без ÑовмеÑтного иÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ - error_can_not_archive_project: Этот проект не может быть заархивирован - button_duplicate: Дублировать - button_copy_and_follow: Копировать и продолжить - label_copy_source: ИÑточник - setting_issue_done_ratio: РаÑÑчитывать готовноÑть задачи Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ Ð¿Ð¾Ð»Ñ - setting_issue_done_ratio_issue_status: Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð·Ð°Ð´Ð°Ñ‡Ð¸ - error_issue_done_ratios_not_updated: Параметр готовноÑть задач не обновлен - error_workflow_copy_target: Выберите целевые трекеры и роли - setting_issue_done_ratio_issue_field: ГотовноÑть задачи - label_copy_same_as_target: То же, что и у цели - label_copy_target: Цель - notice_issue_done_ratios_updated: Параметр «Ð³Ð¾Ñ‚овноÑть» обновлен. - error_workflow_copy_source: Выберите иÑходный трекер или роль - label_update_issue_done_ratios: Обновить готовноÑть задач - setting_start_of_week: День начала недели - label_api_access_key: Ключ доÑтупа к API - text_line_separated: Разрешено неÑколько значений (по одному значению в Ñтроку). - label_revision_id: Ð ÐµÐ²Ð¸Ð·Ð¸Ñ %{value} - permission_view_issues: ПроÑмотр задач - label_display_used_statuses_only: Отображать только те ÑтатуÑÑ‹, которые иÑпользуютÑÑ Ð² Ñтом трекере - label_api_access_key_created_on: Ключ доÑтуп к API был Ñоздан %{value} назад - label_feeds_access_key: Ключ доÑтупа к RSS - notice_api_access_key_reseted: Ваш ключ доÑтупа к API был Ñброшен. - setting_rest_api_enabled: Включить веб-ÑÐµÑ€Ð²Ð¸Ñ REST - button_show: Показать - label_missing_api_access_key: ОтÑутÑтвует ключ доÑтупа к API - label_missing_feeds_access_key: ОтÑутÑтвует ключ доÑтупа к RSS - setting_mail_handler_body_delimiters: Урезать пиÑьмо поÑле одной из Ñтих Ñтрок - permission_add_subprojects: Создание подпроектов - label_subproject_new: Ðовый подпроект - text_own_membership_delete_confirmation: |- - Ð’Ñ‹ ÑобираетеÑÑŒ удалить некоторые или вÑе права, из-за чего могут пропаÑть права на редактирование Ñтого проекта. - Продолжить? - label_close_versions: Закрыть завершенные верÑии - label_board_sticky: Прикреплена - label_board_locked: Заблокирована - field_principal: Ð˜Ð¼Ñ - text_zoom_out: Отдалить - text_zoom_in: Приблизить - notice_unable_delete_time_entry: Ðевозможно удалить запиÑÑŒ журнала. - label_overall_spent_time: Ð’Ñего затрачено времени - label_user_mail_option_none: Ðет Ñобытий - field_member_of_group: Группа назначенного - field_assigned_to_role: Роль назначенного - notice_not_authorized_archived_project: Запрашиваемый проект был архивирован. - label_principal_search: "Ðайти Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¸Ð»Ð¸ группу:" - label_user_search: "Ðайти пользователÑ:" - field_visible: Видимое - setting_emails_header: Заголовок пиÑьма - - setting_commit_logtime_activity_id: ДейÑтвие Ð´Ð»Ñ ÑƒÑ‡ÐµÑ‚Ð° времени - text_time_logged_by_changeset: Учтено в редакции %{value}. - setting_commit_logtime_enabled: Включить учет времени - notice_gantt_chart_truncated: Диаграмма будет уÑечена, поÑкольку превышено макÑимальное кол-во Ñлементов, которые могут отображатьÑÑ (%{max}) - setting_gantt_items_limit: МакÑимальное кол-во Ñлементов отображаемых на диаграмме Ганта - field_warn_on_leaving_unsaved: Предупреждать при закрытии Ñтраницы Ñ Ð½ÐµÑохраненным текÑтом - text_warn_on_leaving_unsaved: Ð¢ÐµÐºÑƒÑ‰Ð°Ñ Ñтраница Ñодержит неÑохраненный текÑÑ‚, который будет потерÑн, еÑли вы покинете Ñту Ñтраницу. - label_my_queries: Мои Ñохраненные запроÑÑ‹ - text_journal_changed_no_detail: "%{label} обновлено" - label_news_comment_added: Добавлен комментарий к новоÑти - button_expand_all: Развернуть вÑе - button_collapse_all: Свернуть вÑе - label_additional_workflow_transitions_for_assignee: Дополнительные переходы, когда пользователь ÑвлÑетÑÑ Ð¸Ñполнителем - label_additional_workflow_transitions_for_author: Дополнительные переходы, когда пользователь ÑвлÑетÑÑ Ð°Ð²Ñ‚Ð¾Ñ€Ð¾Ð¼ - label_bulk_edit_selected_time_entries: МаÑÑовое изменение выбранных запиÑей затраченного времени - text_time_entries_destroy_confirmation: Ð’Ñ‹ уверены что хотите удалить выбранные запиÑи затраченного времени? - label_role_anonymous: Ðноним - label_role_non_member: Ðе учаÑтник - label_issue_note_added: Примечание добавлено - label_issue_status_updated: Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½ - label_issue_priority_updated: Приоритет обновлен - label_issues_visibility_own: Задачи Ñозданные или назначенные пользователю - field_issues_visibility: ВидимоÑть задач - label_issues_visibility_all: Ð’Ñе задачи - permission_set_own_issues_private: УÑтановление видимоÑти (общаÑ/чаÑтнаÑ) Ð´Ð»Ñ ÑобÑтвенных задач - field_is_private: ЧаÑÑ‚Ð½Ð°Ñ - permission_set_issues_private: УÑтановление видимоÑти (общаÑ/чаÑтнаÑ) Ð´Ð»Ñ Ð·Ð°Ð´Ð°Ñ‡ - label_issues_visibility_public: Только общие задачи - text_issues_destroy_descendants_confirmation: Так же будет удалено %{count} задач(и). - field_commit_logs_encoding: Кодировка комментариев в хранилище - field_scm_path_encoding: Кодировка пути - text_scm_path_encoding_note: "По умолчанию: UTF-8" - field_path_to_repository: Путь к хранилищу - field_root_directory: ÐšÐ¾Ñ€Ð½ÐµÐ²Ð°Ñ Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ - field_cvs_module: Модуль - field_cvsroot: CVSROOT - text_mercurial_repository_note: Локальное хранилище (например, /hgrepo, c:\hgrepo) - text_scm_command: Команда - text_scm_command_version: ВерÑÐ¸Ñ - label_git_report_last_commit: Указывать поÑледнее Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð² и директорий - text_scm_config: Ð’Ñ‹ можете наÑтроить команды SCM в файле config/configuration.yml. ПожалуйÑта, перезапуÑтите приложение поÑле Ñ€ÐµÐ´Ð°ÐºÑ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ñтого файла. - text_scm_command_not_available: Команда ÑиÑтемы ÐºÐ¾Ð½Ñ‚Ñ€Ð¾Ð»Ñ Ð²ÐµÑ€Ñий недоÑтупна. ПожалуйÑта, проверьте наÑтройки в админиÑтративной панели. - notice_issue_successful_create: Задача %{id} Ñоздана. - label_between: между - setting_issue_group_assignment: Разрешить назначение задач группам пользователей - label_diff: diff - text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) - description_query_sort_criteria_direction: Sort direction - description_project_scope: Search scope - description_filter: Filter - description_user_mail_notification: Mail notification settings - description_date_from: Enter start date - description_message_content: Message content - description_available_columns: Available Columns - description_date_range_interval: Choose range by selecting start and end date - description_issue_category_reassign: Choose issue category - description_search: Searchfield - description_notes: Notes - description_date_range_list: Choose range from list - description_choose_project: Projects - description_date_to: Enter end date - description_query_sort_criteria_attribute: Sort attribute - description_wiki_subpages_reassign: Choose new parent page - description_selected_columns: Selected Columns - label_parent_revision: Parent - label_child_revision: Child - error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. - setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues - button_edit_section: Edit this section - setting_repositories_encodings: Attachments and repositories encodings - description_all_columns: All Columns - button_export: Export - label_export_options: "%{export_format} export options" - error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/68/689ac1bb7ff5cc9966ab0877e8f6f03c5f72ee2b.svn-base --- a/.svn/pristine/68/689ac1bb7ff5cc9966ab0877e8f6f03c5f72ee2b.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,282 +0,0 @@ -module RedmineDiff - class Diff - - VERSION = 0.3 - - def Diff.lcs(a, b) - astart = 0 - bstart = 0 - afinish = a.length-1 - bfinish = b.length-1 - mvector = [] - - # First we prune off any common elements at the beginning - while (astart <= afinish && bstart <= afinish && a[astart] == b[bstart]) - mvector[astart] = bstart - astart += 1 - bstart += 1 - end - - # now the end - while (astart <= afinish && bstart <= bfinish && a[afinish] == b[bfinish]) - mvector[afinish] = bfinish - afinish -= 1 - bfinish -= 1 - end - - bmatches = b.reverse_hash(bstart..bfinish) - thresh = [] - links = [] - - (astart..afinish).each { |aindex| - aelem = a[aindex] - next unless bmatches.has_key? aelem - k = nil - bmatches[aelem].reverse.each { |bindex| - if k && (thresh[k] > bindex) && (thresh[k-1] < bindex) - thresh[k] = bindex - else - k = thresh.replacenextlarger(bindex, k) - end - links[k] = [ (k==0) ? nil : links[k-1], aindex, bindex ] if k - } - } - - if !thresh.empty? - link = links[thresh.length-1] - while link - mvector[link[1]] = link[2] - link = link[0] - end - end - - return mvector - end - - def makediff(a, b) - mvector = Diff.lcs(a, b) - ai = bi = 0 - while ai < mvector.length - bline = mvector[ai] - if bline - while bi < bline - discardb(bi, b[bi]) - bi += 1 - end - match(ai, bi) - bi += 1 - else - discarda(ai, a[ai]) - end - ai += 1 - end - while ai < a.length - discarda(ai, a[ai]) - ai += 1 - end - while bi < b.length - discardb(bi, b[bi]) - bi += 1 - end - match(ai, bi) - 1 - end - - def compactdiffs - diffs = [] - @diffs.each { |df| - i = 0 - curdiff = [] - while i < df.length - whot = df[i][0] - s = @isstring ? df[i][2].chr : [df[i][2]] - p = df[i][1] - last = df[i][1] - i += 1 - while df[i] && df[i][0] == whot && df[i][1] == last+1 - s << df[i][2] - last = df[i][1] - i += 1 - end - curdiff.push [whot, p, s] - end - diffs.push curdiff - } - return diffs - end - - attr_reader :diffs, :difftype - - def initialize(diffs_or_a, b = nil, isstring = nil) - if b.nil? - @diffs = diffs_or_a - @isstring = isstring - else - @diffs = [] - @curdiffs = [] - makediff(diffs_or_a, b) - @difftype = diffs_or_a.class - end - end - - def match(ai, bi) - @diffs.push @curdiffs unless @curdiffs.empty? - @curdiffs = [] - end - - def discarda(i, elem) - @curdiffs.push ['-', i, elem] - end - - def discardb(i, elem) - @curdiffs.push ['+', i, elem] - end - - def compact - return Diff.new(compactdiffs) - end - - def compact! - @diffs = compactdiffs - end - - def inspect - @diffs.inspect - end - - end -end - -module Diffable - def diff(b) - RedmineDiff::Diff.new(self, b) - end - - # Create a hash that maps elements of the array to arrays of indices - # where the elements are found. - - def reverse_hash(range = (0...self.length)) - revmap = {} - range.each { |i| - elem = self[i] - if revmap.has_key? elem - revmap[elem].push i - else - revmap[elem] = [i] - end - } - return revmap - end - - def replacenextlarger(value, high = nil) - high ||= self.length - if self.empty? || value > self[-1] - push value - return high - end - # binary search for replacement point - low = 0 - while low < high - index = (high+low)/2 - found = self[index] - return nil if value == found - if value > found - low = index + 1 - else - high = index - end - end - - self[low] = value - # $stderr << "replace #{value} : 0/#{low}/#{init_high} (#{steps} steps) (#{init_high-low} off )\n" - # $stderr.puts self.inspect - #gets - #p length - low - return low - end - - def patch(diff) - newary = nil - if diff.difftype == String - newary = diff.difftype.new('') - else - newary = diff.difftype.new - end - ai = 0 - bi = 0 - diff.diffs.each { |d| - d.each { |mod| - case mod[0] - when '-' - while ai < mod[1] - newary << self[ai] - ai += 1 - bi += 1 - end - ai += 1 - when '+' - while bi < mod[1] - newary << self[ai] - ai += 1 - bi += 1 - end - newary << mod[2] - bi += 1 - else - raise "Unknown diff action" - end - } - } - while ai < self.length - newary << self[ai] - ai += 1 - bi += 1 - end - return newary - end -end - -class Array - include Diffable -end - -class String - include Diffable -end - -=begin - = Diff - (({diff.rb})) - computes the differences between two arrays or - strings. Copyright (C) 2001 Lars Christensen - - == Synopsis - - diff = Diff.new(a, b) - b = a.patch(diff) - - == Class Diff - === Class Methods - --- Diff.new(a, b) - --- a.diff(b) - Creates a Diff object which represent the differences between - ((|a|)) and ((|b|)). ((|a|)) and ((|b|)) can be either be arrays - of any objects, strings, or object of any class that include - module ((|Diffable|)) - - == Module Diffable - The module ((|Diffable|)) is intended to be included in any class for - which differences are to be computed. Diffable is included into String - and Array when (({diff.rb})) is (({require}))'d. - - Classes including Diffable should implement (({[]})) to get element at - integer indices, (({<<})) to append elements to the object and - (({ClassName#new})) should accept 0 arguments to create a new empty - object. - - === Instance Methods - --- Diffable#patch(diff) - Applies the differences from ((|diff|)) to the object ((|obj|)) - and return the result. ((|obj|)) is not changed. ((|obj|)) and - can be either an array or a string, but must match the object - from which the ((|diff|)) was created. -=end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/68/68cec9b340a32b5033a24e6ea73291b5b640c745.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/68/68cec9b340a32b5033a24e6ea73291b5b640c745.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,53 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 CommentsController < ApplicationController + default_search_scope :news + model_object News + before_filter :find_model_object + before_filter :find_project_from_association + before_filter :authorize + + def create + raise Unauthorized unless @news.commentable? + + @comment = Comment.new + @comment.safe_attributes = params[:comment] + @comment.author = User.current + if @news.comments << @comment + flash[:notice] = l(:label_comment_added) + end + + redirect_to news_path(@news) + end + + def destroy + @news.comments.find(params[:comment_id]).destroy + redirect_to news_path(@news) + end + + private + + # ApplicationController's find_model_object sets it based on the controller + # name so it needs to be overriden and set to @news instead + def find_model_object + super + @news = @object + @comment = nil + @news + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/68/68ece530a0ccbfc8971b82acf79987aea8d5c3f8.svn-base --- a/.svn/pristine/68/68ece530a0ccbfc8971b82acf79987aea8d5c3f8.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -<%= wiki_page_breadcrumb(@page) %> - -

    <%=h @page.pretty_title %>

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

    <%= l(:text_wiki_page_destroy_question, :descendants => @descendants_count) %>

    -


    - -<% if @reassignable_to.any? %> -
    -: -<%= label_tag "reassign_to_id", l(:description_wiki_subpages_reassign), :class => "hidden-for-sighted" %> -<%= select_tag 'reassign_to_id', wiki_page_options_for_select(@reassignable_to), - :onclick => "$('todo_reassign').checked = true;" %> -<% end %> -

    -
    - -<%= submit_tag l(:button_apply) %> -<%= link_to l(:button_cancel), :controller => 'wiki', :action => 'show', :project_id => @project, :id => @page.title %> -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/69/69158657960dc5c8b5881d15914dd410bb7c7a73.svn-base --- a/.svn/pristine/69/69158657960dc5c8b5881d15914dd410bb7c7a73.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -class Namespace::AppAndPluginController < ApplicationController - def an_action - render_class_and_action 'from app' - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/69/69209f3722ef1c32ee917d4039ec4d55989dd5fc.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/69/69209f3722ef1c32ee917d4039ec4d55989dd5fc.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,194 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class IssuesHelperTest < ActionView::TestCase + include ApplicationHelper + include IssuesHelper + include CustomFieldsHelper + include ERB::Util + + fixtures :projects, :trackers, :issue_statuses, :issues, + :enumerations, :users, :issue_categories, + :projects_trackers, + :roles, + :member_roles, + :members, + :enabled_modules, + :custom_fields, + :attachments, + :versions + + def setup + super + set_language_if_valid('en') + User.current = nil + end + + def test_issue_heading + assert_equal "Bug #1", issue_heading(Issue.find(1)) + end + + def test_issues_destroy_confirmation_message_with_one_root_issue + assert_equal l(:text_issues_destroy_confirmation), issues_destroy_confirmation_message(Issue.find(1)) + end + + def test_issues_destroy_confirmation_message_with_an_arrayt_of_root_issues + assert_equal l(:text_issues_destroy_confirmation), issues_destroy_confirmation_message(Issue.find([1, 2])) + end + + def test_issues_destroy_confirmation_message_with_one_parent_issue + Issue.find(2).update_attribute :parent_issue_id, 1 + assert_equal l(:text_issues_destroy_confirmation) + "\n" + l(:text_issues_destroy_descendants_confirmation, :count => 1), + issues_destroy_confirmation_message(Issue.find(1)) + end + + def test_issues_destroy_confirmation_message_with_one_parent_issue_and_its_child + Issue.find(2).update_attribute :parent_issue_id, 1 + assert_equal l(:text_issues_destroy_confirmation), issues_destroy_confirmation_message(Issue.find([1, 2])) + end + + test 'IssuesHelper#show_detail with no_html should show a changing attribute' do + detail = JournalDetail.new(:property => 'attr', :old_value => '40', :value => '100', :prop_key => 'done_ratio') + assert_equal "% Done changed from 40 to 100", show_detail(detail, true) + end + + test 'IssuesHelper#show_detail with no_html should show a new attribute' do + detail = JournalDetail.new(:property => 'attr', :old_value => nil, :value => '100', :prop_key => 'done_ratio') + assert_equal "% Done set to 100", show_detail(detail, true) + end + + test 'IssuesHelper#show_detail with no_html should show a deleted attribute' do + detail = JournalDetail.new(:property => 'attr', :old_value => '50', :value => nil, :prop_key => 'done_ratio') + assert_equal "% Done deleted (50)", show_detail(detail, true) + end + + test 'IssuesHelper#show_detail with html should show a changing attribute with HTML highlights' do + detail = JournalDetail.new(:property => 'attr', :old_value => '40', :value => '100', :prop_key => 'done_ratio') + html = show_detail(detail, false) + + assert_include '% Done', html + assert_include '40', html + assert_include '100', html + end + + test 'IssuesHelper#show_detail with html should show a new attribute with HTML highlights' do + detail = JournalDetail.new(:property => 'attr', :old_value => nil, :value => '100', :prop_key => 'done_ratio') + html = show_detail(detail, false) + + assert_include '% Done', html + assert_include '100', html + end + + test 'IssuesHelper#show_detail with html should show a deleted attribute with HTML highlights' do + detail = JournalDetail.new(:property => 'attr', :old_value => '50', :value => nil, :prop_key => 'done_ratio') + html = show_detail(detail, false) + + assert_include '% Done', html + assert_include '50', html + end + + test 'IssuesHelper#show_detail with a start_date attribute should format the dates' do + detail = JournalDetail.new( + :property => 'attr', + :old_value => '2010-01-01', + :value => '2010-01-31', + :prop_key => 'start_date' + ) + with_settings :date_format => '%m/%d/%Y' do + assert_match "01/31/2010", show_detail(detail, true) + assert_match "01/01/2010", show_detail(detail, true) + end + end + + test 'IssuesHelper#show_detail with a due_date attribute should format the dates' do + detail = JournalDetail.new( + :property => 'attr', + :old_value => '2010-01-01', + :value => '2010-01-31', + :prop_key => 'due_date' + ) + with_settings :date_format => '%m/%d/%Y' do + assert_match "01/31/2010", show_detail(detail, true) + assert_match "01/01/2010", show_detail(detail, true) + end + end + + test 'IssuesHelper#show_detail should show old and new values with a project attribute' do + detail = JournalDetail.new(:property => 'attr', :prop_key => 'project_id', :old_value => 1, :value => 2) + assert_match 'eCookbook', show_detail(detail, true) + assert_match 'OnlineStore', show_detail(detail, true) + end + + test 'IssuesHelper#show_detail should show old and new values with a issue status attribute' do + detail = JournalDetail.new(:property => 'attr', :prop_key => 'status_id', :old_value => 1, :value => 2) + assert_match 'New', show_detail(detail, true) + assert_match 'Assigned', show_detail(detail, true) + end + + test 'IssuesHelper#show_detail should show old and new values with a tracker attribute' do + detail = JournalDetail.new(:property => 'attr', :prop_key => 'tracker_id', :old_value => 1, :value => 2) + assert_match 'Bug', show_detail(detail, true) + assert_match 'Feature request', show_detail(detail, true) + end + + test 'IssuesHelper#show_detail should show old and new values with a assigned to attribute' do + detail = JournalDetail.new(:property => 'attr', :prop_key => 'assigned_to_id', :old_value => 1, :value => 2) + assert_match 'Redmine Admin', show_detail(detail, true) + assert_match 'John Smith', show_detail(detail, true) + end + + test 'IssuesHelper#show_detail should show old and new values with a priority attribute' do + detail = JournalDetail.new(:property => 'attr', :prop_key => 'priority_id', :old_value => 4, :value => 5) + assert_match 'Low', show_detail(detail, true) + assert_match 'Normal', show_detail(detail, true) + end + + test 'IssuesHelper#show_detail should show old and new values with a category attribute' do + detail = JournalDetail.new(:property => 'attr', :prop_key => 'category_id', :old_value => 1, :value => 2) + assert_match 'Printing', show_detail(detail, true) + assert_match 'Recipes', show_detail(detail, true) + end + + test 'IssuesHelper#show_detail should show old and new values with a fixed version attribute' do + detail = JournalDetail.new(:property => 'attr', :prop_key => 'fixed_version_id', :old_value => 1, :value => 2) + assert_match '0.1', show_detail(detail, true) + assert_match '1.0', show_detail(detail, true) + end + + test 'IssuesHelper#show_detail should show old and new values with a estimated hours attribute' do + detail = JournalDetail.new(:property => 'attr', :prop_key => 'estimated_hours', :old_value => '5', :value => '6.3') + assert_match '5.00', show_detail(detail, true) + assert_match '6.30', show_detail(detail, true) + end + + test 'IssuesHelper#show_detail should show old and new values with a custom field' do + detail = JournalDetail.new(:property => 'cf', :prop_key => '1', :old_value => 'MySQL', :value => 'PostgreSQL') + assert_equal 'Database changed from MySQL to PostgreSQL', show_detail(detail, true) + end + + test 'IssuesHelper#show_detail should show added file' do + detail = JournalDetail.new(:property => 'attachment', :prop_key => '1', :old_value => nil, :value => 'error281.txt') + assert_match 'error281.txt', show_detail(detail, true) + end + + test 'IssuesHelper#show_detail should show removed file' do + detail = JournalDetail.new(:property => 'attachment', :prop_key => '1', :old_value => 'error281.txt', :value => nil) + assert_match 'error281.txt', show_detail(detail, true) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/69/6975387439910072689171fac66ca5755876b0a7.svn-base --- a/.svn/pristine/69/6975387439910072689171fac66ca5755876b0a7.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,107 +0,0 @@ -require File.expand_path('../../test_helper', __FILE__) -require 'issue_statuses_controller' - -# Re-raise errors caught by the controller. -class IssueStatusesController; def rescue_action(e) raise e end; end - - -class IssueStatusesControllerTest < ActionController::TestCase - fixtures :issue_statuses, :issues - - def setup - @controller = IssueStatusesController.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' - end - - def test_index_by_anonymous_should_redirect_to_login_form - @request.session[:user_id] = nil - get :index - assert_redirected_to '/login?back_url=http%3A%2F%2Ftest.host%2Fissue_statuses' - end - - def test_index_by_user_should_respond_with_406 - @request.session[:user_id] = 2 - get :index - assert_response 406 - end - - def test_new - get :new - assert_response :success - assert_template 'new' - end - - def test_create - assert_difference 'IssueStatus.count' do - post :create, :issue_status => {:name => 'New status'} - end - assert_redirected_to :action => 'index' - status = IssueStatus.find(:first, :order => 'id DESC') - assert_equal 'New status', status.name - end - - def test_edit - get :edit, :id => '3' - assert_response :success - assert_template 'edit' - end - - def test_update - put :update, :id => '3', :issue_status => {:name => 'Renamed status'} - assert_redirected_to :action => 'index' - status = IssueStatus.find(3) - assert_equal 'Renamed status', status.name - end - - def test_destroy - Issue.delete_all("status_id = 1") - - assert_difference 'IssueStatus.count', -1 do - delete :destroy, :id => '1' - end - assert_redirected_to :action => 'index' - assert_nil IssueStatus.find_by_id(1) - end - - def test_destroy_should_block_if_status_in_use - assert_not_nil Issue.find_by_status_id(1) - - assert_no_difference 'IssueStatus.count' do - delete :destroy, :id => '1' - end - assert_redirected_to :action => 'index' - assert_not_nil IssueStatus.find_by_id(1) - end - - context "on POST to :update_issue_done_ratio" do - context "with Setting.issue_done_ratio using the issue_field" do - setup do - Setting.issue_done_ratio = 'issue_field' - post :update_issue_done_ratio - end - - should_set_the_flash_to /not updated/ - should_redirect_to('the index') { '/issue_statuses' } - end - - context "with Setting.issue_done_ratio using the issue_status" do - setup do - Setting.issue_done_ratio = 'issue_status' - post :update_issue_done_ratio - end - - should_set_the_flash_to /Issue done ratios updated/ - should_redirect_to('the index') { '/issue_statuses' } - end - end - -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/69/6985b7da5db7388f839f13e1c4ad6f39ec6acc94.svn-base --- a/.svn/pristine/69/6985b7da5db7388f839f13e1c4ad6f39ec6acc94.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,94 +0,0 @@ -require File.expand_path('../../test_helper', __FILE__) - -class AuthSourcesControllerTest < ActionController::TestCase - - def setup - @request.session[:user_id] = 1 - end - - context "get :index" do - setup do - get :index - end - - should_assign_to :auth_sources - should_assign_to :auth_source_pages - should_respond_with :success - should_render_template :index - end - - context "get :new" do - setup do - get :new - end - - should_assign_to :auth_source - should_respond_with :success - should_render_template :new - - should "initilize a new AuthSource" do - assert_equal AuthSource, assigns(:auth_source).class - assert assigns(:auth_source).new_record? - end - end - - context "post :create" do - setup do - post :create, :auth_source => {:name => 'Test'} - end - - should_respond_with :redirect - should_redirect_to("index") {{:action => 'index'}} - should_set_the_flash_to /success/i - end - - context "get :edit" do - setup do - @auth_source = AuthSource.generate!(:name => 'TestEdit') - get :edit, :id => @auth_source.id - end - - should_assign_to(:auth_source) {@auth_source} - should_respond_with :success - should_render_template :edit - end - - context "post :update" do - setup do - @auth_source = AuthSource.generate!(:name => 'TestEdit') - post :update, :id => @auth_source.id, :auth_source => {:name => 'TestUpdate'} - end - - should_respond_with :redirect - should_redirect_to("index") {{:action => 'index'}} - should_set_the_flash_to /update/i - end - - context "post :destroy" do - setup do - @auth_source = AuthSource.generate!(:name => 'TestEdit') - end - - context "without users" do - setup do - post :destroy, :id => @auth_source.id - end - - should_respond_with :redirect - should_redirect_to("index") {{:action => 'index'}} - should_set_the_flash_to /deletion/i - end - - context "with users" do - setup do - User.generate!(:auth_source => @auth_source) - post :destroy, :id => @auth_source.id - end - - should_respond_with :redirect - should "not destroy the AuthSource" do - assert AuthSource.find(@auth_source.id) - end - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/69/698f61b75b6827fc986074ba0064676c404b5ac4.svn-base --- a/.svn/pristine/69/698f61b75b6827fc986074ba0064676c404b5ac4.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,127 +0,0 @@ -// ** I18N - -// Calendar LV language -// Translation: Dzintars Bergs, dzintars.bergs@gmail.com -// 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 -("SvÄ“tdiena", - "Pirmdiena", - "Otrdiena", - "TreÅ¡diena", - "Ceturtdiena", - "Piektdiena", - "Sestdiena", - "SvÄ“tdiena"); - -// 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 -("Sv", - "Pr", - "Ot", - "Tr", - "Ct", - "Pk", - "St", - "Sv"); - -// 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 -("JanvÄris", - "FebruÄris", - "Marts", - "AprÄ«lis", - "Maijs", - "JÅ«nijs", - "JÅ«lijs", - "Augusts", - "Septembris", - "Oktobris", - "Novembris", - "Decembris"); - -// short month names -Calendar._SMN = new Array -("Jan", - "Feb", - "Mar", - "Apr", - "Mai", - "JÅ«n", - "JÅ«l", - "Aug", - "Sep", - "Okt", - "Nov", - "Dec"); - -// tooltips -Calendar._TT = {}; -Calendar._TT["INFO"] = "Par kalendÄru"; - -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"] = "Iepriekšējais gads (pieturÄ“t, lai atvÄ“rtu izvÄ“lni)"; -Calendar._TT["PREV_MONTH"] = "Iepriekšējais mÄ“nesis (pieturÄ“t, lai atvÄ“rtu izvÄ“lni)"; -Calendar._TT["GO_TODAY"] = "Iet uz Å¡odienu"; -Calendar._TT["NEXT_MONTH"] = "NÄkoÅ¡ais mÄ“nesis (pieturÄ“t, lai atvÄ“rtu izvÄ“lni)"; -Calendar._TT["NEXT_YEAR"] = "NÄkoÅ¡ais gads (pieturÄ“t, lai atvÄ“rtu izvÄ“lni)"; -Calendar._TT["SEL_DATE"] = "IzvÄ“lieties datumu"; -Calendar._TT["DRAG_TO_MOVE"] = "Vilkt, lai pÄrvietotu"; -Calendar._TT["PART_TODAY"] = "(Å¡odiena)"; - -// 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"] = "RÄdÄ«t %s pirmo"; - -// 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"] = "AizvÄ“rt"; -Calendar._TT["TODAY"] = "Å odiena"; -Calendar._TT["TIME_PART"] = "(Shift-)Click vai ievilkt, lai mainÄ«tu vÄ“rtÄ«bu"; - -// date formats -Calendar._TT["DEF_DATE_FORMAT"] = "%d.%m.%Y"; -Calendar._TT["TT_DATE_FORMAT"] = " %b, %a %e"; - -Calendar._TT["WK"] = "wk"; -Calendar._TT["TIME"] = "Laiks:"; diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/69/699a58897b9f326128b2eda7e3ea2de970223d84.svn-base --- a/.svn/pristine/69/699a58897b9f326128b2eda7e3ea2de970223d84.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,56 +0,0 @@ -
    -<%= link_to(l(:button_edit), edit_version_path(@version), :class => 'icon icon-edit') if User.current.allowed_to?(:manage_versions, @version.project) %> -<%= link_to_if_authorized(l(:button_edit_associated_wikipage, :page_title => @version.wiki_page_title), {:controller => 'wiki', :action => 'edit', :project_id => @version.project, :id => Wiki.titleize(@version.wiki_page_title)}, :class => 'icon icon-edit') unless @version.wiki_page_title.blank? || @version.project.wiki.nil? %> -<%= link_to(l(:button_delete), version_path(@version, :back_url => url_for(:controller => 'versions', :action => 'index', :project_id => @version.project)), - :confirm => l(:text_are_you_sure), :method => :delete, :class => 'icon icon-del') if User.current.allowed_to?(:manage_versions, @version.project) %> -<%= call_hook(:view_versions_show_contextual, { :version => @version, :project => @project }) %> -
    - -

    <%= h(@version.name) %>

    - -
    -<%= render :partial => 'versions/overview', :locals => {:version => @version} %> -<%= render(:partial => "wiki/content", :locals => {:content => @version.wiki_page.content}) if @version.wiki_page %> - -
    -<% if @version.estimated_hours > 0 || User.current.allowed_to?(:view_time_entries, @project) %> -
    <%= l(:label_time_tracking) %> - - - - - -<% if User.current.allowed_to?(:view_time_entries, @project) %> - - - - -<% end %> -
    <%= l(:field_estimated_hours) %><%= html_hours(l_hours(@version.estimated_hours)) %>
    <%= l(:label_spent_time) %><%= html_hours(l_hours(@version.spent_hours)) %>
    -
    -<% end %> - -
    -<%= render_issue_status_by(@version, params[:status_by]) if @version.fixed_issues.count > 0 %> -
    -
    - -<% if @issues.present? %> -<% form_tag({}) do -%> - - - <%- @issues.each do |issue| -%> - - - - - <% end %> - -<% end %> -<%= context_menu issues_context_menu_path %> -<% end %> -
    - -<%= call_hook :view_versions_show_bottom, :version => @version %> - -<% html_title @version.name %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/69/69aea32e67c1e43bf9542a209fc02cf6c1b5854c.svn-base --- a/.svn/pristine/69/69aea32e67c1e43bf9542a209fc02cf6c1b5854c.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,193 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) -require 'repositories_controller' - -# Re-raise errors caught by the controller. -class RepositoriesController; def rescue_action(e) raise e end; end - -class RepositoriesBazaarControllerTest < ActionController::TestCase - fixtures :projects, :users, :roles, :members, :member_roles, - :repositories, :enabled_modules - - REPOSITORY_PATH = Rails.root.join('tmp/test/bazaar_repository/trunk').to_s - PRJ_ID = 3 - - def setup - @controller = RepositoriesController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - User.current = nil - @project = Project.find(PRJ_ID) - @repository = Repository::Bazaar.create( - :project => @project, - :url => REPOSITORY_PATH, - :log_encoding => 'UTF-8') - assert @repository - end - - if File.directory?(REPOSITORY_PATH) - def test_browse_root - get :show, :id => PRJ_ID - assert_response :success - assert_template 'show' - assert_not_nil assigns(:entries) - assert_equal 2, assigns(:entries).size - assert assigns(:entries).detect {|e| e.name == 'directory' && e.kind == 'dir'} - assert assigns(:entries).detect {|e| e.name == 'doc-mkdir.txt' && e.kind == 'file'} - end - - def test_browse_directory - get :show, :id => PRJ_ID, :path => ['directory'] - assert_response :success - assert_template 'show' - assert_not_nil assigns(:entries) - assert_equal ['doc-ls.txt', 'document.txt', 'edit.png'], assigns(:entries).collect(&:name) - entry = assigns(:entries).detect {|e| e.name == 'edit.png'} - assert_not_nil entry - assert_equal 'file', entry.kind - assert_equal 'directory/edit.png', entry.path - end - - def test_browse_at_given_revision - get :show, :id => PRJ_ID, :path => [], :rev => 3 - assert_response :success - assert_template 'show' - assert_not_nil assigns(:entries) - assert_equal ['directory', 'doc-deleted.txt', 'doc-ls.txt', 'doc-mkdir.txt'], - assigns(:entries).collect(&:name) - end - - def test_changes - get :changes, :id => PRJ_ID, :path => ['doc-mkdir.txt'] - assert_response :success - assert_template 'changes' - assert_tag :tag => 'h2', :content => 'doc-mkdir.txt' - end - - def test_entry_show - get :entry, :id => PRJ_ID, :path => ['directory', 'doc-ls.txt'] - assert_response :success - assert_template 'entry' - # Line 19 - assert_tag :tag => 'th', - :content => /29/, - :attributes => { :class => /line-num/ }, - :sibling => { :tag => 'td', :content => /Show help message/ } - end - - def test_entry_download - get :entry, :id => PRJ_ID, :path => ['directory', 'doc-ls.txt'], :format => 'raw' - assert_response :success - # File content - assert @response.body.include?('Show help message') - end - - def test_directory_entry - get :entry, :id => PRJ_ID, :path => ['directory'] - assert_response :success - assert_template 'show' - assert_not_nil assigns(:entry) - assert_equal 'directory', assigns(:entry).name - end - - def test_diff - # Full diff of changeset 3 - ['inline', 'sbs'].each do |dt| - get :diff, :id => PRJ_ID, :rev => 3, :type => dt - assert_response :success - assert_template 'diff' - # Line 11 removed - assert_tag :tag => 'th', - :content => '11', - :sibling => { :tag => 'td', - :attributes => { :class => /diff_out/ }, - :content => /Display more information/ } - end - end - - def test_annotate - get :annotate, :id => PRJ_ID, :path => ['doc-mkdir.txt'] - assert_response :success - assert_template 'annotate' - assert_tag :tag => 'th', :content => '2', - :sibling => { - :tag => 'td', - :child => { - :tag => 'a', - :content => '3' - } - } - assert_tag :tag => 'th', :content => '2', - :sibling => { :tag => 'td', :content => /jsmith/ } - assert_tag :tag => 'th', :content => '2', - :sibling => { - :tag => 'td', - :child => { - :tag => 'a', - :content => '3' - } - } - assert_tag :tag => 'th', :content => '2', - :sibling => { :tag => 'td', :content => /Main purpose/ } - end - - def test_destroy_valid_repository - @request.session[:user_id] = 1 # admin - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert @repository.changesets.count > 0 - - get :destroy, :id => PRJ_ID - assert_response 302 - @project.reload - assert_nil @project.repository - end - - def test_destroy_invalid_repository - @request.session[:user_id] = 1 # admin - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert @repository.changesets.count > 0 - - get :destroy, :id => PRJ_ID - assert_response 302 - @project.reload - assert_nil @project.repository - - @repository = Repository::Bazaar.create( - :project => @project, - :url => "/invalid", - :log_encoding => 'UTF-8') - assert @repository - @repository.fetch_changesets - @repository.reload - assert_equal 0, @repository.changesets.count - - get :destroy, :id => PRJ_ID - assert_response 302 - @project.reload - assert_nil @project.repository - end - else - puts "Bazaar test repository NOT FOUND. Skipping functional tests !!!" - def test_fake; assert true end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/69/69bc5c1c53ea4e9d8ad8c537b1338f42510d7272.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/69/69bc5c1c53ea4e9d8ad8c537b1338f42510d7272.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,435 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 MenuManager + class MenuError < StandardError #:nodoc: + end + + module MenuController + def self.included(base) + base.extend(ClassMethods) + end + + module ClassMethods + @@menu_items = Hash.new {|hash, key| hash[key] = {:default => key, :actions => {}}} + mattr_accessor :menu_items + + # Set the menu item name for a controller or specific actions + # Examples: + # * menu_item :tickets # => sets the menu name to :tickets for the whole controller + # * menu_item :tickets, :only => :list # => sets the menu name to :tickets for the 'list' action only + # * menu_item :tickets, :only => [:list, :show] # => sets the menu name to :tickets for 2 actions only + # + # The default menu item name for a controller is controller_name by default + # Eg. the default menu item name for ProjectsController is :projects + def menu_item(id, options = {}) + if actions = options[:only] + actions = [] << actions unless actions.is_a?(Array) + actions.each {|a| menu_items[controller_name.to_sym][:actions][a.to_sym] = id} + else + menu_items[controller_name.to_sym][:default] = id + end + end + end + + def menu_items + self.class.menu_items + end + + # Returns the menu item name according to the current action + def current_menu_item + @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 + + module MenuHelper + # Returns the current menu item name + def current_menu_item + controller.current_menu_item + end + + # Renders the application main menu + def render_main_menu(project) + render_menu((project && !project.new_record?) ? :project_menu : :application_menu, project) + end + + def display_main_menu?(project) + menu_name = project && !project.new_record? ? :project_menu : :application_menu + Redmine::MenuManager.items(menu_name).children.present? + end + + def render_menu(menu, project=nil) + links = [] + menu_items_for(menu, project) do |node| + links << render_menu_node(node, project) + end + links.empty? ? nil : content_tag('ul', links.join("\n").html_safe) + end + + def render_menu_node(node, project=nil) + if node.children.present? || !node.child_menus.nil? + return render_menu_node_with_children(node, project) + else + caption, url, selected = extract_node_details(node, project) + return content_tag('li', + render_single_menu_node(node, caption, url, selected)) + end + end + + def render_menu_node_with_children(node, project=nil) + caption, url, selected = extract_node_details(node, project) + + html = [].tap do |html| + html << '
  • ' + # Parent + html << render_single_menu_node(node, caption, url, selected) + + # Standard children + standard_children_list = "".html_safe.tap do |child_html| + node.children.each do |child| + child_html << render_menu_node(child, project) + end + end + + html << content_tag(:ul, standard_children_list, :class => 'menu-children') unless standard_children_list.empty? + + # Unattached children + unattached_children_list = render_unattached_children_menu(node, project) + html << content_tag(:ul, unattached_children_list, :class => 'menu-children unattached') unless unattached_children_list.blank? + + html << '
  • ' + end + return html.join("\n").html_safe + end + + # Returns a list of unattached children menu items + def render_unattached_children_menu(node, project) + return nil unless node.child_menus + + "".html_safe.tap do |child_html| + unattached_children = node.child_menus.call(project) + # Tree nodes support #each so we need to do object detection + if unattached_children.is_a? Array + unattached_children.each do |child| + child_html << content_tag(:li, render_unattached_menu_item(child, project)) + end + else + raise MenuError, ":child_menus must be an array of MenuItems" + end + end + end + + def render_single_menu_node(item, caption, url, selected) + link_to(h(caption), url, item.html_options(:selected => selected)) + end + + def render_unattached_menu_item(menu_item, project) + raise MenuError, ":child_menus must be an array of MenuItems" unless menu_item.is_a? MenuItem + + if User.current.allowed_to?(menu_item.url, project) + link_to(h(menu_item.caption), + menu_item.url, + menu_item.html_options) + end + end + + def menu_items_for(menu, project=nil) + items = [] + Redmine::MenuManager.items(menu).root.children.each do |node| + if allowed_node?(node, User.current, project) + if block_given? + yield node + else + items << node # TODO: not used? + end + end + end + return block_given? ? nil : items + end + + def extract_node_details(node, project=nil) + item = node + url = case item.url + when Hash + project.nil? ? item.url : {item.param => project}.merge(item.url) + when Symbol + send(item.url) + else + item.url + end + caption = item.caption(project) + return [caption, url, (current_menu_item == item.name)] + end + + # Checks if a user is allowed to access the menu item by: + # + # * Checking the url target (project only) + # * Checking the conditions of the item + def allowed_node?(node, user, project) + if project && user && !user.allowed_to?(node.url, project) + return false + end + if node.condition && !node.condition.call(project) + # Condition that doesn't pass + return false + end + return true + end + end + + class << self + def map(menu_name) + @items ||= {} + mapper = Mapper.new(menu_name.to_sym, @items) + if block_given? + yield mapper + else + mapper + end + end + + def items(menu_name) + @items[menu_name.to_sym] || MenuNode.new(:root, {}) + end + end + + class Mapper + def initialize(menu, items) + items[menu] ||= MenuNode.new(:root, {}) + @menu = menu + @menu_items = items[menu] + end + + # Adds an item at the end of the menu. Available options: + # * param: the parameter name that is used for the project id (default is :id) + # * if: a Proc that is called before rendering the item, the item is displayed only if it returns true + # * caption that can be: + # * a localized string Symbol + # * a String + # * a Proc that can take the project as argument + # * before, after: specify where the menu item should be inserted (eg. :after => :activity) + # * parent: menu item will be added as a child of another named menu (eg. :parent => :issues) + # * children: a Proc that is called before rendering the item. The Proc should return an array of MenuItems, which will be added as children to this item. + # eg. :children => Proc.new {|project| [Redmine::MenuManager::MenuItem.new(...)] } + # * last: menu item will stay at the end (eg. :last => true) + # * html_options: a hash of html options that are passed to link_to + def push(name, url, options={}) + options = options.dup + + if options[:parent] + subtree = self.find(options[:parent]) + if subtree + target_root = subtree + else + target_root = @menu_items.root + end + + else + target_root = @menu_items.root + end + + # menu item position + if first = options.delete(:first) + target_root.prepend(MenuItem.new(name, url, options)) + elsif before = options.delete(:before) + + if exists?(before) + target_root.add_at(MenuItem.new(name, url, options), position_of(before)) + else + target_root.add(MenuItem.new(name, url, options)) + end + + elsif after = options.delete(:after) + + if exists?(after) + target_root.add_at(MenuItem.new(name, url, options), position_of(after) + 1) + else + target_root.add(MenuItem.new(name, url, options)) + end + + elsif options[:last] # don't delete, needs to be stored + target_root.add_last(MenuItem.new(name, url, options)) + else + target_root.add(MenuItem.new(name, url, options)) + end + end + + # Removes a menu item + def delete(name) + if found = self.find(name) + @menu_items.remove!(found) + end + end + + # Checks if a menu item exists + def exists?(name) + @menu_items.any? {|node| node.name == name} + end + + def find(name) + @menu_items.find {|node| node.name == name} + end + + def position_of(name) + @menu_items.each do |node| + if node.name == name + return node.position + end + end + end + end + + class MenuNode + include Enumerable + attr_accessor :parent + attr_reader :last_items_count, :name + + def initialize(name, content = nil) + @name = name + @children = [] + @last_items_count = 0 + end + + def children + if block_given? + @children.each {|child| yield child} + else + @children + end + end + + # Returns the number of descendants + 1 + def size + @children.inject(1) {|sum, node| sum + node.size} + end + + def each &block + yield self + children { |child| child.each(&block) } + end + + # Adds a child at first position + def prepend(child) + add_at(child, 0) + end + + # Adds a child at given position + def add_at(child, position) + raise "Child already added" if find {|node| node.name == child.name} + + @children = @children.insert(position, child) + child.parent = self + child + end + + # Adds a child as last child + def add_last(child) + add_at(child, -1) + @last_items_count += 1 + child + end + + # Adds a child + def add(child) + position = @children.size - @last_items_count + add_at(child, position) + end + alias :<< :add + + # Removes a child + def remove!(child) + @children.delete(child) + @last_items_count -= +1 if child && child.last + child.parent = nil + child + end + + # Returns the position for this node in it's parent + def position + self.parent.children.index(self) + end + + # Returns the root for this node + def root + root = self + root = root.parent while root.parent + root + end + end + + class MenuItem < MenuNode + include Redmine::I18n + attr_reader :name, :url, :param, :condition, :parent, :child_menus, :last + + def initialize(name, url, options) + raise ArgumentError, "Invalid option :if for menu item '#{name}'" if options[:if] && !options[:if].respond_to?(:call) + raise ArgumentError, "Invalid option :html for menu item '#{name}'" if options[:html] && !options[:html].is_a?(Hash) + raise ArgumentError, "Cannot set the :parent to be the same as this item" if options[:parent] == name.to_sym + raise ArgumentError, "Invalid option :children for menu item '#{name}'" if options[:children] && !options[:children].respond_to?(:call) + @name = name + @url = url + @condition = options[:if] + @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(' ') + @parent = options[:parent] + @child_menus = options[:children] + @last = options[:last] || false + super @name.to_sym + end + + def caption(project=nil) + if @caption.is_a?(Proc) + c = @caption.call(project).to_s + c = @name.to_s.humanize if c.blank? + c + else + if @caption.nil? + l_or_humanize(name, :prefix => 'label_') + else + @caption.is_a?(Symbol) ? l(@caption) : @caption + end + 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 -r 0a574315af3e -r 4f746d8966dd .svn/pristine/69/69d93dae7af0a934d2b88863b74d127eebc3a82f.svn-base --- a/.svn/pristine/69/69d93dae7af0a934d2b88863b74d127eebc3a82f.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,20 +0,0 @@ -Description: - The plugin generator creates stubs for a new Redmine plugin. - -Example: - ./script/generate redmine_plugin meetings - create vendor/plugins/redmine_meetings/app/controllers - create vendor/plugins/redmine_meetings/app/helpers - create vendor/plugins/redmine_meetings/app/models - create vendor/plugins/redmine_meetings/app/views - create vendor/plugins/redmine_meetings/db/migrate - create vendor/plugins/redmine_meetings/lib/tasks - create vendor/plugins/redmine_meetings/assets/images - create vendor/plugins/redmine_meetings/assets/javascripts - create vendor/plugins/redmine_meetings/assets/stylesheets - create vendor/plugins/redmine_meetings/lang - create vendor/plugins/redmine_meetings/README - create vendor/plugins/redmine_meetings/init.rb - create vendor/plugins/redmine_meetings/lang/en.yml - create vendor/plugins/redmine_meetings/config/locales/en.yml - create vendor/plugins/redmine_meetings/test/test_helper.rb diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6a/6a0944d7e583de44e8a1af8231e3395612ce51a0.svn-base --- a/.svn/pristine/6a/6a0944d7e583de44e8a1af8231e3395612ce51a0.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,40 +0,0 @@ -<%= render :partial => 'action_menu' %> - -

    <%=l(:label_workflow)%>

    - -<% form_tag({}, :id => 'workflow_copy_form') do %> -
    -<%= l(:label_copy_source) %> -

    - - <%= select_tag('source_tracker_id', - "" + - "" + - options_from_collection_for_select(@trackers, 'id', 'name', @source_tracker && @source_tracker.id)) %> -

    -

    - - <%= select_tag('source_role_id', - "" + - "" + - options_from_collection_for_select(@roles, 'id', 'name', @source_role && @source_role.id)) %> -

    -
    - -
    -<%= l(:label_copy_target) %> -

    - - <%= select_tag 'target_tracker_ids', - "" + - options_from_collection_for_select(@trackers, 'id', 'name', @target_trackers && @target_trackers.map(&:id)), :multiple => true %> -

    -

    - - <%= select_tag 'target_role_ids', - "" + - options_from_collection_for_select(@roles, 'id', 'name', @target_roles && @target_roles.map(&:id)), :multiple => true %> -

    -
    -<%= submit_tag l(:button_copy) %> -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6a/6a4fdd885d26844cca5f9f69058f8424de0ff4e2.svn-base --- a/.svn/pristine/6a/6a4fdd885d26844cca5f9f69058f8424de0ff4e2.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,95 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -module Redmine - module Activity - # Class used to retrieve activity events - class Fetcher - attr_reader :user, :project, :scope - - # Needs to be unloaded in development mode - @@constantized_providers = Hash.new {|h,k| h[k] = Redmine::Activity.providers[k].collect {|t| t.constantize } } - - def initialize(user, options={}) - options.assert_valid_keys(:project, :with_subprojects, :author) - @user = user - @project = options[:project] - @options = options - - @scope = event_types - end - - # Returns an array of available event types - def event_types - return @event_types unless @event_types.nil? - - @event_types = Redmine::Activity.available_event_types - @event_types = @event_types.select {|o| @project.self_and_descendants.detect {|p| @user.allowed_to?("view_#{o}".to_sym, p)}} if @project - @event_types - end - - # Yields to filter the activity scope - def scope_select(&block) - @scope = @scope.select {|t| yield t } - end - - # Sets the scope - # Argument can be :all, :default or an array of event types - def scope=(s) - 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 - def default_scope! - @scope = Redmine::Activity.default_event_types - end - - # Returns an array of events for the given date range - # sorted in reverse chronological order - 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 - - e.sort! {|a,b| b.event_datetime <=> a.event_datetime} - - if options[:limit] - e = e.slice(0, options[:limit]) - end - e - end - - private - - def constantized_providers(event_type) - @@constantized_providers[event_type] - end - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6a/6a5d3fae16f610dc522054eb46ce6c8d182d8b9a.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/6a/6a5d3fae16f610dc522054eb46ce6c8d182d8b9a.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,425 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class UsersControllerTest < ActionController::TestCase + include Redmine::I18n + + fixtures :users, :projects, :members, :member_roles, :roles, + :custom_fields, :custom_values, :groups_users, + :auth_sources + + def setup + User.current = nil + @request.session[:user_id] = 1 # admin + end + + def test_index + get :index + assert_response :success + assert_template 'index' + end + + def test_index + get :index + assert_response :success + assert_template 'index' + assert_not_nil assigns(:users) + # active users only + assert_nil assigns(:users).detect {|u| !u.active?} + end + + def test_index_with_status_filter + get :index, :status => 3 + assert_response :success + assert_template 'index' + assert_not_nil assigns(:users) + assert_equal [3], assigns(:users).map(&:status).uniq + end + + def test_index_with_name_filter + get :index, :name => 'john' + assert_response :success + assert_template 'index' + users = assigns(:users) + assert_not_nil users + assert_equal 1, users.size + assert_equal 'John', users.first.firstname + end + + def test_index_with_group_filter + get :index, :group_id => '10' + assert_response :success + assert_template 'index' + users = assigns(:users) + assert users.any? + assert_equal([], (users - Group.find(10).users)) + assert_select 'select[name=group_id]' do + assert_select 'option[value=10][selected=selected]' + end + end + + def test_show + @request.session[:user_id] = nil + get :show, :id => 2 + assert_response :success + assert_template 'show' + assert_not_nil assigns(:user) + + assert_tag 'li', :content => /Phone number/ + end + + def test_show_should_not_display_hidden_custom_fields + @request.session[:user_id] = nil + UserCustomField.find_by_name('Phone number').update_attribute :visible, false + get :show, :id => 2 + assert_response :success + assert_template 'show' + assert_not_nil assigns(:user) + + assert_no_tag 'li', :content => /Phone number/ + end + + def test_show_should_not_fail_when_custom_values_are_nil + user = User.find(2) + + # Create a custom field to illustrate the issue + custom_field = CustomField.create!(:name => 'Testing', :field_format => 'text') + custom_value = user.custom_values.build(:custom_field => custom_field).save! + + get :show, :id => 2 + assert_response :success + end + + def test_show_inactive + @request.session[:user_id] = nil + get :show, :id => 5 + assert_response 404 + end + + def test_show_should_not_reveal_users_with_no_visible_activity_or_project + @request.session[:user_id] = nil + get :show, :id => 9 + assert_response 404 + end + + def test_show_inactive_by_admin + @request.session[:user_id] = 1 + get :show, :id => 5 + assert_response 200 + assert_not_nil assigns(:user) + end + + def test_show_displays_memberships_based_on_project_visibility + @request.session[:user_id] = 1 + get :show, :id => 2 + assert_response :success + memberships = assigns(:memberships) + assert_not_nil memberships + project_ids = memberships.map(&:project_id) + assert project_ids.include?(2) #private project admin can see + end + + def test_show_current_should_require_authentication + @request.session[:user_id] = nil + get :show, :id => 'current' + assert_response 302 + end + + def test_show_current + @request.session[:user_id] = 2 + get :show, :id => 'current' + assert_response :success + assert_template 'show' + assert_equal User.find(2), assigns(:user) + end + + def test_new + get :new + assert_response :success + assert_template :new + assert assigns(:user) + end + + def test_create + Setting.bcc_recipients = '1' + + assert_difference 'User.count' do + assert_difference 'ActionMailer::Base.deliveries.size' do + post :create, + :user => { + :firstname => 'John', + :lastname => 'Doe', + :login => 'jdoe', + :password => 'secret123', + :password_confirmation => 'secret123', + :mail => 'jdoe@gmail.com', + :mail_notification => 'none' + }, + :send_information => '1' + end + end + + user = User.first(:order => 'id DESC') + assert_redirected_to :controller => 'users', :action => 'edit', :id => user.id + + assert_equal 'John', user.firstname + assert_equal 'Doe', user.lastname + assert_equal 'jdoe', user.login + assert_equal 'jdoe@gmail.com', user.mail + assert_equal 'none', user.mail_notification + assert user.check_password?('secret123') + + mail = ActionMailer::Base.deliveries.last + assert_not_nil mail + assert_equal [user.mail], mail.bcc + assert_mail_body_match 'secret', mail + end + + def test_create_with_preferences + assert_difference 'User.count' do + post :create, + :user => { + :firstname => 'John', + :lastname => 'Doe', + :login => 'jdoe', + :password => 'secret123', + :password_confirmation => 'secret123', + :mail => 'jdoe@gmail.com', + :mail_notification => 'none' + }, + :pref => { + 'hide_mail' => '1', + 'time_zone' => 'Paris', + 'comments_sorting' => 'desc', + 'warn_on_leaving_unsaved' => '0' + } + end + user = User.first(:order => 'id DESC') + assert_equal 'jdoe', user.login + assert_equal true, user.pref.hide_mail + assert_equal 'Paris', user.pref.time_zone + assert_equal 'desc', user.pref[:comments_sorting] + assert_equal '0', user.pref[:warn_on_leaving_unsaved] + end + + def test_create_with_failure + assert_no_difference 'User.count' do + post :create, :user => {} + end + assert_response :success + assert_template 'new' + end + + def test_edit + get :edit, :id => 2 + assert_response :success + assert_template 'edit' + assert_equal User.find(2), assigns(:user) + end + + def test_update + ActionMailer::Base.deliveries.clear + put :update, :id => 2, + :user => {:firstname => 'Changed', :mail_notification => 'only_assigned'}, + :pref => {:hide_mail => '1', :comments_sorting => 'desc'} + user = User.find(2) + assert_equal 'Changed', user.firstname + assert_equal 'only_assigned', user.mail_notification + assert_equal true, user.pref[:hide_mail] + assert_equal 'desc', user.pref[:comments_sorting] + assert ActionMailer::Base.deliveries.empty? + end + + def test_update_with_failure + assert_no_difference 'User.count' do + put :update, :id => 2, :user => {:firstname => ''} + end + assert_response :success + assert_template 'edit' + end + + def test_update_with_group_ids_should_assign_groups + put :update, :id => 2, :user => {:group_ids => ['10']} + user = User.find(2) + assert_equal [10], user.group_ids + end + + def test_update_with_activation_should_send_a_notification + u = User.new(:firstname => 'Foo', :lastname => 'Bar', :mail => 'foo.bar@somenet.foo', :language => 'fr') + u.login = 'foo' + u.status = User::STATUS_REGISTERED + u.save! + ActionMailer::Base.deliveries.clear + Setting.bcc_recipients = '1' + + put :update, :id => u.id, :user => {:status => User::STATUS_ACTIVE} + assert u.reload.active? + mail = ActionMailer::Base.deliveries.last + assert_not_nil mail + assert_equal ['foo.bar@somenet.foo'], mail.bcc + assert_mail_body_match ll('fr', :notice_account_activated), mail + end + + def test_update_with_password_change_should_send_a_notification + ActionMailer::Base.deliveries.clear + Setting.bcc_recipients = '1' + + put :update, :id => 2, :user => {:password => 'newpass123', :password_confirmation => 'newpass123'}, :send_information => '1' + u = User.find(2) + assert u.check_password?('newpass123') + + mail = ActionMailer::Base.deliveries.last + assert_not_nil mail + assert_equal [u.mail], mail.bcc + assert_mail_body_match 'newpass123', mail + end + + def test_update_user_switchin_from_auth_source_to_password_authentication + # Configure as auth source + u = User.find(2) + u.auth_source = AuthSource.find(1) + u.save! + + put :update, :id => u.id, :user => {:auth_source_id => '', :password => 'newpass123', :password_confirmation => 'newpass123'} + + assert_equal nil, u.reload.auth_source + assert u.check_password?('newpass123') + end + + def test_update_notified_project + get :edit, :id => 2 + assert_response :success + assert_template 'edit' + u = User.find(2) + assert_equal [1, 2, 5], u.projects.collect{|p| p.id}.sort + assert_equal [1, 2, 5], u.notified_projects_ids.sort + assert_tag :tag => 'input', + :attributes => { + :id => 'notified_project_ids_', + :value => 1, + } + assert_equal 'all', u.mail_notification + put :update, :id => 2, + :user => { + :mail_notification => 'selected', + }, + :notified_project_ids => [1, 2] + u = User.find(2) + assert_equal 'selected', u.mail_notification + assert_equal [1, 2], u.notified_projects_ids.sort + end + + def test_destroy + assert_difference 'User.count', -1 do + delete :destroy, :id => 2 + end + assert_redirected_to '/users' + assert_nil User.find_by_id(2) + end + + def test_destroy_should_be_denied_for_non_admin_users + @request.session[:user_id] = 3 + + assert_no_difference 'User.count' do + get :destroy, :id => 2 + end + assert_response 403 + end + + def test_destroy_should_redirect_to_back_url_param + assert_difference 'User.count', -1 do + delete :destroy, :id => 2, :back_url => '/users?name=foo' + end + assert_redirected_to '/users?name=foo' + end + + def test_create_membership + assert_difference 'Member.count' do + post :edit_membership, :id => 7, :membership => { :project_id => 3, :role_ids => [2]} + end + assert_redirected_to :action => 'edit', :id => '7', :tab => 'memberships' + member = Member.first(:order => 'id DESC') + assert_equal User.find(7), member.principal + assert_equal [2], member.role_ids + assert_equal 3, member.project_id + end + + def test_create_membership_js_format + assert_difference 'Member.count' do + post :edit_membership, :id => 7, :membership => {:project_id => 3, :role_ids => [2]}, :format => 'js' + assert_response :success + assert_template 'edit_membership' + assert_equal 'text/javascript', response.content_type + end + member = Member.first(:order => 'id DESC') + assert_equal User.find(7), member.principal + assert_equal [2], member.role_ids + assert_equal 3, member.project_id + assert_include 'tab-content-memberships', response.body + end + + def test_create_membership_js_format_with_failure + assert_no_difference 'Member.count' do + post :edit_membership, :id => 7, :membership => {:project_id => 3}, :format => 'js' + assert_response :success + assert_template 'edit_membership' + assert_equal 'text/javascript', response.content_type + end + assert_include 'alert', response.body, "Alert message not sent" + assert_include 'Role can\\\'t be empty', response.body, "Error message not sent" + end + + def test_update_membership + assert_no_difference 'Member.count' do + put :edit_membership, :id => 2, :membership_id => 1, :membership => { :role_ids => [2]} + assert_redirected_to :action => 'edit', :id => '2', :tab => 'memberships' + end + assert_equal [2], Member.find(1).role_ids + end + + def test_update_membership_js_format + assert_no_difference 'Member.count' do + put :edit_membership, :id => 2, :membership_id => 1, :membership => {:role_ids => [2]}, :format => 'js' + assert_response :success + assert_template 'edit_membership' + assert_equal 'text/javascript', response.content_type + end + assert_equal [2], Member.find(1).role_ids + assert_include 'tab-content-memberships', response.body + end + + def test_destroy_membership + assert_difference 'Member.count', -1 do + delete :destroy_membership, :id => 2, :membership_id => 1 + end + assert_redirected_to :action => 'edit', :id => '2', :tab => 'memberships' + assert_nil Member.find_by_id(1) + end + + def test_destroy_membership_js_format + assert_difference 'Member.count', -1 do + delete :destroy_membership, :id => 2, :membership_id => 1, :format => 'js' + assert_response :success + assert_template 'destroy_membership' + assert_equal 'text/javascript', response.content_type + end + assert_nil Member.find_by_id(1) + assert_include 'tab-content-memberships', response.body + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6a/6a60898740de505326813cfa3f1724d25fce3f5e.svn-base --- a/.svn/pristine/6a/6a60898740de505326813cfa3f1724d25fce3f5e.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,8 +0,0 @@ -

    <%= link_to l(:label_custom_field_plural), :controller => 'custom_fields', :action => 'index' %> - » <%= link_to l(@custom_field.type_name), :controller => 'custom_fields', :action => 'index', :tab => @custom_field.class.name %> - » <%=h @custom_field.name %>

    - -<% labelled_tabular_form_for :custom_field, @custom_field, :url => { :action => "edit", :id => @custom_field } do |f| %> -<%= render :partial => 'form', :locals => { :f => f } %> -<%= submit_tag l(:button_save) %> -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6a/6a90c258580c8739754206289754513f15068486.svn-base --- a/.svn/pristine/6a/6a90c258580c8739754206289754513f15068486.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,207 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) -require 'my_controller' - -# Re-raise errors caught by the controller. -class MyController; def rescue_action(e) raise e end; end - -class MyControllerTest < ActionController::TestCase - fixtures :users, :user_preferences, :roles, :projects, :issues, :issue_statuses, :trackers, :enumerations, :custom_fields - - def setup - @controller = MyController.new - @request = ActionController::TestRequest.new - @request.session[:user_id] = 2 - @response = ActionController::TestResponse.new - end - - def test_index - get :index - assert_response :success - assert_template 'page' - end - - def test_page - get :page - assert_response :success - assert_template 'page' - end - - 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, - :group_ids => ['10'], - :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 - # ignored - assert !user.admin? - assert user.groups.empty? - end - - def test_change_password - get :password - assert_response :success - assert_template 'password' - - # non matching password confirmation - post :password, :password => 'jsmith', - :new_password => 'hello', - :new_password_confirmation => 'hello2' - assert_response :success - assert_template 'password' - assert_tag :tag => "div", :attributes => { :class => "errorExplanation" } - - # wrong password - post :password, :password => 'wrongpassword', - :new_password => 'hello', - :new_password_confirmation => 'hello' - assert_response :success - assert_template 'password' - assert_equal 'Wrong password', flash[:error] - - # good password - post :password, :password => 'jsmith', - :new_password => 'hello', - :new_password_confirmation => 'hello' - assert_redirected_to '/my/account' - assert User.try_to_login('jsmith', 'hello') - end - - def test_page_layout - get :page_layout - assert_response :success - assert_template 'page_layout' - end - - def test_add_block - xhr :post, :add_block, :block => 'issuesreportedbyme' - assert_response :success - assert User.find(2).pref[:my_page_layout]['top'].include?('issuesreportedbyme') - end - - def test_remove_block - xhr :post, :remove_block, :block => 'issuesassignedtome' - assert_response :success - assert !User.find(2).pref[:my_page_layout].values.flatten.include?('issuesassignedtome') - end - - def test_order_blocks - xhr :post, :order_blocks, :group => 'left', 'list-left' => ['documents', 'calendar', 'latestnews'] - assert_response :success - assert_equal ['documents', 'calendar', 'latestnews'], User.find(2).pref[:my_page_layout]['left'] - end - - context "POST to reset_rss_key" do - context "with an existing rss_token" do - setup do - @previous_token_value = User.find(2).rss_key # Will generate one if it's missing - post :reset_rss_key - end - - should "destroy the existing token" do - assert_not_equal @previous_token_value, User.find(2).rss_key - end - - should "create a new token" do - assert User.find(2).rss_token - end - - should_set_the_flash_to /reset/ - should_redirect_to('my account') {'/my/account' } - end - - context "with no rss_token" do - setup do - assert_nil User.find(2).rss_token - post :reset_rss_key - end - - should "create a new token" do - assert User.find(2).rss_token - end - - should_set_the_flash_to /reset/ - should_redirect_to('my account') {'/my/account' } - end - end - - context "POST to reset_api_key" do - context "with an existing api_token" do - setup do - @previous_token_value = User.find(2).api_key # Will generate one if it's missing - post :reset_api_key - end - - should "destroy the existing token" do - assert_not_equal @previous_token_value, User.find(2).api_key - end - - should "create a new token" do - assert User.find(2).api_token - end - - should_set_the_flash_to /reset/ - should_redirect_to('my account') {'/my/account' } - end - - context "with no api_token" do - setup do - assert_nil User.find(2).api_token - post :reset_api_key - end - - should "create a new token" do - assert User.find(2).api_token - end - - should_set_the_flash_to /reset/ - should_redirect_to('my account') {'/my/account' } - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6a/6ad1a9c5c31bdf856fc5268290ebc1f7efdb0480.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/6a/6ad1a9c5c31bdf856fc5268290ebc1f7efdb0480.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,208 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class Role < ActiveRecord::Base + # Custom coder for the permissions attribute that should be an + # array of symbols. Rails 3 uses Psych which can be *unbelievably* + # slow on some platforms (eg. mingw32). + class PermissionsAttributeCoder + def self.load(str) + str.to_s.scan(/:([a-z0-9_]+)/).flatten.map(&:to_sym) + end + + def self.dump(value) + YAML.dump(value) + end + end + + # Built-in roles + BUILTIN_NON_MEMBER = 1 + BUILTIN_ANONYMOUS = 2 + + ISSUES_VISIBILITY_OPTIONS = [ + ['all', :label_issues_visibility_all], + ['default', :label_issues_visibility_public], + ['own', :label_issues_visibility_own] + ] + + scope :sorted, lambda { order("#{table_name}.builtin ASC, #{table_name}.position ASC") } + scope :givable, lambda { order("#{table_name}.position ASC").where(:builtin => 0) } + scope :builtin, lambda { |*args| + compare = (args.first == true ? 'not' : '') + where("#{compare} builtin = 0") + } + + before_destroy :check_deletable + has_many :workflow_rules, :dependent => :delete_all do + def copy(source_role) + WorkflowRule.copy(nil, source_role, nil, proxy_association.owner) + end + end + + has_many :member_roles, :dependent => :destroy + has_many :members, :through => :member_roles + acts_as_list + + serialize :permissions, ::Role::PermissionsAttributeCoder + attr_protected :builtin + + validates_presence_of :name + validates_uniqueness_of :name + validates_length_of :name, :maximum => 30 + validates_inclusion_of :issues_visibility, + :in => ISSUES_VISIBILITY_OPTIONS.collect(&:first), + :if => lambda {|role| role.respond_to?(:issues_visibility)} + + # Copies attributes from another role, arg can be an id or a Role + def copy_from(arg, options={}) + return unless arg.present? + role = arg.is_a?(Role) ? arg : Role.find_by_id(arg.to_s) + self.attributes = role.attributes.dup.except("id", "name", "position", "builtin", "permissions") + self.permissions = role.permissions.dup + self + end + + def permissions=(perms) + perms = perms.collect {|p| p.to_sym unless p.blank? }.compact.uniq if perms + write_attribute(:permissions, perms) + end + + def add_permission!(*perms) + self.permissions = [] unless permissions.is_a?(Array) + + permissions_will_change! + perms.each do |p| + p = p.to_sym + permissions << p unless permissions.include?(p) + end + save! + end + + def remove_permission!(*perms) + return unless permissions.is_a?(Array) + permissions_will_change! + perms.each { |p| permissions.delete(p.to_sym) } + save! + end + + # Returns true if the role has the given permission + def has_permission?(perm) + !permissions.nil? && permissions.include?(perm.to_sym) + end + + def <=>(role) + if role + if builtin == role.builtin + position <=> role.position + else + builtin <=> role.builtin + end + else + -1 + end + end + + def to_s + name + end + + def name + case builtin + when 1; l(:label_role_non_member, :default => read_attribute(:name)) + when 2; l(:label_role_anonymous, :default => read_attribute(:name)) + else; read_attribute(:name) + end + end + + # Return true if the role is a builtin role + def builtin? + self.builtin != 0 + end + + # Return true if the role is the anonymous role + def anonymous? + builtin == 2 + end + + # Return true if the role is a project member role + def member? + !self.builtin? + end + + # Return true if role is allowed to do the specified action + # action can be: + # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit') + # * a permission Symbol (eg. :edit_project) + def allowed_to?(action) + if action.is_a? Hash + allowed_actions.include? "#{action[:controller]}/#{action[:action]}" + else + allowed_permissions.include? action + end + end + + # Return all the permissions that can be given to the role + def setable_permissions + setable_permissions = Redmine::AccessControl.permissions - Redmine::AccessControl.public_permissions + setable_permissions -= Redmine::AccessControl.members_only_permissions if self.builtin == BUILTIN_NON_MEMBER + setable_permissions -= Redmine::AccessControl.loggedin_only_permissions if self.builtin == BUILTIN_ANONYMOUS + setable_permissions + end + + # Find all the roles that can be given to a project member + def self.find_all_givable + Role.givable.all + end + + # Return the builtin 'non member' role. If the role doesn't exist, + # it will be created on the fly. + def self.non_member + find_or_create_system_role(BUILTIN_NON_MEMBER, 'Non member') + end + + # Return the builtin 'anonymous' role. If the role doesn't exist, + # it will be created on the fly. + def self.anonymous + find_or_create_system_role(BUILTIN_ANONYMOUS, 'Anonymous') + end + +private + + def allowed_permissions + @allowed_permissions ||= permissions + Redmine::AccessControl.public_permissions.collect {|p| p.name} + end + + def allowed_actions + @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten + end + + def check_deletable + raise "Can't delete role" if members.any? + raise "Can't delete builtin role" if builtin? + end + + def self.find_or_create_system_role(builtin, name) + role = where(:builtin => builtin).first + if role.nil? + role = create(:name => name, :position => 0) do |r| + r.builtin = builtin + end + raise "Unable to create the #{name} role." if role.new_record? + end + role + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6a/6ae02e008b39a032972c4d067b7e08a8c9bd1a60.svn-base --- a/.svn/pristine/6a/6ae02e008b39a032972c4d067b7e08a8c9bd1a60.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ -require File.dirname(__FILE__) + '/../test_helper' - -class PluginsTest < Test::Unit::TestCase - - def test_should_allow_access_to_plugins_by_strings_or_symbols - p = Engines.plugins["alpha_plugin"] - q = Engines.plugins[:alpha_plugin] - assert_kind_of Engines::Plugin, p - assert_equal p, q - end -end \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6a/6af6e8ac0e3a74e02bdf5cc6d1f6d7050cfd38b0.svn-base --- a/.svn/pristine/6a/6af6e8ac0e3a74e02bdf5cc6d1f6d7050cfd38b0.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - - -namespace :db do - desc 'Encrypts SCM and LDAP passwords in the database.' - task :encrypt => :environment do - unless (Repository.encrypt_all(:password) && - AuthSource.encrypt_all(:account_password)) - raise "Some objects could not be saved after encryption, update was rollback'ed." - end - end - - desc 'Decrypts SCM and LDAP passwords in the database.' - task :decrypt => :environment do - unless (Repository.decrypt_all(:password) && - AuthSource.decrypt_all(:account_password)) - raise "Some objects could not be saved after decryption, update was rollback'ed." - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6b/6b2c4a778dca89a31620a55a65371a0aa4017062.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/6b/6b2c4a778dca89a31620a55a65371a0aa4017062.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,60 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class RoutingBoardsTest < ActionController::IntegrationTest + def test_boards + assert_routing( + { :method => 'get', :path => "/projects/world_domination/boards" }, + { :controller => 'boards', :action => 'index', :project_id => 'world_domination' } + ) + assert_routing( + { :method => 'get', :path => "/projects/world_domination/boards/new" }, + { :controller => 'boards', :action => 'new', :project_id => 'world_domination' } + ) + assert_routing( + { :method => 'get', :path => "/projects/world_domination/boards/44" }, + { :controller => 'boards', :action => 'show', :project_id => 'world_domination', + :id => '44' } + ) + assert_routing( + { :method => 'get', :path => "/projects/world_domination/boards/44.atom" }, + { :controller => 'boards', :action => 'show', :project_id => 'world_domination', + :id => '44', :format => 'atom' } + ) + assert_routing( + { :method => 'get', :path => "/projects/world_domination/boards/44/edit" }, + { :controller => 'boards', :action => 'edit', :project_id => 'world_domination', + :id => '44' } + ) + assert_routing( + { :method => 'post', :path => "/projects/world_domination/boards" }, + { :controller => 'boards', :action => 'create', :project_id => 'world_domination' } + ) + assert_routing( + { :method => 'put', :path => "/projects/world_domination/boards/44" }, + { :controller => 'boards', :action => 'update', :project_id => 'world_domination', + :id => '44' } + ) + assert_routing( + { :method => 'delete', :path => "/projects/world_domination/boards/44" }, + { :controller => 'boards', :action => 'destroy', :project_id => 'world_domination', + :id => '44' } + ) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6b/6b42e6adb8523e15e9ff195fc3496e5284a69b00.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/6b/6b42e6adb8523e15e9ff195fc3496e5284a69b00.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,33 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2013 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 ActivitiesHelper + def sort_activity_events(events) + events_by_group = events.group_by(&:event_group) + sorted_events = [] + events.sort {|x, y| y.event_datetime <=> x.event_datetime}.each do |event| + if group_events = events_by_group.delete(event.event_group) + group_events.sort {|x, y| y.event_datetime <=> x.event_datetime}.each_with_index do |e, i| + sorted_events << [e, i > 0] + end + end + end + sorted_events + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6b/6b4c50390f939790cfd61f918f38b017e779b22e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/6b/6b4c50390f939790cfd61f918f38b017e779b22e.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,21 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +module TrackersHelper +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6b/6b54a0eb8e943798ef2435d13b405e9ba04b4b70.svn-base --- a/.svn/pristine/6b/6b54a0eb8e943798ef2435d13b405e9ba04b4b70.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -# 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.expand_path('../../test_helper', __FILE__) - -class TrackerTest < ActiveSupport::TestCase - fixtures :trackers, :workflows, :issue_statuses, :roles - - def test_copy_workflows - source = Tracker.find(1) - assert_equal 89, source.workflows.size - - target = Tracker.new(:name => 'Target') - assert target.save - target.workflows.copy(source) - target.reload - assert_equal 89, target.workflows.size - end - - def test_issue_statuses - tracker = Tracker.find(1) - Workflow.delete_all - Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 2, :new_status_id => 3) - Workflow.create!(:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 5) - - assert_kind_of Array, tracker.issue_statuses - assert_kind_of IssueStatus, tracker.issue_statuses.first - assert_equal [2, 3, 5], Tracker.find(1).issue_statuses.collect(&:id) - end - - def test_issue_statuses_empty - Workflow.delete_all("tracker_id = 1") - assert_equal [], Tracker.find(1).issue_statuses - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6b/6b5b987c21a18cc797539b6f0cafd0fae162a091.svn-base --- a/.svn/pristine/6b/6b5b987c21a18cc797539b6f0cafd0fae162a091.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4 +0,0 @@ -

    <%= l(:mail_body_lost_password) %>
    -<%= auto_link(@url) %>

    - -

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

    diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6b/6b825f96e86174ddd396f80166966bf671099ba1.svn-base --- a/.svn/pristine/6b/6b825f96e86174ddd396f80166966bf671099ba1.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,127 +0,0 @@ -// ** I18N - -// Calendar HU language -// Author: Takács Gábor -// Encoding: UTF-8 -// Distributed under the same terms as the calendar itself. - -// For translators: please use UTF-8 if possible. We strongly believe that -// Unicode is the answer to a real internationalized world. Also please -// include your contact information in the header, as can be seen above. - -// full day names -Calendar._DN = new Array -("Vasárnap", - "HétfÅ‘", - "Kedd", - "Szerda", - "Csütörtök", - "Péntek", - "Szombat", - "Vasárnap"); - -// Please note that the following array of short day names (and the same goes -// for short month names, _SMN) isn't absolutely necessary. We give it here -// for exemplification on how one can customize the short day names, but if -// they are simply the first N letters of the full name you can simply say: -// -// Calendar._SDN_len = N; // short day name length -// Calendar._SMN_len = N; // short month name length -// -// If N = 3 then this is not needed either since we assume a value of 3 if not -// present, to be compatible with translation files that were written before -// this feature. - -// short day names -Calendar._SDN = new Array -("Vas", - "Hét", - "Ked", - "Sze", - "Csü", - "Pén", - "Szo", - "Vas"); - -// First day of the week. "0" means display Sunday first, "1" means display -// Monday first, etc. -Calendar._FD = 1; - -// full month names -Calendar._MN = new Array -("Január", - "Február", - "Március", - "Ãprilis", - "Május", - "Június", - "Július", - "Augusztus", - "Szeptember", - "Október", - "November", - "December"); - -// short month names -Calendar._SMN = new Array -("Jan", - "Feb", - "Már", - "Ãpr", - "Máj", - "Jún", - "Júl", - "Aug", - "Szep", - "Okt", - "Nov", - "Dec"); - -// tooltips -Calendar._TT = {}; -Calendar._TT["INFO"] = "A naptár leírása"; - -Calendar._TT["ABOUT"] = -"DHTML Date/Time Selector\n" + -"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) -"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + -"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + -"\n\n" + -"Date selection:\n" + -"- Use the \xab, \xbb buttons to select year\n" + -"- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" + -"- Hold mouse button on any of the above buttons for faster selection."; -Calendar._TT["ABOUT_TIME"] = "\n\n" + -"Time selection:\n" + -"- Click on any of the time parts to increase it\n" + -"- or Shift-click to decrease it\n" + -"- or click and drag for faster selection."; - -Calendar._TT["PREV_YEAR"] = "ElÅ‘zÅ‘ év (nyomvatart = menü)"; -Calendar._TT["PREV_MONTH"] = "ElÅ‘zÅ‘ hónap (nyomvatart = menü)"; -Calendar._TT["GO_TODAY"] = "Irány a Ma"; -Calendar._TT["NEXT_MONTH"] = "KövetkezÅ‘ hónap (nyomvatart = menü)"; -Calendar._TT["NEXT_YEAR"] = "KövetkezÅ‘ év (nyomvatart = menü)"; -Calendar._TT["SEL_DATE"] = "Válasszon dátumot"; -Calendar._TT["DRAG_TO_MOVE"] = "Fogd és vidd"; -Calendar._TT["PART_TODAY"] = " (ma)"; - -// the following is to inform that "%s" is to be the first day of week -// %s will be replaced with the day name. -Calendar._TT["DAY_FIRST"] = "%s megjelenítése elsÅ‘ként"; - -// This may be locale-dependent. It specifies the week-end days, as an array -// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 -// means Monday, etc. -Calendar._TT["WEEKEND"] = "0,6"; - -Calendar._TT["CLOSE"] = "Bezár"; -Calendar._TT["TODAY"] = "Ma"; -Calendar._TT["TIME_PART"] = "(Shift-)Click vagy húzd az érték változtatásához"; - -// date formats -Calendar._TT["DEF_DATE_FORMAT"] = "%Y.%m.%d"; -Calendar._TT["TT_DATE_FORMAT"] = "%B %e, %A"; - -Calendar._TT["WK"] = "hét"; -Calendar._TT["TIME"] = "IdÅ‘:"; diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6b/6b9da48548a1beb99525f99aa84dd3c7f5182449.svn-base --- a/.svn/pristine/6b/6b9da48548a1beb99525f99aa84dd3c7f5182449.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,52 +0,0 @@ -

    <%=l(:label_spent_time)%> (<%= l(:label_last_n_days, 7) %>)

    -<% -entries = TimeEntry.find(:all, - :conditions => ["#{TimeEntry.table_name}.user_id = ? AND #{TimeEntry.table_name}.spent_on BETWEEN ? AND ?", @user.id, Date.today - 6, Date.today], - :include => [:activity, :project, {:issue => [:tracker, :status]}], - :order => "#{TimeEntry.table_name}.spent_on DESC, #{Project.table_name}.name ASC, #{Tracker.table_name}.position ASC, #{Issue.table_name}.id ASC") -entries_by_day = entries.group_by(&:spent_on) -%> - -
    -

    <%= l(:label_total) %>: <%= html_hours("%.2f" % entries.sum(&:hours).to_f) %>

    -
    - -<% if entries.any? %> - - - - - - - - - -<% entries_by_day.keys.sort.reverse.each do |day| %> - - - - - - - <% entries_by_day[day].each do |entry| -%> - - - - - - - - <% end -%> -<% end -%> - -
    <%= l(:label_activity) %><%= l(:label_project) %><%= l(:field_comments) %><%= l(:field_hours) %>
    <%= day == Date.today ? l(:label_today).titleize : format_date(day) %><%= html_hours("%.2f" % entries_by_day[day].sum(&:hours).to_f) %>
    <%=h entry.activity %><%=h entry.project %> <%= ' - ' + link_to_issue(entry.issue, :truncate => 50) if entry.issue %><%=h entry.comments %><%= html_hours("%.2f" % entry.hours) %> - <% if entry.editable_by?(@user) -%> - <%= link_to image_tag('edit.png'), {:controller => 'timelog', :action => 'edit', :id => entry}, - :title => l(:button_edit) %> - <%= link_to image_tag('delete.png'), {:controller => 'timelog', :action => 'destroy', :id => entry}, - :confirm => l(:text_are_you_sure), - :method => :delete, - :title => l(:button_delete) %> - <% end -%> -
    -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6c/6c0616986104fa86ed47ef83a0066e97c4dd3dae.svn-base --- a/.svn/pristine/6c/6c0616986104fa86ed47ef83a0066e97c4dd3dae.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4 +0,0 @@ -<% labelled_form_for @group do |f| %> -<%= render :partial => 'form', :locals => { :f => f } %> -<%= submit_tag l(:button_save) %> -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6c/6c1bc2cf6fdcda95eb68095dcabf8689b24d2903.svn-base --- a/.svn/pristine/6c/6c1bc2cf6fdcda95eb68095dcabf8689b24d2903.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,127 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../../../../test_helper', __FILE__) -require 'iconv' - -class PdfTest < ActiveSupport::TestCase - fixtures :users, :projects, :roles, :members, :member_roles, - :enabled_modules, :issues, :trackers, :attachments - - def test_fix_text_encoding_nil - assert_equal '', Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(nil, "UTF-8") - assert_equal '', Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(nil, "ISO-8859-1") - end - - def test_rdm_pdf_iconv_cannot_convert_ja_cp932 - encoding = ( RUBY_PLATFORM == 'java' ? "SJIS" : "CP932" ) - utf8_txt_1 = "\xe7\x8b\x80\xe6\x85\x8b" - utf8_txt_2 = "\xe7\x8b\x80\xe6\x85\x8b\xe7\x8b\x80" - utf8_txt_3 = "\xe7\x8b\x80\xe7\x8b\x80\xe6\x85\x8b\xe7\x8b\x80" - if utf8_txt_1.respond_to?(:force_encoding) - txt_1 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_1, encoding) - txt_2 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_2, encoding) - txt_3 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_3, encoding) - assert_equal "?\x91\xd4", txt_1 - assert_equal "?\x91\xd4?", txt_2 - assert_equal "??\x91\xd4?", txt_3 - assert_equal "ASCII-8BIT", txt_1.encoding.to_s - assert_equal "ASCII-8BIT", txt_2.encoding.to_s - assert_equal "ASCII-8BIT", txt_3.encoding.to_s - elsif RUBY_PLATFORM == 'java' - assert_equal "??", - Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_1, encoding) - assert_equal "???", - Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_2, encoding) - assert_equal "????", - Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_3, encoding) - else - assert_equal "???\x91\xd4", - Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_1, encoding) - assert_equal "???\x91\xd4???", - Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_2, encoding) - assert_equal "??????\x91\xd4???", - Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_3, encoding) - end - end - - def test_rdm_pdf_iconv_invalid_utf8_should_be_replaced_en - str1 = "Texte encod\xe9 en ISO-8859-1" - str2 = "\xe9a\xe9b\xe9c\xe9d\xe9e test" - str1.force_encoding("UTF-8") if str1.respond_to?(:force_encoding) - str2.force_encoding("ASCII-8BIT") if str2.respond_to?(:force_encoding) - txt_1 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(str1, 'UTF-8') - txt_2 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(str2, 'UTF-8') - if txt_1.respond_to?(:force_encoding) - assert_equal "ASCII-8BIT", txt_1.encoding.to_s - assert_equal "ASCII-8BIT", txt_2.encoding.to_s - end - assert_equal "Texte encod? en ISO-8859-1", txt_1 - assert_equal "?a?b?c?d?e test", txt_2 - end - - def test_rdm_pdf_iconv_invalid_utf8_should_be_replaced_ja - str1 = "Texte encod\xe9 en ISO-8859-1" - str2 = "\xe9a\xe9b\xe9c\xe9d\xe9e test" - str1.force_encoding("UTF-8") if str1.respond_to?(:force_encoding) - str2.force_encoding("ASCII-8BIT") if str2.respond_to?(:force_encoding) - encoding = ( RUBY_PLATFORM == 'java' ? "SJIS" : "CP932" ) - txt_1 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(str1, encoding) - txt_2 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(str2, encoding) - if txt_1.respond_to?(:force_encoding) - assert_equal "ASCII-8BIT", txt_1.encoding.to_s - assert_equal "ASCII-8BIT", txt_2.encoding.to_s - end - assert_equal "Texte encod? en ISO-8859-1", txt_1 - assert_equal "?a?b?c?d?e test", txt_2 - end - - def test_attach - Attachment.storage_path = "#{Rails.root}/test/fixtures/files" - - str2 = "\x83e\x83X\x83g" - str2.force_encoding("ASCII-8BIT") if str2.respond_to?(:force_encoding) - encoding = ( RUBY_PLATFORM == 'java' ? "SJIS" : "CP932" ) - - a1 = Attachment.find(17) - a2 = Attachment.find(19) - - User.current = User.find(1) - assert a1.readable? - assert a1.visible? - assert a2.readable? - assert a2.visible? - - aa1 = Redmine::Export::PDF::RDMPdfEncoding::attach(Attachment.all, "Testfile.PNG", "UTF-8") - assert_equal 17, aa1.id - aa2 = Redmine::Export::PDF::RDMPdfEncoding::attach(Attachment.all, "test#{str2}.png", encoding) - assert_equal 19, aa2.id - - User.current = nil - assert a1.readable? - assert (! a1.visible?) - assert a2.readable? - assert (! a2.visible?) - - aa1 = Redmine::Export::PDF::RDMPdfEncoding::attach(Attachment.all, "Testfile.PNG", "UTF-8") - assert_equal nil, aa1 - aa2 = Redmine::Export::PDF::RDMPdfEncoding::attach(Attachment.all, "test#{str2}.png", encoding) - assert_equal nil, aa2 - - set_tmp_attachments_directory - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6c/6c5422ed0dce695a568f6fb2acde79357b8cbed3.svn-base --- a/.svn/pristine/6c/6c5422ed0dce695a568f6fb2acde79357b8cbed3.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,110 +0,0 @@ -module ActiveRecord - module Acts - module Tree - def self.included(base) - base.extend(ClassMethods) - end - - # Specify this +acts_as+ extension if you want to model a tree structure by providing a parent association and a children - # association. This requires that you have a foreign key column, which by default is called +parent_id+. - # - # class Category < ActiveRecord::Base - # acts_as_tree :order => "name" - # end - # - # Example: - # root - # \_ child1 - # \_ subchild1 - # \_ subchild2 - # - # root = Category.create("name" => "root") - # child1 = root.children.create("name" => "child1") - # subchild1 = child1.children.create("name" => "subchild1") - # - # root.parent # => nil - # child1.parent # => root - # root.children # => [child1] - # root.children.first.children.first # => subchild1 - # - # In addition to the parent and children associations, the following instance methods are added to the class - # after calling acts_as_tree: - # * siblings - Returns all the children of the parent, excluding the current node ([subchild2] when called on subchild1) - # * self_and_siblings - Returns all the children of the parent, including the current node ([subchild1, subchild2] when called on subchild1) - # * ancestors - Returns all the ancestors of the current node ([child1, root] when called on subchild2) - # * root - Returns the root of the current node (root when called on subchild2) - module ClassMethods - # Configuration options are: - # - # * foreign_key - specifies the column name to use for tracking of the tree (default: +parent_id+) - # * order - makes it possible to sort the children according to this SQL snippet. - # * counter_cache - keeps a count in a +children_count+ column if set to +true+ (default: +false+). - def acts_as_tree(options = {}) - configuration = { :foreign_key => "parent_id", :dependent => :destroy, :order => nil, :counter_cache => nil } - configuration.update(options) if options.is_a?(Hash) - - belongs_to :parent, :class_name => name, :foreign_key => configuration[:foreign_key], :counter_cache => configuration[:counter_cache] - has_many :children, :class_name => name, :foreign_key => configuration[:foreign_key], :order => configuration[:order], :dependent => configuration[:dependent] - - class_eval <<-EOV - include ActiveRecord::Acts::Tree::InstanceMethods - - def self.roots - find(:all, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}}) - end - - def self.root - find(:first, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}}) - end - EOV - end - end - - module InstanceMethods - # Returns list of ancestors, starting from parent until root. - # - # subchild1.ancestors # => [child1, root] - def ancestors - node, nodes = self, [] - nodes << node = node.parent while node.parent - nodes - end - - # Returns list of descendants. - # - # root.descendants # => [child1, subchild1, subchild2] - def descendants - children + children.collect(&:children).flatten - end - - # Returns list of descendants and a reference to the current node. - # - # root.self_and_descendants # => [root, child1, subchild1, subchild2] - def self_and_descendants - [self] + descendants - end - - # Returns the root node of the tree. - def root - node = self - node = node.parent while node.parent - node - end - - # Returns all siblings of the current node. - # - # subchild1.siblings # => [subchild2] - def siblings - self_and_siblings - [self] - end - - # Returns all siblings and a reference to the current node. - # - # subchild1.self_and_siblings # => [subchild1, subchild2] - def self_and_siblings - parent ? parent.children : self.class.roots - end - end - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6c/6cbb31787f87074f8260891401b8d0cb7419e27e.svn-base --- a/.svn/pristine/6c/6cbb31787f87074f8260891401b8d0cb7419e27e.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,8 +0,0 @@ -require File.dirname(__FILE__) + '/../test_helper' - -class <%= class_name %>ControllerTest < ActionController::TestCase - # Replace this with your real tests. - def test_truth - assert true - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6c/6cd92c68c4e67170a380505918766fcdb88e0c8f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/6c/6cd92c68c4e67170a380505918766fcdb88e0c8f.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,16 @@ +class CalendarAndActivity < ActiveRecord::Migration + # model removed + class Permission < ActiveRecord::Base; end + + def self.up + Permission.create :controller => "projects", :action => "activity", :description => "label_activity", :sort => 160, :is_public => true, :mail_option => 0, :mail_enabled => 0 + Permission.create :controller => "projects", :action => "calendar", :description => "label_calendar", :sort => 165, :is_public => true, :mail_option => 0, :mail_enabled => 0 + Permission.create :controller => "projects", :action => "gantt", :description => "label_gantt", :sort => 166, :is_public => true, :mail_option => 0, :mail_enabled => 0 + end + + def self.down + Permission.where("controller=? and action=?", 'projects', 'activity').first.destroy + Permission.where("controller=? and action=?", 'projects', 'calendar').first.destroy + Permission.where("controller=? and action=?", 'projects', 'gantt').first.destroy + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6c/6cda2c2cd26c7545900827e5478deaa9348538cb.svn-base --- a/.svn/pristine/6c/6cda2c2cd26c7545900827e5478deaa9348538cb.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,51 +0,0 @@ -
    -<%= link_to(l(:button_change_password), {:action => 'password'}, :class => 'icon icon-passwd') if @user.change_password_allowed? %> -<%= call_hook(:view_my_account_contextual, :user => @user)%> -
    - -

    <%=l(:label_my_account)%>

    -<%= error_messages_for 'user' %> - -<% form_for :user, @user, :url => { :action => "account" }, - :builder => TabularFormBuilder, - :lang => current_language, - :html => { :id => 'my_account_form' } do |f| %> -
    -
    - <%=l(:label_information_plural)%> -

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

    -

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

    -

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

    -

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

    - <% if Setting.openid? %> -

    <%= f.text_field :identity_url %>

    - <% end %> - - <% @user.custom_field_values.select(&:editable?).each do |value| %> -

    <%= custom_field_tag_with_label :user, value %>

    - <% end %> - <%= call_hook(:view_my_account, :user => @user, :form => f) %> -
    - -<%= submit_tag l(:button_save) %> -
    - -
    -
    - <%=l(:field_mail_notification)%> - <%= render :partial => 'users/mail_notifications' %> -
    - -
    - <%=l(:label_preferences)%> - <%= render :partial => 'users/preferences' %> -
    - -
    -<% end %> - -<% content_for :sidebar do %> -<%= render :partial => 'sidebar' %> -<% end %> - -<% html_title(l(:label_my_account)) -%> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6d/6d24a0dc5e3b6f9a7f6072591c406a9d6ee664eb.svn-base --- a/.svn/pristine/6d/6d24a0dc5e3b6f9a7f6072591c406a9d6ee664eb.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,26 +0,0 @@ -# Tests in this file ensure that: -# -# * translations in the application take precedence over those in plugins -# * translations in subsequently loaded plugins take precendence over those in previously loaded plugins - -require File.dirname(__FILE__) + '/../test_helper' - -class LocaleLoadingTest < ActionController::TestCase - def setup - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - end - - # app takes precedence over plugins - - def test_WITH_a_translation_defined_in_both_app_and_plugin_IT_should_find_the_one_in_app - assert_equal I18n.t('hello'), 'Hello world' - end - - # subsequently loaded plugins take precendence over previously loaded plugins - - def test_WITH_a_translation_defined_in_two_plugins_IT_should_find_the_latter_of_both - assert_equal I18n.t('plugin'), 'beta' - end -end - diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6d/6d3caa4ed6a43a4bdaff9ded0c658d9c377a52de.svn-base --- a/.svn/pristine/6d/6d3caa4ed6a43a4bdaff9ded0c658d9c377a52de.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2 +0,0 @@ -# English strings go here -my_label: "My label" diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6d/6d3d8dfed27856a76e41a252f404513d7b64d5b0.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/6d/6d3d8dfed27856a76e41a252f404513d7b64d5b0.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,12 @@ + +require File.expand_path(File.dirname(__FILE__) + '../../../../../test/test_helper') + +class SamplePluginRoutingTest < ActionDispatch::IntegrationTest + def test_example + assert_routing( + { :method => 'get', :path => "/projects/1234/hello" }, + { :controller => 'example', :action => 'say_hello', + :id => '1234' } + ) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6d/6d3df7e3002585d4b82d0225bf30bfb49566483a.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/6d/6d3df7e3002585d4b82d0225bf30bfb49566483a.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,21 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +module NewsHelper +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6d/6d7a886110aeab37816e5215a40006734bda0844.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/6d/6d7a886110aeab37816e5215a40006734bda0844.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,13 @@ +class AddViewWikiEditsPermission < ActiveRecord::Migration + def self.up + Role.all.each do |r| + r.add_permission!(:view_wiki_edits) if r.has_permission?(:view_wiki_pages) + end + end + + def self.down + Role.all.each do |r| + r.remove_permission!(:view_wiki_edits) + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6d/6d7d58f8eaab5d307f2462af49611d6e47d8e3a9.svn-base Binary file .svn/pristine/6d/6d7d58f8eaab5d307f2462af49611d6e47d8e3a9.svn-base has changed diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6d/6dbc9c710fb965b8c65653004923b7333ec3b49d.svn-base --- a/.svn/pristine/6d/6dbc9c710fb965b8c65653004923b7333ec3b49d.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,20 +0,0 @@ -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 -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6d/6dcc3a263131f2160989fed213e79d95e99859f8.svn-base --- a/.svn/pristine/6d/6dcc3a263131f2160989fed213e79d95e99859f8.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,127 +0,0 @@ -// ** I18N - -// Calendar EN language -// Author: Mihai Bazon, -// 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 -("星期日", - "星期一", - "星期二", - "星期三", - "星期四", - "星期五", - "星期六", - "星期日"); - -// 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 = 0; - -// full month names -Calendar._MN = new Array -("一月", - "二月", - "三月", - "四月", - "五月", - "六月", - "七月", - "八月", - "乿œˆ", - "åæœˆ", - "å一月", - "å二月"); - -// short month names -Calendar._SMN = new Array -("一月", - "二月", - "三月", - "四月", - "五月", - "六月", - "七月", - "八月", - "乿œˆ", - "åæœˆ", - "å一月", - "å二月"); - -// tooltips -Calendar._TT = {}; -Calendar._TT["INFO"] = "關於 calendar"; - -Calendar._TT["ABOUT"] = -"DHTML 日期/時間 鏿“‡å™¨\n" + -"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) -"最新版本å–å¾—ä½å€: http://www.dynarch.com/projects/calendar/\n" + -"使用 GNU LGPL 發行. åƒè€ƒ http://gnu.org/licenses/lgpl.html 以å–得更多關於 LGPL 之細節。" + -"\n\n" + -"æ—¥æœŸé¸æ“‡æ–¹å¼:\n" + -"- 使用滑鼠點擊 \xab 〠\xbb æŒ‰éˆ•é¸æ“‡å¹´ä»½\n" + -"- 使用滑鼠點擊 " + String.fromCharCode(0x2039) + " 〠" + String.fromCharCode(0x203a) + " æŒ‰éˆ•é¸æ“‡æœˆä»½\n" + -"- 使用滑鼠點擊上述按鈕並按ä½ä¸æ”¾ï¼Œå¯é–‹å•Ÿå¿«é€Ÿé¸å–®ã€‚"; -Calendar._TT["ABOUT_TIME"] = "\n\n" + -"æ™‚é–“é¸æ“‡æ–¹å¼ï¼š\n" + -"- ã€Œå–®æ“Šã€æ™‚分秒為éžå¢ž\n" + -"- 或 「Shift-單擊ã€ç‚ºéžæ¸›\n" + -"- 或 「單擊且拖拉ã€ç‚ºå¿«é€Ÿé¸æ“‡"; - -Calendar._TT["PREV_YEAR"] = "å‰ä¸€å¹´ (按ä½ä¸æ”¾å¯é¡¯ç¤ºé¸å–®)"; -Calendar._TT["PREV_MONTH"] = "å‰ä¸€å€‹æœˆ (按ä½ä¸æ”¾å¯é¡¯ç¤ºé¸å–®)"; -Calendar._TT["GO_TODAY"] = "鏿“‡ä»Šå¤©"; -Calendar._TT["NEXT_MONTH"] = "後一個月 (按ä½ä¸æ”¾å¯é¡¯ç¤ºé¸å–®)"; -Calendar._TT["NEXT_YEAR"] = "下一年 (按ä½ä¸æ”¾å¯é¡¯å¼é¸å–®)"; -Calendar._TT["SEL_DATE"] = "è«‹é»žé¸æ—¥æœŸ"; -Calendar._TT["DRAG_TO_MOVE"] = "按ä½ä¸æ”¾å¯æ‹–拉視窗"; -Calendar._TT["PART_TODAY"] = " (今天)"; - -// the following is to inform that "%s" is to be the first day of week -// %s will be replaced with the day name. -Calendar._TT["DAY_FIRST"] = "以 %s åšç‚ºä¸€é€±çš„首日"; - -// This may be locale-dependent. It specifies the week-end days, as an array -// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 -// means Monday, etc. -Calendar._TT["WEEKEND"] = "0,6"; - -Calendar._TT["CLOSE"] = "關閉視窗"; -Calendar._TT["TODAY"] = "今天"; -Calendar._TT["TIME_PART"] = "(Shift-)åŠ ã€Œå–®æ“Šã€æˆ–「拖拉ã€å¯è®Šæ›´å€¼"; - -// date formats -Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; -Calendar._TT["TT_DATE_FORMAT"] = "星期 %a, %b %e æ—¥"; - -Calendar._TT["WK"] = "週"; -Calendar._TT["TIME"] = "時間:"; diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6d/6df92bac55b1c152d6b305930ebfee76752ffbdb.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/6d/6df92bac55b1c152d6b305930ebfee76752ffbdb.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,90 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class Redmine::ApiTest::RolesTest < Redmine::ApiTest::Base + fixtures :roles + + def setup + Setting.rest_api_enabled = '1' + end + + context "/roles" do + context "GET" do + context "xml" do + should "return the roles" do + get '/roles.xml' + + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_equal 3, assigns(:roles).size + + assert_tag :tag => 'roles', + :attributes => {:type => 'array'}, + :child => { + :tag => 'role', + :child => { + :tag => 'id', + :content => '2', + :sibling => { + :tag => 'name', + :content => 'Developer' + } + } + } + end + end + + context "json" do + should "return the roles" do + get '/roles.json' + + assert_response :success + assert_equal 'application/json', @response.content_type + assert_equal 3, assigns(:roles).size + + json = ActiveSupport::JSON.decode(response.body) + assert_kind_of Hash, json + assert_kind_of Array, json['roles'] + assert_include({'id' => 2, 'name' => 'Developer'}, json['roles']) + end + end + end + end + + context "/roles/:id" do + context "GET" do + context "xml" do + should "return the role" do + get '/roles/1.xml' + + assert_response :success + assert_equal 'application/xml', @response.content_type + + assert_select 'role' do + assert_select 'name', :text => 'Manager' + assert_select 'role permissions[type=array]' do + assert_select 'permission', Role.find(1).permissions.size + assert_select 'permission', :text => 'view_issues' + end + end + end + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6e/6e054c706d00d186090b4624c3e7d460d0636103.svn-base --- a/.svn/pristine/6e/6e054c706d00d186090b4624c3e7d460d0636103.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,168 +0,0 @@ -module CodeRay -module Scanners - - load :ruby - load :html - load :java_script - - class HAML < Scanner - - register_for :haml - title 'HAML Template' - - KINDS_NOT_LOC = HTML::KINDS_NOT_LOC - - protected - - def setup - super - @ruby_scanner = CodeRay.scanner :ruby, :tokens => @tokens, :keep_tokens => true - @embedded_ruby_scanner = CodeRay.scanner :ruby, :tokens => @tokens, :keep_tokens => true, :state => @ruby_scanner.interpreted_string_state - @html_scanner = CodeRay.scanner :html, :tokens => @tokens, :keep_tokens => true - end - - def scan_tokens encoder, options - - match = nil - code = '' - - until eos? - - if bol? - if match = scan(/!!!.*/) - encoder.text_token match, :doctype - next - end - - if match = scan(/(?>( *)(\/(?!\[if)|-\#|:javascript|:ruby|:\w+) *)(?=\n)/) - encoder.text_token match, :comment - - code = self[2] - if match = scan(/(?:\n+#{self[1]} .*)+/) - case code - when '/', '-#' - encoder.text_token match, :comment - when ':javascript' - # TODO: recognize #{...} snippets inside JavaScript - @java_script_scanner ||= CodeRay.scanner :java_script, :tokens => @tokens, :keep_tokens => true - @java_script_scanner.tokenize match, :tokens => encoder - when ':ruby' - @ruby_scanner.tokenize match, :tokens => encoder - when /:\w+/ - encoder.text_token match, :comment - else - raise 'else-case reached: %p' % [code] - end - end - end - - if match = scan(/ +/) - encoder.text_token match, :space - end - - if match = scan(/\/.*/) - encoder.text_token match, :comment - next - end - - if match = scan(/\\/) - encoder.text_token match, :plain - if match = scan(/.+/) - @html_scanner.tokenize match, :tokens => encoder - end - next - end - - tag = false - - if match = scan(/%[\w:]+\/?/) - encoder.text_token match, :tag - # if match = scan(/( +)(.+)/) - # encoder.text_token self[1], :space - # @embedded_ruby_scanner.tokenize self[2], :tokens => encoder - # end - tag = true - end - - while match = scan(/([.#])[-\w]*\w/) - encoder.text_token match, self[1] == '#' ? :constant : :class - tag = true - end - - if tag && match = scan(/(\()([^)]+)?(\))?/) - # TODO: recognize title=@title, class="widget_#{@widget.number}" - encoder.text_token self[1], :plain - @html_scanner.tokenize self[2], :tokens => encoder, :state => :attribute if self[2] - encoder.text_token self[3], :plain if self[3] - end - - if tag && match = scan(/\{/) - encoder.text_token match, :plain - - code = '' - level = 1 - while true - code << scan(/([^\{\},\n]|, *\n?)*/) - case match = getch - when '{' - level += 1 - code << match - when '}' - level -= 1 - if level > 0 - code << match - else - break - end - when "\n", ",", nil - break - end - end - @ruby_scanner.tokenize code, :tokens => encoder unless code.empty? - - encoder.text_token match, :plain if match - end - - if tag && match = scan(/(\[)([^\]\n]+)?(\])?/) - encoder.text_token self[1], :plain - @ruby_scanner.tokenize self[2], :tokens => encoder if self[2] - encoder.text_token self[3], :plain if self[3] - end - - if tag && match = scan(/\//) - encoder.text_token match, :tag - end - - if scan(/(>? encoder - else - @ruby_scanner.tokenize self[4], :tokens => encoder - end - end - elsif match = scan(/((?:<|> encoder if self[2] - end - - elsif match = scan(/.+/) - @html_scanner.tokenize match, :tokens => encoder - - end - - if match = scan(/\n/) - encoder.text_token match, :space - end - end - - encoder - - end - - end - -end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6e/6e0ffe833f69aa1e063e889d3d2d973e8870aa17.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/6e/6e0ffe833f69aa1e063e889d3d2d973e8870aa17.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,11 @@ +

    <%=l(:label_watched_issues)%> (<%= Issue.visible.watched_by(user.id).count %>)

    +<% watched_issues = issueswatched_items %> + +<%= render :partial => 'issues/list_simple', :locals => { :issues => watched_issues } %> +<% if watched_issues.length > 0 %> +

    <%= link_to l(:label_issue_view_all), :controller => 'issues', + :action => 'index', + :set_filter => 1, + :watcher_id => 'me', + :sort => 'updated_on:desc' %>

    +<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6e/6e2879c545234404374d4007620d9c471722b1c4.svn-base --- a/.svn/pristine/6e/6e2879c545234404374d4007620d9c471722b1c4.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -<%= principals_check_box_tags 'member[user_ids][]', @principals %> \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6e/6e3338f454d707517019dd9e77ecbfdcf1570d99.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/6e/6e3338f454d707517019dd9e77ecbfdcf1570d99.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,355 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class CustomField < ActiveRecord::Base + include Redmine::SubclassFactory + + has_many :custom_values, :dependent => :delete_all + acts_as_list :scope => 'type = \'#{self.class}\'' + serialize :possible_values + + validates_presence_of :name, :field_format + validates_uniqueness_of :name, :scope => :type + validates_length_of :name, :maximum => 30 + validates_inclusion_of :field_format, :in => Redmine::CustomFieldFormat.available_formats + + validate :validate_custom_field + before_validation :set_searchable + after_save :handle_multiplicity_change + + scope :sorted, lambda { order("#{table_name}.position ASC") } + + CUSTOM_FIELDS_TABS = [ + {:name => 'IssueCustomField', :partial => 'custom_fields/index', + :label => :label_issue_plural}, + {:name => 'TimeEntryCustomField', :partial => 'custom_fields/index', + :label => :label_spent_time}, + {:name => 'ProjectCustomField', :partial => 'custom_fields/index', + :label => :label_project_plural}, + {:name => 'VersionCustomField', :partial => 'custom_fields/index', + :label => :label_version_plural}, + {:name => 'UserCustomField', :partial => 'custom_fields/index', + :label => :label_user_plural}, + {:name => 'GroupCustomField', :partial => 'custom_fields/index', + :label => :label_group_plural}, + {:name => 'TimeEntryActivityCustomField', :partial => 'custom_fields/index', + :label => TimeEntryActivity::OptionName}, + {:name => 'IssuePriorityCustomField', :partial => 'custom_fields/index', + :label => IssuePriority::OptionName}, + {:name => 'DocumentCategoryCustomField', :partial => 'custom_fields/index', + :label => DocumentCategory::OptionName} + ] + + CUSTOM_FIELDS_NAMES = CUSTOM_FIELDS_TABS.collect{|v| v[:name]} + + def field_format=(arg) + # cannot change format of a saved custom field + super if new_record? + end + + def set_searchable + # make sure these fields are not searchable + self.searchable = false if %w(int float date bool).include?(field_format) + # make sure only these fields can have multiple values + self.multiple = false unless %w(list user version).include?(field_format) + true + end + + def validate_custom_field + if self.field_format == "list" + errors.add(:possible_values, :blank) if self.possible_values.nil? || self.possible_values.empty? + errors.add(:possible_values, :invalid) unless self.possible_values.is_a? Array + end + + if regexp.present? + begin + Regexp.new(regexp) + rescue + errors.add(:regexp, :invalid) + end + end + + if default_value.present? && !valid_field_value?(default_value) + errors.add(:default_value, :invalid) + end + end + + def possible_values_options(obj=nil) + case field_format + when 'user', 'version' + if obj.respond_to?(:project) && obj.project + case field_format + when 'user' + obj.project.users.sort.collect {|u| [u.to_s, u.id.to_s]} + when 'version' + obj.project.shared_versions.sort.collect {|u| [u.to_s, u.id.to_s]} + end + elsif obj.is_a?(Array) + obj.collect {|o| possible_values_options(o)}.reduce(:&) + else + [] + end + when 'bool' + [[l(:general_text_Yes), '1'], [l(:general_text_No), '0']] + else + possible_values || [] + end + end + + def possible_values(obj=nil) + case field_format + when 'user', 'version' + possible_values_options(obj).collect(&:last) + when 'bool' + ['1', '0'] + else + values = super() + if values.is_a?(Array) + values.each do |value| + value.force_encoding('UTF-8') if value.respond_to?(:force_encoding) + end + end + values || [] + end + end + + # Makes possible_values accept a multiline string + def possible_values=(arg) + if arg.is_a?(Array) + super(arg.compact.collect(&:strip).select {|v| !v.blank?}) + else + self.possible_values = arg.to_s.split(/[\n\r]+/) + end + end + + def cast_value(value) + casted = nil + unless value.blank? + case field_format + when 'string', 'text', 'list' + casted = value + when 'date' + casted = begin; value.to_date; rescue; nil end + when 'bool' + casted = (value == '1' ? true : false) + when 'int' + casted = value.to_i + when 'float' + casted = value.to_f + when 'user', 'version' + casted = (value.blank? ? nil : field_format.classify.constantize.find_by_id(value.to_i)) + end + end + casted + end + + def value_from_keyword(keyword, customized) + possible_values_options = possible_values_options(customized) + if possible_values_options.present? + keyword = keyword.to_s.downcase + if v = possible_values_options.detect {|text, id| text.downcase == keyword} + if v.is_a?(Array) + v.last + else + v + end + end + else + keyword + end + end + + # Returns a ORDER BY clause that can used to sort customized + # objects by their value of the custom field. + # Returns nil if the custom field can not be used for sorting. + def order_statement + return nil if multiple? + case field_format + when 'string', 'text', 'list', 'date', 'bool' + # COALESCE is here to make sure that blank and NULL values are sorted equally + "COALESCE(#{join_alias}.value, '')" + 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 + "CAST(CASE #{join_alias}.value WHEN '' THEN '0' ELSE #{join_alias}.value END AS decimal(30,3))" + when 'user', 'version' + value_class.fields_for_order_statement(value_join_alias) + else + nil + end + end + + # Returns a GROUP BY clause that can used to group by custom value + # Returns nil if the custom field can not be used for grouping. + def group_statement + return nil if multiple? + case field_format + when 'list', 'date', 'bool', 'int' + order_statement + when 'user', 'version' + "COALESCE(#{join_alias}.value, '')" + else + nil + end + end + + def join_for_order_statement + case field_format + when 'user', 'version' + "LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" + + " ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" + + " AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" + + " AND #{join_alias}.custom_field_id = #{id}" + + " AND #{join_alias}.value <> ''" + + " AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" + + " WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" + + " AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" + + " AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)" + + " LEFT OUTER JOIN #{value_class.table_name} #{value_join_alias}" + + " ON CAST(CASE #{join_alias}.value WHEN '' THEN '0' ELSE #{join_alias}.value END AS decimal(30,0)) = #{value_join_alias}.id" + when 'int', 'float' + "LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" + + " ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" + + " AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" + + " AND #{join_alias}.custom_field_id = #{id}" + + " AND #{join_alias}.value <> ''" + + " AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" + + " WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" + + " AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" + + " AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)" + when 'string', 'text', 'list', 'date', 'bool' + "LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" + + " ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" + + " AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" + + " AND #{join_alias}.custom_field_id = #{id}" + + " AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" + + " WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" + + " AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" + + " AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)" + else + nil + end + end + + def join_alias + "cf_#{id}" + end + + def value_join_alias + join_alias + "_" + field_format + end + + def <=>(field) + position <=> field.position + end + + # Returns the class that values represent + def value_class + case field_format + when 'user', 'version' + field_format.classify.constantize + else + nil + end + 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 + where(:is_for_all => true).order('position').all + end + + def type_name + nil + end + + # Returns the error messages for the given value + # or an empty array if value is a valid value for the custom field + def validate_field_value(value) + errs = [] + if value.is_a?(Array) + if !multiple? + errs << ::I18n.t('activerecord.errors.messages.invalid') + end + if is_required? && value.detect(&:present?).nil? + errs << ::I18n.t('activerecord.errors.messages.blank') + end + value.each {|v| errs += validate_field_value_format(v)} + else + if is_required? && value.blank? + errs << ::I18n.t('activerecord.errors.messages.blank') + end + errs += validate_field_value_format(value) + end + errs + end + + # Returns true if value is a valid value for the custom field + def valid_field_value?(value) + validate_field_value(value).empty? + end + + def format_in?(*args) + args.include?(field_format) + end + + protected + + # Returns the error message for the given value regarding its format + def validate_field_value_format(value) + errs = [] + if value.present? + errs << ::I18n.t('activerecord.errors.messages.invalid') unless regexp.blank? or value =~ Regexp.new(regexp) + errs << ::I18n.t('activerecord.errors.messages.too_short', :count => min_length) if min_length > 0 and value.length < min_length + errs << ::I18n.t('activerecord.errors.messages.too_long', :count => max_length) if max_length > 0 and value.length > max_length + + # Format specific validations + case field_format + when 'int' + errs << ::I18n.t('activerecord.errors.messages.not_a_number') unless value =~ /^[+-]?\d+$/ + when 'float' + begin; Kernel.Float(value); rescue; errs << ::I18n.t('activerecord.errors.messages.invalid') end + when 'date' + errs << ::I18n.t('activerecord.errors.messages.not_a_date') unless value =~ /^\d{4}-\d{2}-\d{2}$/ && begin; value.to_date; rescue; false end + when 'list' + errs << ::I18n.t('activerecord.errors.messages.inclusion') unless possible_values.include?(value) + end + end + errs + end + + # Removes multiple values for the custom field after setting the multiple attribute to false + # We kepp the value with the highest id for each customized object + def handle_multiplicity_change + if !new_record? && multiple_was && !multiple + ids = custom_values. + where("EXISTS(SELECT 1 FROM #{CustomValue.table_name} cve WHERE cve.custom_field_id = #{CustomValue.table_name}.custom_field_id" + + " AND cve.customized_type = #{CustomValue.table_name}.customized_type AND cve.customized_id = #{CustomValue.table_name}.customized_id" + + " AND cve.id > #{CustomValue.table_name}.id)"). + pluck(:id) + + if ids.any? + custom_values.where(:id => ids).delete_all + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6e/6e61a701e2749bf002d37820f0ff37fbc966f8bd.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/6e/6e61a701e2749bf002d37820f0ff37fbc966f8bd.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,29 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 #:nodoc: + module CoreExtensions #:nodoc: + module String #:nodoc: + # Custom string inflections + module Inflections + def with_leading_slash + starts_with?('/') ? self : "/#{ self }" + end + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6e/6eadc5531e55c7dfea168413bcc84110076f8d5b.svn-base --- a/.svn/pristine/6e/6eadc5531e55c7dfea168413bcc84110076f8d5b.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,96 +0,0 @@ -module CodeRay -module Encoders - - # Makes a statistic for the given tokens. - # - # Alias: +stats+ - class Statistic < Encoder - - register_for :statistic - - attr_reader :type_stats, :real_token_count # :nodoc: - - TypeStats = Struct.new :count, :size # :nodoc: - - protected - - def setup options - super - - @type_stats = Hash.new { |h, k| h[k] = TypeStats.new 0, 0 } - @real_token_count = 0 - end - - STATS = <<-STATS # :nodoc: - -Code Statistics - -Tokens %8d - Non-Whitespace %8d -Bytes Total %8d - -Token Types (%d): - type count ratio size (average) -------------------------------------------------------------- -%s - STATS - - TOKEN_TYPES_ROW = <<-TKR # :nodoc: - %-20s %8d %6.2f %% %5.1f - TKR - - def finish options - all = @type_stats['TOTAL'] - all_count, all_size = all.count, all.size - @type_stats.each do |type, stat| - stat.size /= stat.count.to_f - end - types_stats = @type_stats.sort_by { |k, v| [-v.count, k.to_s] }.map do |k, v| - TOKEN_TYPES_ROW % [k, v.count, 100.0 * v.count / all_count, v.size] - end.join - @out << STATS % [ - all_count, @real_token_count, all_size, - @type_stats.delete_if { |k, v| k.is_a? String }.size, - types_stats - ] - - super - end - - public - - def text_token text, kind - @real_token_count += 1 unless kind == :space - @type_stats[kind].count += 1 - @type_stats[kind].size += text.size - @type_stats['TOTAL'].size += text.size - @type_stats['TOTAL'].count += 1 - end - - # TODO Hierarchy handling - def begin_group kind - block_token ':begin_group', kind - end - - def end_group kind - block_token ':end_group', kind - end - - def begin_line kind - block_token ':begin_line', kind - end - - def end_line kind - block_token ':end_line', kind - end - - def block_token action, kind - @type_stats['TOTAL'].count += 1 - @type_stats[action].count += 1 - @type_stats[kind].count += 1 - end - - end - -end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6e/6eb8f6ba1fb9f77eecb3f59c8c17c2519975f45e.svn-base --- a/.svn/pristine/6e/6eb8f6ba1fb9f77eecb3f59c8c17c2519975f45e.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,120 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) -require 'news_controller' - -# Re-raise errors caught by the controller. -class NewsController; def rescue_action(e) raise e end; end - -class NewsControllerTest < ActionController::TestCase - fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules, :news, :comments - - def setup - @controller = NewsController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - User.current = nil - end - - def test_index - get :index - assert_response :success - assert_template 'index' - assert_not_nil assigns(:newss) - assert_nil assigns(:project) - end - - def test_index_with_project - get :index, :project_id => 1 - assert_response :success - assert_template 'index' - assert_not_nil assigns(:newss) - end - - def test_show - get :show, :id => 1 - assert_response :success - assert_template 'show' - assert_tag :tag => 'h2', :content => /eCookbook first release/ - end - - def test_show_not_found - get :show, :id => 999 - assert_response 404 - end - - def test_get_new - @request.session[:user_id] = 2 - get :new, :project_id => 1 - assert_response :success - assert_template 'new' - end - - def test_post_create - ActionMailer::Base.deliveries.clear - Setting.notified_events << 'news_added' - - @request.session[:user_id] = 2 - post :create, :project_id => 1, :news => { :title => 'NewsControllerTest', - :description => 'This is the description', - :summary => '' } - assert_redirected_to '/projects/ecookbook/news' - - news = News.find_by_title('NewsControllerTest') - assert_not_nil news - assert_equal 'This is the description', news.description - assert_equal User.find(2), news.author - assert_equal Project.find(1), news.project - assert_equal 1, ActionMailer::Base.deliveries.size - end - - def test_get_edit - @request.session[:user_id] = 2 - get :edit, :id => 1 - assert_response :success - assert_template 'edit' - end - - def test_put_update - @request.session[:user_id] = 2 - put :update, :id => 1, :news => { :description => 'Description changed by test_post_edit' } - assert_redirected_to '/news/1' - news = News.find(1) - assert_equal 'Description changed by test_post_edit', news.description - end - - def test_post_create_with_validation_failure - @request.session[:user_id] = 2 - post :create, :project_id => 1, :news => { :title => '', - :description => 'This is the description', - :summary => '' } - assert_response :success - assert_template 'new' - assert_not_nil assigns(:news) - assert assigns(:news).new_record? - assert_tag :tag => 'div', :attributes => { :id => 'errorExplanation' }, - :content => /1 error/ - end - - def test_destroy - @request.session[:user_id] = 2 - delete :destroy, :id => 1 - assert_redirected_to '/projects/ecookbook/news' - assert_nil News.find_by_id(1) - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6e/6ed00895e314aeaec84d2e309911a6ab1bb54af7.svn-base --- a/.svn/pristine/6e/6ed00895e314aeaec84d2e309911a6ab1bb54af7.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,17 +0,0 @@ -module CodeRay -module Scanners - - load :html - - # Scanner for XML. - # - # Currently this is the same scanner as Scanners::HTML. - class XML < HTML - - register_for :xml - file_extension 'xml' - - end - -end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6f/6f3de0785368c550e0511d7f874b1261b16e18b8.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/6f/6f3de0785368c550e0511d7f874b1261b16e18b8.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,220 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class IssuesTest < ActionController::IntegrationTest + fixtures :projects, + :users, + :roles, + :members, + :member_roles, + :trackers, + :projects_trackers, + :enabled_modules, + :issue_statuses, + :issues, + :enumerations, + :custom_fields, + :custom_values, + :custom_fields_trackers + + # create an issue + def test_add_issue + log_user('jsmith', 'jsmith') + get 'projects/1/issues/new', :tracker_id => '1' + assert_response :success + assert_template 'issues/new' + + post 'projects/1/issues', :tracker_id => "1", + :issue => { :start_date => "2006-12-26", + :priority_id => "4", + :subject => "new test issue", + :category_id => "", + :description => "new issue", + :done_ratio => "0", + :due_date => "", + :assigned_to_id => "" }, + :custom_fields => {'2' => 'Value for field 2'} + # find created issue + issue = Issue.find_by_subject("new test issue") + assert_kind_of Issue, issue + + # check redirection + assert_redirected_to :controller => 'issues', :action => 'show', :id => issue + follow_redirect! + assert_equal issue, assigns(:issue) + + # check issue attributes + assert_equal 'jsmith', issue.author.login + assert_equal 1, issue.project.id + assert_equal 1, issue.status.id + end + + # add then remove 2 attachments to an issue + def test_issue_attachments + log_user('jsmith', 'jsmith') + set_tmp_attachments_directory + + put 'issues/1', + :notes => 'Some notes', + :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'This is an attachment'}} + assert_redirected_to "/issues/1" + + # make sure attachment was saved + attachment = Issue.find(1).attachments.find_by_filename("testfile.txt") + assert_kind_of Attachment, attachment + assert_equal Issue.find(1), attachment.container + assert_equal 'This is an attachment', attachment.description + # verify the size of the attachment stored in db + #assert_equal file_data_1.length, attachment.filesize + # verify that the attachment was written to disk + assert File.exist?(attachment.diskfile) + + # remove the attachments + Issue.find(1).attachments.each(&:destroy) + assert_equal 0, Issue.find(1).attachments.length + end + + def test_other_formats_links_on_index + get '/projects/ecookbook/issues' + + %w(Atom PDF CSV).each do |format| + assert_tag :a, :content => format, + :attributes => { :href => "/projects/ecookbook/issues.#{format.downcase}", + :rel => 'nofollow' } + end + end + + def test_other_formats_links_on_index_without_project_id_in_url + get '/issues', :project_id => 'ecookbook' + + %w(Atom PDF CSV).each do |format| + assert_tag :a, :content => format, + :attributes => { :href => "/projects/ecookbook/issues.#{format.downcase}", + :rel => 'nofollow' } + end + end + + def test_pagination_links_on_index + Setting.per_page_options = '2' + get '/projects/ecookbook/issues' + + assert_tag :a, :content => '2', + :attributes => { :href => '/projects/ecookbook/issues?page=2' } + + end + + def test_pagination_links_on_index_without_project_id_in_url + Setting.per_page_options = '2' + get '/issues', :project_id => 'ecookbook' + + assert_tag :a, :content => '2', + :attributes => { :href => '/projects/ecookbook/issues?page=2' } + + end + + def test_issue_with_user_custom_field + @field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user', :is_for_all => true, :trackers => Tracker.all) + Role.anonymous.add_permission! :add_issues, :edit_issues + users = Project.find(1).users + tester = users.first + + # Issue form + get '/projects/ecookbook/issues/new' + assert_response :success + assert_tag :select, + :attributes => {:name => "issue[custom_field_values][#{@field.id}]"}, + :children => {:count => (users.size + 1)}, # +1 for blank value + :child => { + :tag => 'option', + :attributes => {:value => tester.id.to_s}, + :content => tester.name + } + + # Create issue + assert_difference 'Issue.count' do + post '/projects/ecookbook/issues', + :issue => { + :tracker_id => '1', + :priority_id => '4', + :subject => 'Issue with user custom field', + :custom_field_values => {@field.id.to_s => users.first.id.to_s} + } + end + issue = Issue.first(:order => 'id DESC') + assert_response 302 + + # Issue view + follow_redirect! + assert_tag :th, + :content => /Tester/, + :sibling => { + :tag => 'td', + :content => tester.name + } + assert_tag :select, + :attributes => {:name => "issue[custom_field_values][#{@field.id}]"}, + :children => {:count => (users.size + 1)}, # +1 for blank value + :child => { + :tag => 'option', + :attributes => {:value => tester.id.to_s, :selected => 'selected'}, + :content => tester.name + } + + # Update issue + new_tester = users[1] + assert_difference 'Journal.count' do + put "/issues/#{issue.id}", + :notes => 'Updating custom field', + :issue => { + :custom_field_values => {@field.id.to_s => new_tester.id.to_s} + } + end + assert_response 302 + + # Issue view + follow_redirect! + assert_tag :content => 'Tester', + :ancestor => {:tag => 'ul', :attributes => {:class => /details/}}, + :sibling => { + :content => tester.name, + :sibling => { + :content => new_tester.name + } + } + end + + def test_update_using_invalid_http_verbs + subject = 'Updated by an invalid http verb' + + get '/issues/update/1', {:issue => {:subject => subject}}, credentials('jsmith') + assert_response 404 + assert_not_equal subject, Issue.find(1).subject + + post '/issues/1', {:issue => {:subject => subject}}, credentials('jsmith') + assert_response 404 + assert_not_equal subject, Issue.find(1).subject + end + + def test_get_watch_should_be_invalid + assert_no_difference 'Watcher.count' do + get '/watchers/watch?object_type=issue&object_id=1', {}, credentials('jsmith') + assert_response 404 + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6f/6f3eae848dd2627dfa6aa6d53a27862ad05c9e96.svn-base --- a/.svn/pristine/6f/6f3eae848dd2627dfa6aa6d53a27862ad05c9e96.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,83 +0,0 @@ -The engines plugin enhances Rails' own plugin framework, making it simple to share controllers, helpers, models, public assets, routes and migrations in plugins. - -For more information, see http://rails-engines.org - -= Using the plugin - -Once you've installed the engines plugin, you'll need to add a single line to the top of config/environment.rb: - - require File.join(File.dirname(__FILE__), '../vendor/plugins/engines/boot') - -You should add this line just below the require for Rails' own boot.rb file. This will enabled the enhanced plugin loading mechanism automatically for you (i.e. you don't need to set config.plugin_loader manually). - -With that aside, you're now ready to start using more powerful plugins in your application. Read on to find out more about what the engines plugin enables. - - -== Better plugins - -In addition to the regular set of plugin-supported files (lib, init.rb, tasks, generators, tests), plugins can carry the following when the engines plugin is also installed. - - -=== Controllers, Helpers, and Views - -Include these files in an app directory just like you would in a normal Rails application. If you need to override a method, view or partial, create the corresponding file in your main app directory and it will be used instead. - -* Controllers & Helpers: See Engines::RailsExtensions::Dependencies for more information. -* Views: now handled almost entirely by ActionView itself (see Engines::Plugin#add_plugin_view_paths for more information) - -=== Models - -Model code can similarly be placed in an app/models/ directory. Unfortunately, it's not possible to automatically override methods within a model; if your application needs to change the way a model behaves, consider creating a subclass, or replacing the model entirely within your application's app/models/ directory. See Engines::RailsExtensions::Dependencies for more information. - -IMPORTANT NOTE: when you load code from within plugins, it is typically not handled well by Rails in terms of unloading and reloading changes. Look here for more information - http://rails-engines.org/development/common-issues-when-overloading-code-from-plugins/ - -=== Routes - -Include your route declarations in a routes.rb file at the root of your plugins, e.g.: - - connect "/my/url", :controller => "some_controller" - my_named_route "do_stuff", :controller => "blah", :action => "stuff" - # etc. - -You can then load these files into your application by declaring their inclusion in the application's config/routes.rb: - - map.from_plugin :plugin_name - -See Engines::RailsExtensions::Routing for more information. - -=== Migrations - -Migrations record the changes in your database as your application evolves. With engines 1.2, migrations from plugins can also join in this evolution as first-class entities. To add migrations to a plugin, include a db/migrate/ folder and add migrations there as normal. These migrations can then be integrated into the main flow of database evolution by running the plugin_migration generator: - - script/generate plugin_migration - -This will produce a migration in your application. Running this migration (via rake db:migrate, as normal) will migrate the database according to the latest migrations in each plugin. See Engines::RailsExtensions::Migrations for more information. - - -=== More powerful Rake tasks - -The engines plugin enhances and adds to the suite of default rake tasks for working with plugins. The doc:plugins task now includes controllers, helpers and models under app, and anything other code found under the plugin's code_paths attribute. New testing tasks have been added to run unit, functional and integration tests from plugins, whilst making it easier to load fixtures from plugins. See Engines::Testing for more details about testing, and run - - rake -T - -to see the set of rake tasks available. - -= Testing the engines plugin itself - -Because of the way the engines plugin modifies Rails, the simplest way to consistently test it against multiple versions is by generating a test harness application - a full Rails application that includes tests to verify the engines plugin behaviour in a real, running environment. - -Run the tests like this: - - $ cd engines - $ rake test - -This will generate a test_app directory within the engines plugin (using the default 'rails' command), import tests and code into that application and then run the test suite. - -If you wish to test against a specific version of Rails, run the tests with the RAILS environment variable set to the local directory containing your Rails checkout - - $ rake test RAILS=/Users/james/Code/rails_edge_checkout - -Alternatively, you can clone the latest version of Rails ('edge rails') from github like so: - - $ rake test RAILS=edge - diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6f/6f698b7e19aa2a0b60b2198d129cd28a41bdf55a.svn-base --- a/.svn/pristine/6f/6f698b7e19aa2a0b60b2198d129cd28a41bdf55a.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ -== Sample plugin - -This is a sample plugin for Redmine - -== Installation - -1. Copy the plugin directory into the vendor/plugins directory - -2. Migrate plugin: - rake db:migrate_plugins - -3. Start Redmine - -Installed plugins are listed and can be configured from 'Admin -> Plugins' screen. diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6f/6f837ff76b98a304a7758e9ac959a9fc7a915173.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/6f/6f837ff76b98a304a7758e9ac959a9fc7a915173.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,244 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class CustomFieldTest < ActiveSupport::TestCase + fixtures :custom_fields + + def test_create + field = UserCustomField.new(:name => 'Money money money', :field_format => 'float') + assert field.save + end + + def test_before_validation + field = CustomField.new(:name => 'test_before_validation', :field_format => 'int') + field.searchable = true + assert field.save + assert_equal false, field.searchable + field.searchable = true + assert field.save + assert_equal false, field.searchable + end + + def test_regexp_validation + field = IssueCustomField.new(:name => 'regexp', :field_format => 'text', :regexp => '[a-z0-9') + assert !field.save + assert_include I18n.t('activerecord.errors.messages.invalid'), + field.errors[:regexp] + field.regexp = '[a-z0-9]' + assert field.save + end + + def test_default_value_should_be_validated + field = CustomField.new(:name => 'Test', :field_format => 'int') + field.default_value = 'abc' + assert !field.valid? + field.default_value = '6' + assert field.valid? + end + + def test_default_value_should_not_be_validated_when_blank + field = CustomField.new(:name => 'Test', :field_format => 'list', :possible_values => ['a', 'b'], :is_required => true, :default_value => '') + assert field.valid? + end + + def test_should_not_change_field_format_of_existing_custom_field + field = CustomField.find(1) + field.field_format = 'int' + assert_equal 'list', field.field_format + 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 + + if "string".respond_to?(:encoding) + def test_possible_values_stored_as_binary_should_be_utf8_encoded + field = CustomField.find(11) + assert_kind_of Array, field.possible_values + assert field.possible_values.size > 0 + field.possible_values.each do |value| + assert_equal "UTF-8", value.encoding.name + end + end + end + + def test_destroy + field = CustomField.find(1) + assert field.destroy + end + + def test_new_subclass_instance_should_return_an_instance + f = CustomField.new_subclass_instance('IssueCustomField') + assert_kind_of IssueCustomField, f + end + + def test_new_subclass_instance_should_set_attributes + f = CustomField.new_subclass_instance('IssueCustomField', :name => 'Test') + assert_kind_of IssueCustomField, f + assert_equal 'Test', f.name + end + + def test_new_subclass_instance_with_invalid_class_name_should_return_nil + assert_nil CustomField.new_subclass_instance('WrongClassName') + end + + def test_new_subclass_instance_with_non_subclass_name_should_return_nil + assert_nil CustomField.new_subclass_instance('Project') + end + + def test_string_field_validation_with_blank_value + f = CustomField.new(:field_format => 'string') + + assert f.valid_field_value?(nil) + assert f.valid_field_value?('') + + f.is_required = true + assert !f.valid_field_value?(nil) + assert !f.valid_field_value?('') + end + + def test_string_field_validation_with_min_and_max_lengths + f = CustomField.new(:field_format => 'string', :min_length => 2, :max_length => 5) + + assert f.valid_field_value?(nil) + assert f.valid_field_value?('') + assert f.valid_field_value?('a' * 2) + assert !f.valid_field_value?('a') + assert !f.valid_field_value?('a' * 6) + end + + def test_string_field_validation_with_regexp + f = CustomField.new(:field_format => 'string', :regexp => '^[A-Z0-9]*$') + + assert f.valid_field_value?(nil) + assert f.valid_field_value?('') + assert f.valid_field_value?('ABC') + assert !f.valid_field_value?('abc') + end + + def test_date_field_validation + f = CustomField.new(:field_format => 'date') + + assert f.valid_field_value?(nil) + assert f.valid_field_value?('') + assert f.valid_field_value?('1975-07-14') + assert !f.valid_field_value?('1975-07-33') + assert !f.valid_field_value?('abc') + end + + def test_list_field_validation + f = CustomField.new(:field_format => 'list', :possible_values => ['value1', 'value2']) + + assert f.valid_field_value?(nil) + assert f.valid_field_value?('') + assert f.valid_field_value?('value2') + assert !f.valid_field_value?('abc') + end + + def test_int_field_validation + f = CustomField.new(:field_format => 'int') + + assert f.valid_field_value?(nil) + assert f.valid_field_value?('') + assert f.valid_field_value?('123') + assert f.valid_field_value?('+123') + assert f.valid_field_value?('-123') + assert !f.valid_field_value?('6abc') + end + + def test_float_field_validation + f = CustomField.new(:field_format => 'float') + + assert f.valid_field_value?(nil) + assert f.valid_field_value?('') + assert f.valid_field_value?('11.2') + assert f.valid_field_value?('-6.250') + assert f.valid_field_value?('5') + assert !f.valid_field_value?('6abc') + end + + def test_multi_field_validation + f = CustomField.new(:field_format => 'list', :multiple => 'true', :possible_values => ['value1', 'value2']) + + assert f.valid_field_value?(nil) + assert f.valid_field_value?('') + assert f.valid_field_value?([]) + assert f.valid_field_value?([nil]) + assert f.valid_field_value?(['']) + + assert f.valid_field_value?('value2') + assert !f.valid_field_value?('abc') + + assert f.valid_field_value?(['value2']) + assert !f.valid_field_value?(['abc']) + + assert f.valid_field_value?(['', 'value2']) + assert !f.valid_field_value?(['', 'abc']) + + assert f.valid_field_value?(['value1', 'value2']) + assert !f.valid_field_value?(['value1', 'abc']) + end + + def test_changing_multiple_to_false_should_delete_multiple_values + field = ProjectCustomField.create!(:name => 'field', :field_format => 'list', :multiple => 'true', :possible_values => ['field1', 'field2']) + other = ProjectCustomField.create!(:name => 'other', :field_format => 'list', :multiple => 'true', :possible_values => ['other1', 'other2']) + + item_with_multiple_values = Project.generate!(:custom_field_values => {field.id => ['field1', 'field2'], other.id => ['other1', 'other2']}) + item_with_single_values = Project.generate!(:custom_field_values => {field.id => ['field1'], other.id => ['other2']}) + + assert_difference 'CustomValue.count', -1 do + field.multiple = false + field.save! + end + + item_with_multiple_values = Project.find(item_with_multiple_values.id) + assert_kind_of String, item_with_multiple_values.custom_field_value(field) + assert_kind_of Array, item_with_multiple_values.custom_field_value(other) + assert_equal 2, item_with_multiple_values.custom_field_value(other).size + end + + def test_value_class_should_return_the_class_used_for_fields_values + assert_equal User, CustomField.new(:field_format => 'user').value_class + assert_equal Version, CustomField.new(:field_format => 'version').value_class + end + + def test_value_class_should_return_nil_for_other_fields + assert_nil CustomField.new(:field_format => 'text').value_class + assert_nil CustomField.new.value_class + end + + def test_value_from_keyword_for_list_custom_field + field = CustomField.find(1) + assert_equal 'PostgreSQL', field.value_from_keyword('postgresql', Issue.find(1)) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6f/6f938056d9ea2cfe9c4f2e93944e625cd1a36508.svn-base --- a/.svn/pristine/6f/6f938056d9ea2cfe9c4f2e93944e625cd1a36508.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,341 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -require 'redmine/scm/adapters/abstract_adapter' -require 'cgi' - -module Redmine - module Scm - module Adapters - class MercurialAdapter < AbstractAdapter - - # Mercurial executable name - HG_BIN = Redmine::Configuration['scm_mercurial_command'] || "hg" - HELPERS_DIR = File.dirname(__FILE__) + "/mercurial" - HG_HELPER_EXT = "#{HELPERS_DIR}/redminehelper.py" - TEMPLATE_NAME = "hg-template" - TEMPLATE_EXTENSION = "tmpl" - - # raised if hg command exited with error, e.g. unknown revision. - class HgCommandAborted < CommandFailed; end - - class << self - def client_command - @@bin ||= HG_BIN - end - - def sq_bin - @@sq_bin ||= shell_quote_command - end - - def client_version - @@client_version ||= (hgversion || []) - end - - def client_available - client_version_above?([1, 2]) - end - - def hgversion - # The hg version is expressed either as a - # release number (eg 0.9.5 or 1.0) or as a revision - # id composed of 12 hexa characters. - theversion = hgversion_from_command_line.dup - if theversion.respond_to?(:force_encoding) - theversion.force_encoding('ASCII-8BIT') - end - if m = theversion.match(%r{\A(.*?)((\d+\.)+\d+)}) - m[2].scan(%r{\d+}).collect(&:to_i) - end - end - - def hgversion_from_command_line - shellout("#{sq_bin} --version") { |io| io.read }.to_s - end - - def template_path - @@template_path ||= template_path_for(client_version) - end - - def template_path_for(version) - "#{HELPERS_DIR}/#{TEMPLATE_NAME}-1.0.#{TEMPLATE_EXTENSION}" - end - end - - def initialize(url, root_url=nil, login=nil, password=nil, path_encoding=nil) - super - @path_encoding = path_encoding.blank? ? 'UTF-8' : path_encoding - end - - def path_encoding - @path_encoding - end - - def info - tip = summary['repository']['tip'] - Info.new(:root_url => CGI.unescape(summary['repository']['root']), - :lastrev => Revision.new(:revision => tip['revision'], - :scmid => tip['node'])) - # rescue HgCommandAborted - rescue Exception => e - logger.error "hg: error during getting info: #{e.message}" - nil - end - - def tags - as_ary(summary['repository']['tag']).map { |e| e['name'] } - end - - # Returns map of {'tag' => 'nodeid', ...} - def tagmap - alist = as_ary(summary['repository']['tag']).map do |e| - e.values_at('name', 'node') - end - Hash[*alist.flatten] - end - - def branches - brs = [] - as_ary(summary['repository']['branch']).each do |e| - br = Branch.new(e['name']) - br.revision = e['revision'] - br.scmid = e['node'] - brs << br - end - brs - end - - # Returns map of {'branch' => 'nodeid', ...} - def branchmap - alist = as_ary(summary['repository']['branch']).map do |e| - e.values_at('name', 'node') - end - Hash[*alist.flatten] - end - - def summary - return @summary if @summary - hg 'rhsummary' do |io| - output = io.read - if output.respond_to?(:force_encoding) - output.force_encoding('UTF-8') - end - begin - @summary = ActiveSupport::XmlMini.parse(output)['rhsummary'] - rescue - end - end - end - private :summary - - def entries(path=nil, identifier=nil, options={}) - p1 = scm_iconv(@path_encoding, 'UTF-8', path) - manifest = hg('rhmanifest', '-r', CGI.escape(hgrev(identifier)), - CGI.escape(without_leading_slash(p1.to_s))) do |io| - output = io.read - if output.respond_to?(:force_encoding) - output.force_encoding('UTF-8') - end - begin - ActiveSupport::XmlMini.parse(output)['rhmanifest']['repository']['manifest'] - rescue - end - end - path_prefix = path.blank? ? '' : with_trailling_slash(path) - - entries = Entries.new - as_ary(manifest['dir']).each do |e| - n = scm_iconv('UTF-8', @path_encoding, CGI.unescape(e['name'])) - p = "#{path_prefix}#{n}" - entries << Entry.new(:name => n, :path => p, :kind => 'dir') - end - - as_ary(manifest['file']).each do |e| - n = scm_iconv('UTF-8', @path_encoding, CGI.unescape(e['name'])) - p = "#{path_prefix}#{n}" - lr = Revision.new(:revision => e['revision'], :scmid => e['node'], - :identifier => e['node'], - :time => Time.at(e['time'].to_i)) - entries << Entry.new(:name => n, :path => p, :kind => 'file', - :size => e['size'].to_i, :lastrev => lr) - end - - entries - rescue HgCommandAborted - nil # means not found - end - - def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) - revs = Revisions.new - each_revision(path, identifier_from, identifier_to, options) { |e| revs << e } - revs - end - - # Iterates the revisions by using a template file that - # makes Mercurial produce a xml output. - def each_revision(path=nil, identifier_from=nil, identifier_to=nil, options={}) - hg_args = ['log', '--debug', '-C', '--style', self.class.template_path] - hg_args << '-r' << "#{hgrev(identifier_from)}:#{hgrev(identifier_to)}" - hg_args << '--limit' << options[:limit] if options[:limit] - hg_args << hgtarget(path) unless path.blank? - log = hg(*hg_args) do |io| - output = io.read - if output.respond_to?(:force_encoding) - output.force_encoding('UTF-8') - end - begin - # Mercurial < 1.5 does not support footer template for '' - ActiveSupport::XmlMini.parse("#{output}")['log'] - rescue - end - end - as_ary(log['logentry']).each do |le| - cpalist = as_ary(le['paths']['path-copied']).map do |e| - [e['__content__'], e['copyfrom-path']].map do |s| - scm_iconv('UTF-8', @path_encoding, CGI.unescape(s)) - end - end - cpmap = Hash[*cpalist.flatten] - paths = as_ary(le['paths']['path']).map do |e| - p = scm_iconv('UTF-8', @path_encoding, CGI.unescape(e['__content__']) ) - {:action => e['action'], - :path => with_leading_slash(p), - :from_path => (cpmap.member?(p) ? with_leading_slash(cpmap[p]) : nil), - :from_revision => (cpmap.member?(p) ? le['node'] : nil)} - end.sort { |a, b| a[:path] <=> b[:path] } - parents_ary = [] - as_ary(le['parents']['parent']).map do |par| - parents_ary << par['__content__'] if par['__content__'] != "000000000000" - end - yield Revision.new(:revision => le['revision'], - :scmid => le['node'], - :author => (le['author']['__content__'] rescue ''), - :time => Time.parse(le['date']['__content__']), - :message => le['msg']['__content__'], - :paths => paths, - :parents => parents_ary) - end - self - end - - # Returns list of nodes in the specified branch - def nodes_in_branch(branch, options={}) - hg_args = ['rhlog', '--template', '{node|short}\n', '--rhbranch', CGI.escape(branch)] - hg_args << '--from' << CGI.escape(branch) - hg_args << '--to' << '0' - hg_args << '--limit' << options[:limit] if options[:limit] - hg(*hg_args) { |io| io.readlines.map { |e| e.chomp } } - end - - def diff(path, identifier_from, identifier_to=nil) - hg_args = %w|rhdiff| - if identifier_to - hg_args << '-r' << hgrev(identifier_to) << '-r' << hgrev(identifier_from) - else - hg_args << '-c' << hgrev(identifier_from) - end - unless path.blank? - p = scm_iconv(@path_encoding, 'UTF-8', path) - hg_args << CGI.escape(hgtarget(p)) - end - diff = [] - hg *hg_args do |io| - io.each_line do |line| - diff << line - end - end - diff - rescue HgCommandAborted - nil # means not found - end - - def cat(path, identifier=nil) - p = CGI.escape(scm_iconv(@path_encoding, 'UTF-8', path)) - hg 'rhcat', '-r', CGI.escape(hgrev(identifier)), hgtarget(p) do |io| - io.binmode - io.read - end - rescue HgCommandAborted - nil # means not found - end - - def annotate(path, identifier=nil) - p = CGI.escape(scm_iconv(@path_encoding, 'UTF-8', path)) - blame = Annotate.new - hg 'rhannotate', '-ncu', '-r', CGI.escape(hgrev(identifier)), hgtarget(p) do |io| - io.each_line do |line| - line.force_encoding('ASCII-8BIT') if line.respond_to?(:force_encoding) - next unless line =~ %r{^([^:]+)\s(\d+)\s([0-9a-f]+):\s(.*)$} - r = Revision.new(:author => $1.strip, :revision => $2, :scmid => $3, - :identifier => $3) - blame.add_line($4.rstrip, r) - end - end - blame - rescue HgCommandAborted - # means not found or cannot be annotated - Annotate.new - end - - class Revision < Redmine::Scm::Adapters::Revision - # Returns the readable identifier - def format_identifier - "#{revision}:#{scmid}" - end - end - - # Runs 'hg' command with the given args - def hg(*args, &block) - repo_path = root_url || url - full_args = ['-R', repo_path, '--encoding', 'utf-8'] - full_args << '--config' << "extensions.redminehelper=#{HG_HELPER_EXT}" - full_args << '--config' << 'diff.git=false' - full_args += args - ret = shellout( - self.class.sq_bin + ' ' + full_args.map { |e| shell_quote e.to_s }.join(' '), - &block - ) - if $? && $?.exitstatus != 0 - raise HgCommandAborted, "hg exited with non-zero status: #{$?.exitstatus}" - end - ret - end - private :hg - - # Returns correct revision identifier - def hgrev(identifier, sq=false) - rev = identifier.blank? ? 'tip' : identifier.to_s - rev = shell_quote(rev) if sq - rev - end - private :hgrev - - def hgtarget(path) - path ||= '' - root_url + '/' + without_leading_slash(path) - end - private :hgtarget - - def as_ary(o) - return [] unless o - o.is_a?(Array) ? o : Array[o] - end - private :as_ary - end - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6f/6fc3080d660626f975ab483d311285aeb4746301.svn-base --- a/.svn/pristine/6f/6fc3080d660626f975ab483d311285aeb4746301.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +0,0 @@ -<%= wiki_page_breadcrumb(@page) %> - -

    <%=h @page.pretty_title %>

    - -<% form_for :content, @content, :url => {:action => 'update', :id => @page.title}, :html => {:method => :put, :multipart => true, :id => 'wiki_form'} do |f| %> -<%= f.hidden_field :version %> -<% if @section %> -<%= hidden_field_tag 'section', @section %> -<%= hidden_field_tag 'section_hash', @section_hash %> -<% end %> -<%= error_messages_for 'content' %> - -

    <%= text_area_tag 'content[text]', @text, :cols => 100, :rows => 25, :class => 'wiki-edit', :accesskey => accesskey(:edit) %>

    -


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

    -


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

    - -

    <%= submit_tag l(:button_save) %> - <%= link_to_remote l(:label_preview), - { :url => { :controller => 'wiki', :action => 'preview', :project_id => @project, :id => @page.title }, - :method => :post, - :update => 'preview', - :with => "Form.serialize('wiki_form')", - :complete => "Element.scrollTo('preview')" - }, :accesskey => accesskey(:preview) %>

    -<%= wikitoolbar_for 'content_text' %> -<% end %> - -
    - -<% content_for :header_tags do %> - <%= stylesheet_link_tag 'scm' %> - <%= robot_exclusion_tag %> -<% end %> - -<% html_title @page.pretty_title %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/6f/6ffa0ee9adc461e348a513bb42e310bb6ccba1cd.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/6f/6ffa0ee9adc461e348a513bb42e310bb6ccba1cd.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,217 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class BoardsControllerTest < ActionController::TestCase + fixtures :projects, :users, :members, :member_roles, :roles, :boards, :messages, :enabled_modules + + def setup + User.current = nil + end + + def test_index + get :index, :project_id => 1 + assert_response :success + assert_template 'index' + assert_not_nil assigns(:boards) + assert_not_nil assigns(:project) + end + + def test_index_not_found + get :index, :project_id => 97 + assert_response 404 + end + + def test_index_should_show_messages_if_only_one_board + Project.find(1).boards.slice(1..-1).each(&:destroy) + + get :index, :project_id => 1 + assert_response :success + assert_template 'show' + assert_not_nil assigns(:topics) + end + + def test_show + get :show, :project_id => 1, :id => 1 + assert_response :success + assert_template 'show' + assert_not_nil assigns(:board) + assert_not_nil assigns(:project) + assert_not_nil assigns(:topics) + end + + def test_show_should_display_sticky_messages_first + Message.update_all(:sticky => 0) + Message.update_all({:sticky => 1}, {:id => 1}) + + get :show, :project_id => 1, :id => 1 + assert_response :success + + topics = assigns(:topics) + assert_not_nil topics + assert topics.size > 1, "topics size was #{topics.size}" + assert topics.first.sticky? + assert topics.first.updated_on < topics.second.updated_on + end + + def test_show_should_display_message_with_last_reply_first + Message.update_all(:sticky => 0) + + # Reply to an old topic + old_topic = Message.where(:board_id => 1, :parent_id => nil).order('created_on ASC').first + reply = Message.new(:board_id => 1, :subject => 'New reply', :content => 'New reply', :author_id => 2) + old_topic.children << reply + + get :show, :project_id => 1, :id => 1 + assert_response :success + topics = assigns(:topics) + assert_not_nil topics + assert_equal old_topic, topics.first + end + + def test_show_with_permission_should_display_the_new_message_form + @request.session[:user_id] = 2 + get :show, :project_id => 1, :id => 1 + assert_response :success + assert_template 'show' + + assert_select 'form#message-form' do + assert_select 'input[name=?]', 'message[subject]' + end + end + + def test_show_atom + get :show, :project_id => 1, :id => 1, :format => 'atom' + assert_response :success + assert_template 'common/feed' + assert_not_nil assigns(:board) + assert_not_nil assigns(:project) + assert_not_nil assigns(:messages) + end + + def test_show_not_found + get :index, :project_id => 1, :id => 97 + assert_response 404 + end + + def test_new + @request.session[:user_id] = 2 + get :new, :project_id => 1 + assert_response :success + assert_template 'new' + + assert_select 'select[name=?]', 'board[parent_id]' do + assert_select 'option', (Project.find(1).boards.size + 1) + assert_select 'option[value=]', :text => '' + assert_select 'option[value=1]', :text => 'Help' + end + end + + def test_new_without_project_boards + Project.find(1).boards.delete_all + @request.session[:user_id] = 2 + + get :new, :project_id => 1 + assert_response :success + assert_template 'new' + + assert_select 'select[name=?]', 'board[parent_id]', 0 + end + + def test_create + @request.session[:user_id] = 2 + assert_difference 'Board.count' do + post :create, :project_id => 1, :board => { :name => 'Testing', :description => 'Testing board creation'} + end + assert_redirected_to '/projects/ecookbook/settings/boards' + board = Board.first(:order => 'id DESC') + assert_equal 'Testing', board.name + assert_equal 'Testing board creation', board.description + end + + def test_create_with_parent + @request.session[:user_id] = 2 + assert_difference 'Board.count' do + post :create, :project_id => 1, :board => { :name => 'Testing', :description => 'Testing', :parent_id => 2} + end + assert_redirected_to '/projects/ecookbook/settings/boards' + board = Board.first(:order => 'id DESC') + assert_equal Board.find(2), board.parent + end + + def test_create_with_failure + @request.session[:user_id] = 2 + assert_no_difference 'Board.count' do + post :create, :project_id => 1, :board => { :name => '', :description => 'Testing board creation'} + end + assert_response :success + assert_template 'new' + end + + def test_edit + @request.session[:user_id] = 2 + get :edit, :project_id => 1, :id => 2 + assert_response :success + assert_template 'edit' + end + + def test_edit_with_parent + board = Board.generate!(:project_id => 1, :parent_id => 2) + @request.session[:user_id] = 2 + get :edit, :project_id => 1, :id => board.id + assert_response :success + assert_template 'edit' + + assert_select 'select[name=?]', 'board[parent_id]' do + assert_select 'option[value=2][selected=selected]' + end + end + + def test_update + @request.session[:user_id] = 2 + assert_no_difference 'Board.count' do + put :update, :project_id => 1, :id => 2, :board => { :name => 'Testing', :description => 'Testing board update'} + end + assert_redirected_to '/projects/ecookbook/settings/boards' + assert_equal 'Testing', Board.find(2).name + end + + def test_update_position + @request.session[:user_id] = 2 + put :update, :project_id => 1, :id => 2, :board => { :move_to => 'highest'} + assert_redirected_to '/projects/ecookbook/settings/boards' + board = Board.find(2) + assert_equal 1, board.position + end + + def test_update_with_failure + @request.session[:user_id] = 2 + put :update, :project_id => 1, :id => 2, :board => { :name => '', :description => 'Testing board update'} + assert_response :success + assert_template 'edit' + end + + def test_destroy + @request.session[:user_id] = 2 + assert_difference 'Board.count', -1 do + delete :destroy, :project_id => 1, :id => 2 + end + assert_redirected_to '/projects/ecookbook/settings/boards' + assert_nil Board.find_by_id(2) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/70/7005c1ac60bfaef4c1c73b8db2692b1b9a732a71.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/70/7005c1ac60bfaef4c1c73b8db2692b1b9a732a71.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,64 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class CommentsControllerTest < ActionController::TestCase + fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules, :news, :comments + + def setup + User.current = nil + end + + def test_add_comment + @request.session[:user_id] = 2 + post :create, :id => 1, :comment => { :comments => 'This is a test comment' } + assert_redirected_to '/news/1' + + comment = News.find(1).comments.last + assert_not_nil comment + assert_equal 'This is a test comment', comment.comments + assert_equal User.find(2), comment.author + end + + def test_empty_comment_should_not_be_added + @request.session[:user_id] = 2 + assert_no_difference 'Comment.count' do + post :create, :id => 1, :comment => { :comments => '' } + assert_response :redirect + assert_redirected_to '/news/1' + end + end + + def test_create_should_be_denied_if_news_is_not_commentable + News.any_instance.stubs(:commentable?).returns(false) + @request.session[:user_id] = 2 + assert_no_difference 'Comment.count' do + post :create, :id => 1, :comment => { :comments => 'This is a test comment' } + assert_response 403 + end + end + + def test_destroy_comment + comments_count = News.find(1).comments.size + @request.session[:user_id] = 2 + delete :destroy, :id => 1, :comment_id => 2 + assert_redirected_to '/news/1' + assert_nil Comment.find_by_id(2) + assert_equal comments_count - 1, News.find(1).comments.size + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/70/7056d41df5e2a1e9fde837930bc56e51e678d276.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/70/7056d41df5e2a1e9fde837930bc56e51e678d276.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,1086 @@ +# Bulgarian translation by Nikolay Solakov and Ivan Cenov +bg: + # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl) + direction: ltr + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%d-%m-%Y" + short: "%b %d" + long: "%B %d, %Y" + + day_names: [ÐеделÑ, Понеделник, Вторник, СрÑда, Четвъртък, Петък, Събота] + abbr_day_names: [Ðед, Пон, Вто, СрÑ, Чет, Пет, Съб] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, Януари, Февруари, Март, Ðприл, Май, Юни, Юли, ÐвгуÑÑ‚, Септември, Октомври, Ðоември, Декември] + abbr_month_names: [~, Яну, Фев, Мар, Ðпр, Май, Юни, Юли, Ðвг, Сеп, Окт, Ðое, Дек] + # Used in date_select and datime_select. + order: + - :year + - :month + - :day + + time: + formats: + default: "%a, %d %b %Y %H:%M:%S %z" + time: "%H:%M" + short: "%d %b %H:%M" + long: "%B %d, %Y %H:%M" + am: "am" + pm: "pm" + + datetime: + distance_in_words: + half_a_minute: "half a minute" + less_than_x_seconds: + one: "по-малко от 1 Ñекунда" + other: "по-малко от %{count} Ñекунди" + x_seconds: + one: "1 Ñекунда" + other: "%{count} Ñекунди" + less_than_x_minutes: + one: "по-малко от 1 минута" + other: "по-малко от %{count} минути" + x_minutes: + one: "1 минута" + other: "%{count} минути" + about_x_hours: + one: "около 1 чаÑ" + other: "около %{count} чаÑа" + x_hours: + one: "1 чаÑ" + other: "%{count} чаÑа" + x_days: + one: "1 ден" + other: "%{count} дена" + about_x_months: + one: "около 1 меÑец" + other: "около %{count} меÑеца" + x_months: + one: "1 меÑец" + other: "%{count} меÑеца" + about_x_years: + one: "около 1 година" + other: "около %{count} години" + over_x_years: + one: "над 1 година" + other: "над %{count} години" + almost_x_years: + one: "почти 1 година" + other: "почти %{count} години" + + number: + format: + separator: "." + delimiter: "" + precision: 3 + + human: + format: + delimiter: "" + precision: 3 + storage_units: + format: "%n %u" + units: + byte: + one: байт + other: байта + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + +# Used in array.to_sentence. + support: + array: + sentence_connector: "и" + skip_last_comma: false + + activerecord: + errors: + template: + header: + one: "1 грешка попречи този %{model} да бъде запиÑан" + other: "%{count} грешки попречиха този %{model} да бъде запиÑан" + messages: + inclusion: "не ÑъщеÑтвува в ÑпиÑъка" + exclusion: "е запазено" + invalid: "е невалидно" + confirmation: "липÑва одобрение" + accepted: "трÑбва да Ñе приеме" + empty: "не може да е празно" + blank: "не може да е празно" + too_long: "е прекалено дълго" + too_short: "е прекалено къÑо" + wrong_length: "е Ñ Ð³Ñ€ÐµÑˆÐ½Ð° дължина" + taken: "вече ÑъщеÑтвува" + not_a_number: "не е чиÑло" + not_a_date: "е невалидна дата" + greater_than: "трÑбва да бъде по-голÑм[a/о] от %{count}" + greater_than_or_equal_to: "трÑбва да бъде по-голÑм[a/о] от или равен[a/o] на %{count}" + equal_to: "трÑбва да бъде равен[a/o] на %{count}" + less_than: "трÑбва да бъде по-малък[a/o] от %{count}" + less_than_or_equal_to: "трÑбва да бъде по-малък[a/o] от или равен[a/o] на %{count}" + odd: "трÑбва да бъде нечетен[a/o]" + even: "трÑбва да бъде четен[a/o]" + greater_than_start_date: "трÑбва да е Ñлед началната дата" + not_same_project: "не е от ÑÑŠÑ‰Ð¸Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚" + circular_dependency: "Тази Ñ€ÐµÐ»Ð°Ñ†Ð¸Ñ Ñ‰Ðµ доведе до безкрайна завиÑимоÑÑ‚" + cant_link_an_issue_with_a_descendant: "Една задача не може да бъде Ñвързвана към ÑÐ²Ð¾Ñ Ð¿Ð¾Ð´Ð·Ð°Ð´Ð°Ñ‡Ð°" + + actionview_instancetag_blank_option: Изберете + + general_text_No: 'Ðе' + general_text_Yes: 'Да' + general_text_no: 'не' + general_text_yes: 'да' + general_lang_name: 'Bulgarian (БългарÑки)' + general_csv_separator: ',' + general_csv_decimal_separator: '.' + general_csv_encoding: UTF-8 + general_pdf_encoding: UTF-8 + general_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: Ðепознат e-mail. + notice_can_t_change_password: Този профил е Ñ Ð²ÑŠÐ½ÑˆÐµÐ½ метод за оторизациÑ. Ðевъзможна ÑмÑна на паролата. + notice_account_lost_email_sent: Изпратен ви е e-mail Ñ Ð¸Ð½Ñтрукции за избор на нова парола. + notice_account_activated: Профилът ви е активиран. Вече може да влезете в ÑиÑтемата. + notice_successful_create: УÑпешно Ñъздаване. + notice_successful_update: УÑпешно обновÑване. + notice_successful_delete: УÑпешно изтриване. + notice_successful_connection: УÑпешно Ñвързване. + notice_file_not_found: ÐеÑъщеÑтвуваща или премеÑтена Ñтраница. + notice_locking_conflict: Друг потребител Ð¿Ñ€Ð¾Ð¼ÐµÐ½Ñ Ñ‚ÐµÐ·Ð¸ данни в момента. + notice_not_authorized: ÐÑмате право на доÑтъп до тази Ñтраница. + notice_not_authorized_archived_project: Проектът, който Ñе опитвате да видите е архивиран. Ðко ÑмÑтате, че това не е правилно, обърнете Ñе към админиÑтратора за разархивиране. + notice_email_sent: "Изпратен e-mail на %{value}" + notice_email_error: "Грешка при изпращане на e-mail (%{value})" + notice_feeds_access_key_reseted: Ð’Ð°ÑˆÐ¸Ñ ÐºÐ»ÑŽÑ‡ за RSS доÑтъп беше променен. + notice_api_access_key_reseted: ВашиÑÑ‚ API ключ за доÑтъп беше изчиÑтен. + notice_failed_to_save_issues: "ÐеуÑпешен Ð·Ð°Ð¿Ð¸Ñ Ð½Ð° %{count} задачи от %{total} избрани: %{ids}." + notice_failed_to_save_time_entries: "ÐевъзможноÑÑ‚ за Ð·Ð°Ð¿Ð¸Ñ Ð½Ð° %{count} запиÑа за използвано време от %{total} избрани: %{ids}." + notice_failed_to_save_members: "ÐевъзможноÑÑ‚ за Ð·Ð°Ð¿Ð¸Ñ Ð½Ð° член(ове): %{errors}." + notice_no_issue_selected: "ÐÑма избрани задачи." + notice_account_pending: "Профилът Ви е Ñъздаден и очаква одобрение от админиÑтратор." + notice_default_data_loaded: Примерната Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ðµ заредена уÑпешно. + notice_unable_delete_version: ÐевъзможноÑÑ‚ за изтриване на верÑÐ¸Ñ + notice_unable_delete_time_entry: ÐевъзможноÑÑ‚ за изтриване на Ð·Ð°Ð¿Ð¸Ñ Ð·Ð° използвано време. + notice_issue_done_ratios_updated: Обновен процент на завършените задачи. + notice_gantt_chart_truncated: МрежовиÑÑ‚ график е Ñъкратен, понеже броÑÑ‚ на обектите, които могат да бъдат показани е твърде голÑм (%{max}) + notice_issue_successful_create: Задача %{id} е Ñъздадена. + notice_issue_update_conflict: Задачата е била променена от друг потребител, докато вие Ñте Ñ Ñ€ÐµÐ´Ð°ÐºÑ‚Ð¸Ñ€Ð°Ð»Ð¸. + notice_account_deleted: ВашиÑÑ‚ профил беше премахнат без възможноÑÑ‚ за възÑтановÑване. + notice_user_successful_create: Потребител %{id} е Ñъздаден. + + error_can_t_load_default_data: "Грешка при зареждане на началната информациÑ: %{value}" + error_scm_not_found: ÐеÑъщеÑтвуващ обект в хранилището. + error_scm_command_failed: "Грешка при опит за ÐºÐ¾Ð¼ÑƒÐ½Ð¸ÐºÐ°Ñ†Ð¸Ñ Ñ Ñ…Ñ€Ð°Ð½Ð¸Ð»Ð¸Ñ‰Ðµ: %{value}" + error_scm_annotate: "Обектът не ÑъщеÑтвува или не може да бъде анотиран." + error_scm_annotate_big_text_file: "Файлът не може да бъде анотиран, понеже Ð½Ð°Ð´Ñ…Ð²ÑŠÑ€Ð»Ñ Ð¼Ð°ÐºÑÐ¸Ð¼Ð°Ð»Ð½Ð¸Ñ Ñ€Ð°Ð·Ð¼ÐµÑ€ за текÑтови файлове." + error_issue_not_found_in_project: 'Задачата не е намерена или не принадлежи на този проект' + error_no_tracker_in_project: ÐÑма аÑоциирани тракери Ñ Ñ‚Ð¾Ð·Ð¸ проект. Проверете наÑтройките на проекта. + error_no_default_issue_status: ÐÑма уÑтановено подразбиращо Ñе ÑÑŠÑтоÑние за задачите. ÐœÐ¾Ð»Ñ Ð¿Ñ€Ð¾Ð²ÐµÑ€ÐµÑ‚Ðµ вашата ÐºÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ (Вижте "ÐдминиÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ -> СъÑтоÑÐ½Ð¸Ñ Ð½Ð° задачи"). + error_can_not_delete_custom_field: ÐевъзможноÑÑ‚ за изтриване на потребителÑко поле + error_can_not_delete_tracker: Този тракер Ñъдържа задачи и не може да бъде изтрит. + error_can_not_remove_role: Тази Ñ€Ð¾Ð»Ñ Ñе използва и не може да бъде изтрита. + error_can_not_reopen_issue_on_closed_version: Задача, аÑоциирана ÑÑŠÑ Ð·Ð°Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð° верÑÐ¸Ñ Ð½Ðµ може да бъде отворена отново + error_can_not_archive_project: Този проект не може да бъде архивиран + error_issue_done_ratios_not_updated: Процентът на завършените задачи не е обновен. + error_workflow_copy_source: ÐœÐ¾Ð»Ñ Ð¸Ð·Ð±ÐµÑ€ÐµÑ‚Ðµ source тракер или Ñ€Ð¾Ð»Ñ + error_workflow_copy_target: ÐœÐ¾Ð»Ñ Ð¸Ð·Ð±ÐµÑ€ÐµÑ‚Ðµ тракер(и) и Ñ€Ð¾Ð»Ñ (роли). + error_unable_delete_issue_status: ÐевъзможноÑÑ‚ за изтриване на ÑÑŠÑтоÑние на задача + error_unable_to_connect: ÐевъзможноÑÑ‚ за Ñвързване Ñ (%{value}) + error_attachment_too_big: Този файл не може да бъде качен, понеже Ð½Ð°Ð´Ñ…Ð²ÑŠÑ€Ð»Ñ Ð¼Ð°ÐºÑималната възможна големина (%{max_size}) + error_session_expired: Вашата ÑеÑÐ¸Ñ Ðµ изтекла. ÐœÐ¾Ð»Ñ Ð²Ð»ÐµÐ·ÐµÑ‚Ðµ в Redmine отново. + warning_attachments_not_saved: "%{count} файла не бÑха запиÑани." + + mail_subject_lost_password: "Вашата парола (%{value})" + mail_body_lost_password: 'За да Ñмените паролата Ñи, използвайте ÑÐ»ÐµÐ´Ð½Ð¸Ñ Ð»Ð¸Ð½Ðº:' + mail_subject_register: "ÐÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ñ Ð½Ð° профил (%{value})" + mail_body_register: 'За да активирате профила Ñи използвайте ÑÐ»ÐµÐ´Ð½Ð¸Ñ Ð»Ð¸Ð½Ðº:' + mail_body_account_information_external: "Можете да използвате Ð²Ð°ÑˆÐ¸Ñ %{value} профил за вход." + mail_body_account_information: ИнформациÑта за профила ви + mail_subject_account_activation_request: "ЗаÑвка за активиране на профил в %{value}" + mail_body_account_activation_request: "Има новорегиÑтриран потребител (%{value}), очакващ вашето одобрение:" + mail_subject_reminder: "%{count} задачи Ñ ÐºÑ€Ð°ÐµÐ½ Ñрок Ñ Ñледващите %{days} дни" + mail_body_reminder: "%{count} задачи, назначени на Ð²Ð°Ñ Ñа Ñ ÐºÑ€Ð°ÐµÐ½ Ñрок в Ñледващите %{days} дни:" + mail_subject_wiki_content_added: "Wiki Ñтраницата '%{id}' беше добавена" + mail_body_wiki_content_added: Wiki Ñтраницата '%{id}' беше добавена от %{author}. + mail_subject_wiki_content_updated: "Wiki Ñтраницата '%{id}' беше обновена" + mail_body_wiki_content_updated: Wiki Ñтраницата '%{id}' беше обновена от %{author}. + + field_name: Име + field_description: ОпиÑание + field_summary: ÐÐ½Ð¾Ñ‚Ð°Ñ†Ð¸Ñ + field_is_required: Задължително + field_firstname: Име + field_lastname: Ð¤Ð°Ð¼Ð¸Ð»Ð¸Ñ + field_mail: Email + field_filename: Файл + field_filesize: Големина + field_downloads: Изтеглени файлове + field_author: Ðвтор + field_created_on: От дата + field_updated_on: Обновена + field_closed_on: Затворена + field_field_format: Тип + field_is_for_all: За вÑички проекти + field_possible_values: Възможни ÑтойноÑти + field_regexp: РегулÑрен израз + field_min_length: Мин. дължина + field_max_length: МакÑ. дължина + field_value: СтойноÑÑ‚ + field_category: ÐšÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñ + field_title: Заглавие + field_project: Проект + field_issue: Задача + field_status: СъÑтоÑние + field_notes: Бележка + field_is_closed: Затворена задача + field_is_default: СъÑтоÑние по подразбиране + field_tracker: Тракер + field_subject: Заглавие + field_due_date: Крайна дата + field_assigned_to: Възложена на + field_priority: Приоритет + field_fixed_version: Планувана верÑÐ¸Ñ + field_user: Потребител + field_principal: Principal + field_role: Ð Ð¾Ð»Ñ + field_homepage: Ðачална Ñтраница + field_is_public: Публичен + field_parent: Подпроект на + field_is_in_roadmap: Да Ñе вижда ли в Пътна карта + field_login: Потребител + field_mail_notification: ИзвеÑÑ‚Ð¸Ñ Ð¿Ð¾ пощата + field_admin: ÐдминиÑтратор + field_last_login_on: ПоÑледно Ñвързване + field_language: Език + field_effective_date: Дата + field_password: Парола + field_new_password: Ðова парола + field_password_confirmation: Потвърждение + field_version: ВерÑÐ¸Ñ + field_type: Тип + field_host: ХоÑÑ‚ + field_port: Порт + field_account: Профил + field_base_dn: Base DN + field_attr_login: Ðтрибут Login + field_attr_firstname: Ðтрибут Първо име (Firstname) + field_attr_lastname: Ðтрибут Ð¤Ð°Ð¼Ð¸Ð»Ð¸Ñ (Lastname) + field_attr_mail: Ðтрибут Email + field_onthefly: Динамично Ñъздаване на потребител + field_start_date: Ðачална дата + field_done_ratio: "% ПрогреÑ" + field_auth_source: Ðачин на Ð¾Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ð¸Ñ + field_hide_mail: Скрий e-mail адреÑа ми + field_comments: Коментар + field_url: ÐÐ´Ñ€ÐµÑ + field_start_page: Ðачална Ñтраница + field_subproject: Подпроект + field_hours: ЧаÑове + field_activity: ДейноÑÑ‚ + field_spent_on: Дата + field_identifier: Идентификатор + field_is_filter: Използва Ñе за филтър + field_issue_to: Свързана задача + field_delay: ОтмеÑтване + field_assignable: Възможно е възлагане на задачи за тази Ñ€Ð¾Ð»Ñ + field_redirect_existing_links: ПренаÑочване на ÑъщеÑтвуващи линкове + field_estimated_hours: ИзчиÑлено време + field_column_names: Колони + field_time_entries: Log time + field_time_zone: ЧаÑова зона + field_searchable: С възможноÑÑ‚ за търÑене + field_default_value: СтойноÑÑ‚ по подразбиране + field_comments_sorting: Сортиране на коментарите + field_parent_title: РодителÑка Ñтраница + field_editable: Editable + field_watcher: Ðаблюдател + field_identity_url: OpenID URL + field_content: Съдържание + field_group_by: Групиране на резултатите по + field_sharing: Sharing + field_parent_issue: РодителÑка задача + field_member_of_group: Член на група + field_assigned_to_role: Assignee's role + field_text: ТекÑтово поле + field_visible: Видим + field_warn_on_leaving_unsaved: Предупреди ме, когато напуÑкам Ñтраница Ñ Ð½ÐµÐ·Ð°Ð¿Ð¸Ñано Ñъдържание + field_issues_visibility: ВидимоÑÑ‚ на задачите + field_is_private: Лична + field_commit_logs_encoding: Кодова таблица на ÑъобщениÑта при поверÑване + field_scm_path_encoding: Кодова таблица на пътищата (path) + field_path_to_repository: Път до хранилището + field_root_directory: Коренна Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ (папка) + field_cvsroot: CVSROOT + field_cvs_module: Модул + field_repository_is_default: Главно хранилище + field_multiple: Избор на повече от една ÑтойноÑÑ‚ + field_auth_source_ldap_filter: LDAP филтър + field_core_fields: Стандартни полета + field_timeout: Таймаут (в Ñекунди) + field_board_parent: РодителÑки форум + field_private_notes: Лични бележки + field_inherit_members: ÐаÑледÑване на членовете на родителÑÐºÐ¸Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚ + + setting_app_title: Заглавие + setting_app_subtitle: ОпиÑание + setting_welcome_text: Допълнителен текÑÑ‚ + setting_default_language: Език по подразбиране + setting_login_required: ИзиÑкване за вход в ÑиÑтемата + setting_self_registration: РегиÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ Ð¾Ñ‚ потребители + setting_attachment_max_size: МакÑимална големина на прикачен файл + setting_issues_export_limit: МакÑимален брой задачи за екÑпорт + setting_mail_from: E-mail Ð°Ð´Ñ€ÐµÑ Ð·Ð° емиÑии + setting_bcc_recipients: Получатели на Ñкрито копие (bcc) + setting_plain_text_mail: Ñамо чиÑÑ‚ текÑÑ‚ (без HTML) + setting_host_name: ХоÑÑ‚ + setting_text_formatting: Форматиране на текÑта + setting_wiki_compression: КомпреÑиране на Wiki иÑториÑта + setting_feeds_limit: МакÑимален брой запиÑи в ATOM емиÑии + setting_default_projects_public: Ðовите проекти Ñа публични по подразбиране + setting_autofetch_changesets: Ðвтоматично извличане на ревизиите + setting_sys_api_enabled: Разрешаване на WS за управление + setting_commit_ref_keywords: ОтбелÑзващи ключови думи + setting_commit_fix_keywords: Приключващи ключови думи + setting_autologin: Ðвтоматичен вход + setting_date_format: Формат на датата + setting_time_format: Формат на чаÑа + setting_cross_project_issue_relations: Релации на задачи между проекти + setting_cross_project_subtasks: Подзадачи от други проекти + setting_issue_list_default_columns: Показвани колони по подразбиране + setting_repositories_encodings: Кодова таблица на прикачените файлове и хранилищата + setting_emails_header: Email header + setting_emails_footer: ПодтекÑÑ‚ за e-mail + setting_protocol: Протокол + setting_per_page_options: Опции за Ñтраниране + setting_user_format: ПотребителÑки формат + setting_activity_days_default: Брой дни показвани на таб ДейноÑÑ‚ + setting_display_subprojects_issues: Задачите от подпроектите по подразбиране Ñе показват в главните проекти + setting_enabled_scm: Разрешена SCM + setting_mail_handler_body_delimiters: ОтрÑзване на e-mail-ите Ñлед един от тези редове + setting_mail_handler_api_enabled: Разрешаване на WS за входÑщи e-mail-и + setting_mail_handler_api_key: API ключ + setting_sequential_project_identifiers: Генериране на поÑледователни проектни идентификатори + setting_gravatar_enabled: Използване на портребителÑки икони от Gravatar + setting_gravatar_default: Подразбиращо Ñе изображение от Gravatar + setting_diff_max_lines_displayed: МакÑимален брой показвани diff редове + setting_file_max_size_displayed: МакÑимален размер на текÑтовите файлове, показвани inline + setting_repository_log_display_limit: МакÑимален брой на показванете ревизии в лог файла + setting_openid: Рарешаване на OpenID вход и региÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ + setting_password_min_length: Минимална дължина на парола + setting_new_project_user_role_id: РолÑ, давана на потребител, Ñъздаващ проекти, който не е админиÑтратор + setting_default_projects_modules: Ðктивирани модули по подразбиране за нов проект + setting_issue_done_ratio: ИзчиÑление на процента на готови задачи Ñ + setting_issue_done_ratio_issue_field: Използване на поле '% ПрогреÑ' + setting_issue_done_ratio_issue_status: Използване на ÑÑŠÑтоÑнието на задачите + setting_start_of_week: Първи ден на Ñедмицата + setting_rest_api_enabled: Разрешаване на REST web ÑÑŠÑ€Ð²Ð¸Ñ + setting_cache_formatted_text: Кеширане на форматираните текÑтове + setting_default_notification_option: Подразбиращ Ñе начин за извеÑÑ‚Ñване + setting_commit_logtime_enabled: Разрешаване на отчитането на работното време + setting_commit_logtime_activity_id: ДейноÑÑ‚ при отчитане на работното време + setting_gantt_items_limit: МакÑимален брой обекти, които да Ñе показват в мрежов график + setting_issue_group_assignment: Разрешено назначаването на задачи на групи + setting_default_issue_start_date_to_creation_date: Ðачална дата на новите задачи по подразбиране да бъде днешната дата + setting_commit_cross_project_ref: ОтбелÑзване и приключване на задачи от други проекти, неÑвързани Ñ ÐºÐ¾Ð½ÐºÑ€ÐµÑ‚Ð½Ð¾Ñ‚Ð¾ хранилище + setting_unsubscribe: Потребителите могат да премахват профилите Ñи + setting_session_lifetime: МакÑимален живот на ÑеÑиите + setting_session_timeout: Таймаут за неактивноÑÑ‚ преди прекратÑване на ÑеÑиите + setting_thumbnails_enabled: Показване на миниатюри на прикачените Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ + setting_thumbnails_size: Размер на миниатюрите (в пикÑели) + setting_non_working_week_days: Ðе работни дни + setting_jsonp_enabled: Разрешаване на поддръжка на JSONP + setting_default_projects_tracker_ids: Тракери по подразбиране за нови проекти + + permission_add_project: Създаване на проект + permission_add_subprojects: Създаване на подпроекти + permission_edit_project: Редактиране на проект + permission_close_project: ЗатварÑне / отварÑне на проект + permission_select_project_modules: Избор на проектни модули + permission_manage_members: Управление на членовете (на екип) + permission_manage_project_activities: Управление на дейноÑтите на проекта + permission_manage_versions: Управление на верÑиите + permission_manage_categories: Управление на категориите + permission_view_issues: Разглеждане на задачите + permission_add_issues: ДобавÑне на задачи + permission_edit_issues: Редактиране на задачи + permission_manage_issue_relations: Управление на връзките между задачите + permission_set_own_issues_private: УÑтановÑване на ÑобÑтвените задачи публични или лични + permission_set_issues_private: УÑтановÑване на задачите публични или лични + permission_add_issue_notes: ДобавÑне на бележки + permission_edit_issue_notes: Редактиране на бележки + permission_edit_own_issue_notes: Редактиране на ÑобÑтвени бележки + permission_view_private_notes: Разглеждане на лични бележки + permission_set_notes_private: УÑтановÑване на бележките лични + permission_move_issues: ПремеÑтване на задачи + permission_delete_issues: Изтриване на задачи + permission_manage_public_queries: Управление на публичните заÑвки + permission_save_queries: Ð—Ð°Ð¿Ð¸Ñ Ð½Ð° Ð·Ð°Ð¿Ð¸Ñ‚Ð²Ð°Ð½Ð¸Ñ (queries) + permission_view_gantt: Разглеждане на мрежов график + permission_view_calendar: Разглеждане на календари + permission_view_issue_watchers: Разглеждане на ÑпиÑък Ñ Ð½Ð°Ð±Ð»ÑŽÐ´Ð°Ñ‚ÐµÐ»Ð¸ + permission_add_issue_watchers: ДобавÑне на наблюдатели + permission_delete_issue_watchers: Изтриване на наблюдатели + permission_log_time: Log spent time + permission_view_time_entries: Разглеждане на изразходваното време + permission_edit_time_entries: Редактиране на time logs + permission_edit_own_time_entries: Редактиране на ÑобÑтвените time logs + permission_manage_news: Управление на новини + permission_comment_news: Коментиране на новини + permission_view_documents: Разглеждане на документи + permission_add_documents: ДобавÑне на документи + permission_edit_documents: Редактиране на документи + permission_delete_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: Изтриване на прикачени файлове към wiki Ñтраници + permission_protect_wiki_pages: Заключване на wiki Ñтраници + permission_manage_repository: Управление на хранилища + permission_browse_repository: Разглеждане на хранилища + permission_view_changesets: Разглеждане на changesets + permission_commit_access: ПоверÑване + permission_manage_boards: Управление на boards + permission_view_messages: Разглеждане на ÑÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ + permission_add_messages: Публикуване на ÑÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ + permission_edit_messages: Редактиране на ÑÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ + permission_edit_own_messages: Редактиране на ÑобÑтвени ÑÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ + permission_delete_messages: Изтриване на ÑÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ + permission_delete_own_messages: Изтриване на ÑобÑтвени ÑÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ + permission_export_wiki_pages: ЕкÑпорт на wiki Ñтраници + permission_manage_subtasks: Управление на подзадачите + permission_manage_related_issues: Управление на връзките между задачи и ревизии + + project_module_issue_tracking: Тракинг + project_module_time_tracking: ОтделÑне на време + project_module_news: Ðовини + project_module_documents: Документи + project_module_files: Файлове + project_module_wiki: Wiki + project_module_repository: Хранилище + project_module_boards: Форуми + project_module_calendar: Календар + project_module_gantt: Мрежов график + + label_user: Потребител + label_user_plural: Потребители + label_user_new: Ðов потребител + label_user_anonymous: Ðнонимен + label_project: Проект + label_project_new: Ðов проект + label_project_plural: Проекти + label_x_projects: + zero: 0 проекта + one: 1 проект + other: "%{count} проекта" + label_project_all: Ð’Ñички проекти + label_project_latest: ПоÑледни проекти + label_issue: Задача + label_issue_new: Ðова задача + label_issue_plural: Задачи + label_issue_view_all: Ð’Ñички задачи + label_issues_by: "Задачи по %{value}" + label_issue_added: Добавена задача + label_issue_updated: Обновена задача + label_issue_note_added: Добавена бележка + label_issue_status_updated: Обновено ÑÑŠÑтоÑние + label_issue_priority_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_role_anonymous: Ðнонимен + label_role_non_member: Ðе член + label_member: Член + label_member_new: Ðов член + label_member_plural: Членове + label_tracker: Тракер + label_tracker_plural: Тракери + label_tracker_new: Ðов тракер + label_workflow: Работен Ð¿Ñ€Ð¾Ñ†ÐµÑ + label_issue_status: СъÑтоÑние на задача + label_issue_status_plural: СъÑтоÑÐ½Ð¸Ñ Ð½Ð° задачи + label_issue_status_new: Ðово ÑÑŠÑтоÑние + label_issue_category: ÐšÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° + label_issue_category_plural: Категории задачи + label_issue_category_new: Ðова ÐºÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñ + label_custom_field: ПотребителÑко поле + label_custom_field_plural: ПотребителÑки полета + label_custom_field_new: Ðово потребителÑко поле + label_enumerations: СпиÑъци + label_enumeration_new: Ðова ÑтойноÑÑ‚ + label_information: Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ + label_information_plural: Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ + label_please_login: Вход + label_register: РегиÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ + label_login_with_open_id_option: или вход чрез OpenID + label_password_lost: Забравена парола + label_home: Ðачало + label_my_page: Лична Ñтраница + label_my_account: Профил + label_my_projects: Проекти, в които учаÑтвам + label_my_page_block: Блокове в личната Ñтраница + label_administration: ÐдминиÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ + label_login: Вход + label_logout: Изход + label_help: Помощ + label_reported_issues: Публикувани задачи + label_assigned_to_me_issues: Възложени на мен + label_last_login: ПоÑледно Ñвързване + label_registered_on: РегиÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ + label_activity: ДейноÑÑ‚ + label_overall_activity: ЦÑлоÑтна дейноÑÑ‚ + label_user_activity: "ÐктивноÑÑ‚ на %{value}" + label_new: Ðов + label_logged_as: Здравейте, + label_environment: Среда + label_authentication: ÐžÑ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ð¸Ñ + label_auth_source: Ðачин на Ð¾Ñ‚Ð¾Ñ€Ð¾Ð·Ð°Ñ†Ð¸Ñ + label_auth_source_new: Ðов начин на Ð¾Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ð¸Ñ + label_auth_source_plural: Ðачини на Ð¾Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ð¸Ñ + label_subproject_plural: Подпроекти + label_subproject_new: Ðов подпроект + label_and_its_subprojects: "%{value} и неговите подпроекти" + label_min_max_length: Минимална - макÑимална дължина + label_list: СпиÑък + label_date: Дата + label_integer: ЦелочиÑлен + label_float: Дробно + label_boolean: Ð§ÐµÐºÐ±Ð¾ÐºÑ + label_string: ТекÑÑ‚ + label_text: Дълъг текÑÑ‚ + label_attribute: Ðтрибут + label_attribute_plural: Ðтрибути + 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_news_comment_added: Добавен коментар към новина + label_settings: ÐаÑтройки + label_overview: Общ изглед + label_version: ВерÑÐ¸Ñ + label_version_new: Ðова верÑÐ¸Ñ + label_version_plural: ВерÑии + label_close_versions: ЗатварÑне на завършените верÑии + label_confirmation: Одобрение + label_export_to: ЕкÑпорт към + label_read: Read... + label_public_projects: Публични проекти + label_open_issues: отворена + label_open_issues_plural: отворени + label_closed_issues: затворена + label_closed_issues_plural: затворени + label_x_open_issues_abbr_on_total: + zero: 0 отворени / %{total} + one: 1 отворена / %{total} + other: "%{count} отворени / %{total}" + label_x_open_issues_abbr: + zero: 0 отворени + one: 1 отворена + other: "%{count} отворени" + label_x_closed_issues_abbr: + zero: 0 затворени + one: 1 затворена + other: "%{count} затворени" + label_x_issues: + zero: 0 задачи + one: 1 задача + other: "%{count} задачи" + label_total: Общо + label_total_time: Общо + label_permissions: Права + label_current_status: Текущо ÑÑŠÑтоÑние + label_new_statuses_allowed: Позволени ÑÑŠÑтоÑÐ½Ð¸Ñ + label_all: вÑички + label_any: без значение + 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: Мрежов график + label_internal: Вътрешен + label_last_changes: "поÑледни %{count} промени" + label_change_view_all: Виж вÑички промени + label_personalize_page: ПерÑонализиране + label_comment: Коментар + label_comment_plural: Коментари + label_x_comments: + zero: 0 коментара + one: 1 коментар + other: "%{count} коментара" + label_comment_add: ДобавÑне на коментар + label_comment_added: Добавен коментар + label_comment_delete: Изтриване на коментари + label_query: ПотребителÑка Ñправка + label_query_plural: ПотребителÑки Ñправки + label_query_new: Ðова заÑвка + label_my_queries: Моите заÑвки + label_filter_add: Добави филтър + label_filter_plural: Филтри + label_equals: е + label_not_equals: не е + label_in_less_than: Ñлед по-малко от + label_in_more_than: Ñлед повече от + label_in_the_next_days: в Ñледващите + label_in_the_past_days: в предишните + label_greater_or_equal: ">=" + label_less_or_equal: <= + label_between: между + label_in: в Ñледващите + label_today: Ð´Ð½ÐµÑ + label_all_time: вÑички + label_yesterday: вчера + label_this_week: тази Ñедмица + label_last_week: поÑледната Ñедмица + label_last_n_weeks: поÑледните %{count} Ñедмици + label_last_n_days: "поÑледните %{count} дни" + label_this_month: Ñ‚ÐµÐºÑƒÑ‰Ð¸Ñ Ð¼ÐµÑец + label_last_month: поÑÐ»ÐµÐ´Ð½Ð¸Ñ Ð¼ÐµÑец + label_this_year: текущата година + label_date_range: Период + label_less_than_ago: преди по-малко от + label_more_than_ago: преди повече от + label_ago: преди + label_contains: Ñъдържа + label_not_contains: не Ñъдържа + label_any_issues_in_project: задачи от проект + label_any_issues_not_in_project: задачи, които не Ñа в проект + label_no_issues_in_project: никакви задачи в проект + label_day_plural: дни + label_repository: Хранилище + label_repository_new: Ðово хранилище + label_repository_plural: Хранилища + label_browse: Разглеждане + label_branch: работен вариант + label_tag: ВерÑÐ¸Ñ + label_revision: Ð ÐµÐ²Ð¸Ð·Ð¸Ñ + label_revision_plural: Ревизии + label_revision_id: Ð ÐµÐ²Ð¸Ð·Ð¸Ñ %{value} + label_associated_revisions: ÐÑоциирани ревизии + label_added: добавено + label_modified: променено + label_copied: копирано + label_renamed: преименувано + label_deleted: изтрито + label_latest_revision: ПоÑледна Ñ€ÐµÐ²Ð¸Ð·Ð¸Ñ + label_latest_revision_plural: ПоÑледни ревизии + label_view_revisions: Виж ревизиите + label_view_all_revisions: Разглеждане на вÑички ревизии + label_max_size: МакÑимална големина + label_sort_highest: ПремеÑти най-горе + label_sort_higher: ПремеÑти по-горе + label_sort_lower: ПремеÑти по-долу + label_sort_lowest: ПремеÑти най-долу + label_roadmap: Пътна карта + label_roadmap_due_in: "Излиза Ñлед %{value}" + label_roadmap_overdue: "%{value} закъÑнение" + label_roadmap_no_issues: ÐÑма задачи за тази верÑÐ¸Ñ + label_search: ТърÑене + label_result_plural: Pезултати + label_all_words: Ð’Ñички думи + label_wiki: Wiki + label_wiki_edit: Wiki Ñ€ÐµÐ´Ð°ÐºÑ†Ð¸Ñ + label_wiki_edit_plural: Wiki редакции + label_wiki_page: Wiki Ñтраница + label_wiki_page_plural: Wiki Ñтраници + label_index_by_title: Ð˜Ð½Ð´ÐµÐºÑ + label_index_by_date: Ð˜Ð½Ð´ÐµÐºÑ Ð¿Ð¾ дата + label_current_version: Текуща верÑÐ¸Ñ + label_preview: Преглед + label_feed_plural: ЕмиÑии + label_changes_details: Подробни промени + label_issue_tracking: Тракинг + label_spent_time: Отделено време + label_overall_spent_time: Общо употребено време + label_f_hour: "%{value} чаÑ" + label_f_hour_plural: "%{value} чаÑа" + label_time_tracking: ОтделÑне на време + label_change_plural: Промени + label_statistics: СтатиÑтики + label_commits_per_month: Ревизии по меÑеци + label_commits_per_author: Ревизии по автор + label_diff: diff + label_view_diff: Виж разликите + label_diff_inline: хоризонтално + label_diff_side_by_side: вертикално + label_options: Опции + label_copy_workflow_from: Копирай Ñ€Ð°Ð±Ð¾Ñ‚Ð½Ð¸Ñ Ð¿Ñ€Ð¾Ñ†ÐµÑ Ð¾Ñ‚ + label_permissions_report: Справка за права + label_watched_issues: Ðаблюдавани задачи + label_related_issues: Свързани задачи + label_applied_status: УÑтановено ÑÑŠÑтоÑние + label_loading: Зареждане... + label_relation_new: Ðова Ñ€ÐµÐ»Ð°Ñ†Ð¸Ñ + label_relation_delete: Изтриване на Ñ€ÐµÐ»Ð°Ñ†Ð¸Ñ + label_relates_to: Ñвързана ÑÑŠÑ + label_duplicates: дублира + label_duplicated_by: дублирана от + label_blocks: блокира + label_blocked_by: блокирана от + label_precedes: предшеÑтва + label_follows: изпълнÑва Ñе Ñлед + label_copied_to: копирана в + label_copied_from: копирана от + label_end_to_start: край към начало + label_end_to_end: край към край + label_start_to_start: начало към начало + label_start_to_end: начало към край + label_stay_logged_in: Запомни ме + label_disabled: забранено + label_show_completed_versions: Показване на реализирани верÑии + label_me: аз + label_board: Форум + label_board_new: Ðов форум + label_board_plural: Форуми + label_board_locked: Заключена + label_board_sticky: Sticky + label_topic_plural: Теми + label_message_plural: Ð¡ÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ + label_message_last: ПоÑледно Ñъобщение + label_message_new: Ðова тема + label_message_posted: Добавено Ñъобщение + label_reply_plural: Отговори + label_send_information: Изпращане на информациÑта до Ð¿Ð¾Ñ‚Ñ€ÐµÐ±Ð¸Ñ‚ÐµÐ»Ñ + label_year: Година + label_month: МеÑец + label_week: Седмица + label_date_from: От + label_date_to: До + label_language_based: Ð’ завиÑимоÑÑ‚ от езика + label_sort_by: "Сортиране по %{value}" + label_send_test_email: Изпращане на теÑтов e-mail + label_feeds_access_key: RSS access ключ + label_missing_feeds_access_key: ЛипÑващ RSS ключ за доÑтъп + label_feeds_access_key_created_on: "%{value} от Ñъздаването на RSS ключа" + label_module_plural: Модули + label_added_time_by: "Публикувана от %{author} преди %{age}" + label_updated_time_by: "Обновена от %{author} преди %{age}" + label_updated_time: "Обновена преди %{value}" + label_jump_to_a_project: Проект... + label_file_plural: Файлове + label_changeset_plural: Ревизии + label_default_columns: По подразбиране + label_no_change_option: (Без промÑна) + label_bulk_edit_selected_issues: Групово редактиране на задачи + label_bulk_edit_selected_time_entries: Групово редактиране на запиÑи за използвано време + 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_option_only_my_events: Само за неща, в които Ñъм включен/а + label_user_mail_option_only_assigned: Само за неща, назначени на мен + label_user_mail_option_only_owner: Само за неща, на които аз Ñъм ÑобÑтвеник + label_user_mail_no_self_notified: "Ðе иÑкам извеÑÑ‚Ð¸Ñ Ð·Ð° извършени от мен промени" + label_registration_activation_by_email: активиране на профила по email + label_registration_manual_activation: ръчно активиране + label_registration_automatic_activation: автоматично активиране + label_display_per_page: "Ðа Ñтраница по: %{value}" + label_age: ВъзраÑÑ‚ + label_change_properties: ПромÑна на наÑтройки + label_general: ОÑновни + label_more: Още + label_scm: SCM (СиÑтема за контрол на верÑиите) + label_plugins: Плъгини + label_ldap_authentication: LDAP Ð¾Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ð¸Ñ + label_downloads_abbr: D/L + label_optional_description: Ðезадължително опиÑание + label_add_another_file: ДобавÑне на друг файл + label_preferences: ÐŸÑ€ÐµÐ´Ð¿Ð¾Ñ‡Ð¸Ñ‚Ð°Ð½Ð¸Ñ + label_chronological_order: Хронологичен ред + label_reverse_chronological_order: Обратен хронологичен ред + label_planning: Планиране + label_incoming_emails: ВходÑщи e-mail-и + label_generate_key: Генериране на ключ + label_issue_watchers: Ðаблюдатели + label_example: Пример + label_display: Показване + label_sort: Сортиране + label_ascending: ÐараÑтващ + label_descending: ÐамалÑващ + label_date_from_to: От %{start} до %{end} + label_wiki_content_added: Wiki Ñтраница беше добавена + label_wiki_content_updated: Wiki Ñтраница беше обновена + label_group: Група + label_group_plural: Групи + label_group_new: Ðова група + label_time_entry_plural: Използвано време + label_version_sharing_none: Ðе Ñподелен + label_version_sharing_descendants: С подпроекти + label_version_sharing_hierarchy: С проектна Ð¹ÐµÑ€Ð°Ñ€Ñ…Ð¸Ñ + label_version_sharing_tree: С дърво на проектите + label_version_sharing_system: С вÑички проекти + label_update_issue_done_ratios: ОбновÑване на процента на завършените задачи + label_copy_source: Източник + label_copy_target: Цел + label_copy_same_as_target: Също като целта + label_display_used_statuses_only: Показване Ñамо на ÑÑŠÑтоÑниÑта, използвани от този тракер + label_api_access_key: API ключ за доÑтъп + label_missing_api_access_key: ЛипÑващ API ключ + label_api_access_key_created_on: API ключ за доÑтъп е Ñъздаден преди %{value} + label_profile: Профил + label_subtask_plural: Подзадачи + label_project_copy_notifications: Изпращане на Send e-mail извеÑÑ‚Ð¸Ñ Ð¿Ð¾ време на копирането на проекта + label_principal_search: "ТърÑене на потребител или група:" + label_user_search: "ТърÑене на потребител:" + label_additional_workflow_transitions_for_author: Позволени Ñа допълнителни преходи, когато потребителÑÑ‚ е авторът + label_additional_workflow_transitions_for_assignee: Позволени Ñа допълнителни преходи, когато потребителÑÑ‚ е назначениÑÑ‚ към задачата + label_issues_visibility_all: Ð’Ñички задачи + label_issues_visibility_public: Ð’Ñички не-лични задачи + label_issues_visibility_own: Задачи, Ñъздадени от или назначени на Ð¿Ð¾Ñ‚Ñ€ÐµÐ±Ð¸Ñ‚ÐµÐ»Ñ + label_git_report_last_commit: Извеждане на поÑледното поверÑване за файлове и папки + label_parent_revision: Ð ÐµÐ²Ð¸Ð·Ð¸Ñ Ñ€Ð¾Ð´Ð¸Ñ‚ÐµÐ» + label_child_revision: Ð ÐµÐ²Ð¸Ð·Ð¸Ñ Ð½Ð°Ñледник + label_export_options: "%{export_format} опции за екÑпорт" + label_copy_attachments: Копиране на прикачените файлове + label_copy_subtasks: Копиране на подзадачите + label_item_position: "%{position}/%{count}" + label_completed_versions: Завършени верÑии + label_search_for_watchers: ТърÑене на потребители за наблюдатели + label_session_expiration: Изтичане на ÑеÑиите + label_show_closed_projects: Разглеждане на затворени проекти + label_status_transitions: Преходи между ÑÑŠÑтоÑниÑта + label_fields_permissions: ВидимоÑÑ‚ на полетата + label_readonly: Само за четене + label_required: Задължително + label_attribute_of_project: Project's %{name} + label_attribute_of_issue: Issue's %{name} + label_attribute_of_author: Author's %{name} + label_attribute_of_assigned_to: Assignee's %{name} + label_attribute_of_user: User's %{name} + label_attribute_of_fixed_version: Target version's %{name} + label_cross_project_descendants: С подпроекти + label_cross_project_tree: С дърво на проектите + label_cross_project_hierarchy: С проектна Ð¹ÐµÑ€Ð°Ñ€Ñ…Ð¸Ñ + label_cross_project_system: С вÑички проекти + label_gantt_progress_line: Ð›Ð¸Ð½Ð¸Ñ Ð½Ð° изпълнението + + button_login: Вход + button_submit: Изпращане + button_save: Ð—Ð°Ð¿Ð¸Ñ + button_check_all: Избор на вÑички + button_uncheck_all: ИзчиÑтване на вÑички + button_collapse_all: Скриване вÑички + button_expand_all: Разгъване вÑички + button_delete: Изтриване + button_create: Създаване + button_create_and_continue: Създаване и продължаване + button_test: ТеÑÑ‚ + button_edit: Ð ÐµÐ´Ð°ÐºÑ†Ð¸Ñ + button_edit_associated_wikipage: "Редактиране на аÑоциираната Wiki Ñтраница: %{page_title}" + button_add: ДобавÑне + button_change: ПромÑна + button_apply: Приложи + button_clear: ИзчиÑти + button_lock: Заключване + button_unlock: Отключване + button_download: ИзтеглÑне + button_list: СпиÑък + button_view: Преглед + button_move: ПремеÑтване + button_move_and_follow: ПремеÑтване и продължаване + button_back: Ðазад + button_cancel: Отказ + button_activate: ÐÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ñ + button_sort: Сортиране + button_log_time: ОтделÑне на време + button_rollback: Върни Ñе към тази Ñ€ÐµÐ²Ð¸Ð·Ð¸Ñ + button_watch: Ðаблюдаване + button_unwatch: Край на наблюдението + button_reply: Отговор + button_archive: Ðрхивиране + button_unarchive: Разархивиране + button_reset: Генериране наново + button_rename: Преименуване + button_change_password: ПромÑна на парола + button_copy: Копиране + button_copy_and_follow: Копиране и продължаване + button_annotate: ÐÐ½Ð¾Ñ‚Ð°Ñ†Ð¸Ñ + button_update: ОбновÑване + button_configure: Конфигуриране + button_quote: Цитат + button_duplicate: Дублиране + button_show: Показване + button_hide: Скриване + button_edit_section: Редактиране на тази ÑÐµÐºÑ†Ð¸Ñ + button_export: ЕкÑпорт + button_delete_my_account: Премахване на Ð¼Ð¾Ñ Ð¿Ñ€Ð¾Ñ„Ð¸Ð» + button_close: ЗатварÑне + button_reopen: ОтварÑне + + status_active: активен + status_registered: региÑтриран + status_locked: заключен + + project_status_active: активен + project_status_closed: затворен + project_status_archived: архивиран + + version_status_open: отворена + version_status_locked: заключена + version_status_closed: затворена + + field_active: Ðктивен + + text_select_mail_notifications: Изберете ÑÑŠÐ±Ð¸Ñ‚Ð¸Ñ Ð·Ð° изпращане на e-mail. + text_regexp_info: пр. ^[A-Z0-9]+$ + text_min_max_length_info: 0 - без Ð¾Ð³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ñ + text_project_destroy_confirmation: Сигурни ли Ñте, че иÑкате да изтриете проекта и данните в него? + text_subprojects_destroy_warning: "Ðеговите подпроекти: %{value} Ñъщо ще бъдат изтрити." + text_workflow_edit: Изберете Ñ€Ð¾Ð»Ñ Ð¸ тракер за да редактирате Ñ€Ð°Ð±Ð¾Ñ‚Ð½Ð¸Ñ Ð¿Ñ€Ð¾Ñ†ÐµÑ + text_are_you_sure: Сигурни ли Ñте? + text_journal_changed: "%{label} променен от %{old} на %{new}" + text_journal_changed_no_detail: "%{label} променен" + text_journal_set_to: "%{label} уÑтановен на %{value}" + text_journal_deleted: "%{label} изтрит (%{old})" + text_journal_added: "Добавено %{label} %{value}" + text_tip_issue_begin_day: задача, започваща този ден + text_tip_issue_end_day: задача, завършваща този ден + text_tip_issue_begin_end_day: задача, започваща и завършваща този ден + text_project_identifier_info: 'Позволени Ñа малки букви (a-z), цифри, тирета и _.
    ПромÑна Ñлед Ñъздаването му не е възможна.' + text_caracters_maximum: "До %{count} Ñимвола." + text_caracters_minimum: "Минимум %{count} Ñимвола." + text_length_between: "От %{min} до %{max} Ñимвола." + text_tracker_no_workflow: ÐÑма дефиниран работен Ð¿Ñ€Ð¾Ñ†ÐµÑ Ð·Ð° този тракер + text_unallowed_characters: Ðепозволени Ñимволи + text_comma_separated: Позволено е изброÑване (Ñ Ñ€Ð°Ð·Ð´ÐµÐ»Ð¸Ñ‚ÐµÐ» запетаÑ). + text_line_separated: Позволени Ñа много ÑтойноÑти (по едно на ред). + text_issues_ref_in_commit_messages: ОтбелÑзване и приключване на задачи от ревизии + text_issue_added: "Публикувана е нова задача Ñ Ð½Ð¾Ð¼ÐµÑ€ %{id} (от %{author})." + text_issue_updated: "Задача %{id} е обновена (от %{author})." + text_wiki_destroy_confirmation: Сигурни ли Ñте, че иÑкате да изтриете това Wiki и цÑлото му Ñъдържание? + text_issue_category_destroy_question: "Има задачи (%{count}) обвързани Ñ Ñ‚Ð°Ð·Ð¸ категориÑ. Какво ще изберете?" + text_issue_category_destroy_assignments: Премахване на връзките Ñ ÐºÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñта + text_issue_category_reassign_to: Преобвързване Ñ ÐºÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñ + text_user_mail_option: "За неизбраните проекти, ще получавате извеÑÑ‚Ð¸Ñ Ñамо за наблюдавани дейноÑти или в които учаÑтвате (Ñ‚.е. автор или назначени на мен)." + text_no_configuration_data: "Ð’Ñе още не Ñа конфигурирани Роли, тракери, ÑÑŠÑтоÑÐ½Ð¸Ñ Ð½Ð° задачи и работен процеÑ.\nСтрого Ñе препоръчва зареждането на примерната информациÑ. Веднъж заредена ще имате възможноÑÑ‚ да Ñ Ñ€ÐµÐ´Ð°ÐºÑ‚Ð¸Ñ€Ð°Ñ‚Ðµ." + text_load_default_configuration: Зареждане на примерна Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ + text_status_changed_by_changeset: "Приложено Ñ Ñ€ÐµÐ²Ð¸Ð·Ð¸Ñ %{value}." + text_time_logged_by_changeset: Приложено в Ñ€ÐµÐ²Ð¸Ð·Ð¸Ñ %{value}. + text_issues_destroy_confirmation: 'Сигурни ли Ñте, че иÑкате да изтриете избраните задачи?' + text_issues_destroy_descendants_confirmation: Тази Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñ Ñ‰Ðµ премахне и %{count} подзадача(и). + text_time_entries_destroy_confirmation: Сигурен ли Ñте, че изтриете избраните запиÑи за изразходвано време? + text_select_project_modules: 'Изберете активните модули за този проект:' + text_default_administrator_account_changed: Сменен Ñ„Ð°Ð±Ñ€Ð¸Ñ‡Ð½Ð¸Ñ Ð°Ð´Ð¼Ð¸Ð½Ð¸ÑтраторÑки профил + text_file_repository_writable: ВъзможноÑÑ‚ за пиÑане в хранилището Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð²Ðµ + text_plugin_assets_writable: Папката на приÑтавките е разрешена за Ð·Ð°Ð¿Ð¸Ñ + text_rmagick_available: Ðаличен RMagick (по избор) + text_destroy_time_entries_question: "%{hours} чаÑа Ñа отделени на задачите, които иÑкате да изтриете. Какво избирате?" + text_destroy_time_entries: Изтриване на отделеното време + text_assign_time_entries_to_project: ПрехвърлÑне на отделеното време към проект + text_reassign_time_entries: 'ПрехвърлÑне на отделеното време към задача:' + text_user_wrote: "%{value} напиÑа:" + text_enumeration_destroy_question: "%{count} обекта Ñа Ñвързани Ñ Ñ‚Ð°Ð·Ð¸ ÑтойноÑÑ‚." + text_enumeration_category_reassign_to: 'ПреÑвържете ги към тази ÑтойноÑÑ‚:' + text_email_delivery_not_configured: "Изпращането на e-mail-и не е конфигурирано и извеÑтиÑта не Ñа разрешени.\nКонфигурирайте Ð²Ð°ÑˆÐ¸Ñ SMTP Ñървър в config/configuration.yml и реÑтартирайте Redmine, за да ги разрешите." + text_repository_usernames_mapping: "Изберете или променете потребителите в Redmine, ÑъответÑтващи на потребителите в дневника на хранилището (repository).\nПотребителите Ñ ÐµÐ´Ð½Ð°ÐºÐ²Ð¸ имена в Redmine и хранилищата Ñе ÑъвмеÑÑ‚Ñват автоматично." + text_diff_truncated: '... Този diff не е пълен, понеже е Ð½Ð°Ð´Ñ…Ð²ÑŠÑ€Ð»Ñ Ð¼Ð°ÐºÑÐ¸Ð¼Ð°Ð»Ð½Ð¸Ñ Ñ€Ð°Ð·Ð¼ÐµÑ€, който може да бъде показан.' + text_custom_field_possible_values_info: 'Една ÑтойноÑÑ‚ на ред' + text_wiki_page_destroy_question: Тази Ñтраница има %{descendants} Ñтраници деца и descendant(s). Какво желаете да правите? + text_wiki_page_nullify_children: Запазване на тези Ñтраници като коренни Ñтраници + text_wiki_page_destroy_children: Изтриване на Ñтраниците деца и вÑички техни descendants + text_wiki_page_reassign_children: Преназначаване на Ñтраниците деца на тази родителÑка Ñтраница + text_own_membership_delete_confirmation: "Вие Ñте на път да премахнете нÑкои или вÑички ваши Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð¸ е възможно Ñлед това да не можете да редактирате този проект.\nСигурен ли Ñте, че иÑкате да продължите?" + text_zoom_in: Увеличаване + text_zoom_out: ÐамалÑване + text_warn_on_leaving_unsaved: Страницата Ñъдържа незапиÑано Ñъдържание, което може да бъде загубено, ако Ñ Ð½Ð°Ð¿ÑƒÑнете. + text_scm_path_encoding_note: "По подразбиране: UTF-8" + text_git_repository_note: Празно и локално хранилище (например /gitrepo, c:\gitrepo) + text_mercurial_repository_note: Локално хранилище (например /hgrepo, c:\hgrepo) + text_scm_command: SCM команда + text_scm_command_version: ВерÑÐ¸Ñ + text_scm_config: Можете да конфигурирате SCM командите в config/configuration.yml. За да активирате промените, реÑтартирайте Redmine. + text_scm_command_not_available: SCM командата не е налична или доÑтъпна. Проверете конфигурациÑта в админиÑÑ‚Ñ€Ð°Ñ‚Ð¸Ð²Ð½Ð¸Ñ Ð¿Ð°Ð½ÐµÐ». + text_issue_conflict_resolution_overwrite: Прилагане на моите промени (предишните коментари ще бъдат запазени, но нÑкои други промени може да бъдат презапиÑани) + text_issue_conflict_resolution_add_notes: ДобавÑне на моите коментари и отхвърлÑне на другите мои промени + text_issue_conflict_resolution_cancel: ОтхвърлÑне на вÑички мои промени и презареждане на %{link} + text_account_destroy_confirmation: "Сигурен/на ли Ñте, че желаете да продължите?\nВашиÑÑ‚ профил ще бъде премахнат без възможноÑÑ‚ за възÑтановÑване." + text_session_expiration_settings: "Внимание: промÑната на тези уÑтановÑÐ²Ð°Ð½Ð¾Ñ Ð¼Ð¾Ð¶Ðµ да прекрати вÑички активни ÑеÑии, включително и вашата." + text_project_closed: Този проект е затворен и е Ñамо за четене. + text_turning_multiple_off: Ðко забраните възможноÑтта за повече от една ÑтойноÑÑ‚, повечето ÑтойноÑти ще бъдат + премахнати Ñ Ñ†ÐµÐ» да оÑтане Ñамо по една ÑтойноÑÑ‚ за поле. + + default_role_manager: Мениджър + default_role_developer: Разработчик + default_role_reporter: Публикуващ + default_tracker_bug: Грешка + default_tracker_feature: ФункционалноÑÑ‚ + default_tracker_support: Поддръжка + default_issue_status_new: Ðова + default_issue_status_in_progress: Изпълнение + default_issue_status_resolved: Приключена + default_issue_status_feedback: Обратна връзка + default_issue_status_closed: Затворена + default_issue_status_rejected: Отхвърлена + default_doc_category_user: Ð”Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ñ Ð·Ð° Ð¿Ð¾Ñ‚Ñ€ÐµÐ±Ð¸Ñ‚ÐµÐ»Ñ + default_doc_category_tech: ТехничеÑка Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ñ + default_priority_low: ÐиÑък + default_priority_normal: Ðормален + default_priority_high: ВиÑок + default_priority_urgent: Спешен + default_priority_immediate: Веднага + default_activity_design: Дизайн + default_activity_development: Разработка + + enumeration_issue_priorities: Приоритети на задачи + enumeration_doc_categories: Категории документи + enumeration_activities: ДейноÑти (time tracking) + enumeration_system_activity: СиÑтемна активноÑÑ‚ + description_filter: Филтър + description_search: ТърÑене + description_choose_project: Проекти + description_project_scope: Обхват на търÑенето + description_notes: Бележки + description_message_content: Съдържание на Ñъобщението + description_query_sort_criteria_attribute: Ðтрибут на Ñортиране + description_query_sort_criteria_direction: ПоÑока на Ñортиране + description_user_mail_notification: ÐšÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ Ð¸Ð·Ð²ÐµÑтиÑта по пощата + description_available_columns: Ðалични колони + description_selected_columns: Избрани колони + description_issue_category_reassign: Изберете ÐºÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñ + description_wiki_subpages_reassign: Изберете нова родителÑка Ñтраница + description_all_columns: Ð’Ñички колони + description_date_range_list: Изберете диапазон от ÑпиÑъка + description_date_range_interval: Изберете диапазон чрез задаване на начална и крайна дати + description_date_from: Въведете начална дата + description_date_to: Въведете крайна дата + text_repository_identifier_info: 'Позволени Ñа малки букви (a-z), цифри, тирета и _.
    ПромÑна Ñлед Ñъздаването му не е възможна.' diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/70/706a81236a3578317d828beb9423327b5b69e98d.svn-base --- a/.svn/pristine/70/706a81236a3578317d828beb9423327b5b69e98d.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,13 +0,0 @@ -class <%= class_name %> < ActiveRecord::Migration - def self.up - <%- plugins.each do |plugin| -%> - Engines.plugins["<%= plugin.name %>"].migrate(<%= new_versions[plugin.name] %>) - <%- end -%> - end - - def self.down - <%- plugins.each do |plugin| -%> - Engines.plugins["<%= plugin.name %>"].migrate(<%= current_versions[plugin.name] %>) - <%- end -%> - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/70/70b84ab9e3add639c1ef335fb9d7383dcdf7f5f5.svn-base --- a/.svn/pristine/70/70b84ab9e3add639c1ef335fb9d7383dcdf7f5f5.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,23 +0,0 @@ -require File.expand_path('../../test_helper', __FILE__) - -class LdapAuthSourcesControllerTest < ActionController::TestCase - - def setup - @request.session[:user_id] = 1 - end - - context "get :new" do - setup do - get :new - end - - should_assign_to :auth_source - should_respond_with :success - should_render_template :new - - should "initilize a new AuthSource" do - assert_equal AuthSourceLdap, assigns(:auth_source).class - assert assigns(:auth_source).new_record? - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/70/70dfef569a65ef5db9a25ad36d19a3ee38afe71d.svn-base --- a/.svn/pristine/70/70dfef569a65ef5db9a25ad36d19a3ee38afe71d.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,18 +0,0 @@ -class CopyRepositoriesLogEncoding < ActiveRecord::Migration - def self.up - encoding = Setting.commit_logs_encoding.to_s.strip - encoding = encoding.blank? ? 'UTF-8' : encoding - Repository.find(:all).each do |repo| - scm = repo.scm_name - case scm - when 'Subversion', 'Mercurial', 'Git', 'Filesystem' - repo.update_attribute(:log_encoding, nil) - else - repo.update_attribute(:log_encoding, encoding) - end - end - end - - def self.down - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/70/70e864184fd8408391ed37d61337591c474549c4.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/70/70e864184fd8408391ed37d61337591c474549c4.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,247 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 Macros + module Definitions + # Returns true if +name+ is the name of an existing macro + def macro_exists?(name) + Redmine::WikiFormatting::Macros.available_macros.key?(name.to_sym) + end + + def exec_macro(name, obj, args, text) + macro_options = Redmine::WikiFormatting::Macros.available_macros[name.to_sym] + return unless macro_options + + method_name = "macro_#{name}" + unless macro_options[:parse_args] == false + args = args.split(',').map(&:strip) + end + + begin + if self.class.instance_method(method_name).arity == 3 + send(method_name, obj, args, text) + elsif text + raise "This macro does not accept a block of text" + else + send(method_name, obj, args) + end + rescue => e + "
    Error executing the #{h name} macro (#{h e.to_s})
    ".html_safe + end + 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 = {} + mattr_accessor :available_macros + + class << self + # Plugins can use this method to define new macros: + # + # Redmine::WikiFormatting::Macros.register do + # desc "This is my macro" + # macro :my_macro do |obj, args| + # "My macro output" + # end + # + # desc "This is my macro that accepts a block of text" + # macro :my_macro do |obj, args, text| + # "My macro output" + # end + # end + def register(&block) + class_eval(&block) if block_given? + end + + # Defines a new macro with the given name, options and block. + # + # Options: + # * :desc - A description of the macro + # * :parse_args => false - Disables arguments parsing (the whole arguments + # string is passed to the macro) + # + # Macro blocks accept 2 or 3 arguments: + # * obj: the object that is rendered (eg. an Issue, a WikiContent...) + # * args: macro arguments + # * text: the block of text given to the macro (should be present only if the + # macro accepts a block of text). text is a String or nil if the macro is + # invoked without a block of text. + # + # Examples: + # By default, when the macro is invoked, the coma separated list of arguments + # is split and passed to the macro block as an array. If no argument is given + # the macro will be invoked with an empty array: + # + # macro :my_macro do |obj, args| + # # args is an array + # # and this macro do not accept a block of text + # end + # + # You can disable arguments spliting with the :parse_args => false option. In + # this case, the full string of arguments is passed to the macro: + # + # macro :my_macro, :parse_args => false do |obj, args| + # # args is a string + # end + # + # Macro can optionally accept a block of text: + # + # macro :my_macro do |obj, args, text| + # # this macro accepts a block of text + # end + # + # Macros are invoked in formatted text using double curly brackets. Arguments + # must be enclosed in parenthesis if any. A new line after the macro name or the + # arguments starts the block of text that will be passe to the macro (invoking + # a macro that do not accept a block of text with some text will fail). + # Examples: + # + # No arguments: + # {{my_macro}} + # + # With arguments: + # {{my_macro(arg1, arg2)}} + # + # With a block of text: + # {{my_macro + # multiple lines + # of text + # }} + # + # With arguments and a block of text + # {{my_macro(arg1, arg2) + # multiple lines + # of text + # }} + # + # If a block of text is given, the closing tag }} must be at the start of a new line. + def macro(name, options={}, &block) + options.assert_valid_keys(:desc, :parse_args) + unless name.to_s.match(/\A\w+\z/) + raise "Invalid macro name: #{name} (only 0-9, A-Z, a-z and _ characters are accepted)" + end + unless block_given? + raise "Can not create a macro without a block!" + end + name = name.to_s.downcase.to_sym + available_macros[name] = {:desc => @@desc || ''}.merge(options) + @@desc = nil + Definitions.send :define_method, "macro_#{name}", &block + end + + # Sets description for the next macro to be defined + def desc(txt) + @@desc = txt + end + end + + # Builtin macros + desc "Sample macro." + macro :hello_world do |obj, args, text| + h("Hello world! Object: #{obj.class.name}, " + + (args.empty? ? "Called with no argument" : "Arguments: #{args.join(', ')}") + + " and " + (text.present? ? "a #{text.size} bytes long block of text." : "no block of text.") + ) + end + + desc "Displays a list of all available macros, including description if available." + macro :macro_list do |obj, args| + out = ''.html_safe + @@available_macros.each do |macro, options| + out << content_tag('dt', content_tag('code', macro.to_s)) + out << content_tag('dd', textilizable(options[:desc])) + end + content_tag('dl', out) + end + + 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(depth=2)}} -- display 2 levels nesting 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| + args, options = extract_macro_options(args, :parent, :depth) + options[:depth] = options[:depth].to_i if options[:depth].present? + + page = nil + if args.size > 0 + page = Wiki.find_page(args.first.to_s, :project => @project) + elsif obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version) + 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.self_and_descendants(options[:depth]).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| + 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 + out = textilizable(page.content, :text, :attachments => page.attachments, :headings => false) + @included_wiki_pages.pop + out + end + + desc "Inserts of collapsed block of text. Example:\n\n {{collapse(View details...)\nThis is a block of text that is collapsed by default.\nIt can be expanded by clicking a link.\n}}" + macro :collapse do |obj, args, text| + html_id = "collapse-#{Redmine::Utils.random_hex(4)}" + show_label = args[0] || l(:button_show) + hide_label = args[1] || args[0] || l(:button_hide) + js = "$('##{html_id}-show, ##{html_id}-hide').toggle(); $('##{html_id}').fadeToggle(150);" + out = ''.html_safe + out << link_to_function(show_label, js, :id => "#{html_id}-show", :class => 'collapsible collapsed') + out << link_to_function(hide_label, js, :id => "#{html_id}-hide", :class => 'collapsible', :style => 'display:none;') + out << content_tag('div', textilizable(text, :object => obj), :id => html_id, :class => 'collapsed-text', :style => 'display:none;') + out + end + + desc "Displays a clickable thumbnail of an attached image. Examples:\n\n
    {{thumbnail(image.png)}}\n{{thumbnail(image.png, size=300, title=Thumbnail)}}
    " + macro :thumbnail do |obj, args| + args, options = extract_macro_options(args, :size, :title) + filename = args.first + raise 'Filename required' unless filename.present? + size = options[:size] + raise 'Invalid size parameter' unless size.nil? || size.match(/^\d+$/) + size = size.to_i + size = nil unless size > 0 + if obj && obj.respond_to?(:attachments) && attachment = Attachment.latest_attach(obj.attachments, filename) + title = options[:title] || attachment.title + img = image_tag(url_for(:controller => 'attachments', :action => 'thumbnail', :id => attachment, :size => size), :alt => attachment.filename) + link_to(img, url_for(:controller => 'attachments', :action => 'show', :id => attachment), :class => 'thumbnail', :title => title) + else + raise "Attachment #{filename} not found" + end + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/70/70fcc81fd15a480f7d8ac6f47bf3e67d8c9739ca.svn-base --- a/.svn/pristine/70/70fcc81fd15a480f7d8ac6f47bf3e67d8c9739ca.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,24 +0,0 @@ -module CodeRay -module Encoders - - load :html - - # Wraps the output into a HTML page, using CSS classes and - # line numbers in the table format by default. - # - # See Encoders::HTML for available options. - class Page < HTML - - FILE_EXTENSION = 'html' - - register_for :page - - DEFAULT_OPTIONS = HTML::DEFAULT_OPTIONS.merge \ - :css => :class, - :wrap => :page, - :line_numbers => :table - - end - -end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/71/714859f726ef05e105f7c83270e22457a9138242.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/71/714859f726ef05e105f7c83270e22457a9138242.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,35 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 #:nodoc: + module CoreExtensions #:nodoc: + module Date #:nodoc: + # Custom date calculations + module Calculations + # Returns difference with specified date in months + def months_ago(date = self.class.today) + (date.year - self.year)*12 + (date.month - self.month) + end + + # Returns difference with specified date in weeks + def weeks_ago(date = self.class.today) + (date.year - self.year)*52 + (date.cweek - self.cweek) + end + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/71/71c72fcec779c58b3cb29e26161e013284f30b8a.svn-base --- a/.svn/pristine/71/71c72fcec779c58b3cb29e26161e013284f30b8a.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,63 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) - -class ApplicationTest < ActionController::IntegrationTest - include Redmine::I18n - - fixtures :projects, :trackers, :issue_statuses, :issues, - :enumerations, :users, :issue_categories, - :projects_trackers, - :roles, - :member_roles, - :members, - :enabled_modules, - :workflows - - def test_set_localization - Setting.default_language = 'en' - - # a french user - get 'projects', { }, 'Accept-Language' => 'fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3' - assert_response :success - assert_tag :tag => 'h2', :content => 'Projets' - assert_equal :fr, current_language - - # then an italien user - get 'projects', { }, 'Accept-Language' => 'it;q=0.8,en-us;q=0.5,en;q=0.3' - assert_response :success - assert_tag :tag => 'h2', :content => 'Progetti' - assert_equal :it, current_language - - # not a supported language: default language should be used - get 'projects', { }, 'Accept-Language' => 'zz' - assert_response :success - assert_tag :tag => 'h2', :content => 'Projects' - end - - def test_token_based_access_should_not_start_session - # issue of a private project - get 'issues/4.atom' - assert_response 302 - - rss_key = User.find(2).rss_key - get "issues/4.atom?key=#{rss_key}" - assert_response 200 - assert_nil session[:user_id] - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/72/723213b09aa91cffeb184141cd322a54885c2096.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/72/723213b09aa91cffeb184141cd322a54885c2096.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,1079 @@ +# Norwegian, norsk bokmål, by irb.no +"no": + support: + array: + sentence_connector: "og" + direction: ltr + date: + formats: + default: "%d.%m.%Y" + short: "%e. %b" + long: "%e. %B %Y" + day_names: [søndag, mandag, tirsdag, onsdag, torsdag, fredag, lørdag] + abbr_day_names: [søn, man, tir, ons, tor, fre, lør] + month_names: [~, januar, februar, mars, april, mai, juni, juli, august, september, oktober, november, desember] + abbr_month_names: [~, jan, feb, mar, apr, mai, jun, jul, aug, sep, okt, nov, des] + order: + - :day + - :month + - :year + time: + formats: + default: "%A, %e. %B %Y, %H:%M" + time: "%H:%M" + short: "%e. %B, %H:%M" + long: "%A, %e. %B %Y, %H:%M" + am: "" + pm: "" + datetime: + distance_in_words: + half_a_minute: "et halvt minutt" + less_than_x_seconds: + one: "mindre enn 1 sekund" + other: "mindre enn %{count} sekunder" + x_seconds: + one: "1 sekund" + other: "%{count} sekunder" + less_than_x_minutes: + one: "mindre enn 1 minutt" + other: "mindre enn %{count} minutter" + x_minutes: + one: "1 minutt" + other: "%{count} minutter" + about_x_hours: + one: "rundt 1 time" + other: "rundt %{count} timer" + x_hours: + one: "1 time" + other: "%{count} timer" + x_days: + one: "1 dag" + other: "%{count} dager" + about_x_months: + one: "rundt 1 måned" + other: "rundt %{count} måneder" + x_months: + one: "1 måned" + other: "%{count} måneder" + about_x_years: + one: "rundt 1 år" + other: "rundt %{count} år" + over_x_years: + one: "over 1 år" + other: "over %{count} år" + almost_x_years: + one: "nesten 1 år" + other: "nesten %{count} år" + number: + format: + precision: 3 + separator: "." + delimiter: "," + currency: + format: + unit: "kr" + format: "%n %u" + precision: + format: + delimiter: "" + precision: 4 + human: + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + + activerecord: + errors: + template: + header: "kunne ikke lagre %{model} på grunn av %{count} feil." + body: "det oppstod problemer i følgende felt:" + messages: + inclusion: "er ikke inkludert i listen" + exclusion: "er reservert" + invalid: "er ugyldig" + confirmation: "passer ikke bekreftelsen" + accepted: "må være akseptert" + empty: "kan ikke være tom" + blank: "kan ikke være blank" + too_long: "er for lang (maksimum %{count} tegn)" + too_short: "er for kort (minimum %{count} tegn)" + wrong_length: "er av feil lengde (maksimum %{count} tegn)" + taken: "er allerede i bruk" + not_a_number: "er ikke et tall" + greater_than: "må være større enn %{count}" + greater_than_or_equal_to: "må være større enn eller lik %{count}" + equal_to: "må være lik %{count}" + less_than: "må være mindre enn %{count}" + less_than_or_equal_to: "må være mindre enn eller lik %{count}" + odd: "må være oddetall" + even: "må være partall" + greater_than_start_date: "må være større enn startdato" + not_same_project: "hører ikke til samme prosjekt" + circular_dependency: "Denne relasjonen ville lagd en sirkulær avhengighet" + cant_link_an_issue_with_a_descendant: "En sak kan ikke kobles mot en av sine undersaker" + + + actionview_instancetag_blank_option: Vennligst velg + + general_text_No: 'Nei' + general_text_Yes: 'Ja' + general_text_no: 'nei' + general_text_yes: 'ja' + general_lang_name: 'Norwegian (Norsk bokmål)' + general_csv_separator: ',' + general_csv_decimal_separator: '.' + general_csv_encoding: ISO-8859-1 + general_pdf_encoding: UTF-8 + general_first_day_of_week: '1' + + notice_account_updated: Kontoen er oppdatert. + notice_account_invalid_creditentials: Feil brukernavn eller passord + notice_account_password_updated: Passordet er oppdatert. + notice_account_wrong_password: Feil passord + notice_account_register_done: Kontoen er opprettet. Klikk lenken som er sendt deg i e-post for å aktivere kontoen. + notice_account_unknown_email: Ukjent bruker. + notice_can_t_change_password: Denne kontoen bruker ekstern godkjenning. Passordet kan ikke endres. + notice_account_lost_email_sent: En e-post med instruksjoner for å velge et nytt passord er sendt til deg. + notice_account_activated: Din konto er aktivert. Du kan nå logge inn. + notice_successful_create: Opprettet. + notice_successful_update: Oppdatert. + notice_successful_delete: Slettet. + notice_successful_connection: Koblet opp. + notice_file_not_found: Siden du forsøkte å vise eksisterer ikke, eller er slettet. + notice_locking_conflict: Data har blitt oppdatert av en annen bruker. + notice_not_authorized: Du har ikke adgang til denne siden. + notice_email_sent: "En e-post er sendt til %{value}" + notice_email_error: "En feil oppstod under sending av e-post (%{value})" + notice_feeds_access_key_reseted: Din RSS-tilgangsnøkkel er nullstilt. + notice_failed_to_save_issues: "Lykkes ikke å lagre %{count} sak(er) på %{total} valgt: %{ids}." + notice_no_issue_selected: "Ingen sak valgt! Vennligst merk sakene du vil endre." + notice_account_pending: "Din konto ble opprettet og avventer nå administrativ godkjenning." + notice_default_data_loaded: Standardkonfigurasjonen lastet inn. + + error_can_t_load_default_data: "Standardkonfigurasjonen kunne ikke lastes inn: %{value}" + error_scm_not_found: "Elementet og/eller revisjonen eksisterer ikke i depoet." + error_scm_command_failed: "En feil oppstod under tilkobling til depoet: %{value}" + error_scm_annotate: "Elementet eksisterer ikke, eller kan ikke noteres." + error_issue_not_found_in_project: 'Saken eksisterer ikke, eller hører ikke til dette prosjektet' + + mail_subject_lost_password: "Ditt %{value} passord" + mail_body_lost_password: 'Klikk følgende lenke for å endre ditt passord:' + mail_subject_register: "%{value} kontoaktivering" + mail_body_register: 'Klikk følgende lenke for å aktivere din konto:' + mail_body_account_information_external: "Du kan bruke din %{value}-konto for å logge inn." + mail_body_account_information: Informasjon om din konto + mail_subject_account_activation_request: "%{value} kontoaktivering" + mail_body_account_activation_request: "En ny bruker (%{value}) er registrert, og avventer din godkjenning:" + mail_subject_reminder: "%{count} sak(er) har frist de kommende %{days} dagene" + mail_body_reminder: "%{count} sak(er) som er tildelt deg har frist de kommende %{days} dager:" + + + field_name: Navn + field_description: Beskrivelse + field_summary: Oppsummering + field_is_required: Kreves + field_firstname: Fornavn + field_lastname: Etternavn + field_mail: E-post + field_filename: Fil + field_filesize: Størrelse + field_downloads: Nedlastinger + field_author: Forfatter + field_created_on: Opprettet + field_updated_on: Oppdatert + field_field_format: Format + field_is_for_all: For alle prosjekter + field_possible_values: Lovlige verdier + field_regexp: Regular expression + field_min_length: Minimum lengde + field_max_length: Maksimum lengde + field_value: Verdi + field_category: Kategori + field_title: Tittel + field_project: Prosjekt + field_issue: Sak + field_status: Status + field_notes: Notater + field_is_closed: Lukker saken + field_is_default: Standardverdi + field_tracker: Sakstype + field_subject: Emne + field_due_date: Frist + field_assigned_to: Tildelt til + field_priority: Prioritet + field_fixed_version: Mål-versjon + field_user: Bruker + field_role: Rolle + field_homepage: Hjemmeside + field_is_public: Offentlig + field_parent: Underprosjekt av + field_is_in_roadmap: Vises i veikart + field_login: Brukernavn + field_mail_notification: E-post-varsling + field_admin: Administrator + field_last_login_on: Sist innlogget + field_language: Språk + field_effective_date: Dato + field_password: Passord + field_new_password: Nytt passord + field_password_confirmation: Bekreft passord + field_version: Versjon + field_type: Type + field_host: Vert + field_port: Port + field_account: Konto + field_base_dn: Base DN + field_attr_login: Brukernavnsattributt + field_attr_firstname: Fornavnsattributt + field_attr_lastname: Etternavnsattributt + field_attr_mail: E-post-attributt + field_onthefly: On-the-fly brukeropprettelse + field_start_date: Start + field_done_ratio: "% Ferdig" + field_auth_source: Autentiseringskilde + field_hide_mail: Skjul min epost-adresse + field_comments: Kommentarer + field_url: URL + field_start_page: Startside + field_subproject: Underprosjekt + field_hours: Timer + field_activity: Aktivitet + field_spent_on: Dato + field_identifier: Identifikasjon + field_is_filter: Brukes som filter + field_issue_to: Relaterte saker + field_delay: Forsinkelse + field_assignable: Saker kan tildeles denne rollen + field_redirect_existing_links: Viderekoble eksisterende lenker + field_estimated_hours: Estimert tid + field_column_names: Kolonner + field_time_zone: Tidssone + field_searchable: Søkbar + field_default_value: Standardverdi + field_comments_sorting: Vis kommentarer + + setting_app_title: Applikasjonstittel + setting_app_subtitle: Applikasjonens undertittel + setting_welcome_text: Velkomsttekst + setting_default_language: Standardspråk + setting_login_required: Krever innlogging + setting_self_registration: Selvregistrering + setting_attachment_max_size: Maks. størrelse vedlegg + setting_issues_export_limit: Eksportgrense for saker + setting_mail_from: Avsenders epost + setting_bcc_recipients: Blindkopi (bcc) til mottakere + setting_host_name: Vertsnavn + setting_text_formatting: Tekstformattering + setting_wiki_compression: Komprimering av Wiki-historikk + setting_feeds_limit: Innholdsgrense for Feed + setting_default_projects_public: Nye prosjekter er offentlige som standard + setting_autofetch_changesets: Autohenting av endringssett + setting_sys_api_enabled: Aktiver webservice for depot-administrasjon + setting_commit_ref_keywords: Nøkkelord for referanse + setting_commit_fix_keywords: Nøkkelord for retting + setting_autologin: Autoinnlogging + setting_date_format: Datoformat + setting_time_format: Tidsformat + setting_cross_project_issue_relations: Tillat saksrelasjoner på kryss av prosjekter + setting_issue_list_default_columns: Standardkolonner vist i sakslisten + setting_emails_footer: Epost-signatur + setting_protocol: Protokoll + setting_per_page_options: Alternativer, objekter pr. side + setting_user_format: Visningsformat, brukere + setting_activity_days_default: Dager vist på prosjektaktivitet + setting_display_subprojects_issues: Vis saker fra underprosjekter på hovedprosjekt som standard + setting_enabled_scm: Aktiviserte SCM + + project_module_issue_tracking: Sakshåndtering + project_module_time_tracking: Tidsregistrering + project_module_news: Nyheter + project_module_documents: Dokumenter + project_module_files: Filer + project_module_wiki: Wiki + project_module_repository: Depot + project_module_boards: Forumer + + label_user: Bruker + label_user_plural: Brukere + label_user_new: Ny bruker + label_project: Prosjekt + label_project_new: Nytt prosjekt + label_project_plural: Prosjekter + label_x_projects: + zero: ingen prosjekter + one: 1 prosjekt + other: "%{count} prosjekter" + label_project_all: Alle prosjekter + label_project_latest: Siste prosjekter + label_issue: Sak + label_issue_new: Ny sak + label_issue_plural: Saker + label_issue_view_all: Vis alle saker + label_issues_by: "Saker etter %{value}" + label_issue_added: Sak lagt til + label_issue_updated: Sak oppdatert + label_document: Dokument + label_document_new: Nytt dokument + label_document_plural: Dokumenter + label_document_added: Dokument lagt til + label_role: Rolle + label_role_plural: Roller + label_role_new: Ny rolle + label_role_and_permissions: Roller og rettigheter + label_member: Medlem + label_member_new: Nytt medlem + label_member_plural: Medlemmer + label_tracker: Sakstype + label_tracker_plural: Sakstyper + label_tracker_new: Ny sakstype + label_workflow: Arbeidsflyt + label_issue_status: Saksstatus + label_issue_status_plural: Saksstatuser + label_issue_status_new: Ny status + label_issue_category: Sakskategori + label_issue_category_plural: Sakskategorier + label_issue_category_new: Ny kategori + label_custom_field: Eget felt + label_custom_field_plural: Egne felt + label_custom_field_new: Nytt eget felt + label_enumerations: Listeverdier + label_enumeration_new: Ny verdi + label_information: Informasjon + label_information_plural: Informasjon + label_please_login: Vennlist logg inn + label_register: Registrer + label_password_lost: Mistet passord + label_home: Hjem + label_my_page: Min side + label_my_account: Min konto + label_my_projects: Mine prosjekter + label_administration: Administrasjon + label_login: Logg inn + label_logout: Logg ut + label_help: Hjelp + label_reported_issues: Rapporterte saker + label_assigned_to_me_issues: Saker tildelt meg + label_last_login: Sist innlogget + label_registered_on: Registrert + label_activity: Aktivitet + label_overall_activity: All aktivitet + label_new: Ny + label_logged_as: Innlogget som + label_environment: Miljø + label_authentication: Autentisering + label_auth_source: Autentiseringskilde + label_auth_source_new: Ny autentiseringskilde + label_auth_source_plural: Autentiseringskilder + label_subproject_plural: Underprosjekter + label_and_its_subprojects: "%{value} og dets underprosjekter" + label_min_max_length: Min.-maks. lengde + label_list: Liste + label_date: Dato + label_integer: Heltall + label_float: Kommatall + label_boolean: Sann/usann + label_string: Tekst + label_text: Lang tekst + label_attribute: Attributt + label_attribute_plural: Attributter + label_no_data: Ingen data å vise + label_change_status: Endre status + label_history: Historikk + label_attachment: Fil + label_attachment_new: Ny fil + label_attachment_delete: Slett fil + label_attachment_plural: Filer + label_file_added: Fil lagt til + label_report: Rapport + label_report_plural: Rapporter + label_news: Nyheter + label_news_new: Legg til nyhet + label_news_plural: Nyheter + label_news_latest: Siste nyheter + label_news_view_all: Vis alle nyheter + label_news_added: Nyhet lagt til + label_settings: Innstillinger + label_overview: Oversikt + label_version: Versjon + label_version_new: Ny versjon + label_version_plural: Versjoner + label_confirmation: Bekreftelse + label_export_to: Eksporter til + label_read: Leser... + label_public_projects: Offentlige prosjekt + label_open_issues: åpen + label_open_issues_plural: åpne + label_closed_issues: lukket + label_closed_issues_plural: lukkede + label_x_open_issues_abbr_on_total: + zero: 0 åpne / %{total} + one: 1 åpen / %{total} + other: "%{count} åpne / %{total}" + label_x_open_issues_abbr: + zero: 0 åpne + one: 1 åpen + other: "%{count} åpne" + label_x_closed_issues_abbr: + zero: 0 lukket + one: 1 lukket + other: "%{count} lukket" + label_total: Totalt + label_permissions: Rettigheter + label_current_status: Nåværende status + label_new_statuses_allowed: Tillate nye statuser + label_all: alle + label_none: ingen + label_nobody: ingen + label_next: Neste + label_previous: Forrige + label_used_by: Brukt av + label_details: Detaljer + label_add_note: Legg til notat + label_per_page: Pr. side + label_calendar: Kalender + label_months_from: måneder fra + label_gantt: Gantt + label_internal: Intern + label_last_changes: "siste %{count} endringer" + label_change_view_all: Vis alle endringer + label_personalize_page: Tilpass denne siden + label_comment: Kommentar + label_comment_plural: Kommentarer + label_x_comments: + zero: ingen kommentarer + one: 1 kommentar + other: "%{count} kommentarer" + label_comment_add: Legg til kommentar + label_comment_added: Kommentar lagt til + label_comment_delete: Slett kommentar + label_query: Egen spørring + label_query_plural: Egne spørringer + label_query_new: Ny spørring + label_filter_add: Legg til filter + label_filter_plural: Filtre + label_equals: er + label_not_equals: er ikke + label_in_less_than: er mindre enn + label_in_more_than: in mer enn + label_in: i + label_today: idag + label_all_time: all tid + label_yesterday: i går + label_this_week: denne uken + label_last_week: sist uke + label_last_n_days: "siste %{count} dager" + label_this_month: denne måneden + label_last_month: siste måned + label_this_year: dette året + label_date_range: Dato-spenn + label_less_than_ago: mindre enn dager siden + label_more_than_ago: mer enn dager siden + label_ago: dager siden + label_contains: inneholder + label_not_contains: ikke inneholder + label_day_plural: dager + label_repository: Depot + label_repository_plural: Depoter + label_browse: Utforsk + label_revision: Revisjon + label_revision_plural: Revisjoner + label_associated_revisions: Assosierte revisjoner + label_added: lagt til + label_modified: endret + label_deleted: slettet + label_latest_revision: Siste revisjon + label_latest_revision_plural: Siste revisjoner + label_view_revisions: Vis revisjoner + label_max_size: Maksimum størrelse + label_sort_highest: Flytt til toppen + label_sort_higher: Flytt opp + label_sort_lower: Flytt ned + label_sort_lowest: Flytt til bunnen + label_roadmap: Veikart + label_roadmap_due_in: "Frist om %{value}" + label_roadmap_overdue: "%{value} over fristen" + label_roadmap_no_issues: Ingen saker for denne versjonen + label_search: Søk + label_result_plural: Resultater + label_all_words: Alle ord + label_wiki: Wiki + label_wiki_edit: Wiki endring + label_wiki_edit_plural: Wiki endringer + label_wiki_page: Wiki-side + label_wiki_page_plural: Wiki-sider + label_index_by_title: Indekser etter tittel + label_index_by_date: Indekser etter dato + label_current_version: Gjeldende versjon + label_preview: Forhåndsvis + label_feed_plural: Feeder + label_changes_details: Detaljer om alle endringer + label_issue_tracking: Sakshåndtering + label_spent_time: Brukt tid + label_f_hour: "%{value} time" + label_f_hour_plural: "%{value} timer" + label_time_tracking: Tidsregistrering + label_change_plural: Endringer + label_statistics: Statistikk + label_commits_per_month: Innsendinger pr. måned + label_commits_per_author: Innsendinger pr. forfatter + label_view_diff: Vis forskjeller + label_diff_inline: i teksten + label_diff_side_by_side: side ved side + label_options: Alternativer + label_copy_workflow_from: Kopier arbeidsflyt fra + label_permissions_report: Rettighetsrapport + label_watched_issues: Overvåkede saker + label_related_issues: Relaterte saker + label_applied_status: Gitt status + label_loading: Laster... + label_relation_new: Ny relasjon + label_relation_delete: Slett relasjon + label_relates_to: relatert til + label_duplicates: dupliserer + label_duplicated_by: duplisert av + label_blocks: blokkerer + label_blocked_by: blokkert av + label_precedes: kommer før + label_follows: følger + label_end_to_start: slutt til start + label_end_to_end: slutt til slutt + label_start_to_start: start til start + label_start_to_end: start til slutt + label_stay_logged_in: Hold meg innlogget + label_disabled: avslått + label_show_completed_versions: Vis ferdige versjoner + label_me: meg + label_board: Forum + label_board_new: Nytt forum + label_board_plural: Forumer + label_topic_plural: Emner + label_message_plural: Meldinger + label_message_last: Siste melding + label_message_new: Ny melding + label_message_posted: Melding lagt til + label_reply_plural: Svar + label_send_information: Send kontoinformasjon til brukeren + label_year: År + label_month: Måned + label_week: Uke + label_date_from: Fra + label_date_to: Til + label_language_based: Basert på brukerens språk + label_sort_by: "Sorter etter %{value}" + label_send_test_email: Send en epost-test + label_feeds_access_key_created_on: "RSS tilgangsnøkkel opprettet for %{value} siden" + label_module_plural: Moduler + label_added_time_by: "Lagt til av %{author} for %{age} siden" + label_updated_time: "Oppdatert for %{value} siden" + label_jump_to_a_project: Gå til et prosjekt... + label_file_plural: Filer + label_changeset_plural: Endringssett + label_default_columns: Standardkolonner + label_no_change_option: (Ingen endring) + label_bulk_edit_selected_issues: Samlet endring av valgte saker + label_theme: Tema + label_default: Standard + label_search_titles_only: Søk bare i titler + label_user_mail_option_all: "For alle hendelser på mine prosjekter" + label_user_mail_option_selected: "For alle hendelser på valgte prosjekt..." + label_user_mail_no_self_notified: "Jeg vil ikke bli varslet om endringer jeg selv gjør" + label_registration_activation_by_email: kontoaktivering pr. e-post + label_registration_manual_activation: manuell kontoaktivering + label_registration_automatic_activation: automatisk kontoaktivering + label_display_per_page: "Pr. side: %{value}" + label_age: Alder + label_change_properties: Endre egenskaper + label_general: Generell + label_more: Mer + label_scm: SCM + label_plugins: Tillegg + label_ldap_authentication: LDAP-autentisering + label_downloads_abbr: Nedl. + label_optional_description: Valgfri beskrivelse + label_add_another_file: Legg til en fil til + label_preferences: Brukerinnstillinger + label_chronological_order: I kronologisk rekkefølge + label_reverse_chronological_order: I omvendt kronologisk rekkefølge + label_planning: Planlegging + + button_login: Logg inn + button_submit: Send + button_save: Lagre + button_check_all: Merk alle + button_uncheck_all: Avmerk alle + button_delete: Slett + button_create: Opprett + button_test: Test + button_edit: Endre + button_add: Legg til + button_change: Endre + button_apply: Bruk + button_clear: Nullstill + button_lock: Lås + button_unlock: Lås opp + button_download: Last ned + button_list: Liste + button_view: Vis + button_move: Flytt + button_back: Tilbake + button_cancel: Avbryt + button_activate: Aktiver + button_sort: Sorter + button_log_time: Logg tid + button_rollback: Rull tilbake til denne versjonen + button_watch: Overvåk + button_unwatch: Stopp overvåkning + button_reply: Svar + button_archive: Arkiver + button_unarchive: Gjør om arkivering + button_reset: Nullstill + button_rename: Endre navn + button_change_password: Endre passord + button_copy: Kopier + button_annotate: Notér + button_update: Oppdater + button_configure: Konfigurer + + status_active: aktiv + status_registered: registrert + status_locked: låst + + text_select_mail_notifications: Velg hendelser som skal varsles med e-post. + text_regexp_info: f.eks. ^[A-Z0-9]+$ + text_min_max_length_info: 0 betyr ingen begrensning + text_project_destroy_confirmation: Er du sikker på at du vil slette dette prosjekter og alle relatert data ? + text_subprojects_destroy_warning: "Underprojekt(ene): %{value} vil også bli slettet." + text_workflow_edit: Velg en rolle og en sakstype for å endre arbeidsflyten + text_are_you_sure: Er du sikker ? + text_tip_issue_begin_day: oppgaven starter denne dagen + text_tip_issue_end_day: oppgaven avsluttes denne dagen + text_tip_issue_begin_end_day: oppgaven starter og avsluttes denne dagen + text_caracters_maximum: "%{count} tegn maksimum." + text_caracters_minimum: "Må være minst %{count} tegn langt." + text_length_between: "Lengde mellom %{min} og %{max} tegn." + text_tracker_no_workflow: Ingen arbeidsflyt definert for denne sakstypen + text_unallowed_characters: Ugyldige tegn + text_comma_separated: Flere verdier tillat (kommaseparert). + text_issues_ref_in_commit_messages: Referering og retting av saker i innsendingsmelding + text_issue_added: "Sak %{id} er innrapportert av %{author}." + text_issue_updated: "Sak %{id} er oppdatert av %{author}." + text_wiki_destroy_confirmation: Er du sikker på at du vil slette denne wikien og alt innholdet ? + text_issue_category_destroy_question: "Noen saker (%{count}) er lagt til i denne kategorien. Hva vil du gjøre ?" + text_issue_category_destroy_assignments: Fjern bruk av kategorier + text_issue_category_reassign_to: Overfør sakene til denne kategorien + text_user_mail_option: "For ikke-valgte prosjekter vil du bare motta varsling om ting du overvåker eller er involveret i (eks. saker du er forfatter av eller er tildelt)." + text_no_configuration_data: "Roller, arbeidsflyt, sakstyper og -statuser er ikke konfigurert enda.\nDet anbefales sterkt å laste inn standardkonfigurasjonen. Du vil kunne endre denne etter den er innlastet." + text_load_default_configuration: Last inn standardkonfigurasjonen + text_status_changed_by_changeset: "Brukt i endringssett %{value}." + text_issues_destroy_confirmation: 'Er du sikker på at du vil slette valgte sak(er) ?' + text_select_project_modules: 'Velg moduler du vil aktivere for dette prosjektet:' + text_default_administrator_account_changed: Standard administrator-konto er endret + text_file_repository_writable: Fil-arkivet er skrivbart + text_rmagick_available: RMagick er tilgjengelig (valgfritt) + text_destroy_time_entries_question: "%{hours} timer er ført på sakene du er i ferd med å slette. Hva vil du gjøre ?" + text_destroy_time_entries: Slett førte timer + text_assign_time_entries_to_project: Overfør førte timer til prosjektet + text_reassign_time_entries: 'Overfør førte timer til denne saken:' + text_user_wrote: "%{value} skrev:" + + default_role_manager: Leder + default_role_developer: Utvikler + default_role_reporter: Rapportør + default_tracker_bug: Feil + default_tracker_feature: Funksjon + default_tracker_support: Support + default_issue_status_new: Ny + default_issue_status_in_progress: Pågår + default_issue_status_resolved: Avklart + default_issue_status_feedback: Tilbakemelding + default_issue_status_closed: Lukket + default_issue_status_rejected: Avvist + default_doc_category_user: Brukerdokumentasjon + default_doc_category_tech: Teknisk dokumentasjon + default_priority_low: Lav + default_priority_normal: Normal + default_priority_high: Høy + default_priority_urgent: Haster + default_priority_immediate: Omgående + default_activity_design: Design + default_activity_development: Utvikling + + enumeration_issue_priorities: Sakssprioriteringer + enumeration_doc_categories: Dokumentkategorier + enumeration_activities: Aktiviteter (tidsregistrering) + text_enumeration_category_reassign_to: 'Endre dem til denne verdien:' + text_enumeration_destroy_question: "%{count} objekter er endret til denne verdien." + label_incoming_emails: Innkommende e-post + label_generate_key: Generer en nøkkel + setting_mail_handler_api_enabled: Skru på WS for innkommende epost + setting_mail_handler_api_key: API-nøkkel + text_email_delivery_not_configured: "Levering av epost er ikke satt opp, og varsler er skrudd av.\nStill inn din SMTP-tjener i config/configuration.yml og start programmet på nytt for å skru det på." + field_parent_title: Overordnet side + label_issue_watchers: Overvåkere + button_quote: Sitat + setting_sequential_project_identifiers: Generer sekvensielle prosjekt-IDer + notice_unable_delete_version: Kan ikke slette versjonen + label_renamed: gitt nytt navn + label_copied: kopiert + setting_plain_text_mail: kun ren tekst (ikke HTML) + permission_view_files: Vise filer + permission_edit_issues: Redigere saker + permission_edit_own_time_entries: Redigere egne timelister + permission_manage_public_queries: Administrere delte søk + permission_add_issues: Legge inn saker + permission_log_time: Loggføre timer + permission_view_changesets: Vise endringssett + permission_view_time_entries: Vise brukte timer + permission_manage_versions: Administrere versjoner + permission_manage_wiki: Administrere wiki + permission_manage_categories: Administrere kategorier for saker + permission_protect_wiki_pages: Beskytte wiki-sider + permission_comment_news: Kommentere nyheter + permission_delete_messages: Slette meldinger + permission_select_project_modules: Velge prosjektmoduler + permission_edit_wiki_pages: Redigere wiki-sider + permission_add_issue_watchers: Legge til overvåkere + permission_view_gantt: Vise gantt-diagram + permission_move_issues: Flytte saker + permission_manage_issue_relations: Administrere saksrelasjoner + permission_delete_wiki_pages: Slette wiki-sider + permission_manage_boards: Administrere forum + permission_delete_wiki_pages_attachments: Slette vedlegg + permission_view_wiki_edits: Vise wiki-historie + permission_add_messages: Sende meldinger + permission_view_messages: Vise meldinger + permission_manage_files: Administrere filer + permission_edit_issue_notes: Redigere notater + permission_manage_news: Administrere nyheter + permission_view_calendar: Vise kalender + permission_manage_members: Administrere medlemmer + permission_edit_messages: Redigere meldinger + permission_delete_issues: Slette saker + permission_view_issue_watchers: Vise liste over overvåkere + permission_manage_repository: Administrere depot + permission_commit_access: Tilgang til innsending + permission_browse_repository: Bla gjennom depot + permission_view_documents: Vise dokumenter + permission_edit_project: Redigere prosjekt + permission_add_issue_notes: Legge til notater + permission_save_queries: Lagre søk + permission_view_wiki_pages: Vise wiki + permission_rename_wiki_pages: Gi wiki-sider nytt navn + permission_edit_time_entries: Redigere timelister + permission_edit_own_issue_notes: Redigere egne notater + setting_gravatar_enabled: Bruk Gravatar-brukerikoner + label_example: Eksempel + 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: Rediger egne meldinger + permission_delete_own_messages: Slett egne meldinger + label_user_activity: "%{value}s aktivitet" + label_updated_time_by: "Oppdatert av %{author} for %{age} siden" + 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: "%{count} fil(er) kunne ikke lagres." + button_create_and_continue: Opprett og fortsett + text_custom_field_possible_values_info: 'En linje for hver verdi' + label_display: Visning + field_editable: Redigerbar + setting_repository_log_display_limit: Maks antall revisjoner vist i fil-loggen + setting_file_max_size_displayed: Max size of text files displayed inline + field_watcher: Overvåker + setting_openid: Tillat OpenID innlogging og registrering + field_identity_url: OpenID URL + label_login_with_open_id_option: eller logg inn med OpenID + field_content: Innhold + label_descending: Synkende + label_sort: Sorter + label_ascending: Stigende + label_date_from_to: Fra %{start} til %{end} + label_greater_or_equal: ">=" + label_less_or_equal: <= + text_wiki_page_destroy_question: Denne siden har %{descendants} underside(r). Hva ønsker du å gjøre? + text_wiki_page_reassign_children: Tilknytt undersider til denne overordnede siden + text_wiki_page_nullify_children: Behold undersider som rotsider + text_wiki_page_destroy_children: Slett undersider og alle deres underliggende sider + setting_password_min_length: Minimum passordlengde + field_group_by: Grupper resultater etter + mail_subject_wiki_content_updated: "Wiki-side '%{id}' er oppdatert" + label_wiki_content_added: Wiki-side opprettet + mail_subject_wiki_content_added: "Wiki-side '%{id}' er opprettet" + mail_body_wiki_content_added: Wiki-siden '%{id}' ble opprettet av %{author}. + label_wiki_content_updated: Wiki-side oppdatert + mail_body_wiki_content_updated: Wiki-siden '%{id}' ble oppdatert av %{author}. + permission_add_project: Opprett prosjekt + setting_new_project_user_role_id: Rolle gitt en ikke-administratorbruker som oppretter et prosjekt + label_view_all_revisions: Se alle revisjoner + label_tag: Tag + label_branch: Gren + error_no_tracker_in_project: Ingen sakstyper er tilknyttet dette prosjektet. Vennligst kontroller prosjektets innstillinger. + error_no_default_issue_status: Ingen standard saksstatus er angitt. Vennligst kontroller konfigurasjonen (Gå til "Administrasjon -> Saksstatuser"). + text_journal_changed: "%{label} endret fra %{old} til %{new}" + text_journal_set_to: "%{label} satt til %{value}" + text_journal_deleted: "%{label} slettet (%{old})" + label_group_plural: Grupper + label_group: Gruppe + label_group_new: Ny gruppe + label_time_entry_plural: Brukt tid + text_journal_added: "%{label} %{value} lagt til" + field_active: Aktiv + enumeration_system_activity: Systemaktivitet + permission_delete_issue_watchers: Slett overvåkere + version_status_closed: stengt + version_status_locked: låst + version_status_open: åpen + error_can_not_reopen_issue_on_closed_version: En sak tilknyttet en stengt versjon kan ikke gjenåpnes. + label_user_anonymous: Anonym + button_move_and_follow: Flytt og følg etter + setting_default_projects_modules: Standard aktiverte moduler for nye prosjekter + setting_gravatar_default: Standard Gravatar-bilde + field_sharing: Deling + label_version_sharing_hierarchy: Med prosjekt-hierarki + label_version_sharing_system: Med alle prosjekter + label_version_sharing_descendants: Med underprosjekter + label_version_sharing_tree: Med prosjekt-tre + label_version_sharing_none: Ikke delt + error_can_not_archive_project: Dette prosjektet kan ikke arkiveres + button_duplicate: Duplikat + button_copy_and_follow: Kopier og følg etter + label_copy_source: Kilde + setting_issue_done_ratio: Kalkuler ferdigstillingsprosent ut i fra + setting_issue_done_ratio_issue_status: Bruk saksstatuser + error_issue_done_ratios_not_updated: Ferdigstillingsprosent oppdateres ikke. + error_workflow_copy_target: Vennligst velg sakstype(r) og rolle(r) + setting_issue_done_ratio_issue_field: Bruk felt fra saker + label_copy_same_as_target: Samme som mål + label_copy_target: Mål + notice_issue_done_ratios_updated: Ferdigstillingsprosent oppdatert. + error_workflow_copy_source: Vennligst velg en kilde-sakstype eller rolle. + label_update_issue_done_ratios: Oppdatert ferdigstillingsprosent + setting_start_of_week: Start kalender på + permission_view_issues: Se på saker + label_display_used_statuses_only: Vis kun statuser som brukes av denne sakstypen + label_revision_id: Revision %{value} + label_api_access_key: API tilgangsnøkkel + label_api_access_key_created_on: API tilgangsnøkkel opprettet for %{value} siden + label_feeds_access_key: RSS tilgangsnøkkel + notice_api_access_key_reseted: Din API tilgangsnøkkel ble resatt. + setting_rest_api_enabled: Aktiver REST webservice + label_missing_api_access_key: Mangler en API tilgangsnøkkel + label_missing_feeds_access_key: Mangler en RSS tilgangsnøkkel + button_show: Vis + text_line_separated: Flere verdier er tillatt (en linje per verdi). + setting_mail_handler_body_delimiters: Avkort epost etter en av disse linjene + permission_add_subprojects: Opprett underprosjekt + label_subproject_new: Nytt underprosjekt + text_own_membership_delete_confirmation: |- + Du er i ferd med å fjerne noen eller alle rettigheter og vil kanskje ikke være i stand til å redigere dette prosjektet etterpå. + Er du sikker på at du vil fortsette? + label_close_versions: Steng fullførte versjoner + label_board_sticky: Fast + label_board_locked: Låst + permission_export_wiki_pages: Eksporter wiki-sider + setting_cache_formatted_text: Mellomlagre formattert tekst + permission_manage_project_activities: Administrere prosjektaktiviteter + error_unable_delete_issue_status: Kan ikke slette saksstatus + label_profile: Profil + permission_manage_subtasks: Administrere undersaker + field_parent_issue: Overordnet sak + label_subtask_plural: Undersaker + label_project_copy_notifications: Send epost-varslinger under prosjektkopiering + error_can_not_delete_custom_field: Kan ikke slette eget felt + error_unable_to_connect: Kunne ikke koble til (%{value}) + error_can_not_remove_role: Denne rollen er i bruk og kan ikke slettes. + error_can_not_delete_tracker: Denne sakstypen inneholder saker og kan ikke slettes. + field_principal: Principal + label_my_page_block: Min side felt + notice_failed_to_save_members: "Feil ved lagring av medlem(mer): %{errors}." + text_zoom_out: Zoom ut + text_zoom_in: Zoom inn + notice_unable_delete_time_entry: Kan ikke slette oppføring fra timeliste. + label_overall_spent_time: All tidsbruk + field_time_entries: Loggfør tid + project_module_gantt: Gantt + project_module_calendar: Kalender + button_edit_associated_wikipage: "Rediger tilhørende Wiki-side: %{page_title}" + field_text: Tekstfelt + label_user_mail_option_only_owner: Kun for ting jeg eier + setting_default_notification_option: Standardvalg for varslinger + label_user_mail_option_only_my_events: Kun for ting jeg overvåker eller er involvert i + label_user_mail_option_only_assigned: Kun for ting jeg er tildelt + label_user_mail_option_none: Ingen hendelser + field_member_of_group: Den tildeltes gruppe + field_assigned_to_role: Den tildeltes rolle + notice_not_authorized_archived_project: Prosjektet du forsøker å åpne er blitt arkivert. + label_principal_search: "Søk etter bruker eller gruppe:" + label_user_search: "Søk etter bruker:" + field_visible: Synlig + setting_emails_header: Eposthode + setting_commit_logtime_activity_id: Aktivitet for logget tid. + text_time_logged_by_changeset: Lagt til i endringssett %{value}. + setting_commit_logtime_enabled: Muliggjør loggføring av tid + notice_gantt_chart_truncated: Diagrammet ble avkortet fordi det overstiger det maksimale antall elementer som kan vises (%{max}) + setting_gantt_items_limit: Maksimalt antall elementer vist på gantt-diagrammet + field_warn_on_leaving_unsaved: Vis meg en advarsel når jeg forlater en side med ikke lagret tekst + text_warn_on_leaving_unsaved: Siden inneholder tekst som ikke er lagret og som vil bli tapt om du forlater denne siden. + label_my_queries: Mine egne spørringer + text_journal_changed_no_detail: "%{label} oppdatert" + label_news_comment_added: Kommentar lagt til en nyhet + button_expand_all: Utvid alle + button_collapse_all: Kollaps alle + label_additional_workflow_transitions_for_assignee: Ytterligere overganger tillatt når brukeren er den som er tildelt saken + label_additional_workflow_transitions_for_author: Ytterligere overganger tillatt når brukeren er den som har opprettet saken + label_bulk_edit_selected_time_entries: Masserediger valgte timeliste-oppføringer + text_time_entries_destroy_confirmation: Er du sikker på du vil slette de(n) valgte timeliste-oppføringen(e)? + label_role_anonymous: Anonym + label_role_non_member: Ikke medlem + label_issue_note_added: Notat lagt til + label_issue_status_updated: Status oppdatert + label_issue_priority_updated: Prioritet oppdatert + label_issues_visibility_own: Saker opprettet av eller tildelt brukeren + field_issues_visibility: Synlighet på saker + label_issues_visibility_all: Alle saker + permission_set_own_issues_private: Gjør egne saker offentlige eller private + field_is_private: Privat + permission_set_issues_private: Gjør saker offentlige eller private + label_issues_visibility_public: Alle ikke-private saker + text_issues_destroy_descendants_confirmation: Dette vil også slette %{count} undersak(er). + field_commit_logs_encoding: Tegnkoding for innsendingsmeldinger + field_scm_path_encoding: Koding av sti + text_scm_path_encoding_note: "Standard: UTF-8" + field_path_to_repository: Sti til depot + field_root_directory: Rotkatalog + field_cvs_module: Modul + field_cvsroot: CVSROOT + text_mercurial_repository_note: Lokalt depot (f.eks. /hgrepo, c:\hgrepo) + text_scm_command: Kommando + text_scm_command_version: Versjon + label_git_report_last_commit: Rapporter siste innsending for filer og kataloger + text_scm_config: Du kan konfigurere scm kommandoer i config/configuration.yml. Vennligst restart applikasjonen etter å ha redigert filen. + text_scm_command_not_available: Scm kommando er ikke tilgjengelig. Vennligst kontroller innstillingene i administrasjonspanelet. + + text_git_repository_note: Depot er bart og lokalt (f.eks. /gitrepo, c:\gitrepo) + + notice_issue_successful_create: Sak %{id} opprettet. + label_between: mellom + setting_issue_group_assignment: Tillat tildeling av saker til grupper + label_diff: diff + + description_query_sort_criteria_direction: Sorteringsretning + description_project_scope: Search scope + description_filter: Filter + description_user_mail_notification: Mail notification settings + description_date_from: Oppgi startdato + description_message_content: Meldingsinnhold + description_available_columns: Tilgjengelige kolonner + description_date_range_interval: Velg datointervall ved å spesifisere start- og sluttdato + description_issue_category_reassign: Choose issue category + description_search: Søkefelt + description_notes: Notes + description_date_range_list: Choose range from list + description_choose_project: Prosjekter + description_date_to: Oppgi sluttdato + description_query_sort_criteria_attribute: Sort attribute + description_wiki_subpages_reassign: Velg ny overordnet side + description_selected_columns: Valgte kolonner + label_parent_revision: Overordnet + label_child_revision: Underordnet + error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. + setting_default_issue_start_date_to_creation_date: Bruk dagens dato som startdato for nye saker + button_edit_section: Rediger denne seksjonen + setting_repositories_encodings: Attachments and repositories encodings + description_all_columns: Alle kolonnene + button_export: Eksporter + label_export_options: "%{export_format} eksportvalg" + error_attachment_too_big: Filen overstiger maksimum filstørrelse (%{max_size}) og kan derfor ikke lastes opp + notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." + label_x_issues: + zero: 0 saker + one: 1 sak + other: "%{count} saker" + label_repository_new: Nytt depot + field_repository_is_default: Hoveddepot + label_copy_attachments: Kopier vedlegg + label_item_position: "%{position}/%{count}" + label_completed_versions: Completed versions + text_project_identifier_info: Kun små bokstaver (a-z), tall, bindestrek (-) og "underscore" (_) er tillatt.
    Etter lagring er det ikke mulig å gjøre endringer. + field_multiple: Flere verdier + setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed + text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes + text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) + notice_issue_update_conflict: Saken ble oppdatert av en annen bruker mens du redigerte den. + text_issue_conflict_resolution_cancel: Forkast alle endringen mine og vis %{link} på nytt + permission_manage_related_issues: Manage related issues + field_auth_source_ldap_filter: LDAP filter + label_search_for_watchers: Search for watchers to add + notice_account_deleted: Din konto er ugjenkallelig slettet. + setting_unsubscribe: Tillat brukere å slette sin egen konto + button_delete_my_account: Slett kontoen min + text_account_destroy_confirmation: |- + Er du sikker på at du ønsker å fortsette? + Kontoen din vil bli ugjenkallelig slettet uten mulighet for å reaktiveres igjen. + error_session_expired: Økten har gått ut på tid. Vennligst logg på igjen. + text_session_expiration_settings: "Advarsel: ved å endre disse innstillingene kan aktive økter gå ut på tid, inkludert din egen." + setting_session_lifetime: Øktenes makslengde + setting_session_timeout: Økten er avsluttet på grunn av inaktivitet + label_session_expiration: Økten er avsluttet + permission_close_project: Close / reopen the project + label_show_closed_projects: View closed projects + button_close: Close + button_reopen: Reopen + project_status_active: active + project_status_closed: closed + project_status_archived: archived + text_project_closed: This project is closed and read-only. + notice_user_successful_create: User %{id} created. + field_core_fields: Standard fields + field_timeout: Timeout (in seconds) + setting_thumbnails_enabled: Display attachment thumbnails + setting_thumbnails_size: Thumbnails size (in pixels) + label_status_transitions: Status transitions + label_fields_permissions: Fields permissions + label_readonly: Read-only + label_required: Required + text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. + field_board_parent: Parent forum + label_attribute_of_project: Project's %{name} + label_attribute_of_author: Author's %{name} + label_attribute_of_assigned_to: Assignee's %{name} + label_attribute_of_fixed_version: Target version's %{name} + label_copy_subtasks: Copy subtasks + label_copied_to: copied to + label_copied_from: copied from + label_any_issues_in_project: any issues in project + label_any_issues_not_in_project: any issues not in project + field_private_notes: Private notes + permission_view_private_notes: View private notes + permission_set_notes_private: Set notes as private + label_no_issues_in_project: no issues in project + label_any: alle + label_last_n_weeks: last %{count} weeks + setting_cross_project_subtasks: Allow cross-project subtasks + label_cross_project_descendants: Med underprosjekter + label_cross_project_tree: Med prosjekt-tre + label_cross_project_hierarchy: Med prosjekt-hierarki + label_cross_project_system: Med alle prosjekter + button_hide: Hide + setting_non_working_week_days: Non-working days + label_in_the_next_days: in the next + label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Totalt diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/72/724819b2d9d18cf8969d26980587e66cfd665de8.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/72/724819b2d9d18cf8969d26980587e66cfd665de8.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,20 @@ +<%= form_tag({:action => 'edit', :tab => 'projects'}) do %> + +
    +

    <%= setting_check_box :default_projects_public %>

    + +

    <%= setting_multiselect(:default_projects_modules, + Redmine::AccessControl.available_project_modules.collect {|m| [l_or_humanize(m, :prefix => "project_module_"), m.to_s]}) %>

    + +

    <%= setting_multiselect(:default_projects_tracker_ids, + Tracker.sorted.all.collect {|t| [t.name, t.id.to_s]}) %>

    + +

    <%= setting_check_box :sequential_project_identifiers %>

    + +

    <%= setting_select :new_project_user_role_id, + Role.find_all_givable.collect {|r| [r.name, r.id.to_s]}, + :blank => "--- #{l(:actionview_instancetag_blank_option)} ---" %>

    +
    + +<%= submit_tag l(:button_save) %> +<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/72/72607bd4ab6cb744483458ba6f765737733a7ddf.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/72/72607bd4ab6cb744483458ba6f765737733a7ddf.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,51 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 'action_view/helpers/form_helper' + +class Redmine::Views::LabelledFormBuilder < ActionView::Helpers::FormBuilder + include Redmine::I18n + + (field_helpers.map(&:to_s) - %w(radio_button hidden_field fields_for) + + %w(date_select)).each do |selector| + src = <<-END_SRC + def #{selector}(field, options = {}) + label_for_field(field, options) + super(field, options.except(:label)).html_safe + end + END_SRC + class_eval src, __FILE__, __LINE__ + end + + def select(field, choices, options = {}, html_options = {}) + label_for_field(field, options) + super(field, choices, options, html_options.except(:label)).html_safe + end + + def time_zone_select(field, priority_zones = nil, options = {}, html_options = {}) + label_for_field(field, options) + super(field, priority_zones, options, html_options.except(:label)).html_safe + end + + # Returns a label tag for the given field + def label_for_field(field, options = {}) + return ''.html_safe if options.delete(:no_label) + text = options[:label].is_a?(Symbol) ? l(options[:label]) : 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.html_safe, + :class => (@object && @object.errors[field].present? ? "error" : nil), + :for => (@object_name.to_s + "_" + field.to_s)) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/72/727598dda4e9d027f8791398b5529d584c755883.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/72/727598dda4e9d027f8791398b5529d584c755883.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,1127 @@ +# Swedish translation for Ruby on Rails +# by Johan Lundström (johanlunds@gmail.com), +# with parts taken from http://github.com/daniel/swe_rails + +sv: + number: + # Used in number_with_delimiter() + # These are also the defaults for 'currency', 'percentage', 'precision', and 'human' + format: + # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5) + separator: "," + # Delimets thousands (e.g. 1,000,000 is a million) (always in groups of three) + delimiter: "." + # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00) + precision: 2 + + # Used in number_to_currency() + currency: + format: + # Where is the currency sign? %u is the currency unit, %n the number (default: $5.00) + format: "%n %u" + unit: "kr" + # These three are to override number.format and are optional + # separator: "." + # delimiter: "," + # precision: 2 + + # Used in number_to_percentage() + percentage: + format: + # These three are to override number.format and are optional + # separator: + delimiter: "" + # precision: + + # Used in number_to_precision() + precision: + format: + # These three are to override number.format and are optional + # separator: + delimiter: "" + # precision: + + # Used in number_to_human_size() + human: + format: + # These three are to override number.format and are optional + # separator: + delimiter: "" + precision: 3 + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + + # Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words() + datetime: + distance_in_words: + half_a_minute: "en halv minut" + less_than_x_seconds: + one: "mindre än en sekund" + other: "mindre än %{count} sekunder" + x_seconds: + one: "en sekund" + other: "%{count} sekunder" + less_than_x_minutes: + one: "mindre än en minut" + other: "mindre än %{count} minuter" + x_minutes: + one: "en minut" + other: "%{count} minuter" + about_x_hours: + one: "ungefär en timme" + other: "ungefär %{count} timmar" + x_hours: + one: "1 timme" + other: "%{count} timmar" + x_days: + one: "en dag" + other: "%{count} dagar" + about_x_months: + one: "ungefär en månad" + other: "ungefär %{count} månader" + x_months: + one: "en månad" + other: "%{count} månader" + about_x_years: + one: "ungefär ett år" + other: "ungefär %{count} år" + over_x_years: + one: "mer än ett år" + other: "mer än %{count} år" + almost_x_years: + one: "nästan 1 år" + other: "nästan %{count} år" + + activerecord: + errors: + template: + header: + one: "Ett fel förhindrade denna %{model} från att sparas" + other: "%{count} fel förhindrade denna %{model} från att sparas" + # The variable :count is also available + body: "Det var problem med följande fält:" + # The values :model, :attribute and :value are always available for interpolation + # The value :count is available when applicable. Can be used for pluralization. + messages: + inclusion: "finns inte i listan" + exclusion: "är reserverat" + invalid: "är ogiltigt" + confirmation: "stämmer inte överens" + accepted : "måste vara accepterad" + empty: "får ej vara tom" + blank: "måste anges" + too_long: "är för lång (maximum är %{count} tecken)" + too_short: "är för kort (minimum är %{count} tecken)" + wrong_length: "har fel längd (ska vara %{count} tecken)" + taken: "har redan tagits" + not_a_number: "är inte ett nummer" + greater_than: "måste vara större än %{count}" + greater_than_or_equal_to: "måste vara större än eller lika med %{count}" + equal_to: "måste vara samma som" + less_than: "måste vara mindre än %{count}" + less_than_or_equal_to: "måste vara mindre än eller lika med %{count}" + odd: "måste vara udda" + even: "måste vara jämnt" + greater_than_start_date: "måste vara senare än startdatumet" + not_same_project: "tillhör inte samma projekt" + circular_dependency: "Denna relation skulle skapa ett cirkulärt beroende" + cant_link_an_issue_with_a_descendant: "Ett ärende kan inte länkas till ett av dess underärenden" + + direction: ltr + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%Y-%m-%d" + short: "%e %b" + long: "%e %B, %Y" + + day_names: [söndag, måndag, tisdag, onsdag, torsdag, fredag, lördag] + abbr_day_names: [sön, mån, tis, ons, tor, fre, lör] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, januari, februari, mars, april, maj, juni, juli, augusti, september, oktober, november, december] + abbr_month_names: [~, jan, feb, mar, apr, maj, jun, jul, aug, sep, okt, nov, dec] + # Used in date_select and datime_select. + order: + - :day + - :month + - :year + + time: + formats: + default: "%Y-%m-%d %H:%M" + time: "%H:%M" + short: "%d %b %H:%M" + long: "%d %B, %Y %H:%M" + am: "" + pm: "" + +# Used in array.to_sentence. + support: + array: + sentence_connector: "och" + skip_last_comma: true + + actionview_instancetag_blank_option: Var god välj + + general_text_No: 'Nej' + general_text_Yes: 'Ja' + general_text_no: 'nej' + general_text_yes: 'ja' + general_lang_name: 'Svenska' + general_csv_separator: ',' + general_csv_decimal_separator: '.' + general_csv_encoding: ISO-8859-1 + general_pdf_encoding: UTF-8 + 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. 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: Skapades korrekt. + 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: Du saknar behörighet att komma åt den här sidan. + notice_not_authorized_archived_project: Projektet du försöker komma åt har arkiverats. + notice_email_sent: "Ett mail skickades till %{value}" + notice_email_error: "Ett fel inträffade när mail skickades (%{value})" + notice_feeds_access_key_reseted: Din RSS-nyckel återställdes. + notice_api_access_key_reseted: Din API-nyckel återställdes. + notice_failed_to_save_issues: "Misslyckades med att spara %{count} ärende(n) på %{total} valda: %{ids}." + notice_failed_to_save_time_entries: "Misslyckades med att spara %{count} tidloggning(ar) på %{total} valda: %{ids}." + notice_failed_to_save_members: "Misslyckades med att spara medlem(mar): %{errors}." + 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. + notice_unable_delete_time_entry: Tidloggning kunde inte tas bort. + notice_issue_done_ratios_updated: "% klart uppdaterade." + notice_gantt_chart_truncated: "Schemat förminskades eftersom det överskrider det maximala antalet aktiviteter som kan visas (%{max})" + notice_issue_successful_create: "Ärende %{id} skapades." + notice_issue_update_conflict: "Detta ärende har uppdaterats av en annan användare samtidigt som du redigerade det." + notice_account_deleted: "Ditt konto har avslutats permanent." + notice_user_successful_create: "Användare %{id} skapad." + + error_can_t_load_default_data: "Standardkonfiguration gick inte att läsa in: %{value}" + error_scm_not_found: "Inlägg och/eller revision finns inte i detta versionsarkiv." + error_scm_command_failed: "Ett fel inträffade vid försök att nå versionsarkivet: %{value}" + error_scm_annotate: "Inlägget existerar inte eller kan inte kommenteras." + error_scm_annotate_big_text_file: Inlägget kan inte annoteras eftersom det överskrider maximal storlek för textfiler. + error_issue_not_found_in_project: 'Ärendet hittades inte eller så tillhör det inte detta projekt' + error_no_tracker_in_project: 'Ingen ärendetyp är associerad med projektet. Vänligen kontrollera projektinställningarna.' + error_no_default_issue_status: 'Ingen status är definierad som standard för nya ärenden. Vänligen kontrollera din konfiguration (Gå till "Administration -> Ärendestatus").' + error_can_not_delete_custom_field: Kan inte ta bort användardefinerat fält + error_can_not_delete_tracker: "Det finns ärenden av denna typ och den är därför inte möjlig att ta bort." + error_can_not_remove_role: "Denna roll används och den är därför inte möjlig att ta bort." + error_can_not_reopen_issue_on_closed_version: 'Ett ärende tilldelat en stängd version kan inte öppnas på nytt' + error_can_not_archive_project: Detta projekt kan inte arkiveras + error_issue_done_ratios_not_updated: "% klart inte uppdaterade." + error_workflow_copy_source: 'Vänligen välj källans ärendetyp eller roll' + error_workflow_copy_target: 'Vänligen välj ärendetyp(er) och roll(er) för mål' + error_unable_delete_issue_status: 'Ärendestatus kunde inte tas bort' + error_unable_to_connect: "Kan inte ansluta (%{value})" + error_attachment_too_big: "Denna fil kan inte laddas upp eftersom den överstiger maximalt tillåten filstorlek (%{max_size})" + error_session_expired: "Din session har gått ut. Vänligen logga in på nytt." + warning_attachments_not_saved: "%{count} fil(er) kunde inte sparas." + + mail_subject_lost_password: "Ditt %{value} lösenord" + mail_body_lost_password: 'För att ändra ditt lösenord, klicka på följande länk:' + mail_subject_register: "Din %{value} 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 %{value}-konto för att logga in." + mail_body_account_information: Din kontoinformation + mail_subject_account_activation_request: "%{value} begäran om kontoaktivering" + mail_body_account_activation_request: "En ny användare (%{value}) har registrerat sig och avvaktar ditt godkännande:" + mail_subject_reminder: "%{count} ärende(n) har deadline under de kommande %{days} dagarna" + mail_body_reminder: "%{count} ärende(n) som är tilldelat dig har deadline under de %{days} dagarna:" + mail_subject_wiki_content_added: "'%{id}' wikisida has lagts till" + mail_body_wiki_content_added: "The '%{id}' wikisida has lagts till av %{author}." + mail_subject_wiki_content_updated: "'%{id}' wikisida har uppdaterats" + mail_body_wiki_content_updated: "The '%{id}' wikisida har uppdaterats av %{author}." + + + field_name: Namn + field_description: Beskrivning + field_summary: Sammanfattning + field_is_required: Obligatorisk + field_firstname: Förnamn + field_lastname: Efternamn + field_mail: Mail + field_filename: Fil + field_filesize: Storlek + field_downloads: Nerladdningar + field_author: Författare + field_created_on: Skapad + field_updated_on: Uppdaterad + field_closed_on: Stängd + field_field_format: Format + field_is_for_all: För alla projekt + field_possible_values: Möjliga värden + field_regexp: Reguljärt uttryck + field_min_length: Minimilängd + field_max_length: Maxlängd + field_value: Värde + field_category: Kategori + field_title: Titel + field_project: Projekt + field_issue: Ärende + field_status: Status + field_notes: Anteckningar + 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: Versionsmål + field_user: Användare + field_principal: Principal + field_role: Roll + field_homepage: Hemsida + field_is_public: Publik + field_parent: Underprojekt till + 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 lösenord + field_version: Version + field_type: Typ + field_host: Värddator + field_port: Port + field_account: Konto + field_base_dn: Bas-DN + field_attr_login: Inloggningsattribut + field_attr_firstname: Förnamnsattribut + field_attr_lastname: Efternamnsattribut + field_attr_mail: Mailattribut + field_onthefly: Skapa användare on-the-fly + field_start_date: Startdatum + 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: Underprojekt + field_hours: Timmar + field_activity: Aktivitet + field_spent_on: Datum + field_identifier: Identifierare + field_is_filter: Använd som filter + field_issue_to: 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_entries: Spenderad tid + 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 + field_watcher: Bevakare + field_identity_url: OpenID URL + field_content: Innehåll + field_group_by: Gruppera resultat efter + field_sharing: Delning + field_parent_issue: Förälderaktivitet + field_member_of_group: "Tilldelad användares grupp" + field_assigned_to_role: "Tilldelad användares roll" + field_text: Textfält + field_visible: Synlig + field_warn_on_leaving_unsaved: Varna om jag lämnar en sida med osparad text + field_issues_visibility: Ärendesynlighet + field_is_private: Privat + field_commit_logs_encoding: Teckenuppsättning för commit-meddelanden + field_scm_path_encoding: Sökvägskodning + field_path_to_repository: Sökväg till versionsarkiv + field_root_directory: Rotmapp + field_cvsroot: CVSROOT + field_cvs_module: Modul + field_repository_is_default: Huvudarkiv + field_multiple: Flera värden + field_auth_source_ldap_filter: LDAP-filter + field_core_fields: Standardfält + field_timeout: "Timeout (i sekunder)" + field_board_parent: Förälderforum + field_private_notes: Privata anteckningar + field_inherit_members: Ärv medlemmar + + 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: Avsändaradress + 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: Textformatering + setting_wiki_compression: Komprimering av wikihistorik + setting_feeds_limit: Innehållsgräns för Feed + setting_default_projects_public: Nya projekt är publika + setting_autofetch_changesets: Automatisk hämtning av commits + setting_sys_api_enabled: Aktivera WS för versionsarkivhantering + 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_subtasks: Tillåt underaktiviteter mellan projekt + setting_cross_project_issue_relations: Tillåt ärenderelationer mellan projekt + setting_issue_list_default_columns: Standardkolumner i ärendelistan + setting_repositories_encodings: Encoding för bilagor och versionsarkiv + setting_emails_header: Mail-header + setting_emails_footer: Signatur + 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 + setting_enabled_scm: Aktivera SCM + setting_mail_handler_body_delimiters: "Trunkera mail efter en av följande rader" + setting_mail_handler_api_enabled: Aktivera WS för inkommande mail + setting_mail_handler_api_key: API-nyckel + setting_sequential_project_identifiers: Generera projektidentifierare sekventiellt + setting_gravatar_enabled: Använd Gravatar-avatarer + setting_gravatar_default: Förvald Gravatar-bild + setting_diff_max_lines_displayed: Maximalt antal synliga rader i diff + setting_file_max_size_displayed: Maxstorlek på textfiler som visas inline + setting_repository_log_display_limit: Maximalt antal revisioner i filloggen + setting_openid: Tillåt inloggning och registrering med OpenID + setting_password_min_length: Minsta tillåtna lösenordslängd + setting_new_project_user_role_id: Tilldelad roll för en icke-administratör som skapar ett projekt + setting_default_projects_modules: Aktiverade moduler för nya projekt + setting_issue_done_ratio: Beräkna % klart med + setting_issue_done_ratio_issue_field: Använd ärendefältet + setting_issue_done_ratio_issue_status: Använd ärendestatus + setting_start_of_week: Första dagen i veckan + setting_rest_api_enabled: Aktivera REST webbtjänst + setting_cache_formatted_text: Cacha formaterad text + setting_default_notification_option: Standard notifieringsalternativ + setting_commit_logtime_enabled: Aktivera tidloggning + setting_commit_logtime_activity_id: Aktivitet för loggad tid + setting_gantt_items_limit: Maximalt antal aktiviteter som visas i gantt-schemat + setting_issue_group_assignment: Tillåt att ärenden tilldelas till grupper + setting_default_issue_start_date_to_creation_date: Använd dagens datum som startdatum för nya ärenden + setting_commit_cross_project_ref: Tillåt ärende i alla de andra projekten att bli refererade och fixade + setting_unsubscribe: Tillåt användare att avsluta prenumereration + setting_session_lifetime: Maximal sessionslivslängd + setting_session_timeout: Tidsgräns för sessionsinaktivitet + setting_thumbnails_enabled: Visa miniatyrbilder av bilagor + setting_thumbnails_size: Storlek på miniatyrbilder (i pixlar) + setting_non_working_week_days: Lediga dagar + setting_jsonp_enabled: Aktivera JSONP-stöd + setting_default_projects_tracker_ids: Standardärendetyper för nya projekt + + permission_add_project: Skapa projekt + permission_add_subprojects: Skapa underprojekt + permission_edit_project: Ändra projekt + permission_close_project: Stänga / återöppna projektet + permission_select_project_modules: Välja projektmoduler + permission_manage_members: Hantera medlemmar + permission_manage_project_activities: Hantera projektaktiviteter + permission_manage_versions: Hantera versioner + permission_manage_categories: Hantera ärendekategorier + permission_add_issues: Lägga till ärenden + permission_edit_issues: Ändra ärenden + permission_view_issues: Visa ärenden + permission_manage_issue_relations: Hantera ärenderelationer + permission_set_issues_private: Sätta ärenden publika eller privata + permission_set_own_issues_private: Sätta egna ärenden publika eller privata + permission_add_issue_notes: Lägga till ärendeanteckning + permission_edit_issue_notes: Ändra ärendeanteckningar + permission_edit_own_issue_notes: Ändra egna ärendeanteckningar + permission_view_private_notes: Visa privata anteckningar + permission_set_notes_private: Ställa in anteckningar som privata + 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_delete_issue_watchers: Ta bort bevakare + permission_log_time: Logga spenderad tid + permission_view_time_entries: Visa spenderad tid + permission_edit_time_entries: Ändra tidloggningar + permission_edit_own_time_entries: Ändra egna tidloggningar + permission_manage_news: Hantera nyheter + permission_comment_news: Kommentera nyheter + permission_view_documents: Visa dokument + permission_add_documents: Lägga till dokument + permission_edit_documents: Ändra dokument + permission_delete_documents: Ta bort 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 versionsarkiv + permission_browse_repository: Bläddra i versionsarkiv + permission_view_changesets: Visa changesets + permission_commit_access: Commit-åtkomst + 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 + permission_export_wiki_pages: Exportera wikisidor + permission_manage_subtasks: Hantera underaktiviteter + permission_manage_related_issues: Hantera relaterade ärenden + + 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: Versionsarkiv + project_module_boards: Forum + project_module_calendar: Kalender + project_module_gantt: Gantt + + label_user: Användare + label_user_plural: Användare + label_user_new: Ny användare + label_user_anonymous: Anonym + label_project: Projekt + label_project_new: Nytt projekt + label_project_plural: Projekt + label_x_projects: + zero: inga projekt + one: 1 projekt + other: "%{count} projekt" + label_project_all: Alla projekt + label_project_latest: Senaste projekt + label_issue: Ärende + label_issue_new: Nytt ärende + label_issue_plural: Ärenden + label_issue_view_all: Visa alla ärenden + label_issues_by: "Ärenden %{value}" + label_issue_added: Ärende tillagt + label_issue_updated: Ärende uppdaterat + label_issue_note_added: Anteckning tillagd + label_issue_status_updated: Status uppdaterad + label_issue_priority_updated: Prioritet uppdaterad + 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 behörigheter + label_role_anonymous: Anonym + label_role_non_member: Icke-medlem + label_member: Medlem + label_member_new: Ny medlem + label_member_plural: Medlemmar + label_tracker: Ärendetyp + label_tracker_plural: Ärendetyper + label_tracker_new: Ny ärendetyp + label_workflow: Arbetsflöde + label_issue_status: Ärendestatus + label_issue_status_plural: Ärendestatus + label_issue_status_new: Ny status + 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_enumeration_new: Nytt värde + label_information: Information + label_information_plural: Information + label_please_login: Var god logga in + label_register: Registrera + label_login_with_open_id_option: eller logga in med OpenID + label_password_lost: Glömt lösenord + label_home: Hem + label_my_page: Min sida + label_my_account: Mitt konto + label_my_projects: Mina projekt + label_my_page_block: '"Min sida"-block' + label_administration: Administration + label_login: Logga in + label_logout: Logga ut + label_help: Hjälp + label_reported_issues: Rapporterade ärenden + label_assigned_to_me_issues: Ärenden tilldelade till mig + label_last_login: Senaste inloggning + label_registered_on: Registrerad + label_activity: Aktivitet + label_overall_activity: All aktivitet + label_user_activity: "Aktiviteter för %{value}" + label_new: Ny + label_logged_as: Inloggad som + label_environment: Miljö + 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_subproject_new: Nytt underprojekt + label_and_its_subprojects: "%{value} 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: Lång text + label_attribute: Attribut + label_attribute_plural: Attribut + label_no_data: Ingen data att visa + label_change_status: Ändra status + label_history: Historia + 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 nyheterna + label_news_view_all: Visa alla nyheter + label_news_added: Nyhet tillagd + label_news_comment_added: Kommentar tillagd till en nyhet + label_settings: Inställningar + label_overview: Översikt + label_version: Version + label_version_new: Ny version + label_version_plural: Versioner + label_close_versions: Stäng klara versioner + label_confirmation: Bekräftelse + label_export_to: 'Finns även som:' + label_read: Läs... + 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_x_open_issues_abbr_on_total: + zero: 0 öppna av %{total} + one: 1 öppen av %{total} + other: "%{count} öppna av %{total}" + label_x_open_issues_abbr: + zero: 0 öppna + one: 1 öppen + other: "%{count} öppna" + label_x_closed_issues_abbr: + zero: 0 stängda + one: 1 stängd + other: "%{count} stängda" + label_x_issues: + zero: 0 ärenden + one: 1 ärende + other: "%{count} ärenden" + label_total: Total + label_total_time: Total tid + label_permissions: Behörigheter + label_current_status: Nuvarande status + label_new_statuses_allowed: Nya tillåtna statusvärden + label_all: alla + label_any: vad/vem som helst + label_none: inget/ingen + label_nobody: ingen + label_next: Nästa + label_previous: Föregående + label_used_by: Använd av + label_details: Detaljer + label_add_note: Lägg till anteckning + label_per_page: Per sida + label_calendar: Kalender + label_months_from: månader från + label_gantt: Gantt + label_internal: Intern + label_last_changes: "senaste %{count} ändringar" + label_change_view_all: Visa alla ändringar + label_personalize_page: Anpassa denna sida + label_comment: Kommentar + label_comment_plural: Kommentarer + label_x_comments: + zero: inga kommentarer + one: 1 kommentar + other: "%{count} kommentarer" + label_comment_add: Lägg till kommentar + label_comment_added: Kommentar tillagd + label_comment_delete: Ta bort kommentar + label_query: Användardefinerad fråga + label_query_plural: Användardefinerade frågor + label_query_new: Ny fråga + label_my_queries: Mina egna frågor + label_filter_add: Lägg till filter + label_filter_plural: Filter + label_equals: är + label_not_equals: är inte + label_in_less_than: om mindre än + label_in_more_than: om mer än + label_in_the_next_days: under kommande + label_in_the_past_days: under föregående + label_greater_or_equal: '>=' + label_less_or_equal: '<=' + label_between: mellan + label_in: om + label_today: idag + label_all_time: närsom + label_yesterday: igår + label_this_week: denna vecka + label_last_week: senaste veckan + label_last_n_weeks: "senaste %{count} veckorna" + label_last_n_days: "senaste %{count} 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_any_issues_in_project: några ärenden i projektet + label_any_issues_not_in_project: några ärenden utanför projektet + label_no_issues_in_project: inga ärenden i projektet + label_day_plural: dagar + label_repository: Versionsarkiv + label_repository_new: Nytt versionsarkiv + label_repository_plural: Versionsarkiv + label_browse: Bläddra + label_branch: Branch + label_tag: Tag + label_revision: Revision + label_revision_plural: Revisioner + label_revision_id: "Revision %{value}" + 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_view_all_revisions: Visa alla revisioner + label_max_size: Maxstorlek + 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 %{value}" + label_roadmap_overdue: "%{value} 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ä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: Förhandsgranska + label_feed_plural: Feeds + label_changes_details: Detaljer om alla ändringar + label_issue_tracking: Ärendeuppföljning + label_spent_time: Spenderad tid + label_overall_spent_time: Total tid spenderad + label_f_hour: "%{value} timme" + label_f_hour_plural: "%{value} timmar" + label_time_tracking: Tidsuppföljning + label_change_plural: Ändringar + label_statistics: Statistik + label_commits_per_month: Commits per månad + label_commits_per_author: Commits per författare + label_diff: diff + label_view_diff: Visa skillnader + label_diff_inline: i texten + label_diff_side_by_side: sida vid sida + label_options: Inställningar + 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_copied_to: Kopierad till + label_copied_from: Kopierad från + 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: Nytt forum + label_board_plural: Forum + label_board_locked: Låst + label_board_sticky: Sticky + 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å %{value}" + label_send_test_email: Skicka testmail + label_feeds_access_key: RSS-nyckel + label_missing_feeds_access_key: Saknar en RSS-nyckel + label_feeds_access_key_created_on: "RSS-nyckel skapad för %{value} sedan" + label_module_plural: Moduler + label_added_time_by: "Tillagd av %{author} för %{age} sedan" + label_updated_time_by: "Uppdaterad av %{author} för %{age} sedan" + label_updated_time: "Uppdaterad för %{value} 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_bulk_edit_selected_time_entries: Gruppredigera valda tidloggningar + 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: "Inga händelser" + label_user_mail_option_only_my_events: "Endast för saker jag bevakar eller är inblandad i" + label_user_mail_option_only_assigned: "Endast för saker jag är tilldelad" + label_user_mail_option_only_owner: "Endast för saker jag äger" + label_user_mail_no_self_notified: "Jag vill inte bli underrättad 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: %{value}" + 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 + label_sort: Sortera + label_descending: Fallande + label_ascending: Stigande + label_date_from_to: Från %{start} till %{end} + label_wiki_content_added: Wikisida tillagd + label_wiki_content_updated: Wikisida uppdaterad + label_group: Grupp + label_group_plural: Grupper + label_group_new: Ny grupp + label_time_entry_plural: Spenderad tid + label_version_sharing_none: Inte delad + label_version_sharing_descendants: Med underprojekt + label_version_sharing_hierarchy: Med projekthierarki + label_version_sharing_tree: Med projektträd + label_version_sharing_system: Med alla projekt + label_update_issue_done_ratios: Uppdatera % klart + label_copy_source: Källa + label_copy_target: Mål + label_copy_same_as_target: Samma som mål + label_display_used_statuses_only: Visa endast status som används av denna ärendetyp + label_api_access_key: API-nyckel + label_missing_api_access_key: Saknar en API-nyckel + label_api_access_key_created_on: "API-nyckel skapad för %{value} sedan" + label_profile: Profil + label_subtask_plural: Underaktiviteter + label_project_copy_notifications: Skicka mailnotifieringar när projektet kopieras + label_principal_search: "Sök efter användare eller grupp:" + label_user_search: "Sök efter användare:" + label_additional_workflow_transitions_for_author: Ytterligare övergångar tillåtna när användaren är den som skapat ärendet + label_additional_workflow_transitions_for_assignee: Ytterligare övergångar tillåtna när användaren är den som tilldelats ärendet + label_issues_visibility_all: Alla ärenden + label_issues_visibility_public: Alla icke-privata ärenden + label_issues_visibility_own: Ärenden skapade av eller tilldelade till användaren + label_git_report_last_commit: Rapportera senaste commit av filer och mappar + label_parent_revision: Förälder + label_child_revision: Barn + label_export_options: "%{export_format} exportalternativ" + label_copy_attachments: Kopiera bilagor + label_copy_subtasks: Kopiera underaktiviteter + label_item_position: "%{position}/%{count}" + label_completed_versions: Klara versioner + label_search_for_watchers: Sök efter bevakare att lägga till + label_session_expiration: Sessionsutgång + label_show_closed_projects: Visa stängda projekt + label_status_transitions: Statusövergångar + label_fields_permissions: Fältbehörigheter + label_readonly: Skrivskyddad + label_required: Nödvändig + label_attribute_of_project: Projektets %{name} + label_attribute_of_issue: Ärendets %{name} + label_attribute_of_author: Författarens %{name} + label_attribute_of_assigned_to: Tilldelad användares %{name} + label_attribute_of_user: Användarens %{name} + label_attribute_of_fixed_version: Målversionens %{name} + label_cross_project_descendants: Med underprojekt + label_cross_project_tree: Med projektträd + label_cross_project_hierarchy: Med projekthierarki + label_cross_project_system: Med alla projekt + label_gantt_progress_line: Framstegslinje + + button_login: Logga in + button_submit: Skicka + button_save: Spara + button_check_all: Markera alla + button_uncheck_all: Avmarkera alla + button_collapse_all: Kollapsa alla + button_expand_all: Expandera alla + button_delete: Ta bort + button_create: Skapa + button_create_and_continue: Skapa och fortsätt + button_test: Testa + button_edit: Ändra + button_edit_associated_wikipage: "Ändra associerad Wikisida: %{page_title}" + button_add: Lägg till + button_change: Ändra + button_apply: Verkställ + button_clear: Återställ + button_lock: Lås + button_unlock: Lås upp + button_download: Ladda ner + button_list: Lista + button_view: Visa + button_move: Flytta + button_move_and_follow: Flytta och följ efter + button_back: Tillbaka + button_cancel: Avbryt + button_activate: Aktivera + button_sort: Sortera + button_log_time: Logga tid + 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_copy_and_follow: Kopiera och följ efter + button_annotate: Kommentera + button_update: Uppdatera + button_configure: Konfigurera + button_quote: Citera + button_duplicate: Duplicera + button_show: Visa + button_hide: Göm + button_edit_section: Redigera denna sektion + button_export: Exportera + button_delete_my_account: Ta bort mitt konto + button_close: Stäng + button_reopen: Återöppna + + status_active: aktiv + status_registered: registrerad + status_locked: låst + + project_status_active: aktiv + project_status_closed: stängd + project_status_archived: arkiverad + + version_status_open: öppen + version_status_locked: låst + version_status_closed: stängd + + field_active: Aktiv + + 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_subprojects_destroy_warning: "Alla underprojekt: %{value} 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: "%{label} ändrad från %{old} till %{new}" + text_journal_changed_no_detail: "%{label} uppdaterad" + text_journal_set_to: "%{label} satt till %{value}" + text_journal_deleted: "%{label} borttagen (%{old})" + text_journal_added: "%{label} %{value} tillagd" + text_tip_issue_begin_day: ärende som börjar denna dag + text_tip_issue_end_day: ärende som slutar denna dag + text_tip_issue_begin_end_day: ärende som börjar och slutar denna dag + text_project_identifier_info: 'Endast gemener (a-z), siffror, streck och understreck är tillåtna, måste börja med en bokstav.
    När identifieraren sparats kan den inte ändras.' + text_caracters_maximum: "max %{count} tecken." + text_caracters_minimum: "Måste vara minst %{count} tecken lång." + text_length_between: "Längd mellan %{min} och %{max} tecken." + 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_line_separated: Flera värden tillåtna (ett värde per rad). + text_issues_ref_in_commit_messages: Referera och fixa ärenden i commit-meddelanden + text_issue_added: "Ärende %{id} har rapporterats (av %{author})." + text_issue_updated: "Ärende %{id} har uppdaterats (av %{author})." + 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 (%{count}) ä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 bli underrättad om saker du bevakar eller är inblandad i (T.ex. ärenden du skapat eller tilldelats)." + text_no_configuration_data: "Roller, ärendetyper, ärendestatus 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 %{value}." + text_time_logged_by_changeset: "Tilldelad i changeset %{value}." + text_issues_destroy_confirmation: 'Är du säker på att du vill radera markerade ärende(n) ?' + text_issues_destroy_descendants_confirmation: Detta kommer även ta bort %{count} underaktivitet(er). + text_time_entries_destroy_confirmation: Är du säker på att du vill ta bort valda tidloggningar? + 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: Arkivet för bifogade filer är skrivbart + text_plugin_assets_writable: Arkivet för plug-ins är skrivbart + text_rmagick_available: RMagick tillgängligt (ej obligatoriskt) + text_destroy_time_entries_question: "%{hours} 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: "%{value} skrev:" + text_enumeration_destroy_question: "%{count} 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 via mail kan därför inte skickas.\nKonfigurera din SMTP-server i config/configuration.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 versionarkivloggen.\nAnvändare med samma användarnamn eller mailadress i både Redmine och versionsarkivet 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: 'Ett värde per rad' + text_wiki_page_destroy_question: "Denna sida har %{descendants} underliggande sidor. Vad vill du göra?" + text_wiki_page_nullify_children: "Behåll undersidor som rotsidor" + text_wiki_page_destroy_children: "Ta bort alla underliggande sidor" + text_wiki_page_reassign_children: "Flytta undersidor till denna föräldersida" + text_own_membership_delete_confirmation: "Några av, eller alla, dina behörigheter kommer att tas bort och du kanske inte längre kommer kunna göra ändringar i det här projektet.\nVill du verkligen fortsätta?" + text_zoom_out: Zooma ut + text_zoom_in: Zooma in + text_warn_on_leaving_unsaved: "Nuvarande sida innehåller osparad text som kommer försvinna om du lämnar sidan." + text_scm_path_encoding_note: "Standard: UTF-8" + text_git_repository_note: Versionsarkiv är tomt och lokalt (t.ex. /gitrepo, c:\gitrepo) + text_mercurial_repository_note: Lokalt versionsarkiv (t.ex. /hgrepo, c:\hgrepo) + text_scm_command: Kommando + text_scm_command_version: Version + text_scm_config: Du kan konfigurera dina scm-kommando i config/configuration.yml. Vänligen starta om applikationen när ändringar gjorts. + text_scm_command_not_available: Scm-kommando är inte tillgängligt. Vänligen kontrollera inställningarna i administratörspanelen. + text_issue_conflict_resolution_overwrite: "Använd mina ändringar i alla fall (tidigare anteckningar kommer behållas men några ändringar kan bli överskrivna)" + text_issue_conflict_resolution_add_notes: "Lägg till mina anteckningar och kasta mina andra ändringar" + text_issue_conflict_resolution_cancel: "Kasta alla mina ändringar och visa igen %{link}" + text_account_destroy_confirmation: "Är du säker på att du vill fortsätta?\nDitt konto kommer tas bort permanent, utan möjlighet att återaktivera det." + text_session_expiration_settings: "Varning: ändring av dessa inställningar kan få alla nuvarande sessioner, inklusive din egen, att gå ut." + text_project_closed: Detta projekt är stängt och skrivskyddat. + text_turning_multiple_off: "Om du inaktiverar möjligheten till flera värden kommer endast ett värde per objekt behållas." + + default_role_manager: Projektledare + default_role_developer: Utvecklare + default_role_reporter: Rapportör + default_tracker_bug: Bugg + default_tracker_feature: Funktionalitet + default_tracker_support: Support + default_issue_status_new: Ny + default_issue_status_in_progress: Pågår + default_issue_status_resolved: Löst + default_issue_status_feedback: Återkoppling + default_issue_status_closed: Stängd + default_issue_status_rejected: Avslagen + default_doc_category_user: Användardokumentation + default_doc_category_tech: Teknisk dokumentation + default_priority_low: Låg + default_priority_normal: Normal + default_priority_high: Hög + default_priority_urgent: Brådskande + default_priority_immediate: Omedelbar + default_activity_design: Design + default_activity_development: Utveckling + + enumeration_issue_priorities: Ärendeprioriteter + enumeration_doc_categories: Dokumentkategorier + enumeration_activities: Aktiviteter (tidsuppföljning) + enumeration_system_activity: Systemaktivitet + description_filter: Filter + description_search: Sökfält + description_choose_project: Projekt + description_project_scope: Sökomfång + description_notes: Anteckningar + description_message_content: Meddelandeinnehåll + description_query_sort_criteria_attribute: Sorteringsattribut + description_query_sort_criteria_direction: Sorteringsriktning + description_user_mail_notification: Mailnotifieringsinställningar + description_available_columns: Tillgängliga Kolumner + description_selected_columns: Valda Kolumner + description_all_columns: Alla kolumner + description_issue_category_reassign: Välj ärendekategori + description_wiki_subpages_reassign: Välj ny föräldersida + description_date_range_list: Välj intervall från listan + description_date_range_interval: Ange intervall genom att välja start- och slutdatum + description_date_from: Ange startdatum + description_date_to: Ange slutdatum + text_repository_identifier_info: 'Endast gemener (a-z), siffror, streck och understreck är tillåtna.
    När identifieraren sparats kan den inte ändras.' diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/72/727874d7bc35b744eb4b647417de27d95212b4b8.svn-base --- a/.svn/pristine/72/727874d7bc35b744eb4b647417de27d95212b4b8.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1150 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) - -class IssueTest < ActiveSupport::TestCase - fixtures :projects, :users, :members, :member_roles, :roles, - :trackers, :projects_trackers, - :enabled_modules, - :versions, - :issue_statuses, :issue_categories, :issue_relations, :workflows, - :enumerations, - :issues, - :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values, - :time_entries - - def test_create - issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, - :status_id => 1, :priority => IssuePriority.all.first, - :subject => 'test_create', - :description => 'IssueTest#test_create', :estimated_hours => '1:30') - assert issue.save - issue.reload - 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 => IssuePriority.all.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) - - issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, - :status_id => 1, :subject => 'test_create', - :description => 'IssueTest#test_create_with_required_custom_field') - assert issue.available_custom_fields.include?(field) - # No value for the custom field - assert !issue.save - assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values) - # Blank value - issue.custom_field_values = { field.id => '' } - assert !issue.save - assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values) - # Invalid value - issue.custom_field_values = { field.id => 'SQLServer' } - assert !issue.save - assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values) - # Valid value - issue.custom_field_values = { field.id => 'PostgreSQL' } - assert issue.save - issue.reload - assert_equal 'PostgreSQL', issue.custom_value_for(field).value - end - - def test_create_with_group_assignment - with_settings :issue_group_assignment => '1' do - assert Issue.new(:project_id => 2, :tracker_id => 1, :author_id => 1, - :subject => 'Group assignment', - :assigned_to_id => 11).save - issue = Issue.first(:order => 'id DESC') - assert_kind_of Group, issue.assigned_to - assert_equal Group.find(11), issue.assigned_to - end - end - - def assert_visibility_match(user, issues) - assert_equal issues.collect(&:id).sort, Issue.all.select {|issue| issue.visible?(user)}.collect(&:id).sort - end - - def test_visible_scope_for_anonymous - # Anonymous user should see issues of public projects only - issues = Issue.visible(User.anonymous).all - assert issues.any? - assert_nil issues.detect {|issue| !issue.project.is_public?} - assert_nil issues.detect {|issue| issue.is_private?} - assert_visibility_match User.anonymous, issues - end - - def test_visible_scope_for_anonymous_with_own_issues_visibility - Role.anonymous.update_attribute :issues_visibility, 'own' - Issue.create!(:project_id => 1, :tracker_id => 1, - :author_id => User.anonymous.id, - :subject => 'Issue by anonymous') - - issues = Issue.visible(User.anonymous).all - assert issues.any? - assert_nil issues.detect {|issue| issue.author != User.anonymous} - assert_visibility_match User.anonymous, issues - end - - def test_visible_scope_for_anonymous_without_view_issues_permissions - # Anonymous user should not see issues without permission - Role.anonymous.remove_permission!(:view_issues) - issues = Issue.visible(User.anonymous).all - assert issues.empty? - assert_visibility_match User.anonymous, issues - end - - def test_visible_scope_for_non_member - user = User.find(9) - assert user.projects.empty? - # Non member user should see issues of public projects only - issues = Issue.visible(user).all - assert issues.any? - assert_nil issues.detect {|issue| !issue.project.is_public?} - assert_nil issues.detect {|issue| issue.is_private?} - assert_visibility_match user, issues - end - - def test_visible_scope_for_non_member_with_own_issues_visibility - Role.non_member.update_attribute :issues_visibility, 'own' - Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 9, :subject => 'Issue by non member') - user = User.find(9) - - issues = Issue.visible(user).all - assert issues.any? - assert_nil issues.detect {|issue| issue.author != user} - assert_visibility_match user, issues - end - - def test_visible_scope_for_non_member_without_view_issues_permissions - # Non member user should not see issues without permission - Role.non_member.remove_permission!(:view_issues) - user = User.find(9) - assert user.projects.empty? - issues = Issue.visible(user).all - assert issues.empty? - assert_visibility_match user, issues - end - - def test_visible_scope_for_member - user = User.find(9) - # User should see issues of projects for which he has view_issues permissions only - Role.non_member.remove_permission!(:view_issues) - Member.create!(:principal => user, :project_id => 3, :role_ids => [2]) - issues = Issue.visible(user).all - assert issues.any? - assert_nil issues.detect {|issue| issue.project_id != 3} - assert_nil issues.detect {|issue| issue.is_private?} - assert_visibility_match user, issues - end - - def test_visible_scope_for_member_with_groups_should_return_assigned_issues - user = User.find(8) - assert user.groups.any? - Member.create!(:principal => user.groups.first, :project_id => 1, :role_ids => [2]) - Role.non_member.remove_permission!(:view_issues) - - issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3, - :status_id => 1, :priority => IssuePriority.all.first, - :subject => 'Assignment test', - :assigned_to => user.groups.first, - :is_private => true) - - Role.find(2).update_attribute :issues_visibility, 'default' - issues = Issue.visible(User.find(8)).all - assert issues.any? - assert issues.include?(issue) - - Role.find(2).update_attribute :issues_visibility, 'own' - issues = Issue.visible(User.find(8)).all - assert issues.any? - assert issues.include?(issue) - end - - def test_visible_scope_for_admin - user = User.find(1) - user.members.each(&:destroy) - assert user.projects.empty? - issues = Issue.visible(user).all - assert issues.any? - # Admin should see issues on private projects that he does not belong to - assert issues.detect {|issue| !issue.project.is_public?} - # Admin should see private issues of other users - assert issues.detect {|issue| issue.is_private? && issue.author != user} - assert_visibility_match user, issues - end - - def test_visible_scope_with_project - project = Project.find(1) - issues = Issue.visible(User.find(2), :project => project).all - projects = issues.collect(&:project).uniq - assert_equal 1, projects.size - assert_equal project, projects.first - end - - def test_visible_scope_with_project_and_subprojects - project = Project.find(1) - issues = Issue.visible(User.find(2), :project => project, :with_subprojects => true).all - projects = issues.collect(&:project).uniq - assert projects.size > 1 - assert_equal [], projects.select {|p| !p.is_or_is_descendant_of?(project)} - end - - def test_visible_and_nested_set_scopes - assert_equal 0, Issue.find(1).descendants.visible.all.size - end - - def test_errors_full_messages_should_include_custom_fields_errors - field = IssueCustomField.find_by_name('Database') - - issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, - :status_id => 1, :subject => 'test_create', - :description => 'IssueTest#test_create_with_required_custom_field') - assert issue.available_custom_fields.include?(field) - # Invalid value - issue.custom_field_values = { field.id => 'SQLServer' } - - assert !issue.valid? - assert_equal 1, issue.errors.full_messages.size - assert_equal "Database #{I18n.translate('activerecord.errors.messages.inclusion')}", - issue.errors.full_messages.first - end - - def test_update_issue_with_required_custom_field - field = IssueCustomField.find_by_name('Database') - field.update_attribute(:is_required, true) - - issue = Issue.find(1) - assert_nil issue.custom_value_for(field) - assert issue.available_custom_fields.include?(field) - # No change to custom values, issue can be saved - assert issue.save - # Blank value - issue.custom_field_values = { field.id => '' } - assert !issue.save - # Valid value - issue.custom_field_values = { field.id => 'PostgreSQL' } - assert issue.save - issue.reload - assert_equal 'PostgreSQL', issue.custom_value_for(field).value - end - - def test_should_not_update_attributes_if_custom_fields_validation_fails - issue = Issue.find(1) - field = IssueCustomField.find_by_name('Database') - assert issue.available_custom_fields.include?(field) - - issue.custom_field_values = { field.id => 'Invalid' } - issue.subject = 'Should be not be saved' - assert !issue.save - - issue.reload - assert_equal "Can't print recipes", issue.subject - end - - def test_should_not_recreate_custom_values_objects_on_update - field = IssueCustomField.find_by_name('Database') - - issue = Issue.find(1) - issue.custom_field_values = { field.id => 'PostgreSQL' } - assert issue.save - custom_value = issue.custom_value_for(field) - issue.reload - issue.custom_field_values = { field.id => 'MySQL' } - assert issue.save - issue.reload - assert_equal custom_value.id, issue.custom_value_for(field).id - end - - def test_should_not_update_custom_fields_on_changing_tracker_with_different_custom_fields - issue = Issue.new(:project_id => 1) - issue.attributes = {:tracker_id => 1, :author_id => 1, :status_id => 1, :subject => 'Test', :custom_field_values => {'2' => 'Test'}} - issue.save! - - assert !Tracker.find(2).custom_field_ids.include?(2) - - issue = Issue.find(issue.id) - issue.attributes = {:tracker_id => 2, :custom_field_values => {'1' => ''}} - - issue = Issue.find(issue.id) - custom_value = issue.custom_value_for(2) - assert_not_nil custom_value - assert_equal 'Test', custom_value.value - end - - def test_assigning_tracker_id_should_reload_custom_fields_values - issue = Issue.new(:project => Project.find(1)) - assert issue.custom_field_values.empty? - issue.tracker_id = 1 - assert issue.custom_field_values.any? - end - - def test_assigning_attributes_should_assign_tracker_id_first - attributes = ActiveSupport::OrderedHash.new - attributes['custom_field_values'] = { '1' => 'MySQL' } - attributes['tracker_id'] = '1' - issue = Issue.new(:project => Project.find(1)) - issue.attributes = attributes - assert_not_nil issue.custom_value_for(1) - assert_equal 'MySQL', issue.custom_value_for(1).value - end - - def test_should_update_issue_with_disabled_tracker - p = Project.find(1) - issue = Issue.find(1) - - p.trackers.delete(issue.tracker) - assert !p.trackers.include?(issue.tracker) - - issue.reload - issue.subject = 'New subject' - assert issue.save - end - - def test_should_not_set_a_disabled_tracker - p = Project.find(1) - p.trackers.delete(Tracker.find(2)) - - issue = Issue.find(1) - issue.tracker_id = 2 - issue.subject = 'New subject' - assert !issue.save - assert_not_nil issue.errors[:tracker_id] - end - - def test_category_based_assignment - issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3, - :status_id => 1, :priority => IssuePriority.all.first, - :subject => 'Assignment test', - :description => 'Assignment test', :category_id => 1) - assert_equal IssueCategory.find(1).assigned_to, issue.assigned_to - end - - def test_new_statuses_allowed_to - Workflow.delete_all - - Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2, :author => false, :assignee => false) - Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3, :author => true, :assignee => false) - Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4, :author => false, :assignee => true) - Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 5, :author => true, :assignee => true) - status = IssueStatus.find(1) - role = Role.find(1) - tracker = Tracker.find(1) - user = User.find(2) - - issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1) - assert_equal [1, 2], issue.new_statuses_allowed_to(user).map(&:id) - - issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1, :author => user) - assert_equal [1, 2, 3, 5], issue.new_statuses_allowed_to(user).map(&:id) - - issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1, :assigned_to => user) - assert_equal [1, 2, 4, 5], issue.new_statuses_allowed_to(user).map(&:id) - - issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1, :author => user, :assigned_to => user) - assert_equal [1, 2, 3, 4, 5], issue.new_statuses_allowed_to(user).map(&:id) - end - - def test_copy - issue = Issue.new.copy_from(1) - assert issue.save - issue.reload - orig = Issue.find(1) - assert_equal orig.subject, issue.subject - assert_equal orig.tracker, issue.tracker - assert_equal "125", issue.custom_value_for(2).value - end - - def test_copy_should_copy_status - orig = Issue.find(8) - assert orig.status != IssueStatus.default - - issue = Issue.new.copy_from(orig) - assert issue.save - issue.reload - assert_equal orig.status, issue.status - end - - def test_should_close_duplicates - # Create 3 issues - issue1 = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, - :status_id => 1, :priority => IssuePriority.all.first, - :subject => 'Duplicates test', :description => 'Duplicates test') - assert issue1.save - issue2 = issue1.clone - assert issue2.save - issue3 = issue1.clone - assert issue3.save - - # 2 is a dupe of 1 - IssueRelation.create(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES) - # And 3 is a dupe of 2 - IssueRelation.create(:issue_from => issue3, :issue_to => issue2, :relation_type => IssueRelation::TYPE_DUPLICATES) - # And 3 is a dupe of 1 (circular duplicates) - IssueRelation.create(:issue_from => issue3, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES) - - assert issue1.reload.duplicates.include?(issue2) - - # Closing issue 1 - issue1.init_journal(User.find(:first), "Closing issue1") - issue1.status = IssueStatus.find :first, :conditions => {:is_closed => true} - assert issue1.save - # 2 and 3 should be also closed - assert issue2.reload.closed? - assert issue3.reload.closed? - end - - def test_should_not_close_duplicated_issue - # Create 3 issues - issue1 = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, - :status_id => 1, :priority => IssuePriority.all.first, - :subject => 'Duplicates test', :description => 'Duplicates test') - assert issue1.save - issue2 = issue1.clone - assert issue2.save - - # 2 is a dupe of 1 - IssueRelation.create(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES) - # 2 is a dup of 1 but 1 is not a duplicate of 2 - assert !issue2.reload.duplicates.include?(issue1) - - # Closing issue 2 - issue2.init_journal(User.find(:first), "Closing issue2") - issue2.status = IssueStatus.find :first, :conditions => {:is_closed => true} - assert issue2.save - # 1 should not be also closed - assert !issue1.reload.closed? - end - - def test_assignable_versions - issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 1, :subject => 'New issue') - assert_equal ['open'], issue.assignable_versions.collect(&:status).uniq - end - - def test_should_not_be_able_to_assign_a_new_issue_to_a_closed_version - issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 1, :subject => 'New issue') - assert !issue.save - assert_not_nil issue.errors[:fixed_version_id] - end - - def test_should_not_be_able_to_assign_a_new_issue_to_a_locked_version - issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 2, :subject => 'New issue') - assert !issue.save - assert_not_nil issue.errors[:fixed_version_id] - end - - def test_should_be_able_to_assign_a_new_issue_to_an_open_version - issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 3, :subject => 'New issue') - assert issue.save - end - - def test_should_be_able_to_update_an_issue_assigned_to_a_closed_version - issue = Issue.find(11) - assert_equal 'closed', issue.fixed_version.status - issue.subject = 'Subject changed' - assert issue.save - end - - def test_should_not_be_able_to_reopen_an_issue_assigned_to_a_closed_version - issue = Issue.find(11) - issue.status_id = 1 - assert !issue.save - assert_not_nil issue.errors[:base] - end - - def test_should_be_able_to_reopen_and_reassign_an_issue_assigned_to_a_closed_version - issue = Issue.find(11) - issue.status_id = 1 - issue.fixed_version_id = 3 - assert issue.save - end - - def test_should_be_able_to_reopen_an_issue_assigned_to_a_locked_version - issue = Issue.find(12) - assert_equal 'locked', issue.fixed_version.status - issue.status_id = 1 - assert issue.save - end - - def test_move_to_another_project_with_same_category - issue = Issue.find(1) - assert issue.move_to_project(Project.find(2)) - issue.reload - assert_equal 2, issue.project_id - # Category changes - assert_equal 4, issue.category_id - # Make sure time entries were move to the target project - assert_equal 2, issue.time_entries.first.project_id - end - - def test_move_to_another_project_without_same_category - issue = Issue.find(2) - assert issue.move_to_project(Project.find(2)) - issue.reload - assert_equal 2, issue.project_id - # Category cleared - assert_nil issue.category_id - end - - def test_move_to_another_project_should_clear_fixed_version_when_not_shared - issue = Issue.find(1) - issue.update_attribute(:fixed_version_id, 1) - assert issue.move_to_project(Project.find(2)) - issue.reload - assert_equal 2, issue.project_id - # Cleared fixed_version - assert_equal nil, issue.fixed_version - end - - def test_move_to_another_project_should_keep_fixed_version_when_shared_with_the_target_project - issue = Issue.find(1) - issue.update_attribute(:fixed_version_id, 4) - assert issue.move_to_project(Project.find(5)) - issue.reload - assert_equal 5, issue.project_id - # Keep fixed_version - assert_equal 4, issue.fixed_version_id - end - - def test_move_to_another_project_should_clear_fixed_version_when_not_shared_with_the_target_project - issue = Issue.find(1) - issue.update_attribute(:fixed_version_id, 1) - assert issue.move_to_project(Project.find(5)) - issue.reload - assert_equal 5, issue.project_id - # Cleared fixed_version - assert_equal nil, issue.fixed_version - end - - def test_move_to_another_project_should_keep_fixed_version_when_shared_systemwide - issue = Issue.find(1) - issue.update_attribute(:fixed_version_id, 7) - assert issue.move_to_project(Project.find(2)) - issue.reload - assert_equal 2, issue.project_id - # Keep fixed_version - assert_equal 7, issue.fixed_version_id - end - - def test_move_to_another_project_with_disabled_tracker - issue = Issue.find(1) - target = Project.find(2) - target.tracker_ids = [3] - target.save - assert_equal false, issue.move_to_project(target) - issue.reload - assert_equal 1, issue.project_id - end - - def test_copy_to_the_same_project - issue = Issue.find(1) - copy = nil - assert_difference 'Issue.count' do - copy = issue.move_to_project(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(Project.find(3), Tracker.find(2), :copy => true) - end - copy.reload - 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 - - context "#move_to_project" do - context "as a copy" do - setup do - @issue = Issue.find(1) - @copy = nil - end - - should "not create a journal" do - @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:assigned_to_id => 3}}) - assert_equal 0, @copy.reload.journals.size - end - - should "allow assigned_to changes" do - @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:assigned_to_id => 3}}) - assert_equal 3, @copy.assigned_to_id - end - - should "allow status changes" do - @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:status_id => 2}}) - assert_equal 2, @copy.status_id - end - - should "allow start date changes" do - date = Date.today - @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:start_date => date}}) - assert_equal date, @copy.start_date - end - - should "allow due date changes" do - date = Date.today - @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:due_date => date}}) - - assert_equal date, @copy.due_date - end - - should "set current user as author" do - User.current = User.find(9) - @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {}}) - - assert_equal User.current, @copy.author - end - - should "keep journal notes" do - date = Date.today - notes = "Notes added when copying" - User.current = User.find(9) - @issue.init_journal(User.current, notes) - @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:start_date => date}}) - - assert_equal 1, @copy.journals.size - journal = @copy.journals.first - assert_equal 0, journal.details.size - assert_equal notes, journal.notes - end - end - end - - def test_recipients_should_not_include_users_that_cannot_view_the_issue - issue = Issue.find(12) - assert issue.recipients.include?(issue.author.mail) - # move the issue to a private project - copy = issue.move_to_project(Project.find(5), Tracker.find(2), :copy => true) - # author is not a member of project anymore - assert !copy.recipients.include?(copy.author.mail) - end - - def test_recipients_should_include_the_assigned_group_members - group_member = User.generate_with_protected! - group = Group.generate! - group.users << group_member - - issue = Issue.find(12) - issue.assigned_to = group - assert issue.recipients.include?(group_member.mail) - end - - def test_watcher_recipients_should_not_include_users_that_cannot_view_the_issue - user = User.find(3) - issue = Issue.find(9) - Watcher.create!(:user => user, :watchable => issue) - assert issue.watched_by?(user) - assert !issue.watcher_recipients.include?(user.mail) - 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_blocked - blocked_issue = Issue.find(9) - blocking_issue = Issue.find(10) - - assert blocked_issue.blocked? - assert !blocking_issue.blocked? - end - - def test_blocked_issues_dont_allow_closed_statuses - blocked_issue = Issue.find(9) - - allowed_statuses = blocked_issue.new_statuses_allowed_to(users(:users_002)) - assert !allowed_statuses.empty? - closed_statuses = allowed_statuses.select {|st| st.is_closed?} - assert closed_statuses.empty? - end - - def test_unblocked_issues_allow_closed_statuses - blocking_issue = Issue.find(10) - - allowed_statuses = blocking_issue.new_statuses_allowed_to(users(:users_002)) - assert !allowed_statuses.empty? - closed_statuses = allowed_statuses.select {|st| st.is_closed?} - assert !closed_statuses.empty? - end - - def test_rescheduling_an_issue_should_reschedule_following_issue - issue1 = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => '-', :start_date => Date.today, :due_date => Date.today + 2) - issue2 = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => '-', :start_date => Date.today, :due_date => Date.today + 2) - IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES) - assert_equal issue1.due_date + 1, issue2.reload.start_date - - issue1.due_date = Date.today + 5 - issue1.save! - assert_equal issue1.due_date + 1, issue2.reload.start_date - 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 - - context "#behind_schedule?" do - should "be false if the issue has no start_date" do - assert !Issue.new(:start_date => nil, :due_date => 1.day.from_now.to_date, :done_ratio => 0).behind_schedule? - end - - should "be false if the issue has no end_date" do - assert !Issue.new(:start_date => 1.day.from_now.to_date, :due_date => nil, :done_ratio => 0).behind_schedule? - end - - should "be false if the issue has more done than it's calendar time" do - assert !Issue.new(:start_date => 50.days.ago.to_date, :due_date => 50.days.from_now.to_date, :done_ratio => 90).behind_schedule? - end - - should "be true if the issue hasn't been started at all" do - assert Issue.new(:start_date => 1.day.ago.to_date, :due_date => 1.day.from_now.to_date, :done_ratio => 0).behind_schedule? - end - - should "be true if the issue has used more calendar time than it's done ratio" do - assert Issue.new(:start_date => 100.days.ago.to_date, :due_date => Date.today, :done_ratio => 90).behind_schedule? - end - end - - context "#assignable_users" do - should "be Users" do - assert_kind_of User, Issue.find(1).assignable_users.first - end - - should "include the issue author" do - project = Project.find(1) - non_project_member = User.generate! - issue = Issue.generate_for_project!(project, :author => non_project_member) - - assert issue.assignable_users.include?(non_project_member) - end - - should "include the current assignee" do - project = Project.find(1) - user = User.generate! - issue = Issue.generate_for_project!(project, :assigned_to => user) - user.lock! - - assert Issue.find(issue.id).assignable_users.include?(user) - end - - should "not show the issue author twice" do - assignable_user_ids = Issue.find(1).assignable_users.collect(&:id) - assert_equal 2, assignable_user_ids.length - - assignable_user_ids.each do |user_id| - assert_equal 1, assignable_user_ids.select {|i| i == user_id}.length, "User #{user_id} appears more or less than once" - end - end - - context "with issue_group_assignment" do - should "include groups" do - issue = Issue.new(:project => Project.find(2)) - - with_settings :issue_group_assignment => '1' do - assert_equal %w(Group User), issue.assignable_users.map {|a| a.class.name}.uniq.sort - assert issue.assignable_users.include?(Group.find(11)) - end - end - end - - context "without issue_group_assignment" do - should "not include groups" do - issue = Issue.new(:project => Project.find(2)) - - with_settings :issue_group_assignment => '0' do - assert_equal %w(User), issue.assignable_users.map {|a| a.class.name}.uniq.sort - assert !issue.assignable_users.include?(Group.find(11)) - end - end - end - end - - def test_create_should_send_email_notification - ActionMailer::Base.deliveries.clear - issue = Issue.new(:project_id => 1, :tracker_id => 1, - :author_id => 3, :status_id => 1, - :priority => IssuePriority.all.first, - :subject => 'test_create', :estimated_hours => '1:30') - - assert issue.save - assert_equal 1, ActionMailer::Base.deliveries.size - end - - def test_stale_issue_should_not_send_email_notification - ActionMailer::Base.deliveries.clear - issue = Issue.find(1) - stale = Issue.find(1) - - issue.init_journal(User.find(1)) - issue.subject = 'Subjet update' - assert issue.save - assert_equal 1, ActionMailer::Base.deliveries.size - ActionMailer::Base.deliveries.clear - - stale.init_journal(User.find(1)) - stale.subject = 'Another subjet update' - assert_raise ActiveRecord::StaleObjectError do - stale.save - end - assert ActionMailer::Base.deliveries.empty? - end - - def test_journalized_description - IssueCustomField.delete_all - - i = Issue.first - old_description = i.description - new_description = "This is the new description" - - i.init_journal(User.find(2)) - i.description = new_description - assert_difference 'Journal.count', 1 do - assert_difference 'JournalDetail.count', 1 do - i.save! - end - end - - detail = JournalDetail.first(:order => 'id DESC') - assert_equal i, detail.journal.journalized - assert_equal 'attr', detail.property - assert_equal 'description', detail.prop_key - assert_equal old_description, detail.old_value - assert_equal new_description, detail.value - end - - def test_blank_descriptions_should_not_be_journalized - IssueCustomField.delete_all - Issue.update_all("description = NULL", "id=1") - - i = Issue.find(1) - i.init_journal(User.find(2)) - i.subject = "blank description" - i.description = "\r\n" - - assert_difference 'Journal.count', 1 do - assert_difference 'JournalDetail.count', 1 do - i.save! - end - end - end - - def test_description_eol_should_be_normalized - i = Issue.new(:description => "CR \r LF \n CRLF \r\n") - assert_equal "CR \r\n LF \r\n CRLF \r\n", i.description - end - - def test_saving_twice_should_not_duplicate_journal_details - i = Issue.find(:first) - i.init_journal(User.find(2), 'Some notes') - # initial changes - i.subject = 'New subject' - i.done_ratio = i.done_ratio + 10 - assert_difference 'Journal.count' do - assert i.save - end - # 1 more change - i.priority = IssuePriority.find(:first, :conditions => ["id <> ?", i.priority_id]) - assert_no_difference 'Journal.count' do - assert_difference 'JournalDetail.count', 1 do - i.save - end - end - # no more change - assert_no_difference 'Journal.count' do - assert_no_difference 'JournalDetail.count' do - i.save - end - end - end - - def test_all_dependent_issues - IssueRelation.delete_all - assert IssueRelation.create!(:issue_from => Issue.find(1), - :issue_to => Issue.find(2), - :relation_type => IssueRelation::TYPE_PRECEDES) - assert IssueRelation.create!(:issue_from => Issue.find(2), - :issue_to => Issue.find(3), - :relation_type => IssueRelation::TYPE_PRECEDES) - assert IssueRelation.create!(:issue_from => Issue.find(3), - :issue_to => Issue.find(8), - :relation_type => IssueRelation::TYPE_PRECEDES) - - assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort - end - - def test_all_dependent_issues_with_persistent_circular_dependency - IssueRelation.delete_all - assert IssueRelation.create!(:issue_from => Issue.find(1), - :issue_to => Issue.find(2), - :relation_type => IssueRelation::TYPE_PRECEDES) - assert IssueRelation.create!(:issue_from => Issue.find(2), - :issue_to => Issue.find(3), - :relation_type => IssueRelation::TYPE_PRECEDES) - # Validation skipping - assert IssueRelation.new(:issue_from => Issue.find(3), - :issue_to => Issue.find(1), - :relation_type => IssueRelation::TYPE_PRECEDES).save(false) - - assert_equal [2, 3], Issue.find(1).all_dependent_issues.collect(&:id).sort - end - - def test_all_dependent_issues_with_persistent_multiple_circular_dependencies - IssueRelation.delete_all - assert IssueRelation.create!(:issue_from => Issue.find(1), - :issue_to => Issue.find(2), - :relation_type => IssueRelation::TYPE_RELATES) - assert IssueRelation.create!(:issue_from => Issue.find(2), - :issue_to => Issue.find(3), - :relation_type => IssueRelation::TYPE_RELATES) - assert IssueRelation.create!(:issue_from => Issue.find(3), - :issue_to => Issue.find(8), - :relation_type => IssueRelation::TYPE_RELATES) - # Validation skipping - assert IssueRelation.new(:issue_from => Issue.find(8), - :issue_to => Issue.find(2), - :relation_type => IssueRelation::TYPE_RELATES).save(false) - assert IssueRelation.new(:issue_from => Issue.find(3), - :issue_to => Issue.find(1), - :relation_type => IssueRelation::TYPE_RELATES).save(false) - - assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort - end - - context "#done_ratio" do - setup do - @issue = Issue.find(1) - @issue_status = IssueStatus.find(1) - @issue_status.update_attribute(:default_done_ratio, 50) - @issue2 = Issue.find(2) - @issue_status2 = IssueStatus.find(2) - @issue_status2.update_attribute(:default_done_ratio, 0) - end - - context "with Setting.issue_done_ratio using the issue_field" do - setup do - Setting.issue_done_ratio = 'issue_field' - end - - should "read the issue's field" do - assert_equal 0, @issue.done_ratio - assert_equal 30, @issue2.done_ratio - end - end - - context "with Setting.issue_done_ratio using the issue_status" do - setup do - Setting.issue_done_ratio = 'issue_status' - end - - should "read the Issue Status's default done ratio" do - assert_equal 50, @issue.done_ratio - assert_equal 0, @issue2.done_ratio - end - end - end - - context "#update_done_ratio_from_issue_status" do - setup do - @issue = Issue.find(1) - @issue_status = IssueStatus.find(1) - @issue_status.update_attribute(:default_done_ratio, 50) - @issue2 = Issue.find(2) - @issue_status2 = IssueStatus.find(2) - @issue_status2.update_attribute(:default_done_ratio, 0) - end - - context "with Setting.issue_done_ratio using the issue_field" do - setup do - Setting.issue_done_ratio = 'issue_field' - end - - should "not change the issue" do - @issue.update_done_ratio_from_issue_status - @issue2.update_done_ratio_from_issue_status - - assert_equal 0, @issue.read_attribute(:done_ratio) - assert_equal 30, @issue2.read_attribute(:done_ratio) - end - end - - context "with Setting.issue_done_ratio using the issue_status" do - setup do - Setting.issue_done_ratio = 'issue_status' - end - - should "change the issue's done ratio" do - @issue.update_done_ratio_from_issue_status - @issue2.update_done_ratio_from_issue_status - - assert_equal 50, @issue.read_attribute(:done_ratio) - assert_equal 0, @issue2.read_attribute(:done_ratio) - end - end - end - - test "#by_tracker" do - User.current = User.anonymous - groups = Issue.by_tracker(Project.find(1)) - assert_equal 3, groups.size - assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i} - end - - test "#by_version" do - User.current = User.anonymous - groups = Issue.by_version(Project.find(1)) - assert_equal 3, groups.size - assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i} - end - - test "#by_priority" do - User.current = User.anonymous - groups = Issue.by_priority(Project.find(1)) - assert_equal 4, groups.size - assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i} - end - - test "#by_category" do - User.current = User.anonymous - groups = Issue.by_category(Project.find(1)) - assert_equal 2, groups.size - assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i} - end - - test "#by_assigned_to" do - User.current = User.anonymous - groups = Issue.by_assigned_to(Project.find(1)) - assert_equal 2, groups.size - assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i} - end - - test "#by_author" do - User.current = User.anonymous - groups = Issue.by_author(Project.find(1)) - assert_equal 4, groups.size - assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i} - end - - test "#by_subproject" do - User.current = User.anonymous - groups = Issue.by_subproject(Project.find(1)) - # Private descendant not visible - assert_equal 1, groups.size - assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i} - end - - context ".allowed_target_projects_on_move" do - should "return all active projects for admin users" do - User.current = User.find(1) - assert_equal Project.active.count, Issue.allowed_target_projects_on_move.size - end - - should "return allowed projects for non admin users" do - User.current = User.find(2) - Role.non_member.remove_permission! :move_issues - assert_equal 3, Issue.allowed_target_projects_on_move.size - - Role.non_member.add_permission! :move_issues - assert_equal Project.active.count, Issue.allowed_target_projects_on_move.size - end - end - - def test_recently_updated_with_limit_scopes - #should return the last updated issue - assert_equal 1, Issue.recently_updated.with_limit(1).length - assert_equal Issue.find(:first, :order => "updated_on DESC"), Issue.recently_updated.with_limit(1).first - end - - def test_on_active_projects_scope - assert Project.find(2).archive - - before = Issue.on_active_project.length - # test inclusion to results - issue = Issue.generate_for_project!(Project.find(1), :tracker => Project.find(2).trackers.first) - assert_equal before + 1, Issue.on_active_project.length - - # Move to an archived project - issue.project = Project.find(2) - assert issue.save - assert_equal before, Issue.on_active_project.length - end - - context "Issue#recipients" do - setup do - @project = Project.find(1) - @author = User.generate_with_protected! - @assignee = User.generate_with_protected! - @issue = Issue.generate_for_project!(@project, :assigned_to => @assignee, :author => @author) - end - - should "include project recipients" do - assert @project.recipients.present? - @project.recipients.each do |project_recipient| - assert @issue.recipients.include?(project_recipient) - end - end - - should "include the author if the author is active" do - assert @issue.author, "No author set for Issue" - assert @issue.recipients.include?(@issue.author.mail) - end - - should "include the assigned to user if the assigned to user is active" do - assert @issue.assigned_to, "No assigned_to set for Issue" - assert @issue.recipients.include?(@issue.assigned_to.mail) - end - - should "not include users who opt out of all email" do - @author.update_attribute(:mail_notification, :none) - - assert !@issue.recipients.include?(@issue.author.mail) - end - - should "not include the issue author if they are only notified of assigned issues" do - @author.update_attribute(:mail_notification, :only_assigned) - - assert !@issue.recipients.include?(@issue.author.mail) - end - - should "not include the assigned user if they are only notified of owned issues" do - @assignee.update_attribute(:mail_notification, :only_owner) - - assert !@issue.recipients.include?(@issue.assigned_to.mail) - end - - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/72/72b134a74f37265288938527276f0807a1362abf.svn-base --- a/.svn/pristine/72/72b134a74f37265288938527276f0807a1362abf.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,6 +0,0 @@ -

    <%= link_to l(:label_issue_status_plural), issue_statuses_path %> » <%=l(:label_issue_status_new)%>

    - -<% form_for @issue_status, :builder => TabularFormBuilder do |f| %> - <%= render :partial => 'form', :locals => {:f => f} %> - <%= submit_tag l(:button_create) %> -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/72/72f0350ace5f91c2452fe68fefaac12651ddb55a.svn-base --- a/.svn/pristine/72/72f0350ace5f91c2452fe68fefaac12651ddb55a.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,6 +0,0 @@ -

    <%= l(:label_query) %>

    - -<% form_tag(query_path(@query), :onsubmit => 'selectAllOptions("selected_columns");', :method => :put) do %> - <%= render :partial => 'form', :locals => {:query => @query} %> - <%= submit_tag l(:button_save) %> -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/72/72fa979a7098a778692a53bd6065af66a4a95005.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/72/72fa979a7098a778692a53bd6065af66a4a95005.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,1089 @@ +mk: + # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl) + direction: ltr + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%d/%m/%Y" + short: "%d %b" + long: "%d %B, %Y" + + day_names: [недела, понеделник, вторник, Ñреда, четврток, петок, Ñабота] + abbr_day_names: [нед, пон, вто, Ñре, чет, пет, Ñаб] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, јануари, февруари, март, април, мај, јуни, јули, авгуÑÑ‚, Ñептември, октомври, ноември, декември] + abbr_month_names: [~, јан, фев, мар, апр, мај, јун, јул, авг, Ñеп, окт, ное, дек] + # Used in date_select and datime_select. + order: + - :day + - :month + - :year + + time: + formats: + default: "%d/%m/%Y %H:%M" + time: "%H:%M" + short: "%d %b %H:%M" + long: "%d %B, %Y %H:%M" + am: "предпладне" + pm: "попладне" + + datetime: + distance_in_words: + half_a_minute: "пола минута" + less_than_x_seconds: + one: "помалку од 1 Ñекунда" + other: "помалку од %{count} Ñекунди" + x_seconds: + one: "1 Ñекунда" + other: "%{count} Ñекунди" + less_than_x_minutes: + one: "помалку од 1 минута" + other: "помалку од %{count} минути" + x_minutes: + one: "1 минута" + other: "%{count} минути" + about_x_hours: + one: "околу 1 чаÑ" + other: "околу %{count} чаÑа" + x_hours: + one: "1 чаÑ" + other: "%{count} чаÑа" + x_days: + one: "1 ден" + other: "%{count} дена" + about_x_months: + one: "околу 1 меÑец" + other: "околу %{count} меÑеци" + x_months: + one: "1 меÑец" + other: "%{count} меÑеци" + about_x_years: + one: "околу 1 година" + other: "околу %{count} години" + over_x_years: + one: "преку 1 година" + other: "преку %{count} години" + almost_x_years: + one: "Ñкоро 1 година" + other: "Ñкоро %{count} години" + + number: + # Default format for numbers + format: + separator: "." + delimiter: "" + precision: 3 + human: + format: + delimiter: "" + precision: 3 + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + + +# Used in array.to_sentence. + support: + array: + sentence_connector: "и" + skip_last_comma: false + + activerecord: + errors: + template: + header: + one: "1 error prohibited this %{model} from being saved" + other: "%{count} errors prohibited this %{model} from being saved" + messages: + inclusion: "не е вклучено во лиÑтата" + exclusion: "е резервирано" + invalid: "е невалидно" + confirmation: "не Ñе Ñовпаѓа Ñо потврдата" + accepted: "мора да е прифатено" + empty: "неможе да е празно" + blank: "неможе да е празно" + too_long: "е предолго (макÑ. %{count} знаци)" + too_short: "е прекратко (мин. %{count} знаци)" + wrong_length: "е погрешна должина (треба да е %{count} знаци)" + taken: "е веќе зафатено" + not_a_number: "не е број" + not_a_date: "не е валидна дата" + greater_than: "мора да е поголемо од %{count}" + greater_than_or_equal_to: "мора да е поголемо или еднакво на %{count}" + equal_to: "мора да е еднакво на %{count}" + less_than: "мора да е помало од %{count}" + less_than_or_equal_to: "мора да е помало или еднакво на %{count}" + odd: "мора да е непарно" + even: "мора да е парно" + greater_than_start_date: "мора да е поголема од почетната дата" + not_same_project: "не припаѓа на иÑтиот проект" + circular_dependency: "Оваа врÑка ќе креира кружна завиÑноÑÑ‚" + cant_link_an_issue_with_a_descendant: "Задача неможе да Ñе поврзе Ñо една од нејзините подзадачи" + + actionview_instancetag_blank_option: Изберете + + general_text_No: 'Ðе' + general_text_Yes: 'Да' + general_text_no: 'не' + general_text_yes: 'да' + general_lang_name: 'Macedonian (МакедонÑки)' + general_csv_separator: ',' + general_csv_decimal_separator: '.' + general_csv_encoding: UTF-8 + general_pdf_encoding: UTF-8 + 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: This account uses an external authentication source. Impossible to change the password. + notice_account_lost_email_sent: An email with instructions to choose a new password has been sent to you. + notice_account_activated: Your account has been activated. You can now log in. + notice_successful_create: УÑпешно креирање. + notice_successful_update: УÑпешно ажурирање. + notice_successful_delete: УÑпешно бришење. + notice_successful_connection: УÑпешна конекција. + notice_file_not_found: The page you were trying to access doesn't exist or has been removed. + notice_locking_conflict: Data has been updated by another user. + notice_not_authorized: You are not authorized to access this page. + notice_email_sent: "Е-порака е пратена на %{value}" + notice_email_error: "Се Ñлучи грешка при праќање на е-пораката (%{value})" + notice_feeds_access_key_reseted: Вашиот RSS клуч за приÑтап е reset. + notice_api_access_key_reseted: Вашиот API клуч за приÑтап е reset. + notice_failed_to_save_issues: "Failed to save %{count} issue(s) on %{total} selected: %{ids}." + notice_failed_to_save_members: "Failed to save member(s): %{errors}." + notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit." + notice_account_pending: "Your account was created and is now pending administrator approval." + notice_default_data_loaded: Default configuration successfully loaded. + notice_unable_delete_version: Unable to delete version. + notice_unable_delete_time_entry: Unable to delete time log entry. + notice_issue_done_ratios_updated: Issue done ratios updated. + + error_can_t_load_default_data: "Default configuration could not be loaded: %{value}" + 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: %{value}" + 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' + error_no_tracker_in_project: 'No tracker is associated to this project. Please check the Project settings.' + error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").' + error_can_not_delete_custom_field: Unable to delete custom field + error_can_not_delete_tracker: "This tracker contains issues and can't be deleted." + error_can_not_remove_role: "This role is in use and can not be deleted." + error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version can not be reopened' + error_can_not_archive_project: This project can not be archived + error_issue_done_ratios_not_updated: "Issue done ratios not updated." + error_workflow_copy_source: 'Please select a source tracker or role' + error_workflow_copy_target: 'Please select target tracker(s) and role(s)' + error_unable_delete_issue_status: 'Unable to delete issue status' + error_unable_to_connect: "Unable to connect (%{value})" + warning_attachments_not_saved: "%{count} file(s) could not be saved." + + mail_subject_lost_password: "Вашата %{value} лозинка" + mail_body_lost_password: 'To change your password, click on the following link:' + mail_subject_register: "Your %{value} account activation" + mail_body_register: 'To activate your account, click on the following link:' + mail_body_account_information_external: "You can use your %{value} account to log in." + mail_body_account_information: Your account information + mail_subject_account_activation_request: "%{value} account activation request" + mail_body_account_activation_request: "Ðов кориÑник (%{value}) е региÑтриран. The account is pending your approval:" + mail_subject_reminder: "%{count} issue(s) due in the next %{days} days" + mail_body_reminder: "%{count} issue(s) that are assigned to you are due in the next %{days} days:" + mail_subject_wiki_content_added: "'%{id}' wiki page has been added" + mail_body_wiki_content_added: "The '%{id}' wiki page has been added by %{author}." + mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated" + mail_body_wiki_content_updated: "The '%{id}' wiki page has been updated by %{author}." + + + 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: Default value + field_tracker: Tracker + field_subject: ÐаÑлов + field_due_date: Краен рок + field_assigned_to: Доделена на + field_priority: Приоритет + field_fixed_version: Target version + field_user: КориÑник + field_principal: Principal + field_role: Улога + field_homepage: Веб Ñтрана + field_is_public: Јавен + field_parent: Подпроект на + field_is_in_roadmap: Issues displayed in roadmap + field_login: КориÑник + field_mail_notification: ИзвеÑтувања по e-пошта + 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) креирање на кориÑници + field_start_date: Почеток + field_done_ratio: "% Завршено" + field_auth_source: Режим на автентикација + field_hide_mail: Криј ја мојата адреÑа на е-пошта + field_comments: Коментар + field_url: URL + field_start_page: Почетна Ñтрана + field_subproject: Подпроект + field_hours: ЧаÑови + field_activity: ÐктивноÑÑ‚ + field_spent_on: Дата + field_identifier: Идентификатор + field_is_filter: КориÑти како филтер + field_issue_to: Поврзана задача + field_delay: Доцнење + field_assignable: Ðа оваа улога може да Ñе доделуваат задачи + field_redirect_existing_links: ПренаÑочи ги поÑтоечките врÑки + field_estimated_hours: Проценето време + field_column_names: Колони + field_time_entries: Бележи време + field_time_zone: ВременÑка зона + field_searchable: Може да Ñе пребарува + field_default_value: Default value + field_comments_sorting: Прикажувај коментари + field_parent_title: Parent page + field_editable: Може да Ñе уредува + field_watcher: Watcher + field_identity_url: OpenID URL + field_content: Содржина + field_group_by: Групирај ги резултатите Ñпоред + field_sharing: Споделување + field_parent_issue: Parent task + + setting_app_title: ÐаÑлов на апликацијата + setting_app_subtitle: ПоднаÑлов на апликацијата + setting_welcome_text: ТекÑÑ‚ за добредојде + setting_default_language: Default јазик + setting_login_required: Задолжителна автентикација + setting_self_registration: Само-региÑтрација + setting_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_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: Дозволи релации на задачи меѓу проекти + setting_issue_list_default_columns: Default columns displayed on the issue list + setting_protocol: Протокол + setting_per_page_options: Objects per page options + setting_user_format: Приказ на кориÑниците + setting_activity_days_default: Денови прикажана во активноÑта на проектот + setting_display_subprojects_issues: Прикажи ги задачите на подпроектите во главните проекти + setting_enabled_scm: Овозможи SCM + setting_mail_handler_body_delimiters: "Truncate emails after one of these lines" + 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_gravatar_default: Default Gravatar image + setting_diff_max_lines_displayed: Max number of diff lines displayed + setting_file_max_size_displayed: Max size of text files displayed inline + setting_repository_log_display_limit: Maximum number of revisions displayed on file log + setting_openid: Дозволи OpenID најава и региÑтрација + setting_password_min_length: Мин. должина на лозинка + setting_new_project_user_role_id: Улога доделена на неадминиÑтраторÑки кориÑник кој креира проект + setting_default_projects_modules: Default enabled modules for new projects + setting_issue_done_ratio: Calculate the issue done ratio with + setting_issue_done_ratio_issue_field: Use the issue field + setting_issue_done_ratio_issue_status: Use the issue status + setting_start_of_week: Start calendars on + setting_rest_api_enabled: Enable REST web service + setting_cache_formatted_text: Cache formatted text + + permission_add_project: Креирај проекти + permission_add_subprojects: Креирај подпроекти + permission_edit_project: Уреди проект + permission_select_project_modules: Изберете модули за проект + permission_manage_members: Manage members + permission_manage_project_activities: Manage project activities + permission_manage_versions: Manage versions + permission_manage_categories: Manage issue categories + permission_view_issues: Прегледај задачи + 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: View calendar + permission_view_issue_watchers: View watchers list + permission_add_issue_watchers: Add watchers + permission_delete_issue_watchers: Delete watchers + permission_log_time: Бележи потрошено време + permission_view_time_entries: Прегледај потрошено време + permission_edit_time_entries: Уредувај белешки за потрошено време + permission_edit_own_time_entries: Уредувај ÑопÑтвени белешки за потрошено време + permission_manage_news: Manage news + permission_comment_news: Коментирај на веÑти + permission_view_documents: Прегледувај документи + permission_manage_files: Manage files + permission_view_files: Прегледувај датотеки + permission_manage_wiki: 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: Заштитувај вики Ñтраници + 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: Уредувај пораки + permission_edit_own_messages: Уредувај ÑопÑтвени пораки + permission_delete_messages: Бриши пораки + permission_delete_own_messages: Бриши ÑопÑтвени пораки + permission_export_wiki_pages: Export wiki pages + permission_manage_subtasks: Manage subtasks + + 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: Форуми + project_module_calendar: Календар + project_module_gantt: Gantt + + label_user: КориÑник + label_user_plural: КориÑници + label_user_new: Ðов кориÑник + label_user_anonymous: Ðнонимен + label_project: Проект + label_project_new: Ðов проект + label_project_plural: Проекти + label_x_projects: + zero: нема проекти + one: 1 проект + other: "%{count} проекти" + label_project_all: Сите проекти + label_project_latest: ПоÑледните проекти + label_issue: Задача + label_issue_new: Ðова задача + label_issue_plural: Задачи + label_issue_view_all: Прегледај ги Ñите задачи + label_issues_by: "Задачи по %{value}" + 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: Прилагодено поле + label_custom_field_plural: Прилагодени полиња + label_custom_field_new: Ðово прилагодено поле + label_enumerations: Enumerations + label_enumeration_new: Ðова вредноÑÑ‚ + label_information: Информација + label_information_plural: Информации + label_please_login: Ðајави Ñе + label_register: РегиÑтрирај Ñе + label_login_with_open_id_option: или најави Ñе Ñо OpenID + label_password_lost: Изгубена лозинка + label_home: Почетна + label_my_page: Мојата Ñтрана + label_my_account: Мојот профил + label_my_projects: Мои проекти + label_my_page_block: Блок елемент + label_administration: ÐдминиÑтрација + label_login: Ðајави Ñе + label_logout: Одјави Ñе + label_help: Помош + label_reported_issues: Пријавени задачи + label_assigned_to_me_issues: Задачи доделени на мене + label_last_login: ПоÑледна најава + label_registered_on: РегиÑтриран на + label_activity: ÐктивноÑÑ‚ + label_overall_activity: Севкупна активноÑÑ‚ + label_user_activity: "ÐктивноÑÑ‚ на %{value}" + label_new: Ðова + label_logged_as: Ðајавени Ñте како + label_environment: Опкружување + label_authentication: Ðвтентикација + label_auth_source: Режим на автентикација + label_auth_source_new: Ðов режим на автентикација + label_auth_source_plural: Режими на автентикација + label_subproject_plural: Подпроекти + label_subproject_new: Ðов подпроект + label_and_its_subprojects: "%{value} и неговите подпроекти" + label_min_max_length: Мин. - МакÑ. должина + label_list: ЛиÑта + label_date: Дата + label_integer: Integer + label_float: Float + label_boolean: Boolean + label_string: ТекÑÑ‚ + label_text: Долг текÑÑ‚ + label_attribute: Ðтрибут + label_attribute_plural: Ðтрибути + 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_settings: Settings + label_overview: Преглед + label_version: Верзија + label_version_new: Ðова верзија + label_version_plural: Верзии + label_close_versions: Затвори ги завршените врзии + 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_x_open_issues_abbr_on_total: + zero: 0 отворени / %{total} + one: 1 отворена / %{total} + other: "%{count} отворени / %{total}" + label_x_open_issues_abbr: + zero: 0 отворени + one: 1 отворена + other: "%{count} отворени" + label_x_closed_issues_abbr: + zero: 0 затворени + one: 1 затворена + other: "%{count} затворени" + 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: Internal + label_last_changes: "поÑледни %{count} промени" + label_change_view_all: Прегледај ги Ñите промени + label_personalize_page: Прилагоди ја Ñтранава + label_comment: Коментар + label_comment_plural: Коментари + label_x_comments: + zero: нема коментари + one: 1 коментар + other: "%{count} коментари" + 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_greater_or_equal: '>=' + label_less_or_equal: '<=' + label_in: во + label_today: Ð´ÐµÐ½ÐµÑ + label_all_time: цело време + label_yesterday: вчера + label_this_week: оваа недела + label_last_week: минатата недела + label_last_n_days: "поÑледните %{count} дена" + 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: Складиште + label_repository_plural: Складишта + label_browse: ПрелиÑтувај + label_branch: Гранка + label_tag: Tag + label_revision: Ревизија + label_revision_plural: Ревизии + label_revision_id: "Ревизија %{value}" + label_associated_revisions: Associated revisions + label_added: added + label_modified: modified + label_copied: copied + label_renamed: renamed + label_deleted: deleted + label_latest_revision: ПоÑледна ревизија + label_latest_revision_plural: ПоÑледни ревизии + label_view_revisions: Прегледај ги ревизиите + label_view_all_revisions: Прегледај ги Ñите ревизии + label_max_size: МакÑ. големина + label_sort_highest: ПремеÑти најгоре + label_sort_higher: ПремеÑти нагоре + label_sort_lower: ПремеÑти надоле + label_sort_lowest: ПремеÑти најдоле + label_roadmap: Roadmap + label_roadmap_due_in: "Due in %{value}" + label_roadmap_overdue: "КаÑни %{value}" + 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: Ð˜Ð½Ð´ÐµÐºÑ Ð¿Ð¾ наÑлов + label_index_by_date: Ð˜Ð½Ð´ÐµÐºÑ Ð¿Ð¾ дата + label_current_version: Current version + label_preview: Preview + label_feed_plural: Feeds + label_changes_details: Детали за Ñите промени + label_issue_tracking: Следење на задачи + label_spent_time: Потрошено време + label_overall_spent_time: Вкупно потрошено време + label_f_hour: "%{value} чаÑ" + label_f_hour_plural: "%{value} чаÑа" + 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: View differences + label_diff_inline: inline + label_diff_side_by_side: side by side + label_options: Опции + label_copy_workflow_from: Copy workflow from + label_permissions_report: Permissions report + label_watched_issues: Watched issues + label_related_issues: Поврзани задачи + label_applied_status: Applied status + label_loading: Loading... + label_relation_new: Ðова релација + label_relation_delete: Избриши релација + label_relates_to: related to + label_duplicates: дупликати + label_duplicated_by: duplicated by + label_blocks: blocks + label_blocked_by: блокирано од + label_precedes: претходи + label_follows: Ñледи + label_end_to_start: крај до почеток + label_end_to_end: крај до крај + label_start_to_start: почеток до почеток + label_start_to_end: почеток до крај + label_stay_logged_in: ОÑтанете најавени + label_disabled: disabled + label_show_completed_versions: Show completed versions + label_me: Ñ˜Ð°Ñ + label_board: Форум + label_board_new: Ðов форум + label_board_plural: Форуми + label_board_locked: Заклучен + label_board_sticky: Sticky + label_topic_plural: Теми + label_message_plural: Пораки + label_message_last: ПоÑледна порака + label_message_new: Ðова порака + label_message_posted: Поракате е додадена + label_reply_plural: Одговори + label_send_information: ИÑпрати ги информациите за профилот на кориÑникот + label_year: Година + label_month: МеÑец + label_week: Ðедела + label_date_from: Од + label_date_to: До + label_language_based: Според јазикот на кориÑникот + label_sort_by: "Подреди Ñпоред %{value}" + label_send_test_email: ИÑпрати теÑÑ‚ е-порака + label_feeds_access_key: RSS клуч за приÑтап + label_missing_feeds_access_key: ÐедоÑтика RSS клуч за приÑтап + label_feeds_access_key_created_on: "RSS клучот за приÑтап креиран пред %{value}" + label_module_plural: Модули + label_added_time_by: "Додадено од %{author} пред %{age}" + label_updated_time_by: "Ðжурирано од %{author} пред %{age}" + label_updated_time: "Ðжурирано пред %{value}" + 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: Default + label_search_titles_only: Пребарувај Ñамо наÑлови + label_user_mail_option_all: "За било кој наÑтан во Ñите мои проекти" + label_user_mail_option_selected: "За било кој наÑтан Ñамо во избраните проекти..." + label_user_mail_no_self_notified: "Ðе ме извеÑтувај за промените што Ñ˜Ð°Ñ Ð³Ð¸ правам" + label_registration_activation_by_email: активација на профил преку е-пошта + label_registration_manual_activation: мануелна активација на профил + label_registration_automatic_activation: автоматÑка активација на профил + label_display_per_page: "По Ñтрана: %{value}" + label_age: Age + label_change_properties: 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: Preferences + label_chronological_order: Во хронолошки ред + label_reverse_chronological_order: In reverse chronological order + label_planning: Планирање + label_incoming_emails: Дојдовни е-пораки + label_generate_key: Генерирај клуч + label_issue_watchers: Watchers + label_example: Пример + label_display: Прикажи + label_sort: Подреди + label_ascending: РаÑтечки + label_descending: Опаѓачки + label_date_from_to: Од %{start} до %{end} + label_wiki_content_added: Вики Ñтраница додадена + label_wiki_content_updated: Вики Ñтраница ажурирана + label_group: Група + label_group_plural: Групи + label_group_new: Ðова група + label_time_entry_plural: Потрошено време + label_version_sharing_none: Ðе Ñподелено + label_version_sharing_descendants: Со Ñите подпроекти + label_version_sharing_hierarchy: Со хиерархијата на проектот + label_version_sharing_tree: Со дрвото на проектот + label_version_sharing_system: Со Ñите проекти + label_update_issue_done_ratios: Update issue done ratios + label_copy_source: Извор + label_copy_target: ДеÑтинација + label_copy_same_as_target: ИÑто како деÑтинацијата + label_display_used_statuses_only: Only display statuses that are used by this tracker + label_api_access_key: API клуч за приÑтап + label_missing_api_access_key: ÐедоÑтига API клуч за приÑтап + label_api_access_key_created_on: "API клучот за приÑтап е креиран пред %{value}" + label_profile: Профил + label_subtask_plural: Подзадачи + label_project_copy_notifications: Праќај извеÑтувања по е-пошта при копирање на проект + + button_login: Ðајави Ñе + button_submit: ИÑпрати + button_save: Зачувај + button_check_all: Штиклирај ги Ñите + button_uncheck_all: Одштиклирај ги Ñите + button_delete: Избриши + button_create: Креирај + button_create_and_continue: Креирај и продолжи + button_test: ТеÑÑ‚ + button_edit: Уреди + button_add: Додади + button_change: Промени + button_apply: Примени + button_clear: Избриши + button_lock: Заклучи + button_unlock: Отклучи + button_download: Превземи + button_list: List + button_view: Прегледај + button_move: ПремеÑти + button_move_and_follow: ПремеÑти и Ñледи + button_back: Back + button_cancel: Откажи + button_activate: Ðктивирај + button_sort: Подреди + button_log_time: Бележи време + button_rollback: Rollback to this version + button_watch: Следи + button_unwatch: Ðе Ñледи + button_reply: Одговори + button_archive: Ðрхивирај + button_unarchive: Одархивирај + button_reset: Reset + button_rename: Преименувај + button_change_password: Промени лозинка + button_copy: Копирај + button_copy_and_follow: Копирај и Ñледи + button_annotate: Annotate + button_update: Ðжурирај + button_configure: Конфигурирај + button_quote: Цитирај + button_duplicate: Копирај + button_show: Show + + status_active: активни + status_registered: региÑтрирани + status_locked: заклучени + + version_status_open: отворени + version_status_locked: заклучени + version_status_closed: затворени + + field_active: Active + + text_select_mail_notifications: Изберете за кои наÑтани да Ñе праќаат извеÑтувања по е-пошта да Ñе праќаат. + text_regexp_info: eg. ^[A-Z0-9]+$ + text_min_max_length_info: 0 значи без ограничување + text_project_destroy_confirmation: Дали Ñте Ñигурни дека Ñакате да го избришете проектот и Ñите поврзани податоци? + text_subprojects_destroy_warning: "Ðеговите подпроекти: %{value} иÑто така ќе бидат избришани." + text_workflow_edit: Select a role and a tracker to edit the workflow + text_are_you_sure: Дали Ñте Ñигурни? + text_journal_changed: "%{label} променето од %{old} во %{new}" + text_journal_set_to: "%{label} set to %{value}" + text_journal_deleted: "%{label} избришан (%{old})" + text_journal_added: "%{label} %{value} додаден" + text_tip_issue_begin_day: задачи што почнуваат овој ден + text_tip_issue_end_day: задачи што завршуваат овој ден + text_tip_issue_begin_end_day: задачи што почнуваат и завршуваат овој ден + text_caracters_maximum: "%{count} знаци макÑимум." + text_caracters_minimum: "Мора да е најмалку %{count} знаци долго." + text_length_between: "Должина помеѓу %{min} и %{max} знаци." + text_tracker_no_workflow: No workflow defined for this tracker + text_unallowed_characters: Ðедозволени знаци + text_comma_separated: Дозволени Ñе повеќе вредноÑти (разделени Ñо запирка). + text_line_separated: Дозволени Ñе повеќе вредноÑти (една линија за Ñекоја вредноÑÑ‚). + text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages + text_issue_added: "Задачата %{id} е пријавена од %{author}." + text_issue_updated: "Задачата %{id} е ажурирана од %{author}." + text_wiki_destroy_confirmation: Дали Ñте Ñигурни дека Ñакате да го избришете ова вики и целата негова Ñодржина? + text_issue_category_destroy_question: "Ðекои задачи (%{count}) Ñе доделени на оваа категорија. Што Ñакате да правите?" + text_issue_category_destroy_assignments: Remove category assignments + text_issue_category_reassign_to: Додели ги задачите на оваа категорија + text_user_mail_option: "For unselected projects, you will only receive notifications about things you watch or you're involved in (eg. issues you're the author or assignee)." + text_no_configuration_data: "Roles, trackers, issue statuses and workflow have not been configured yet.\nIt is highly recommended to load the default configuration. You will be able to modify it once loaded." + text_load_default_configuration: Load the default configuration + text_status_changed_by_changeset: "Applied in changeset %{value}." + text_issues_destroy_confirmation: 'Дали Ñте Ñигурни дека Ñакате да ги избришете избраните задачи?' + text_select_project_modules: 'Изберете модули за овој проект:' + text_default_administrator_account_changed: Default administrator account changed + text_file_repository_writable: Во папката за прилози може да Ñе запишува + text_plugin_assets_writable: Во папката за додатоци може да Ñе запишува + text_rmagick_available: RMagick available (незадолжително) + text_destroy_time_entries_question: "%{hours} 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: Додели ги пријавените чаÑови на проектот + text_reassign_time_entries: 'Reassign reported hours to this issue:' + text_user_wrote: "%{value} напиша:" + text_enumeration_destroy_question: "%{count} objects are assigned to this value." + text_enumeration_category_reassign_to: 'Reassign them to this value:' + text_email_delivery_not_configured: "ДоÑтавата по е-пошта не е конфигурирана, и извеÑтувањата Ñе оневозможени.\nКонфигурирајте го Вашиот SMTP Ñервер во config/configuration.yml и реÑтартирајте ја апликацијата." + 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' + text_wiki_page_destroy_question: "This page has %{descendants} child page(s) and descendant(s). What do you want to do?" + text_wiki_page_nullify_children: "Keep child pages as root pages" + text_wiki_page_destroy_children: "Delete child pages and all their descendants" + text_wiki_page_reassign_children: "Reassign child pages to this parent page" + text_own_membership_delete_confirmation: "You are about to remove some or all of your permissions and may no longer be able to edit this project after that.\nAre you sure you want to continue?" + text_zoom_in: Zoom in + text_zoom_out: Zoom out + + default_role_manager: Менаџер + default_role_developer: Developer + default_role_reporter: Reporter + default_tracker_bug: Грешка + default_tracker_feature: ФункционалноÑÑ‚ + default_tracker_support: Поддршка + default_issue_status_new: Ðова + default_issue_status_in_progress: Во Ð¿Ñ€Ð¾Ð³Ñ€ÐµÑ + 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: ÐктивноÑти (Ñледење на време) + enumeration_system_activity: СиÑтемÑка активноÑÑ‚ + + button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" + field_text: Text field + label_user_mail_option_only_owner: Only for things I am the owner of + setting_default_notification_option: Default notification option + label_user_mail_option_only_my_events: Only for things I watch or I'm involved in + label_user_mail_option_only_assigned: Only for things I am assigned to + label_user_mail_option_none: No events + field_member_of_group: Assignee's group + field_assigned_to_role: Assignee's role + notice_not_authorized_archived_project: The project you're trying to access has been archived. + label_principal_search: "Search for user or group:" + label_user_search: "Search for user:" + field_visible: Visible + setting_commit_logtime_activity_id: Activity for logged time + text_time_logged_by_changeset: Applied in changeset %{value}. + setting_commit_logtime_enabled: Enable time logging + notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) + setting_gantt_items_limit: Maximum number of items displayed on the gantt chart + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. + label_my_queries: My custom queries + text_journal_changed_no_detail: "%{label} updated" + label_news_comment_added: Comment added to a news + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + label_bulk_edit_selected_time_entries: Bulk edit selected time entries + text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? + label_role_anonymous: Anonymous + label_role_non_member: Non member + label_issue_note_added: Note added + label_issue_status_updated: Status updated + label_issue_priority_updated: Priority updated + label_issues_visibility_own: Issues created by or assigned to the user + field_issues_visibility: Issues visibility + label_issues_visibility_all: All issues + permission_set_own_issues_private: Set own issues public or private + field_is_private: Private + permission_set_issues_private: Set issues public or private + label_issues_visibility_public: All non private issues + text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). + field_commit_logs_encoding: Commit messages encoding + field_scm_path_encoding: Path encoding + text_scm_path_encoding_note: "Default: UTF-8" + field_path_to_repository: Path to repository + field_root_directory: Root directory + field_cvs_module: Module + field_cvsroot: CVSROOT + text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) + text_scm_command: Command + text_scm_command_version: Version + label_git_report_last_commit: Report last commit for files and directories + notice_issue_successful_create: Issue %{id} created. + label_between: between + setting_issue_group_assignment: Allow issue assignment to groups + label_diff: diff + text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) + description_query_sort_criteria_direction: Sort direction + description_project_scope: Search scope + description_filter: Filter + description_user_mail_notification: Mail notification settings + description_date_from: Enter start date + description_message_content: Message content + description_available_columns: Available Columns + description_date_range_interval: Choose range by selecting start and end date + description_issue_category_reassign: Choose issue category + description_search: Searchfield + description_notes: Notes + description_date_range_list: Choose range from list + description_choose_project: Projects + description_date_to: Enter end date + description_query_sort_criteria_attribute: Sort attribute + description_wiki_subpages_reassign: Choose new parent page + description_selected_columns: Selected Columns + label_parent_revision: Parent + label_child_revision: Child + error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. + setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues + button_edit_section: Edit this section + setting_repositories_encodings: Attachments and repositories encodings + description_all_columns: All Columns + button_export: Export + label_export_options: "%{export_format} export options" + error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) + notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." + label_x_issues: + zero: 0 Задача + one: 1 Задача + other: "%{count} Задачи" + label_repository_new: New repository + field_repository_is_default: Main repository + label_copy_attachments: Copy attachments + label_item_position: "%{position}/%{count}" + label_completed_versions: Completed versions + text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. + field_multiple: Multiple values + setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed + text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes + text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) + notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. + text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} + permission_manage_related_issues: Manage related issues + field_auth_source_ldap_filter: LDAP filter + label_search_for_watchers: Search for watchers to add + notice_account_deleted: Your account has been permanently deleted. + setting_unsubscribe: Allow users to delete their own account + button_delete_my_account: Delete my account + text_account_destroy_confirmation: |- + Are you sure you want to proceed? + Your account will be permanently deleted, with no way to reactivate it. + error_session_expired: Your session has expired. Please login again. + text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." + setting_session_lifetime: Session maximum lifetime + setting_session_timeout: Session inactivity timeout + label_session_expiration: Session expiration + permission_close_project: Close / reopen the project + label_show_closed_projects: View closed projects + button_close: Close + button_reopen: Reopen + project_status_active: active + project_status_closed: closed + project_status_archived: archived + text_project_closed: This project is closed and read-only. + notice_user_successful_create: User %{id} created. + field_core_fields: Standard fields + field_timeout: Timeout (in seconds) + setting_thumbnails_enabled: Display attachment thumbnails + setting_thumbnails_size: Thumbnails size (in pixels) + label_status_transitions: Status transitions + label_fields_permissions: Fields permissions + label_readonly: Read-only + label_required: Required + text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. + field_board_parent: Parent forum + label_attribute_of_project: Project's %{name} + label_attribute_of_author: Author's %{name} + label_attribute_of_assigned_to: Assignee's %{name} + label_attribute_of_fixed_version: Target version's %{name} + label_copy_subtasks: Copy subtasks + label_copied_to: copied to + label_copied_from: copied from + label_any_issues_in_project: any issues in project + label_any_issues_not_in_project: any issues not in project + field_private_notes: Private notes + permission_view_private_notes: View private notes + permission_set_notes_private: Set notes as private + label_no_issues_in_project: no issues in project + label_any: Ñите + label_last_n_weeks: last %{count} weeks + setting_cross_project_subtasks: Allow cross-project subtasks + label_cross_project_descendants: Со Ñите подпроекти + label_cross_project_tree: Со дрвото на проектот + label_cross_project_hierarchy: Со хиерархијата на проектот + label_cross_project_system: Со Ñите проекти + button_hide: Hide + setting_non_working_week_days: Non-working days + label_in_the_next_days: in the next + label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Вкупно + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_footer: Email footer + setting_emails_header: Email header diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/73/730cecaf2c14ad4ffdebffd122755f219888a9de.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/73/730cecaf2c14ad4ffdebffd122755f219888a9de.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,1093 @@ +# Update to 2.2 by Karel Picman +# Update to 1.1 by Michal Gebauer +# Updated by Josef LiÅ¡ka +# CZ translation by Maxim KruÅ¡ina | Massimo Filippi, s.r.o. | maxim@mxm.cz +# Based on original CZ translation by Jan KadleÄek +cs: + # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl) + direction: ltr + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%Y-%m-%d" + short: "%b %d" + long: "%B %d, %Y" + + day_names: [NedÄ›le, PondÄ›lí, Úterý, StÅ™eda, ÄŒtvrtek, Pátek, Sobota] + abbr_day_names: [Ne, Po, Út, St, ÄŒt, Pá, So] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, Leden, Únor, BÅ™ezen, Duben, KvÄ›ten, ÄŒerven, ÄŒervenec, Srpen, Září, Říjen, Listopad, Prosinec] + abbr_month_names: [~, Led, Úno, BÅ™e, Dub, KvÄ›, ÄŒer, ÄŒec, Srp, Zář, Říj, Lis, Pro] + # Used in date_select and datime_select. + order: + - :year + - :month + - :day + + time: + formats: + default: "%a, %d %b %Y %H:%M:%S %z" + time: "%H:%M" + short: "%d %b %H:%M" + long: "%B %d, %Y %H:%M" + am: "dop." + pm: "odp." + + datetime: + distance_in_words: + half_a_minute: "půl minuty" + less_than_x_seconds: + one: "ménÄ› než sekunda" + other: "ménÄ› než %{count} sekund" + x_seconds: + one: "1 sekunda" + other: "%{count} sekund" + less_than_x_minutes: + one: "ménÄ› než minuta" + other: "ménÄ› než %{count} minut" + x_minutes: + one: "1 minuta" + other: "%{count} minut" + about_x_hours: + one: "asi 1 hodina" + other: "asi %{count} hodin" + x_hours: + one: "1 hodina" + other: "%{count} hodin" + x_days: + one: "1 den" + other: "%{count} dnů" + about_x_months: + one: "asi 1 mÄ›síc" + other: "asi %{count} mÄ›síců" + x_months: + one: "1 mÄ›síc" + other: "%{count} mÄ›síců" + about_x_years: + one: "asi 1 rok" + other: "asi %{count} let" + over_x_years: + one: "více než 1 rok" + other: "více než %{count} roky" + almost_x_years: + one: "témeÅ™ 1 rok" + other: "téměř %{count} roky" + + number: + # Výchozí formát pro Äísla + format: + separator: "." + delimiter: "" + precision: 3 + human: + format: + delimiter: "" + precision: 3 + storage_units: + format: "%n %u" + units: + byte: + one: "Bajt" + other: "Bajtů" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + +# Used in array.to_sentence. + support: + array: + sentence_connector: "a" + skip_last_comma: false + + activerecord: + errors: + template: + header: + one: "1 chyba zabránila uložení %{model}" + other: "%{count} chyb zabránilo uložení %{model}" + messages: + inclusion: "není zahrnuto v seznamu" + exclusion: "je rezervováno" + invalid: "je neplatné" + confirmation: "se neshoduje s potvrzením" + accepted: "musí být akceptováno" + empty: "nemůže být prázdný" + blank: "nemůže být prázdný" + too_long: "je příliÅ¡ dlouhý" + too_short: "je příliÅ¡ krátký" + wrong_length: "má chybnou délku" + taken: "je již použito" + not_a_number: "není Äíslo" + not_a_date: "není platné datum" + greater_than: "musí být vÄ›tší než %{count}" + greater_than_or_equal_to: "musí být vÄ›tší nebo rovno %{count}" + equal_to: "musí být pÅ™esnÄ› %{count}" + less_than: "musí být ménÄ› než %{count}" + less_than_or_equal_to: "musí být ménÄ› nebo rovno %{count}" + odd: "musí být liché" + even: "musí být sudé" + greater_than_start_date: "musí být vÄ›tší než poÄáteÄní datum" + not_same_project: "nepatří stejnému projektu" + circular_dependency: "Tento vztah by vytvoÅ™il cyklickou závislost" + cant_link_an_issue_with_a_descendant: "Úkol nemůže být spojen s jedním z jeho dílÄích úkolů" + + actionview_instancetag_blank_option: Prosím vyberte + + general_text_No: 'Ne' + general_text_Yes: 'Ano' + general_text_no: 'ne' + general_text_yes: 'ano' + general_lang_name: 'Czech (ÄŒeÅ¡tina)' + general_csv_separator: ',' + general_csv_decimal_separator: '.' + general_csv_encoding: UTF-8 + general_pdf_encoding: UTF-8 + general_first_day_of_week: '1' + + notice_account_updated: ÚÄet byl úspěšnÄ› zmÄ›nÄ›n. + notice_account_invalid_creditentials: Chybné jméno nebo heslo + notice_account_password_updated: Heslo bylo úspěšnÄ› zmÄ›nÄ›no. + notice_account_wrong_password: Chybné heslo + notice_account_register_done: ÚÄet byl úspěšnÄ› vytvoÅ™en. Pro aktivaci úÄtu kliknÄ›te na odkaz v emailu, který vám byl zaslán. + notice_account_unknown_email: Neznámý uživatel. + notice_can_t_change_password: Tento úÄet používá externí autentifikaci. Zde heslo zmÄ›nit nemůžete. + notice_account_lost_email_sent: Byl vám zaslán email s intrukcemi jak si nastavíte nové heslo. + notice_account_activated: Váš úÄet byl aktivován. Nyní se můžete pÅ™ihlásit. + notice_successful_create: ÚspěšnÄ› vytvoÅ™eno. + notice_successful_update: ÚspěšnÄ› aktualizováno. + notice_successful_delete: ÚspěšnÄ› odstranÄ›no. + notice_successful_connection: Úspěšné pÅ™ipojení. + notice_file_not_found: Stránka, kterou se snažíte zobrazit, neexistuje nebo byla smazána. + notice_locking_conflict: Údaje byly zmÄ›nÄ›ny jiným uživatelem. + notice_not_authorized: Nemáte dostateÄná práva pro zobrazení této stránky. + notice_not_authorized_archived_project: Projekt, ke kterému se snažíte pÅ™istupovat, byl archivován. + notice_email_sent: "Na adresu %{value} byl odeslán email" + notice_email_error: "PÅ™i odesílání emailu nastala chyba (%{value})" + notice_feeds_access_key_reseted: Váš klÃ­Ä pro přístup k RSS byl resetován. + notice_api_access_key_reseted: Váš API přístupový klÃ­Ä byl resetován. + notice_failed_to_save_issues: "Chyba pÅ™i uložení %{count} úkolu(ů) z %{total} vybraných: %{ids}." + notice_failed_to_save_members: "NepodaÅ™ilo se uložit Älena(y): %{errors}." + notice_no_issue_selected: "Nebyl zvolen žádný úkol. Prosím, zvolte úkoly, které chcete editovat" + notice_account_pending: "Váš úÄet byl vytvoÅ™en, nyní Äeká na schválení administrátorem." + notice_default_data_loaded: Výchozí konfigurace úspěšnÄ› nahrána. + notice_unable_delete_version: Nemohu odstanit verzi + notice_unable_delete_time_entry: Nelze smazat záznam Äasu. + notice_issue_done_ratios_updated: Koeficienty dokonÄení úkolu byly aktualizovány. + notice_gantt_chart_truncated: Graf byl oříznut, poÄet položek pÅ™esáhl limit pro zobrazení (%{max}) + + error_can_t_load_default_data: "Výchozí konfigurace nebyla nahrána: %{value}" + error_scm_not_found: "Položka a/nebo revize neexistují v repozitáři." + error_scm_command_failed: "PÅ™i pokusu o přístup k repozitáři doÅ¡lo k chybÄ›: %{value}" + error_scm_annotate: "Položka neexistuje nebo nemůže být komentována." + error_issue_not_found_in_project: 'Úkol nebyl nalezen nebo nepatří k tomuto projektu' + error_no_tracker_in_project: Žádná fronta nebyla pÅ™iÅ™azena tomuto projektu. Prosím zkontroluje nastavení projektu. + error_no_default_issue_status: Není nastaven výchozí stav úkolů. Prosím zkontrolujte nastavení ("Administrace -> Stavy úkolů"). + error_can_not_delete_custom_field: Nelze smazat volitelné pole + error_can_not_delete_tracker: Tato fronta obsahuje úkoly a nemůže být smazána. + error_can_not_remove_role: Tato role je právÄ› používaná a nelze ji smazat. + error_can_not_reopen_issue_on_closed_version: Úkol pÅ™iÅ™azený k uzavÅ™ené verzi nemůže být znovu otevÅ™en + error_can_not_archive_project: Tento projekt nemůže být archivován + error_issue_done_ratios_not_updated: Koeficient dokonÄení úkolu nebyl aktualizován. + error_workflow_copy_source: Prosím vyberte zdrojovou frontu nebo roli + error_workflow_copy_target: Prosím vyberte cílovou frontu(y) a roli(e) + error_unable_delete_issue_status: Nelze smazat stavy úkolů + error_unable_to_connect: Nelze se pÅ™ipojit (%{value}) + warning_attachments_not_saved: "%{count} soubor(ů) nebylo možné uložit." + + mail_subject_lost_password: "VaÅ¡e heslo (%{value})" + mail_body_lost_password: 'Pro zmÄ›nu vaÅ¡eho hesla kliknÄ›te na následující odkaz:' + mail_subject_register: "Aktivace úÄtu (%{value})" + mail_body_register: 'Pro aktivaci vaÅ¡eho úÄtu kliknÄ›te na následující odkaz:' + mail_body_account_information_external: "Pomocí vaÅ¡eho úÄtu %{value} se můžete pÅ™ihlásit." + mail_body_account_information: Informace o vaÅ¡em úÄtu + mail_subject_account_activation_request: "Aktivace %{value} úÄtu" + mail_body_account_activation_request: "Byl zaregistrován nový uživatel %{value}. Aktivace jeho úÄtu závisí na vaÅ¡em potvrzení." + mail_subject_reminder: "%{count} úkol(ů) má termín bÄ›hem nÄ›kolik dní (%{days})" + mail_body_reminder: "%{count} úkol(ů), které máte pÅ™iÅ™azeny má termín bÄ›hem nÄ›kolika dní (%{days}):" + mail_subject_wiki_content_added: "'%{id}' Wiki stránka byla pÅ™idána" + mail_body_wiki_content_added: "'%{id}' Wiki stránka byla pÅ™idána od %{author}." + mail_subject_wiki_content_updated: "'%{id}' Wiki stránka byla aktualizována" + mail_body_wiki_content_updated: "'%{id}' Wiki stránka byla aktualizována od %{author}." + + + field_name: Název + field_description: Popis + field_summary: PÅ™ehled + field_is_required: Povinné pole + field_firstname: Jméno + field_lastname: Příjmení + field_mail: Email + field_filename: Soubor + field_filesize: Velikost + field_downloads: Staženo + field_author: Autor + field_created_on: VytvoÅ™eno + field_updated_on: Aktualizováno + field_field_format: Formát + field_is_for_all: Pro vÅ¡echny projekty + field_possible_values: Možné hodnoty + field_regexp: Regulární výraz + field_min_length: Minimální délka + field_max_length: Maximální délka + field_value: Hodnota + field_category: Kategorie + field_title: Název + field_project: Projekt + field_issue: Úkol + field_status: Stav + field_notes: Poznámka + field_is_closed: Úkol uzavÅ™en + field_is_default: Výchozí stav + field_tracker: Fronta + field_subject: PÅ™edmÄ›t + field_due_date: Uzavřít do + field_assigned_to: PÅ™iÅ™azeno + field_priority: Priorita + field_fixed_version: Cílová verze + field_user: Uživatel + field_principal: Hlavní + field_role: Role + field_homepage: Domovská stránka + field_is_public: VeÅ™ejný + field_parent: NadÅ™azený projekt + field_is_in_roadmap: Úkoly zobrazené v plánu + field_login: PÅ™ihlášení + field_mail_notification: Emailová oznámení + field_admin: Administrátor + field_last_login_on: Poslední pÅ™ihlášení + field_language: Jazyk + field_effective_date: Datum + field_password: Heslo + field_new_password: Nové heslo + field_password_confirmation: Potvrzení + field_version: Verze + field_type: Typ + field_host: Host + field_port: Port + field_account: ÚÄet + field_base_dn: Base DN + field_attr_login: PÅ™ihlášení (atribut) + field_attr_firstname: Jméno (atribut) + field_attr_lastname: Příjemní (atribut) + field_attr_mail: Email (atribut) + field_onthefly: Automatické vytváření uživatelů + field_start_date: ZaÄátek + field_done_ratio: "% Hotovo" + field_auth_source: AutentifikaÄní mód + field_hide_mail: Nezobrazovat můj email + field_comments: Komentář + field_url: URL + field_start_page: Výchozí stránka + field_subproject: Podprojekt + field_hours: Hodiny + field_activity: Aktivita + field_spent_on: Datum + field_identifier: Identifikátor + field_is_filter: Použít jako filtr + field_issue_to: Související úkol + field_delay: ZpoždÄ›ní + field_assignable: Úkoly mohou být pÅ™iÅ™azeny této roli + field_redirect_existing_links: PÅ™esmÄ›rovat stávající odkazy + field_estimated_hours: Odhadovaná doba + field_column_names: Sloupce + field_time_entries: Zaznamenaný Äas + field_time_zone: ÄŒasové pásmo + field_searchable: Umožnit vyhledávání + field_default_value: Výchozí hodnota + field_comments_sorting: Zobrazit komentáře + field_parent_title: RodiÄovská stránka + field_editable: Editovatelný + field_watcher: Sleduje + field_identity_url: OpenID URL + field_content: Obsah + field_group_by: Seskupovat výsledky podle + field_sharing: Sdílení + field_parent_issue: RodiÄovský úkol + field_member_of_group: Skupina pÅ™iÅ™aditele + field_assigned_to_role: Role pÅ™iÅ™aditele + field_text: Textové pole + field_visible: Viditelný + + setting_app_title: Název aplikace + setting_app_subtitle: Podtitulek aplikace + setting_welcome_text: Uvítací text + setting_default_language: Výchozí jazyk + setting_login_required: Autentifikace vyžadována + setting_self_registration: Povolena automatická registrace + setting_attachment_max_size: Maximální velikost přílohy + setting_issues_export_limit: Limit pro export úkolů + setting_mail_from: Odesílat emaily z adresy + setting_bcc_recipients: Příjemci jako skrytá kopie (bcc) + setting_plain_text_mail: pouze prostý text (ne HTML) + setting_host_name: Jméno serveru + setting_text_formatting: Formátování textu + setting_wiki_compression: Komprese historie Wiki + setting_feeds_limit: Limit obsahu příspÄ›vků + setting_default_projects_public: Nové projekty nastavovat jako veÅ™ejné + setting_autofetch_changesets: Automaticky stahovat commity + setting_sys_api_enabled: Povolit WS pro správu repozitory + setting_commit_ref_keywords: KlíÄová slova pro odkazy + setting_commit_fix_keywords: KlíÄová slova pro uzavÅ™ení + setting_autologin: Automatické pÅ™ihlaÅ¡ování + setting_date_format: Formát data + setting_time_format: Formát Äasu + setting_cross_project_issue_relations: Povolit vazby úkolů napÅ™Ã­Ä projekty + setting_issue_list_default_columns: Výchozí sloupce zobrazené v seznamu úkolů + setting_emails_header: Záhlaví emailů + setting_emails_footer: Zápatí emailů + setting_protocol: Protokol + setting_per_page_options: Povolené poÄty řádků na stránce + setting_user_format: Formát zobrazení uživatele + setting_activity_days_default: Dny zobrazené v Äinnosti projektu + setting_display_subprojects_issues: Automaticky zobrazit úkoly podprojektu v hlavním projektu + setting_enabled_scm: Povolené SCM + setting_mail_handler_body_delimiters: Zkrátit e-maily po jednom z tÄ›chto řádků + setting_mail_handler_api_enabled: Povolit WS pro příchozí e-maily + setting_mail_handler_api_key: API klÃ­Ä + setting_sequential_project_identifiers: Generovat sekvenÄní identifikátory projektů + setting_gravatar_enabled: Použít uživatelské ikony Gravatar + setting_gravatar_default: Výchozí Gravatar + setting_diff_max_lines_displayed: Maximální poÄet zobrazených řádků rozdílu + setting_file_max_size_displayed: Maximální velikost textových souborů zobrazených přímo na stránce + setting_repository_log_display_limit: Maximální poÄet revizí zobrazených v logu souboru + setting_openid: Umožnit pÅ™ihlaÅ¡ování a registrace s OpenID + setting_password_min_length: Minimální délka hesla + setting_new_project_user_role_id: Role pÅ™iÅ™azená uživateli bez práv administrátora, který projekt vytvoÅ™il + setting_default_projects_modules: Výchozí zapnutné moduly pro nový projekt + setting_issue_done_ratio: SpoÄítat koeficient dokonÄení úkolu s + setting_issue_done_ratio_issue_field: Použít pole úkolu + setting_issue_done_ratio_issue_status: Použít stav úkolu + setting_start_of_week: ZaÄínat kalendáře + setting_rest_api_enabled: Zapnout službu REST + setting_cache_formatted_text: Ukládat formátovaný text do vyrovnávací pamÄ›ti + setting_default_notification_option: Výchozí nastavení oznámení + setting_commit_logtime_enabled: Povolit zapisování Äasu + setting_commit_logtime_activity_id: Aktivita pro zapsaný Äas + setting_gantt_items_limit: Maximální poÄet položek zobrazený na ganttovÄ› diagramu + + permission_add_project: VytvoÅ™it projekt + permission_add_subprojects: VytvoÅ™it podprojekty + permission_edit_project: Úprava projektů + permission_select_project_modules: VýbÄ›r modulů projektu + permission_manage_members: Spravování Älenství + permission_manage_project_activities: Spravovat aktivity projektu + permission_manage_versions: Spravování verzí + permission_manage_categories: Spravování kategorií úkolů + permission_view_issues: Zobrazit úkoly + permission_add_issues: PÅ™idávání úkolů + permission_edit_issues: Upravování úkolů + permission_manage_issue_relations: Spravování vztahů mezi úkoly + permission_add_issue_notes: PÅ™idávání poznámek + permission_edit_issue_notes: Upravování poznámek + permission_edit_own_issue_notes: Upravování vlastních poznámek + permission_move_issues: PÅ™esouvání úkolů + permission_delete_issues: Mazání úkolů + permission_manage_public_queries: Správa veÅ™ejných dotazů + permission_save_queries: Ukládání dotazů + permission_view_gantt: Zobrazení ganttova diagramu + permission_view_calendar: Prohlížení kalendáře + permission_view_issue_watchers: Zobrazení seznamu sledujících uživatelů + permission_add_issue_watchers: PÅ™idání sledujících uživatelů + permission_delete_issue_watchers: Smazat sledující uživatele + permission_log_time: Zaznamenávání stráveného Äasu + permission_view_time_entries: Zobrazení stráveného Äasu + permission_edit_time_entries: Upravování záznamů o stráveném Äasu + permission_edit_own_time_entries: Upravování vlastních zázamů o stráveném Äase + permission_manage_news: Spravování novinek + permission_comment_news: Komentování novinek + permission_view_documents: Prohlížení dokumentů + permission_manage_files: Spravování souborů + permission_view_files: Prohlížení souborů + permission_manage_wiki: Spravování Wiki + permission_rename_wiki_pages: PÅ™ejmenovávání Wiki stránek + permission_delete_wiki_pages: Mazání stránek na Wiki + permission_view_wiki_pages: Prohlížení Wiki + permission_view_wiki_edits: Prohlížení historie Wiki + permission_edit_wiki_pages: Upravování stránek Wiki + permission_delete_wiki_pages_attachments: Mazání příloh + permission_protect_wiki_pages: ZabezpeÄení Wiki stránek + permission_manage_repository: Spravování repozitáře + permission_browse_repository: Procházení repozitáře + permission_view_changesets: Zobrazování sady zmÄ›n + permission_commit_access: Commit přístup + permission_manage_boards: Správa diskusních fór + permission_view_messages: Prohlížení zpráv + permission_add_messages: Posílání zpráv + permission_edit_messages: Upravování zpráv + permission_edit_own_messages: Upravit vlastní zprávy + permission_delete_messages: Mazání zpráv + permission_delete_own_messages: Smazat vlastní zprávy + permission_export_wiki_pages: Exportovat Wiki stránky + permission_manage_subtasks: Spravovat dílÄí úkoly + + project_module_issue_tracking: Sledování úkolů + project_module_time_tracking: Sledování Äasu + project_module_news: Novinky + project_module_documents: Dokumenty + project_module_files: Soubory + project_module_wiki: Wiki + project_module_repository: Repozitář + project_module_boards: Diskuse + project_module_calendar: Kalendář + project_module_gantt: Gantt + + label_user: Uživatel + label_user_plural: Uživatelé + label_user_new: Nový uživatel + label_user_anonymous: Anonymní + label_project: Projekt + label_project_new: Nový projekt + label_project_plural: Projekty + label_x_projects: + zero: žádné projekty + one: 1 projekt + other: "%{count} projekty(ů)" + label_project_all: VÅ¡echny projekty + label_project_latest: Poslední projekty + label_issue: Úkol + label_issue_new: Nový úkol + label_issue_plural: Úkoly + label_issue_view_all: VÅ¡echny úkoly + label_issues_by: "Úkoly podle %{value}" + label_issue_added: Úkol pÅ™idán + label_issue_updated: Úkol aktualizován + label_document: Dokument + label_document_new: Nový dokument + label_document_plural: Dokumenty + label_document_added: Dokument pÅ™idán + label_role: Role + label_role_plural: Role + label_role_new: Nová role + label_role_and_permissions: Role a práva + label_member: ÄŒlen + label_member_new: Nový Älen + label_member_plural: ÄŒlenové + label_tracker: Fronta + label_tracker_plural: Fronty + label_tracker_new: Nová fronta + label_workflow: PrůbÄ›h práce + label_issue_status: Stav úkolu + label_issue_status_plural: Stavy úkolů + label_issue_status_new: Nový stav + label_issue_category: Kategorie úkolu + label_issue_category_plural: Kategorie úkolů + label_issue_category_new: Nová kategorie + label_custom_field: Uživatelské pole + label_custom_field_plural: Uživatelská pole + label_custom_field_new: Nové uživatelské pole + label_enumerations: Seznamy + label_enumeration_new: Nová hodnota + label_information: Informace + label_information_plural: Informace + label_please_login: PÅ™ihlaÅ¡te se, prosím + label_register: Registrovat + label_login_with_open_id_option: nebo se pÅ™ihlaÅ¡te s OpenID + label_password_lost: Zapomenuté heslo + label_home: Úvodní + label_my_page: Moje stránka + label_my_account: Můj úÄet + label_my_projects: Moje projekty + label_my_page_block: Bloky na mé stránce + label_administration: Administrace + label_login: PÅ™ihlášení + label_logout: Odhlášení + label_help: NápovÄ›da + label_reported_issues: Nahlášené úkoly + label_assigned_to_me_issues: Mé úkoly + label_last_login: Poslední pÅ™ihlášení + label_registered_on: Registrován + label_activity: Aktivita + label_overall_activity: Celková aktivita + label_user_activity: "Aktivita uživatele: %{value}" + label_new: Nový + label_logged_as: PÅ™ihlášen jako + label_environment: ProstÅ™edí + label_authentication: Autentifikace + label_auth_source: Mód autentifikace + label_auth_source_new: Nový mód autentifikace + label_auth_source_plural: Módy autentifikace + label_subproject_plural: Podprojekty + label_subproject_new: Nový podprojekt + label_and_its_subprojects: "%{value} a jeho podprojekty" + label_min_max_length: Min - Max délka + label_list: Seznam + label_date: Datum + label_integer: Celé Äíslo + label_float: Desetinné Äíslo + label_boolean: Ano/Ne + label_string: Text + label_text: Dlouhý text + label_attribute: Atribut + label_attribute_plural: Atributy + label_no_data: Žádné položky + label_change_status: ZmÄ›nit stav + label_history: Historie + label_attachment: Soubor + label_attachment_new: Nový soubor + label_attachment_delete: Odstranit soubor + label_attachment_plural: Soubory + label_file_added: Soubor pÅ™idán + label_report: PÅ™ehled + label_report_plural: PÅ™ehledy + label_news: Novinky + label_news_new: PÅ™idat novinku + label_news_plural: Novinky + label_news_latest: Poslední novinky + label_news_view_all: Zobrazit vÅ¡echny novinky + label_news_added: Novinka pÅ™idána + label_settings: Nastavení + label_overview: PÅ™ehled + label_version: Verze + label_version_new: Nová verze + label_version_plural: Verze + label_close_versions: Zavřít dokonÄené verze + label_confirmation: Potvrzení + label_export_to: 'Také k dispozici:' + label_read: NaÄítá se... + label_public_projects: VeÅ™ejné projekty + label_open_issues: otevÅ™ený + label_open_issues_plural: otevÅ™ené + label_closed_issues: uzavÅ™ený + label_closed_issues_plural: uzavÅ™ené + label_x_open_issues_abbr_on_total: + zero: 0 otevÅ™ených / %{total} + one: 1 otevÅ™ený / %{total} + other: "%{count} otevÅ™ených / %{total}" + label_x_open_issues_abbr: + zero: 0 otevÅ™ených + one: 1 otevÅ™ený + other: "%{count} otevÅ™ených" + label_x_closed_issues_abbr: + zero: 0 uzavÅ™ených + one: 1 uzavÅ™ený + other: "%{count} uzavÅ™ených" + label_total: Celkem + label_permissions: Práva + label_current_status: Aktuální stav + label_new_statuses_allowed: Nové povolené stavy + label_all: vÅ¡e + label_none: nic + label_nobody: nikdo + label_next: Další + label_previous: PÅ™edchozí + label_used_by: Použito + label_details: Detaily + label_add_note: PÅ™idat poznámku + label_per_page: Na stránku + label_calendar: Kalendář + label_months_from: mÄ›síců od + label_gantt: Ganttův diagram + label_internal: Interní + label_last_changes: "posledních %{count} zmÄ›n" + label_change_view_all: Zobrazit vÅ¡echny zmÄ›ny + label_personalize_page: PÅ™izpůsobit tuto stránku + label_comment: Komentář + label_comment_plural: Komentáře + label_x_comments: + zero: žádné komentáře + one: 1 komentář + other: "%{count} komentářů" + label_comment_add: PÅ™idat komentáře + label_comment_added: Komentář pÅ™idán + label_comment_delete: Odstranit komentář + label_query: Uživatelský dotaz + label_query_plural: Uživatelské dotazy + label_query_new: Nový dotaz + label_filter_add: PÅ™idat filtr + label_filter_plural: Filtry + label_equals: je + label_not_equals: není + label_in_less_than: je měší než + label_in_more_than: je vÄ›tší než + label_greater_or_equal: '>=' + label_less_or_equal: '<=' + label_in: v + label_today: dnes + label_all_time: vÅ¡e + label_yesterday: vÄera + label_this_week: tento týden + label_last_week: minulý týden + label_last_n_days: "posledních %{count} dnů" + label_this_month: tento mÄ›síc + label_last_month: minulý mÄ›síc + label_this_year: tento rok + label_date_range: ÄŒasový rozsah + label_less_than_ago: pÅ™ed ménÄ› jak (dny) + label_more_than_ago: pÅ™ed více jak (dny) + label_ago: pÅ™ed (dny) + label_contains: obsahuje + label_not_contains: neobsahuje + label_day_plural: dny + label_repository: Repozitář + label_repository_plural: Repozitáře + label_browse: Procházet + label_branch: VÄ›tev + label_tag: Tag + label_revision: Revize + label_revision_plural: Revizí + label_revision_id: "Revize %{value}" + label_associated_revisions: Související verze + label_added: pÅ™idáno + label_modified: zmÄ›nÄ›no + label_copied: zkopírováno + label_renamed: pÅ™ejmenováno + label_deleted: odstranÄ›no + label_latest_revision: Poslední revize + label_latest_revision_plural: Poslední revize + label_view_revisions: Zobrazit revize + label_view_all_revisions: Zobrazit vÅ¡echny revize + label_max_size: Maximální velikost + label_sort_highest: PÅ™esunout na zaÄátek + label_sort_higher: PÅ™esunout nahoru + label_sort_lower: PÅ™esunout dolů + label_sort_lowest: PÅ™esunout na konec + label_roadmap: Plán + label_roadmap_due_in: "Zbývá %{value}" + label_roadmap_overdue: "%{value} pozdÄ›" + label_roadmap_no_issues: Pro tuto verzi nejsou žádné úkoly + label_search: Hledat + label_result_plural: Výsledky + label_all_words: VÅ¡echna 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 dle názvu + label_index_by_date: Index dle data + label_current_version: Aktuální verze + label_preview: Náhled + label_feed_plural: PříspÄ›vky + label_changes_details: Detail vÅ¡ech zmÄ›n + label_issue_tracking: Sledování úkolů + label_spent_time: Strávený Äas + label_overall_spent_time: Celkem strávený Äas + label_f_hour: "%{value} hodina" + label_f_hour_plural: "%{value} hodin" + label_time_tracking: Sledování Äasu + label_change_plural: ZmÄ›ny + label_statistics: Statistiky + label_commits_per_month: Commitů za mÄ›síc + label_commits_per_author: Commitů za autora + label_view_diff: Zobrazit rozdíly + label_diff_inline: uvnitÅ™ + label_diff_side_by_side: vedle sebe + label_options: Nastavení + label_copy_workflow_from: Kopírovat průbÄ›h práce z + label_permissions_report: PÅ™ehled práv + label_watched_issues: Sledované úkoly + label_related_issues: Související úkoly + label_applied_status: Použitý stav + label_loading: Nahrávám... + label_relation_new: Nová souvislost + label_relation_delete: Odstranit souvislost + label_relates_to: související s + label_duplicates: duplikuje + label_duplicated_by: duplikován + label_blocks: blokuje + label_blocked_by: blokován + label_precedes: pÅ™edchází + label_follows: následuje + label_end_to_start: od konce do zaÄátku + label_end_to_end: od konce do konce + label_start_to_start: od zaÄátku do zaÄátku + label_start_to_end: od zaÄátku do konce + label_stay_logged_in: Zůstat pÅ™ihlášený + label_disabled: zakázán + label_show_completed_versions: Zobrazit dokonÄené verze + label_me: já + label_board: Fórum + label_board_new: Nové fórum + label_board_plural: Fóra + label_board_locked: ZamÄeno + label_board_sticky: Nálepka + label_topic_plural: Témata + label_message_plural: Zprávy + label_message_last: Poslední zpráva + label_message_new: Nová zpráva + label_message_posted: Zpráva pÅ™idána + label_reply_plural: OdpovÄ›di + label_send_information: Zaslat informace o úÄtu uživateli + label_year: Rok + label_month: MÄ›síc + label_week: Týden + label_date_from: Od + label_date_to: Do + label_language_based: Podle výchozího jazyka + label_sort_by: "SeÅ™adit podle %{value}" + label_send_test_email: Poslat testovací email + label_feeds_access_key: Přístupový klÃ­Ä pro RSS + label_missing_feeds_access_key: Postrádá přístupový klÃ­Ä pro RSS + label_feeds_access_key_created_on: "Přístupový klÃ­Ä pro RSS byl vytvoÅ™en pÅ™ed %{value}" + label_module_plural: Moduly + label_added_time_by: "PÅ™idáno uživatelem %{author} pÅ™ed %{age}" + label_updated_time_by: "Aktualizováno uživatelem %{author} pÅ™ed %{age}" + label_updated_time: "Aktualizováno pÅ™ed %{value}" + label_jump_to_a_project: Vyberte projekt... + label_file_plural: Soubory + label_changeset_plural: Sady zmÄ›n + label_default_columns: Výchozí sloupce + label_no_change_option: (beze zmÄ›ny) + label_bulk_edit_selected_issues: Hromadná úprava vybraných úkolů + label_theme: Téma + label_default: Výchozí + label_search_titles_only: Vyhledávat pouze v názvech + label_user_mail_option_all: "Pro vÅ¡echny události vÅ¡ech mých projektů" + label_user_mail_option_selected: "Pro vÅ¡echny události vybraných projektů..." + label_user_mail_option_none: "Žádné události" + label_user_mail_option_only_my_events: "Jen pro vÄ›ci, co sleduji nebo jsem v nich zapojen" + label_user_mail_option_only_assigned: "Jen pro vÄ›ci, ke kterým sem pÅ™iÅ™azen" + label_user_mail_option_only_owner: "Jen pro vÄ›ci, které vlastním" + label_user_mail_no_self_notified: "Nezasílat informace o mnou vytvoÅ™ených zmÄ›nách" + label_registration_activation_by_email: aktivace úÄtu emailem + label_registration_manual_activation: manuální aktivace úÄtu + label_registration_automatic_activation: automatická aktivace úÄtu + label_display_per_page: "%{value} na stránku" + label_age: VÄ›k + label_change_properties: ZmÄ›nit vlastnosti + label_general: Obecné + label_more: Více + label_scm: SCM + label_plugins: Doplňky + label_ldap_authentication: Autentifikace LDAP + label_downloads_abbr: Staž. + label_optional_description: Volitelný popis + label_add_another_file: PÅ™idat další soubor + label_preferences: Nastavení + label_chronological_order: V chronologickém poÅ™adí + label_reverse_chronological_order: V obrácaném chronologickém poÅ™adí + label_planning: Plánování + label_incoming_emails: Příchozí e-maily + label_generate_key: Generovat klÃ­Ä + label_issue_watchers: Sledování + label_example: Příklad + label_display: Zobrazit + label_sort: Řazení + label_ascending: VzestupnÄ› + label_descending: SestupnÄ› + label_date_from_to: Od %{start} do %{end} + label_wiki_content_added: Wiki stránka pÅ™idána + label_wiki_content_updated: Wiki stránka aktualizována + label_group: Skupina + label_group_plural: Skupiny + label_group_new: Nová skupina + label_time_entry_plural: Strávený Äas + label_version_sharing_none: Nesdíleno + label_version_sharing_descendants: S podprojekty + label_version_sharing_hierarchy: S hierarchií projektu + label_version_sharing_tree: Se stromem projektu + label_version_sharing_system: Se vÅ¡emi projekty + label_update_issue_done_ratios: Aktualizovat koeficienty dokonÄení úkolů + label_copy_source: Zdroj + label_copy_target: Cíl + label_copy_same_as_target: Stejný jako cíl + label_display_used_statuses_only: Zobrazit pouze stavy které jsou použité touto frontou + label_api_access_key: API přístupový klÃ­Ä + label_missing_api_access_key: ChybÄ›jící přístupový klÃ­Ä API + label_api_access_key_created_on: API přístupový klÃ­Ä vytvoÅ™en %{value} + label_profile: Profil + label_subtask_plural: DílÄí úkoly + label_project_copy_notifications: Odeslat email oznámení v průbÄ›hu kopie projektu + label_principal_search: "Hledat uživatele nebo skupinu:" + label_user_search: "Hledat uživatele:" + + button_login: PÅ™ihlásit + button_submit: Potvrdit + button_save: Uložit + button_check_all: ZaÅ¡rtnout vÅ¡e + button_uncheck_all: OdÅ¡rtnout vÅ¡e + button_delete: Odstranit + button_create: VytvoÅ™it + button_create_and_continue: VytvoÅ™it a pokraÄovat + button_test: Testovat + button_edit: Upravit + button_edit_associated_wikipage: "Upravit pÅ™iÅ™azenou Wiki stránku: %{page_title}" + button_add: PÅ™idat + button_change: ZmÄ›nit + button_apply: Použít + button_clear: Smazat + button_lock: Zamknout + button_unlock: Odemknout + button_download: Stáhnout + button_list: Vypsat + button_view: Zobrazit + button_move: PÅ™esunout + button_move_and_follow: PÅ™esunout a následovat + button_back: ZpÄ›t + button_cancel: Storno + button_activate: Aktivovat + button_sort: SeÅ™adit + button_log_time: PÅ™idat Äas + button_rollback: ZpÄ›t k této verzi + button_watch: Sledovat + button_unwatch: Nesledovat + button_reply: OdpovÄ›dÄ›t + button_archive: Archivovat + button_unarchive: Dearchivovat + button_reset: Resetovat + button_rename: PÅ™ejmenovat + button_change_password: ZmÄ›nit heslo + button_copy: Kopírovat + button_copy_and_follow: Kopírovat a následovat + button_annotate: Komentovat + button_update: Aktualizovat + button_configure: Konfigurovat + button_quote: Citovat + button_duplicate: Duplikovat + button_show: Zobrazit + + status_active: aktivní + status_registered: registrovaný + status_locked: zamÄený + + version_status_open: otevÅ™ený + version_status_locked: zamÄený + version_status_closed: zavÅ™ený + + field_active: Aktivní + + text_select_mail_notifications: Vyberte akci, pÅ™i které bude zasláno upozornÄ›ní emailem. + text_regexp_info: napÅ™. ^[A-Z0-9]+$ + text_min_max_length_info: 0 znamená bez limitu + text_project_destroy_confirmation: Jste si jisti, že chcete odstranit tento projekt a vÅ¡echna související data? + text_subprojects_destroy_warning: "Jeho podprojek(y): %{value} budou také smazány." + text_workflow_edit: Vyberte roli a frontu k editaci průbÄ›hu práce + text_are_you_sure: Jste si jisti? + text_journal_changed: "%{label} zmÄ›nÄ›n z %{old} na %{new}" + text_journal_set_to: "%{label} nastaven na %{value}" + text_journal_deleted: "%{label} smazán (%{old})" + text_journal_added: "%{label} %{value} pÅ™idán" + text_tip_issue_begin_day: úkol zaÄíná v tento den + text_tip_issue_end_day: úkol konÄí v tento den + text_tip_issue_begin_end_day: úkol zaÄíná a konÄí v tento den + text_caracters_maximum: "%{count} znaků maximálnÄ›." + text_caracters_minimum: "Musí být alespoň %{count} znaků dlouhé." + text_length_between: "Délka mezi %{min} a %{max} znaky." + text_tracker_no_workflow: Pro tuto frontu není definován žádný průbÄ›h práce + text_unallowed_characters: Nepovolené znaky + text_comma_separated: Povoleno více hodnot (oddÄ›lÄ›né Äárkou). + text_line_separated: Více hodnot povoleno (jeden řádek pro každou hodnotu). + text_issues_ref_in_commit_messages: Odkazování a opravování úkolů v poznámkách commitů + text_issue_added: "Úkol %{id} byl vytvoÅ™en uživatelem %{author}." + text_issue_updated: "Úkol %{id} byl aktualizován uživatelem %{author}." + text_wiki_destroy_confirmation: Opravdu si pÅ™ejete odstranit tuto Wiki a celý její obsah? + text_issue_category_destroy_question: "NÄ›které úkoly (%{count}) jsou pÅ™iÅ™azeny k této kategorii. Co s nimi chtete udÄ›lat?" + text_issue_category_destroy_assignments: ZruÅ¡it pÅ™iÅ™azení ke kategorii + text_issue_category_reassign_to: PÅ™iÅ™adit úkoly do této kategorie + text_user_mail_option: "U projektů, které nebyly vybrány, budete dostávat oznámení pouze o vaÅ¡ich Äi o sledovaných položkách (napÅ™. o položkách jejichž jste autor nebo ke kterým jste pÅ™iÅ™azen(a))." + text_no_configuration_data: "Role, fronty, stavy úkolů ani průbÄ›h práce nebyly zatím nakonfigurovány.\nVelice doporuÄujeme nahrát výchozí konfiguraci. Po té si můžete vÅ¡e upravit" + text_load_default_configuration: Nahrát výchozí konfiguraci + text_status_changed_by_changeset: "Použito v sadÄ› zmÄ›n %{value}." + text_time_logged_by_changeset: Aplikováno v sadÄ› zmÄ›n %{value}. + text_issues_destroy_confirmation: 'Opravdu si pÅ™ejete odstranit vÅ¡echny zvolené úkoly?' + text_select_project_modules: 'Aktivní moduly v tomto projektu:' + text_default_administrator_account_changed: Výchozí nastavení administrátorského úÄtu zmÄ›nÄ›no + text_file_repository_writable: Povolen zápis do adresáře ukládání souborů + text_plugin_assets_writable: Možnost zápisu do adresáře plugin assets + text_rmagick_available: RMagick k dispozici (volitelné) + text_destroy_time_entries_question: "U úkolů, které chcete odstranit, je evidováno %{hours} práce. Co chete udÄ›lat?" + text_destroy_time_entries: Odstranit zaznamenané hodiny. + text_assign_time_entries_to_project: PÅ™iÅ™adit zaznamenané hodiny projektu + text_reassign_time_entries: 'PÅ™eÅ™adit zaznamenané hodiny k tomuto úkolu:' + text_user_wrote: "%{value} napsal:" + text_enumeration_destroy_question: "NÄ›kolik (%{count}) objektů je pÅ™iÅ™azeno k této hodnotÄ›." + text_enumeration_category_reassign_to: 'PÅ™eÅ™adit je do této:' + text_email_delivery_not_configured: "DoruÄování e-mailů není nastaveno a odesílání notifikací je zakázáno.\nNastavte Váš SMTP server v souboru config/configuration.yml a restartujte aplikaci." + text_repository_usernames_mapping: "Vybrat nebo upravit mapování mezi Redmine uživateli a uživatelskými jmény nalezenými v logu repozitáře.\nUživatelé se shodným Redmine uživatelským jménem a uživatelským jménem v repozitáři jsou mapováni automaticky." + text_diff_truncated: '... Rozdílový soubor je zkrácen, protože jeho délka pÅ™esahuje max. limit.' + text_custom_field_possible_values_info: 'Každá hodnota na novém řádku' + text_wiki_page_destroy_question: Tato stránka má %{descendants} podstránek a potomků. Co chcete udÄ›lat? + text_wiki_page_nullify_children: Ponechat podstránky jako koÅ™enové stránky + text_wiki_page_destroy_children: Smazat podstránky a vÅ¡echny jejich potomky + text_wiki_page_reassign_children: PÅ™iÅ™adit podstránky k tomuto rodiÄi + text_own_membership_delete_confirmation: "Chystáte se odebrat si nÄ›která nebo vÅ¡echna svá oprávnÄ›ní, potom již nemusíte být schopni upravit tento projekt.\nOpravdu chcete pokraÄovat?" + text_zoom_in: PÅ™iblížit + text_zoom_out: Oddálit + + default_role_manager: Manažer + default_role_developer: Vývojář + default_role_reporter: Reportér + default_tracker_bug: Chyba + default_tracker_feature: Požadavek + default_tracker_support: Podpora + default_issue_status_new: Nový + default_issue_status_in_progress: Ve vývoji + default_issue_status_resolved: VyÅ™eÅ¡ený + default_issue_status_feedback: ÄŒeká se + default_issue_status_closed: UzavÅ™ený + default_issue_status_rejected: Odmítnutý + default_doc_category_user: Uživatelská dokumentace + default_doc_category_tech: Technická dokumentace + default_priority_low: Nízká + default_priority_normal: Normální + default_priority_high: Vysoká + default_priority_urgent: Urgentní + default_priority_immediate: Okamžitá + default_activity_design: Návhr + default_activity_development: Vývoj + + enumeration_issue_priorities: Priority úkolů + enumeration_doc_categories: Kategorie dokumentů + enumeration_activities: Aktivity (sledování Äasu) + enumeration_system_activity: Systémová aktivita + + field_warn_on_leaving_unsaved: Varuj mÄ› pÅ™ed opuÅ¡tÄ›ním stránky s neuloženým textem + text_warn_on_leaving_unsaved: Aktuální stránka obsahuje neuložený text, který bude ztracen, když opustíte stránku. + label_my_queries: Moje vlastní dotazy + text_journal_changed_no_detail: "%{label} aktualizován" + label_news_comment_added: K novince byl pÅ™idán komentář + button_expand_all: Rozbal vÅ¡e + button_collapse_all: Sbal vÅ¡e + label_additional_workflow_transitions_for_assignee: Další zmÄ›na stavu povolena, jestliže je uživatel pÅ™iÅ™azen + label_additional_workflow_transitions_for_author: Další zmÄ›na stavu povolena, jestliže je uživatel autorem + label_bulk_edit_selected_time_entries: Hromadná zmÄ›na záznamů Äasu + text_time_entries_destroy_confirmation: Jste si jistí, že chcete smazat vybraný záznam(y) Äasu? + label_role_anonymous: Anonymní + label_role_non_member: Není Älenem + label_issue_note_added: PÅ™idána poznámka + label_issue_status_updated: Aktualizován stav + label_issue_priority_updated: Aktualizována priorita + label_issues_visibility_own: Úkol vytvoÅ™en nebo pÅ™iÅ™azen uživatel(i/em) + field_issues_visibility: Viditelnost úkolů + label_issues_visibility_all: VÅ¡echny úkoly + permission_set_own_issues_private: Nastavit vlastní úkoly jako veÅ™ejné nebo soukromé + field_is_private: Soukromý + permission_set_issues_private: Nastavit úkoly jako veÅ™ejné nebo soukromé + label_issues_visibility_public: VÅ¡echny úkoly, které nejsou soukromé + text_issues_destroy_descendants_confirmation: "%{count} dílÄí(ch) úkol(ů) bude rovněž smazán(o)." + field_commit_logs_encoding: Kódování zpráv pÅ™i commitu + field_scm_path_encoding: Kódování cesty SCM + text_scm_path_encoding_note: "Výchozí: UTF-8" + field_path_to_repository: Cesta k repositáři + field_root_directory: KoÅ™enový adresář + field_cvs_module: Modul + field_cvsroot: CVSROOT + text_mercurial_repository_note: Lokální repositář (napÅ™. /hgrepo, c:\hgrepo) + text_scm_command: Příkaz + text_scm_command_version: Verze + label_git_report_last_commit: Reportovat poslední commit pro soubory a adresáře + text_scm_config: Můžete si nastavit vaÅ¡e SCM příkazy v config/configuration.yml. Restartujte, prosím, aplikaci po jejich úpravÄ›. + text_scm_command_not_available: SCM příkaz není k dispozici. Zkontrolujte, prosím, nastavení v panelu Administrace. + notice_issue_successful_create: Úkol %{id} vytvoÅ™en. + label_between: mezi + setting_issue_group_assignment: Povolit pÅ™iÅ™azení úkolu skupinÄ› + label_diff: rozdíl + text_git_repository_note: Repositář je "bare and local" (napÅ™. /gitrepo, c:\gitrepo) + description_query_sort_criteria_direction: SmÄ›r třídÄ›ní + description_project_scope: Rozsah vyhledávání + description_filter: Filtr + description_user_mail_notification: Nastavení emailových notifikací + description_date_from: Zadejte poÄáteÄní datum + description_message_content: Obsah zprávy + description_available_columns: Dostupné sloupce + description_date_range_interval: Zvolte rozsah výbÄ›rem poÄáteÄního a koncového data + description_issue_category_reassign: Zvolte kategorii úkolu + description_search: Vyhledávací pole + description_notes: Poznámky + description_date_range_list: Zvolte rozsah ze seznamu + description_choose_project: Projekty + description_date_to: Zadejte datum + description_query_sort_criteria_attribute: Třídící atribut + description_wiki_subpages_reassign: Zvolte novou rodiÄovskou stránku + description_selected_columns: Vybraný sloupec + label_parent_revision: RodiÄ + label_child_revision: Potomek + error_scm_annotate_big_text_file: Vstup nemůže být komentován, protože pÅ™ekraÄuje povolenou velikost textového souboru + setting_default_issue_start_date_to_creation_date: Použij aktuální datum jako poÄáteÄní datum pro nové úkoly + button_edit_section: Uprav tuto Äást + setting_repositories_encodings: Kódování příloh a repositářů + description_all_columns: VÅ¡echny sloupce + button_export: Export + label_export_options: "nastavení exportu %{export_format}" + error_attachment_too_big: Soubor nemůže být nahrán, protože jeho velikost je vÄ›tší než maximální (%{max_size}) + notice_failed_to_save_time_entries: "Chyba pÅ™i ukládání %{count} Äasov(ých/ého) záznam(ů) z %{total} vybraného: %{ids}." + label_x_issues: + zero: 0 Úkol + one: 1 Úkol + other: "%{count} Úkoly" + label_repository_new: Nový repositář + field_repository_is_default: Hlavní repositář + label_copy_attachments: Kopírovat přílohy + label_item_position: "%{position}/%{count}" + label_completed_versions: DokonÄené verze + text_project_identifier_info: Jsou povolena pouze malá písmena (a-z), Äíslice, pomlÄky a podtržítka.
    Po uložení již nelze identifikátor mÄ›nit. + field_multiple: Více hodnot + setting_commit_cross_project_ref: Povolit reference a opravy úkolů ze vÅ¡ech ostatních projektů + text_issue_conflict_resolution_add_notes: PÅ™idat moje poznámky a zahodit ostatní zmÄ›ny + text_issue_conflict_resolution_overwrite: PÅ™esto pÅ™ijmout moje úpravy (pÅ™edchozí poznámky budou zachovány, ale nÄ›které zmÄ›ny mohou být pÅ™epsány) + notice_issue_update_conflict: BÄ›hem vaÅ¡ich úprav byl úkol aktualizován jiným uživatelem. + text_issue_conflict_resolution_cancel: ZahoÄ vÅ¡echny moje zmÄ›ny a znovu zobraz %{link} + permission_manage_related_issues: Spravuj související úkoly + field_auth_source_ldap_filter: LDAP filtr + label_search_for_watchers: Hledej sledující pro pÅ™idání + notice_account_deleted: Váš úÄet byl trvale smazán. + setting_unsubscribe: Povolit uživatelům smazání jejich vlastního úÄtu + button_delete_my_account: Smazat můj úÄet + text_account_destroy_confirmation: |- + SkuteÄnÄ› chcete pokraÄovat? + Váš úÄet bude nenávratnÄ› smazán. + error_session_expired: VaÅ¡e sezení vyprÅ¡elo. Znovu se pÅ™ihlaste, prosím. + text_session_expiration_settings: "Varování: zmÄ›nou tohoto nastavení mohou vyprÅ¡et aktuální sezení vÄetnÄ› toho vaÅ¡eho." + setting_session_lifetime: Maximální Äas sezení + setting_session_timeout: VyprÅ¡ení sezení bez aktivity + label_session_expiration: VyprÅ¡ení sezení + permission_close_project: Zavřít / Otevřít projekt + label_show_closed_projects: Zobrazit zavÅ™ené projekty + button_close: Zavřít + button_reopen: Znovu otevřít + project_status_active: aktivní + project_status_closed: zavÅ™ený + project_status_archived: archivovaný + text_project_closed: Tento projekt je uzevÅ™ený a je pouze pro Ätení. + notice_user_successful_create: Uživatel %{id} vytvoÅ™en. + field_core_fields: Standardní pole + field_timeout: VyprÅ¡ení (v sekundách) + setting_thumbnails_enabled: Zobrazit náhled přílohy + setting_thumbnails_size: Velikost náhledu (v pixelech) + label_status_transitions: ZmÄ›na stavu + label_fields_permissions: Práva k polím + label_readonly: Pouze pro Ätení + label_required: Vyžadováno + text_repository_identifier_info: Jou povoleny pouze malá písmena (a-z), Äíslice, pomlÄky a podtržítka.
    Po uložení již nelze identifikátor zmÄ›nit. + field_board_parent: RodiÄovské fórum + label_attribute_of_project: Projektové %{name} + label_attribute_of_author: Autorovo %{name} + label_attribute_of_assigned_to: "%{name} pÅ™iÅ™azené(ho)" + label_attribute_of_fixed_version: Cílová verze %{name} + label_copy_subtasks: Kopírovat dílÄí úkoly + label_copied_to: zkopírováno do + label_copied_from: zkopírováno z + label_any_issues_in_project: jakékoli úkoly v projektu + label_any_issues_not_in_project: jakékoli úkoly mimo projekt + field_private_notes: Soukromé poznámky + permission_view_private_notes: Zobrazit soukromé poznámky + permission_set_notes_private: Nastavit poznámky jako soukromé + label_no_issues_in_project: žádné úkoly v projektu + label_any: vÅ¡e + label_last_n_weeks: poslední %{count} týdny + setting_cross_project_subtasks: Povolit dílÄí úkoly napÅ™Ã­Ä projekty + label_cross_project_descendants: S podprojekty + label_cross_project_tree: Se stromem projektu + label_cross_project_hierarchy: S hierarchií projektu + label_cross_project_system: Se vÅ¡emi projekty + button_hide: Skrýt + setting_non_working_week_days: Dny pracovního volna/klidu + label_in_the_next_days: v přístích + label_in_the_past_days: v minulých + label_attribute_of_user: "%{name} uživatel(e/ky)" + text_turning_multiple_off: Jestliže zakážete více hodnot, + hodnoty budou smazány za úÄelem rezervace pouze jediné hodnoty na položku. + label_attribute_of_issue: "%{name} úkolu" + permission_add_documents: PÅ™idat dokument + permission_edit_documents: Upravit dokumenty + permission_delete_documents: Smazet dokumenty + label_gantt_progress_line: Vývojová Äára + setting_jsonp_enabled: Povolit podporu JSONP + field_inherit_members: ZdÄ›dit Äleny + field_closed_on: UzavÅ™eno + setting_default_projects_tracker_ids: Výchozí fronta pro nové projekty + label_total_time: Celkem diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/73/730e7c9bc9c48dc81b3322907773ad6f6d8d03cc.svn-base --- a/.svn/pristine/73/730e7c9bc9c48dc81b3322907773ad6f6d8d03cc.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,94 +0,0 @@ ---- -enumerations_001: - name: Uncategorized - id: 1 - type: DocumentCategory - active: true -enumerations_002: - name: User documentation - id: 2 - type: DocumentCategory - active: true -enumerations_003: - name: Technical documentation - id: 3 - type: DocumentCategory - active: true -enumerations_004: - name: Low - id: 4 - type: IssuePriority - active: true - position: 1 -enumerations_005: - name: Normal - id: 5 - type: IssuePriority - is_default: true - active: true - position: 2 -enumerations_006: - name: High - id: 6 - type: IssuePriority - active: true - position: 3 -enumerations_007: - name: Urgent - id: 7 - type: IssuePriority - active: true - position: 4 -enumerations_008: - name: Immediate - id: 8 - type: IssuePriority - active: true - position: 5 -enumerations_009: - name: Design - id: 9 - type: TimeEntryActivity - position: 1 - active: true -enumerations_010: - name: Development - id: 10 - type: TimeEntryActivity - position: 2 - is_default: true - active: true -enumerations_011: - name: QA - id: 11 - type: TimeEntryActivity - position: 3 - active: true -enumerations_012: - name: Default Enumeration - id: 12 - type: Enumeration - is_default: true - active: true -enumerations_013: - name: Another Enumeration - id: 13 - type: Enumeration - active: true -enumerations_014: - name: Inactive Activity - id: 14 - type: TimeEntryActivity - position: 4 - active: false -enumerations_015: - name: Inactive Priority - id: 15 - type: IssuePriority - position: 6 - active: false -enumerations_016: - name: Inactive Document Category - id: 16 - type: DocumentCategory - active: false diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/73/7312a1538a08cc5ceaf5579a66a48a6614d8eeab.svn-base --- a/.svn/pristine/73/7312a1538a08cc5ceaf5579a66a48a6614d8eeab.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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 TimeEntryActivityCustomField < CustomField - def type_name - :enumeration_activities - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/73/73173a3d1c043da06788c9ebc548d80e9e88eac5.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/73/73173a3d1c043da06788c9ebc548d80e9e88eac5.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,14 @@ +class AddCommentsPermissions < ActiveRecord::Migration + # model removed + class Permission < ActiveRecord::Base; end + + def self.up + Permission.create :controller => "news", :action => "add_comment", :description => "label_comment_add", :sort => 1130, :is_public => false, :mail_option => 0, :mail_enabled => 0 + Permission.create :controller => "news", :action => "destroy_comment", :description => "label_comment_delete", :sort => 1133, :is_public => false, :mail_option => 0, :mail_enabled => 0 + end + + def self.down + Permission.where("controller=? and action=?", 'news', 'add_comment').first.destroy + Permission.where("controller=? and action=?", 'news', 'destroy_comment').first.destroy + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/73/73179d2fe09358707afe7a7d33051ef88d9c1267.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/73/73179d2fe09358707afe7a7d33051ef88d9c1267.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,34 @@ + + + + + + + +
    + <%= label_tag "available_columns", l(:description_available_columns) %> +
    + <%= select_tag 'available_columns', + options_for_select(query_available_inline_columns_options(query)), + :multiple => true, :size => 10, :style => "width:150px", + :ondblclick => "moveOptions(this.form.available_columns, this.form.selected_columns);" %> +
    +
    + +
    + <%= label_tag "selected_columns", l(:description_selected_columns) %> +
    + <%= select_tag tag_name, + options_for_select(query_selected_inline_columns_options(query)), + :id => 'selected_columns', :multiple => true, :size => 10, :style => "width:150px", + :ondblclick => "moveOptions(this.form.selected_columns, this.form.available_columns);" %> +
    +
    + +
    + +<% content_for :header_tags do %> +<%= javascript_include_tag 'select_list_move' %> +<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/73/7324bdf575eb0516e737af169c50f43705081a91.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/73/7324bdf575eb0516e737af169c50f43705081a91.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,170 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class IssueRelationTest < ActiveSupport::TestCase + fixtures :projects, + :users, + :roles, + :members, + :member_roles, + :issues, + :issue_statuses, + :issue_relations, + :enabled_modules, + :enumerations, + :trackers + + include Redmine::I18n + + def test_create + from = Issue.find(1) + to = Issue.find(2) + + relation = IssueRelation.new :issue_from => from, :issue_to => to, + :relation_type => IssueRelation::TYPE_PRECEDES + assert relation.save + relation.reload + assert_equal IssueRelation::TYPE_PRECEDES, relation.relation_type + assert_equal from, relation.issue_from + assert_equal to, relation.issue_to + end + + def test_create_minimum + relation = IssueRelation.new :issue_from => Issue.find(1), :issue_to => Issue.find(2) + assert relation.save + assert_equal IssueRelation::TYPE_RELATES, relation.relation_type + end + + def test_follows_relation_should_be_reversed + from = Issue.find(1) + to = Issue.find(2) + + relation = IssueRelation.new :issue_from => from, :issue_to => to, + :relation_type => IssueRelation::TYPE_FOLLOWS + assert relation.save + relation.reload + assert_equal IssueRelation::TYPE_PRECEDES, relation.relation_type + assert_equal to, relation.issue_from + assert_equal from, relation.issue_to + end + + def test_follows_relation_should_not_be_reversed_if_validation_fails + from = Issue.find(1) + to = Issue.find(2) + + relation = IssueRelation.new :issue_from => from, :issue_to => to, + :relation_type => IssueRelation::TYPE_FOLLOWS, + :delay => 'xx' + assert !relation.save + assert_equal IssueRelation::TYPE_FOLLOWS, relation.relation_type + assert_equal from, relation.issue_from + assert_equal to, relation.issue_to + end + + def test_relation_type_for + from = Issue.find(1) + to = Issue.find(2) + + relation = IssueRelation.new :issue_from => from, :issue_to => to, + :relation_type => IssueRelation::TYPE_PRECEDES + assert_equal IssueRelation::TYPE_PRECEDES, relation.relation_type_for(from) + assert_equal IssueRelation::TYPE_FOLLOWS, relation.relation_type_for(to) + end + + def test_set_issue_to_dates_without_issue_to + r = IssueRelation.new(:issue_from => Issue.new(:start_date => Date.today), + :relation_type => IssueRelation::TYPE_PRECEDES, + :delay => 1) + assert_nil r.set_issue_to_dates + end + + def test_set_issue_to_dates_without_issues + r = IssueRelation.new(:relation_type => IssueRelation::TYPE_PRECEDES, :delay => 1) + assert_nil r.set_issue_to_dates + end + + def test_validates_circular_dependency + IssueRelation.delete_all + assert IssueRelation.create!( + :issue_from => Issue.find(1), :issue_to => Issue.find(2), + :relation_type => IssueRelation::TYPE_PRECEDES + ) + assert IssueRelation.create!( + :issue_from => Issue.find(2), :issue_to => Issue.find(3), + :relation_type => IssueRelation::TYPE_PRECEDES + ) + r = IssueRelation.new( + :issue_from => Issue.find(3), :issue_to => Issue.find(1), + :relation_type => IssueRelation::TYPE_PRECEDES + ) + assert !r.save + assert_not_nil r.errors[:base] + end + + def test_validates_circular_dependency_of_subtask + set_language_if_valid 'en' + issue1 = Issue.generate! + issue2 = Issue.generate! + IssueRelation.create!( + :issue_from => issue1, :issue_to => issue2, + :relation_type => IssueRelation::TYPE_PRECEDES + ) + child = Issue.generate!(:parent_issue_id => issue2.id) + issue1.reload + child.reload + + r = IssueRelation.new( + :issue_from => child, :issue_to => issue1, + :relation_type => IssueRelation::TYPE_PRECEDES + ) + assert !r.save + assert_include 'This relation would create a circular dependency', r.errors.full_messages + end + + def test_subtasks_should_allow_precedes_relation + parent = Issue.generate! + child1 = Issue.generate!(:parent_issue_id => parent.id) + child2 = Issue.generate!(:parent_issue_id => parent.id) + + r = IssueRelation.new( + :issue_from => child1, :issue_to => child2, + :relation_type => IssueRelation::TYPE_PRECEDES + ) + assert r.valid? + assert r.save + end + + def test_validates_circular_dependency_on_reverse_relations + IssueRelation.delete_all + assert IssueRelation.create!( + :issue_from => Issue.find(1), :issue_to => Issue.find(3), + :relation_type => IssueRelation::TYPE_BLOCKS + ) + assert IssueRelation.create!( + :issue_from => Issue.find(1), :issue_to => Issue.find(2), + :relation_type => IssueRelation::TYPE_BLOCKED + ) + r = IssueRelation.new( + :issue_from => Issue.find(2), :issue_to => Issue.find(1), + :relation_type => IssueRelation::TYPE_BLOCKED + ) + assert !r.save + assert_not_nil r.errors[:base] + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/73/733b596d53c40ed4baf1da5919b8ef878d966efe.svn-base --- a/.svn/pristine/73/733b596d53c40ed4baf1da5919b8ef878d966efe.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,37 +0,0 @@ -<%= wiki_page_breadcrumb(@page) %> - -

    <%= h(@page.pretty_title) %>

    - -

    <%= l(:label_history) %>

    - -<% form_tag({:action => "diff"}, :method => :get) do %> - - - - - - - - - - - -<% show_diff = @versions.size > 1 %> -<% line_num = 1 %> -<% @versions.each do |ver| %> -"> - - - - - - - - -<% line_num += 1 %> -<% end %> - -
    #<%= l(:field_updated_on) %><%= l(:field_author) %><%= l(:field_comments) %>
    <%= link_to h(ver.version), :action => 'show', :id => @page.title, :project_id => @page.project, :version => ver.version %><%= radio_button_tag('version', ver.version, (line_num==1), :id => "cb-#{line_num}", :onclick => "$('cbto-#{line_num+1}').checked=true;") if show_diff && (line_num < @versions.size) %><%= radio_button_tag('version_from', ver.version, (line_num==2), :id => "cbto-#{line_num}") if show_diff && (line_num > 1) %><%= format_time(ver.updated_on) %><%= link_to_user ver.author %><%=h ver.comments %><%= link_to l(:button_annotate), :action => 'annotate', :id => @page.title, :version => ver.version %>
    -<%= submit_tag l(:label_view_diff), :class => 'small' if show_diff %> -<%= pagination_links_full @version_pages, @version_count, :page_param => :p %> -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/73/734682732665154f00709d93da629e994804ff9b.svn-base --- a/.svn/pristine/73/734682732665154f00709d93da629e994804ff9b.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,10 +0,0 @@ -require 'redmine' - -Redmine::Plugin.register :<%= plugin_name %> do - name '<%= plugin_pretty_name %> plugin' - author 'Author name' - description 'This is a plugin for Redmine' - version '0.0.1' - url 'http://example.com/path/to/plugin' - author_url 'http://example.com/about' -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/73/734f23aed8fb2921f077702e98029d9edca35572.svn-base --- a/.svn/pristine/73/734f23aed8fb2921f077702e98029d9edca35572.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,187 +0,0 @@ -#!/usr/bin/env ruby - -# == Synopsis -# -# Reads an email from standard input and forward it to a Redmine server -# through a HTTP request. -# -# == Usage -# -# rdm-mailhandler [options] --url= --key= -# -# == Arguments -# -# -u, --url URL of the Redmine server -# -k, --key Redmine API key -# -# General options: -# --unknown-user=ACTION how to handle emails from an unknown user -# ACTION can be one of the following values: -# ignore: email is ignored (default) -# accept: accept as anonymous user -# create: create a user account -# --no-permission-check disable permission checking when receiving -# the email -# --key-file=PATH path to a file that contains the Redmine -# API key (use this option instead of --key -# if you don't the key to appear in the -# command line) -# --no-check-certificate do not check server certificate -# -h, --help show this help -# -v, --verbose show extra information -# -V, --version show version information and exit -# -# Issue attributes control options: -# -p, --project=PROJECT identifier of the target project -# -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 -# -o, --allow-override=ATTRS allow email content to override attributes -# specified by previous options -# ATTRS is a comma separated list of attributes -# -# == Examples -# No project specified. Emails MUST contain the 'Project' keyword: -# -# rdm-mailhandler --url http://redmine.domain.foo --key secret -# -# Fixed project and default tracker specified, but emails can override -# both tracker and priority attributes using keywords: -# -# rdm-mailhandler --url https://domain.foo/redmine --key secret \\ -# --project foo \\ -# --tracker bug \\ -# --allow-override tracker,priority - -require 'net/http' -require 'net/https' -require 'uri' -require 'getoptlong' -require 'rdoc/usage' - -module Net - class HTTPS < HTTP - def self.post_form(url, params, headers, options={}) - request = Post.new(url.path) - request.form_data = params - request.basic_auth url.user, url.password if url.user - request.initialize_http_header(headers) - http = new(url.host, url.port) - http.use_ssl = (url.scheme == 'https') - if options[:no_check_certificate] - http.verify_mode = OpenSSL::SSL::VERIFY_NONE - end - http.start {|h| h.request(request) } - end - end -end - -class RedmineMailHandler - VERSION = '0.1' - - attr_accessor :verbose, :issue_attributes, :allow_override, :unknown_user, :no_permission_check, :url, :key, :no_check_certificate - - def initialize - self.issue_attributes = {} - - opts = GetoptLong.new( - [ '--help', '-h', GetoptLong::NO_ARGUMENT ], - [ '--version', '-V', GetoptLong::NO_ARGUMENT ], - [ '--verbose', '-v', GetoptLong::NO_ARGUMENT ], - [ '--url', '-u', GetoptLong::REQUIRED_ARGUMENT ], - [ '--key', '-k', GetoptLong::REQUIRED_ARGUMENT], - [ '--key-file', 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], - [ '--allow-override', '-o', GetoptLong::REQUIRED_ARGUMENT], - [ '--unknown-user', GetoptLong::REQUIRED_ARGUMENT], - [ '--no-permission-check', GetoptLong::NO_ARGUMENT], - [ '--no-check-certificate', GetoptLong::NO_ARGUMENT] - ) - - opts.each do |opt, arg| - case opt - when '--url' - self.url = arg.dup - when '--key' - self.key = arg.dup - when '--key-file' - begin - self.key = File.read(arg).strip - rescue Exception => e - $stderr.puts "Unable to read the key from #{arg}: #{e.message}" - exit 1 - end - when '--help' - usage - when '--verbose' - self.verbose = true - when '--version' - puts VERSION; exit - when '--project', '--status', '--tracker', '--category', '--priority' - self.issue_attributes[opt.gsub(%r{^\-\-}, '')] = arg.dup - when '--allow-override' - self.allow_override = arg.dup - when '--unknown-user' - self.unknown_user = arg.dup - when '--no-permission-check' - self.no_permission_check = '1' - when '--no-check-certificate' - self.no_check_certificate = true - end - end - - RDoc.usage if url.nil? - end - - def submit(email) - uri = url.gsub(%r{/*$}, '') + '/mail_handler' - - headers = { 'User-Agent' => "Redmine mail handler/#{VERSION}" } - - data = { 'key' => key, 'email' => email, - 'allow_override' => allow_override, - 'unknown_user' => unknown_user, - 'no_permission_check' => no_permission_check} - issue_attributes.each { |attr, value| data["issue[#{attr}]"] = value } - - debug "Posting to #{uri}..." - response = Net::HTTPS.post_form(URI.parse(uri), data, headers, :no_check_certificate => no_check_certificate) - debug "Response received: #{response.code}" - - case response.code.to_i - when 403 - warn "Request was denied by your Redmine server. " + - "Make sure that 'WS for incoming emails' is enabled in application settings and that you provided the correct API key." - return 77 - when 422 - warn "Request was denied by your Redmine server. " + - "Possible reasons: email is sent from an invalid email address or is missing some information." - return 77 - when 400..499 - warn "Request was denied by your Redmine server (#{response.code})." - return 77 - when 500..599 - warn "Failed to contact your Redmine server (#{response.code})." - return 75 - when 201 - debug "Proccessed successfully" - return 0 - else - return 1 - end - end - - private - - def debug(msg) - puts msg if verbose - end -end - -handler = RedmineMailHandler.new -exit(handler.submit(STDIN.read)) diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/73/736844820e97591097b5dc584954a779de2b5787.svn-base --- a/.svn/pristine/73/736844820e97591097b5dc584954a779de2b5787.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,283 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class AccountController < ApplicationController - helper :custom_fields - include CustomFieldsHelper - - # prevents login action to be filtered by check_if_login_required application scope filter - skip_before_filter :check_if_login_required - - # Login request and validation - def login - if request.get? - logout_user - else - authenticate_user - end - end - - # Log out current user and redirect to welcome page - def logout - logout_user - redirect_to home_url - end - - # Enable user to choose a new password - def lost_password - redirect_to(home_url) && return unless Setting.lost_password? - if params[:token] - @token = Token.find_by_action_and_value("recovery", params[:token]) - redirect_to(home_url) && return unless @token and !@token.expired? - @user = @token.user - if request.post? - @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation] - if @user.save - @token.destroy - flash[:notice] = l(:notice_account_password_updated) - redirect_to :action => 'login' - return - end - end - render :template => "account/password_recovery" - return - else - if request.post? - user = User.find_by_mail(params[:mail]) - # user not found in db - (flash.now[:error] = l(:notice_account_unknown_email); return) unless user - # user uses an external authentification - (flash.now[:error] = l(:notice_can_t_change_password); return) if user.auth_source_id - # create a new token for password recovery - token = Token.new(:user => user, :action => "recovery") - if token.save - Mailer.deliver_lost_password(token) - flash[:notice] = l(:notice_account_lost_email_sent) - redirect_to :action => 'login' - return - end - end - end - end - - # User self-registration - def register - redirect_to(home_url) && return unless Setting.self_registration? || session[:auth_source_registration] - if request.get? - session[:auth_source_registration] = nil - @user = User.new(:language => Setting.default_language) - else - @user = User.new(params[:user]) - @user.admin = false - @user.register - if session[:auth_source_registration] - @user.activate - @user.login = session[:auth_source_registration][:login] - @user.auth_source_id = session[:auth_source_registration][:auth_source_id] - if @user.save - session[:auth_source_registration] = nil - self.logged_user = @user - flash[:notice] = l(:notice_account_activated) - redirect_to :controller => 'my', :action => 'account' - end - else - @user.login = params[:user][:login] - @user.password, @user.password_confirmation = params[:password], params[:password_confirmation] - - case Setting.self_registration - when '1' - register_by_email_activation(@user) - when '3' - register_automatically(@user) - else - register_manually_by_administrator(@user) - end - end - end - end - - # Token based account activation - def activate - redirect_to(home_url) && return unless Setting.self_registration? && params[:token] - token = Token.find_by_action_and_value('register', params[:token]) - redirect_to(home_url) && return unless token and !token.expired? - user = token.user - redirect_to(home_url) && return unless user.registered? - user.activate - if user.save - token.destroy - flash[:notice] = l(:notice_account_activated) - end - redirect_to :action => 'login' - end - - private - - def logout_user - if User.current.logged? - cookies.delete :autologin - Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin']) - self.logged_user = nil - end - end - - def authenticate_user - if Setting.openid? && using_open_id? - open_id_authenticate(params[:openid_url]) - else - password_authentication - end - end - - def password_authentication - user = User.try_to_login(params[:username], params[:password]) - - if user.nil? - invalid_credentials - elsif user.new_record? - onthefly_creation_failed(user, {:login => user.login, :auth_source_id => user.auth_source_id }) - 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.register - - case Setting.self_registration - when '1' - register_by_email_activation(user) do - onthefly_creation_failed(user) - end - when '3' - register_automatically(user) do - onthefly_creation_failed(user) - end - else - register_manually_by_administrator(user) do - onthefly_creation_failed(user) - end - end - else - # Existing record - if user.active? - successful_authentication(user) - else - account_pending - end - 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? - set_autologin_cookie(user) - end - call_hook(:controller_account_success_authentication_after, {:user => user }) - redirect_back_or_default :controller => 'my', :action => 'page' - end - - def set_autologin_cookie(user) - token = Token.create(:user => user, :action => 'autologin') - cookie_name = Redmine::Configuration['autologin_cookie_name'] || 'autologin' - cookie_options = { - :value => token.value, - :expires => 1.year.from_now, - :path => (Redmine::Configuration['autologin_cookie_path'] || '/'), - :secure => (Redmine::Configuration['autologin_cookie_secure'] ? true : false), - :httponly => true - } - cookies[cookie_name] = cookie_options - 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 - - def invalid_credentials - logger.warn "Failed login for '#{params[:username]}' from #{request.remote_ip} at #{Time.now.utc}" - flash.now[:error] = l(:notice_account_invalid_creditentials) - 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.activate - user.last_login_on = Time.now - 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) - account_pending - else - yield if block_given? - end - end - - def account_pending - flash[:notice] = l(:notice_account_pending) - redirect_to :action => 'login' - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/73/73f54ba2c494fc625d52c6218a1293d90db7ca39.svn-base --- a/.svn/pristine/73/73f54ba2c494fc625d52c6218a1293d90db7ca39.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,46 +0,0 @@ -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 -r 0a574315af3e -r 4f746d8966dd .svn/pristine/74/7404304328de19a679b0fab3d9a0a81e75864fb9.svn-base --- a/.svn/pristine/74/7404304328de19a679b0fab3d9a0a81e75864fb9.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -

    <%= l(:text_say_hello) %>

    - -

    : <%= @value %>

    - -<%= link_to_if_authorized 'Good bye', :action => 'say_goodbye', :id => @project %> - -<% content_for :header_tags do %> - <%= stylesheet_link_tag "example.css", :plugin => "sample_plugin", :media => "screen" %> -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/74/741eb5d407aff37a5362c7877d98828f37bf017e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/74/741eb5d407aff37a5362c7877d98828f37bf017e.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,1090 @@ +hr: + direction: ltr + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%m/%d/%Y" + short: "%b %d" + long: "%B %d, %Y" + + day_names: [Ponedjeljak, Utorak, Srijeda, ÄŒetvrtak, Petak, Subota, Nedjelja] + abbr_day_names: [Ned, Pon, Uto, Sri, ÄŒet, Pet, Sub] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, Sijecanj, Veljaca, Ožujak, Travanj, Svibanj, Lipanj, Srpanj, Kolovoz, Rujan, Listopad, Studeni, Prosinac] + abbr_month_names: [~, Sij, Velj, Ožu, Tra, Svi, Lip, Srp, Kol, Ruj, List, Stu, Pro] + # Used in date_select and datime_select. + order: + - :year + - :month + - :day + + time: + formats: + default: "%m/%d/%Y %I:%M %p" + time: "%I:%M %p" + short: "%d %b %H:%M" + long: "%B %d, %Y %H:%M" + am: "am" + pm: "pm" + + datetime: + distance_in_words: + half_a_minute: "pola minute" + less_than_x_seconds: + one: "manje od sekunde" + other: "manje od %{count} sekundi" + x_seconds: + one: "1 sekunda" + other: "%{count} sekundi" + less_than_x_minutes: + one: "manje od minute" + other: "manje od %{count} minuta" + x_minutes: + one: "1 minuta" + other: "%{count} minuta" + about_x_hours: + one: "oko sat vremena" + other: "oko %{count} sati" + x_hours: + one: "1 sata" + other: "%{count} sati" + x_days: + one: "1 dan" + other: "%{count} dana" + about_x_months: + one: "oko 1 mjesec" + other: "oko %{count} mjeseci" + x_months: + one: "mjesec" + other: "%{count} mjeseci" + about_x_years: + one: "1 godina" + other: "%{count} godina" + over_x_years: + one: "preko 1 godine" + other: "preko %{count} godina" + + number: + format: + separator: "." + delimiter: "" + precision: 3 + human: + format: + delimiter: "" + precision: 3 + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + +# Used in array.to_sentence. + support: + array: + sentence_connector: "i" + skip_last_comma: false + + activerecord: + errors: + template: + header: + one: "1 error prohibited this %{model} from being saved" + other: "%{count} errors prohibited this %{model} from being saved" + messages: + inclusion: "nije ukljuceno u listu" + exclusion: "je rezervirano" + invalid: "nije ispravno" + confirmation: "ne odgovara za potvrdu" + accepted: "mora biti prihvaćen" + empty: "ne može biti prazno" + blank: "ne može biti razmaka" + too_long: "je predug (maximum is %{count} characters)" + too_short: "je prekratak (minimum is %{count} characters)" + wrong_length: "je pogreÅ¡ne dužine (should be %{count} characters)" + taken: "već je zauzeto" + not_a_number: "nije broj" + not_a_date: "nije ispravan datum" + greater_than: "mora biti veći od %{count}" + greater_than_or_equal_to: "mora biti veći ili jednak %{count}" + equal_to: "mora biti jednak %{count}" + less_than: "mora biti manji od %{count}" + less_than_or_equal_to: "mora bit manji ili jednak%{count}" + odd: "mora biti neparan" + even: "mora biti paran" + greater_than_start_date: "mora biti veci nego pocetni datum" + not_same_project: "ne pripada istom projektu" + circular_dependency: "Ovaj relacija stvara kružnu ovisnost" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" + + actionview_instancetag_blank_option: Molimo odaberite + + general_text_No: 'Ne' + general_text_Yes: 'Da' + general_text_no: 'ne' + general_text_yes: 'da' + general_lang_name: 'Hrvatski' + general_csv_separator: ',' + general_csv_decimal_separator: '.' + general_csv_encoding: UTF-8 + general_pdf_encoding: UTF-8 + general_first_day_of_week: '7' + + notice_account_updated: VaÅ¡ profil je uspjeÅ¡no promijenjen. + notice_account_invalid_creditentials: Neispravno korisniÄko ime ili zaporka. + notice_account_password_updated: Zaporka je uspjeÅ¡no promijenjena. + notice_account_wrong_password: PogreÅ¡na zaporka + notice_account_register_done: Racun je uspjeÅ¡no napravljen. Da biste aktivirali svoj raÄun, kliknite na link koji vam je poslan na e-mail. + notice_account_unknown_email: Nepoznati korisnik. + notice_can_t_change_password: Ovaj raÄun koristi eksterni izvor prijavljivanja. Nemoguće je promijeniti zaporku. + notice_account_lost_email_sent: E-mail s uputama kako bi odabrali novu zaporku je poslan na na vaÅ¡u e-mail adresu. + notice_account_activated: VaÅ¡ racun je aktiviran. Možete se prijaviti. + notice_successful_create: UspjeÅ¡no napravljeno. + notice_successful_update: UspjeÅ¡na promjena. + notice_successful_delete: UspjeÅ¡no brisanje. + notice_successful_connection: UspjeÅ¡na veza. + notice_file_not_found: Stranica kojoj ste pokuÅ¡ali pristupiti ne postoji ili je uklonjena. + notice_locking_conflict: Podataci su ažurirani od strane drugog korisnika. + notice_not_authorized: Niste ovlaÅ¡teni za pristup ovoj stranici. + notice_email_sent: E-mail je poslan %{value}" + notice_email_error: Dogodila se pogreÅ¡ka tijekom slanja E-maila (%{value})" + notice_feeds_access_key_reseted: VaÅ¡ RSS pristup je resetovan. + notice_api_access_key_reseted: VaÅ¡ API pristup je resetovan. + notice_failed_to_save_issues: "Neuspjelo spremanje %{count} predmeta na %{total} odabrane: %{ids}." + notice_no_issue_selected: "Niti jedan predmet nije odabran! Molim, odaberite predmete koje želite urediti." + notice_account_pending: "VaÅ¡ korisnicki raÄun je otvoren, Äeka odobrenje administratora." + notice_default_data_loaded: Konfiguracija je uspjeÅ¡no uÄitana. + notice_unable_delete_version: Nije moguće izbrisati verziju. + notice_issue_done_ratios_updated: Issue done ratios updated. + + error_can_t_load_default_data: "Zadanu konfiguracija nije uÄitana: %{value}" + error_scm_not_found: "Unos i/ili revizija nije pronaÄ‘en." + error_scm_command_failed: "Dogodila se pogreÅ¡ka prilikom pokuÅ¡aja pristupa: %{value}" + error_scm_annotate: "Ne postoji ili ne može biti obilježen." + error_issue_not_found_in_project: 'Nije pronaÄ‘en ili ne pripada u ovaj projekt' + error_no_tracker_in_project: 'No tracker is associated to this project. Please check the Project settings.' + error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").' + error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version can not be reopened' + error_can_not_archive_project: This project can not be archived + error_issue_done_ratios_not_updated: "Issue done ratios not updated." + error_workflow_copy_source: 'Please select a source tracker or role' + error_workflow_copy_target: 'Please select target tracker(s) and role(s)' + + warning_attachments_not_saved: "%{count} Datoteka/e nije mogla biti spremljena." + + mail_subject_lost_password: "VaÅ¡a %{value} zaporka" + mail_body_lost_password: 'Kako biste promijenili VaÅ¡u zaporku slijedite poveznicu:' + mail_subject_register: "Aktivacija korisniÄog raÄuna %{value}" + mail_body_register: 'Da biste aktivirali svoj raÄun, kliknite na sljedeci link:' + mail_body_account_information_external: "Možete koristiti vaÅ¡ raÄun %{value} za prijavu." + mail_body_account_information: VaÅ¡i korisniÄki podaci + mail_subject_account_activation_request: "%{value} predmet za aktivaciju korisniÄkog raÄuna" + mail_body_account_activation_request: "Novi korisnik (%{value}) je registriran. Njegov korisniÄki raÄun Äeka vaÅ¡e odobrenje:" + mail_subject_reminder: "%{count} predmet(a) dospijeva sljedećih %{days} dana" + mail_body_reminder: "%{count} vama dodijeljen(ih) predmet(a) dospijeva u sljedećih %{days} dana:" + mail_subject_wiki_content_added: "'%{id}' wiki page has been added" + mail_body_wiki_content_added: "The '%{id}' wiki page has been added by %{author}." + mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated" + mail_body_wiki_content_updated: "The '%{id}' wiki page has been updated by %{author}." + + + field_name: Ime + field_description: Opis + field_summary: Sažetak + field_is_required: Obavezno + field_firstname: Ime + field_lastname: Prezime + field_mail: E-poÅ¡ta + field_filename: Datoteka + field_filesize: VeliÄina + field_downloads: Preuzimanja + field_author: Autor + field_created_on: Napravljen + field_updated_on: Promijenjen + field_field_format: Format + field_is_for_all: Za sve projekte + field_possible_values: Moguće vrijednosti + field_regexp: Regularni izraz + field_min_length: Minimalna dužina + field_max_length: Maksimalna dužina + field_value: Vrijednost + field_category: Kategorija + field_title: Naslov + field_project: Projekt + field_issue: Predmet + field_status: Status + field_notes: Napomene + field_is_closed: Predmet je zatvoren + field_is_default: Zadana vrijednost + field_tracker: Tracker + field_subject: Predmet + field_due_date: Do datuma + field_assigned_to: Dodijeljeno + field_priority: Prioritet + field_fixed_version: Verzija + field_user: Korisnik + field_role: Uloga + field_homepage: Naslovnica + field_is_public: Javni projekt + field_parent: Potprojekt od + field_is_in_roadmap: Predmeti se prikazuju u Putokazu + field_login: KorisniÄko ime + field_mail_notification: Obavijest putem e-poÅ¡te + field_admin: Administrator + field_last_login_on: Zadnja prijava + field_language: Primarni jezik + field_effective_date: Datum + field_password: Zaporka + field_new_password: Nova zaporka + field_password_confirmation: Potvrda zaporke + field_version: Verzija + field_type: Tip + field_host: Host + field_port: Port + field_account: Racun + field_base_dn: Osnovni DN + field_attr_login: Login atribut + field_attr_firstname: Atribut imena + field_attr_lastname: Atribut prezimena + field_attr_mail: Atribut e-poÅ¡te + field_onthefly: "Izrada korisnika \"u hodu\"" + field_start_date: Pocetak + field_done_ratio: "% UÄinjeno" + field_auth_source: Vrsta prijavljivanja + field_hide_mail: Sakrij moju adresu e-poÅ¡te + field_comments: Komentar + field_url: URL + field_start_page: PoÄetna stranica + field_subproject: Potprojekt + field_hours: Sati + field_activity: Aktivnost + field_spent_on: Datum + field_identifier: Identifikator + field_is_filter: KoriÅ¡teno kao filtar + field_issue_to_id: Povezano s predmetom + field_delay: Odgodeno + field_assignable: Predmeti mogu biti dodijeljeni ovoj ulozi + field_redirect_existing_links: Preusmjeravanje postojećih linkova + field_estimated_hours: Procijenjeno vrijeme + field_column_names: Stupci + field_time_zone: Vremenska zona + field_searchable: Pretraživo + field_default_value: Zadana vrijednost + field_comments_sorting: Prikaz komentara + field_parent_title: Parent page + field_editable: Editable + field_watcher: Watcher + field_identity_url: OpenID URL + field_content: Content + field_group_by: Group results by + + setting_app_title: Naziv aplikacije + setting_app_subtitle: Podnaslov aplikacije + setting_welcome_text: Tekst dobrodoÅ¡lice + setting_default_language: Zadani jezik + setting_login_required: Potrebna je prijava + setting_self_registration: Samoregistracija je dozvoljena + setting_attachment_max_size: Maksimalna veliÄina privitka + setting_issues_export_limit: OgraniÄenje izvoza predmeta + setting_mail_from: Izvorna adresa e-poÅ¡te + setting_bcc_recipients: Blind carbon copy primatelja (bcc) + setting_plain_text_mail: obiÄni tekst poÅ¡te (bez HTML-a) + setting_host_name: Naziv domaćina (host) + setting_text_formatting: Oblikovanje teksta + setting_wiki_compression: Sažimanje + setting_feeds_limit: Ogranicenje unosa sadržaja + setting_default_projects_public: Novi projekti su javni po defaultu + setting_autofetch_changesets: Autofetch commits + setting_sys_api_enabled: Omogući WS za upravljanje skladiÅ¡tem + setting_commit_ref_keywords: Referentne kljuÄne rijeÄi + setting_commit_fix_keywords: Fiksne kljuÄne rijeÄi + setting_autologin: Automatska prijava + setting_date_format: Format datuma + setting_time_format: Format vremena + setting_cross_project_issue_relations: Dozvoli povezivanje predmeta izmedu razliÄitih projekata + setting_issue_list_default_columns: Stupci prikazani na listi predmeta + setting_emails_footer: Zaglavlje e-poÅ¡te + setting_protocol: Protokol + setting_per_page_options: Objekata po stranici opcija + setting_user_format: Oblik prikaza korisnika + setting_activity_days_default: Dani prikazane aktivnosti na projektu + setting_display_subprojects_issues: Prikaz predmeta potprojekta na glavnom projektu po defaultu + setting_enabled_scm: Omogućen SCM + setting_mail_handler_body_delimiters: "Truncate emails after one of these lines" + setting_mail_handler_api_enabled: Omoguci WS za dolaznu e-poÅ¡tu + setting_mail_handler_api_key: API kljuÄ + setting_sequential_project_identifiers: Generiraj slijedne identifikatore projekta + setting_gravatar_enabled: Koristi Gravatar korisniÄke ikone + setting_gravatar_default: Default Gravatar image + setting_diff_max_lines_displayed: Maksimalni broj diff linija za prikazati + setting_file_max_size_displayed: Max size of text files displayed inline + setting_repository_log_display_limit: Maximum number of revisions displayed on file log + setting_openid: Allow OpenID login and registration + setting_password_min_length: Minimum password length + setting_new_project_user_role_id: Role given to a non-admin user who creates a project + setting_default_projects_modules: Default enabled modules for new projects + setting_issue_done_ratio: Calculate the issue done ratio with + setting_issue_done_ratio_issue_field: Use the issue field + setting_issue_done_ratio_issue_status: Use the issue status + setting_start_of_week: Start calendars on + setting_rest_api_enabled: Enable REST web service + + permission_add_project: Dodaj projekt + permission_add_subprojects: Dodaj potprojekt + permission_edit_project: Uredi projekt + permission_select_project_modules: Odaberi projektne module + permission_manage_members: Upravljaj Älanovima + permission_manage_versions: Upravljaj verzijama + permission_manage_categories: Upravljaj kategorijama predmeta + permission_view_issues: Pregledaj zahtjeve + permission_add_issues: Dodaj predmete + permission_edit_issues: Uredi predmete + permission_manage_issue_relations: Upravljaj relacijama predmeta + permission_add_issue_notes: Dodaj biljeÅ¡ke + permission_edit_issue_notes: Uredi biljeÅ¡ke + permission_edit_own_issue_notes: Uredi vlastite biljeÅ¡ke + permission_move_issues: Premjesti predmete + permission_delete_issues: Brisanje predmeta + permission_manage_public_queries: Upravljaj javnim upitima + permission_save_queries: Spremi upite + permission_view_gantt: Pregledaj gantt grafikon + permission_view_calendar: Pregledaj kalendar + permission_view_issue_watchers: Pregledaj listu promatraca + permission_add_issue_watchers: Dodaj promatraÄa + permission_delete_issue_watchers: Delete watchers + permission_log_time: Dnevnik utroÅ¡enog vremena + permission_view_time_entries: Pregledaj utroÅ¡eno vrijeme + permission_edit_time_entries: Uredi vremenske dnevnike + permission_edit_own_time_entries: Edit own time logs + permission_manage_news: Upravljaj novostima + permission_comment_news: Komentiraj novosti + permission_view_documents: Pregledaj dokumente + permission_manage_files: Upravljaj datotekama + permission_view_files: Pregledaj datoteke + permission_manage_wiki: Upravljaj wikijem + permission_rename_wiki_pages: Promijeni ime wiki stranicama + permission_delete_wiki_pages: ObriÅ¡i wiki stranice + permission_view_wiki_pages: Pregledaj wiki + permission_view_wiki_edits: Pregledaj povijest wikija + permission_edit_wiki_pages: Uredi wiki stranice + permission_delete_wiki_pages_attachments: ObriÅ¡i privitke + permission_protect_wiki_pages: ZaÅ¡titi wiki stranice + permission_manage_repository: Upravljaj skladiÅ¡tem + permission_browse_repository: Browse repository + permission_view_changesets: View changesets + permission_commit_access: Mogućnost pohranjivanja + permission_manage_boards: Manage boards + permission_view_messages: Pregledaj poruke + permission_add_messages: Objavi poruke + permission_edit_messages: Uredi poruke + permission_edit_own_messages: Uredi vlastite poruke + permission_delete_messages: ObriÅ¡i poruke + permission_delete_own_messages: ObriÅ¡i vlastite poruke + + project_module_issue_tracking: Praćenje predmeta + project_module_time_tracking: Praćenje vremena + project_module_news: Novosti + project_module_documents: Dokumenti + project_module_files: Datoteke + project_module_wiki: Wiki + project_module_repository: SkladiÅ¡te + project_module_boards: Boards + + label_user: Korisnik + label_user_plural: Korisnici + label_user_new: Novi korisnik + label_user_anonymous: Anonymous + label_project: Projekt + label_project_new: Novi projekt + label_project_plural: Projekti + label_x_projects: + zero: no projects + one: 1 project + other: "%{count} projects" + label_project_all: Svi Projekti + label_project_latest: Najnoviji projekt + label_issue: Predmet + label_issue_new: Novi predmet + label_issue_plural: Predmeti + label_issue_view_all: Pregled svih predmeta + label_issues_by: "Predmeti od %{value}" + label_issue_added: Predmet dodan + label_issue_updated: Predmet promijenjen + label_document: Dokument + label_document_new: Novi dokument + label_document_plural: Dokumenti + label_document_added: Dokument dodan + label_role: Uloga + label_role_plural: Uloge + label_role_new: Nova uloga + label_role_and_permissions: Uloge i ovlasti + label_member: ÄŒlan + label_member_new: Novi Älan + label_member_plural: ÄŒlanovi + label_tracker: Vrsta + label_tracker_plural: Vrste predmeta + label_tracker_new: Nova vrsta + label_workflow: Tijek rada + label_issue_status: Status predmeta + label_issue_status_plural: Status predmeta + label_issue_status_new: Novi status + label_issue_category: Kategorija predmeta + label_issue_category_plural: Kategorije predmeta + label_issue_category_new: Nova kategorija + label_custom_field: KorisniÄki definirano polje + label_custom_field_plural: KorisniÄki definirana polja + label_custom_field_new: Novo korisniÄki definirano polje + label_enumerations: Pobrojenice + label_enumeration_new: Nova vrijednost + label_information: Informacija + label_information_plural: Informacije + label_please_login: Molim prijavite se + label_register: Registracija + label_login_with_open_id_option: or login with OpenID + label_password_lost: Izgubljena zaporka + label_home: PoÄetna stranica + label_my_page: Moja stranica + label_my_account: Moj profil + label_my_projects: Moji projekti + label_administration: Administracija + label_login: Korisnik + label_logout: Odjava + label_help: Pomoć + label_reported_issues: Prijavljeni predmeti + label_assigned_to_me_issues: Moji predmeti + label_last_login: Last connection + label_registered_on: Registrirano + label_activity: Aktivnosti + label_overall_activity: Aktivnosti + label_user_activity: "%{value} ova/ina aktivnost" + label_new: Novi + label_logged_as: Prijavljeni ste kao + label_environment: Okolina + label_authentication: Autentikacija + label_auth_source: NaÄin prijavljivanja + label_auth_source_new: Novi naÄin prijavljivanja + label_auth_source_plural: NaÄini prijavljivanja + label_subproject_plural: Potprojekti + label_subproject_new: Novi potprojekt + label_and_its_subprojects: "%{value} i njegovi potprojekti" + label_min_max_length: Min - Maks veliÄina + label_list: Liste + label_date: Datum + label_integer: Integer + label_float: Float + label_boolean: Boolean + label_string: Text + label_text: Long text + label_attribute: Atribut + label_attribute_plural: Atributi + label_no_data: Nema podataka za prikaz + label_change_status: Promjena statusa + label_history: Povijest + label_attachment: Datoteka + label_attachment_new: Nova datoteka + label_attachment_delete: Brisanje datoteke + label_attachment_plural: Datoteke + label_file_added: Datoteka dodana + label_report: Izvješće + label_report_plural: Izvješća + label_news: Novosti + label_news_new: Dodaj novost + label_news_plural: Novosti + label_news_latest: Novosti + label_news_view_all: Pregled svih novosti + label_news_added: Novosti dodane + label_settings: Postavke + label_overview: Pregled + label_version: Verzija + label_version_new: Nova verzija + label_version_plural: Verzije + label_confirmation: Potvrda + label_export_to: 'Izvoz u:' + label_read: ÄŒitaj... + label_public_projects: Javni projekti + label_open_issues: Otvoren + label_open_issues_plural: Otvoreno + label_closed_issues: Zatvoren + label_closed_issues_plural: Zatvoreno + label_x_open_issues_abbr_on_total: + zero: 0 open / %{total} + one: 1 open / %{total} + other: "%{count} open / %{total}" + label_x_open_issues_abbr: + zero: 0 open + one: 1 open + other: "%{count} open" + label_x_closed_issues_abbr: + zero: 0 closed + one: 1 closed + other: "%{count} closed" + label_total: Ukupno + label_permissions: Dozvole + label_current_status: Trenutni status + label_new_statuses_allowed: Novi status je dozvoljen + label_all: Svi + label_none: nema + label_nobody: nitko + label_next: Naredni + label_previous: Prethodni + label_used_by: KoriÅ¡ten od + label_details: Detalji + label_add_note: Dodaj napomenu + label_per_page: Po stranici + label_calendar: Kalendar + label_months_from: Mjeseci od + label_gantt: Gantt + label_internal: Interno + label_last_changes: "Posljednjih %{count} promjena" + label_change_view_all: Prikaz svih promjena + label_personalize_page: Prilagodite ovu stranicu + label_comment: Komentar + label_comment_plural: Komentari + label_x_comments: + zero: no comments + one: 1 comment + other: "%{count} comments" + label_comment_add: Dodaj komentar + label_comment_added: Komentar dodan + label_comment_delete: Brisanje komentara + label_query: KorisniÄki upit + label_query_plural: KorisniÄki upiti + label_query_new: Novi upit + label_filter_add: Dodaj filtar + label_filter_plural: Filtri + label_equals: je + label_not_equals: nije + label_in_less_than: za manje od + label_in_more_than: za viÅ¡e od + label_greater_or_equal: '>=' + label_less_or_equal: '<=' + label_in: za toÄno + label_today: danas + label_all_time: sva vremena + label_yesterday: juÄer + label_this_week: ovog tjedna + label_last_week: proÅ¡log tjedna + label_last_n_days: "zadnjih %{count} dana" + label_this_month: ovog mjeseca + label_last_month: proÅ¡log mjeseca + label_this_year: ove godine + label_date_range: vremenski raspon + label_less_than_ago: manje od + label_more_than_ago: viÅ¡e od + label_ago: prije + label_contains: Sadrži + label_not_contains: ne sadrži + label_day_plural: dana + label_repository: SkladiÅ¡te + label_repository_plural: SkladiÅ¡ta + label_browse: Pregled + label_branch: Branch + label_tag: Tag + label_revision: Revizija + label_revision_plural: Revizije + label_revision_id: "Revision %{value}" + label_associated_revisions: Dodijeljene revizije + label_added: dodano + label_modified: promijenjen + label_copied: kopirano + label_renamed: preimenovano + label_deleted: obrisano + label_latest_revision: Posljednja revizija + label_latest_revision_plural: Posljednje revizije + label_view_revisions: Pregled revizija + label_view_all_revisions: View all revisions + label_max_size: Maksimalna veliÄina + label_sort_highest: Premjesti na vrh + label_sort_higher: Premjesti prema gore + label_sort_lower: Premjesti prema dolje + label_sort_lowest: Premjesti na dno + label_roadmap: Putokaz + label_roadmap_due_in: "ZavrÅ¡ava se za %{value}" + label_roadmap_overdue: "%{value} kasni" + label_roadmap_no_issues: Nema predmeta za ovu verziju + label_search: Traži + label_result_plural: Rezultati + label_all_words: Sve rijeÄi + label_wiki: Wiki + label_wiki_edit: Wiki promjena + label_wiki_edit_plural: Wiki promjene + label_wiki_page: Wiki stranica + label_wiki_page_plural: Wiki stranice + label_index_by_title: Indeks po naslovima + 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 promjena + label_issue_tracking: Praćenje predmeta + label_spent_time: UtroÅ¡eno vrijeme + label_f_hour: "%{value} sata" + label_f_hour_plural: "%{value} sati" + label_time_tracking: Praćenje vremena + label_change_plural: Promjene + label_statistics: Statistika + label_commits_per_month: Pohrana po mjesecu + label_commits_per_author: Pohrana po autoru + label_view_diff: Pregled razlika + label_diff_inline: uvuÄeno + label_diff_side_by_side: paralelno + label_options: Opcije + label_copy_workflow_from: Kopiraj tijek rada od + label_permissions_report: Izvješće o dozvolama + label_watched_issues: Praćeni predmeti + label_related_issues: Povezani predmeti + label_applied_status: Primijenjen status + label_loading: UÄitavam... + label_relation_new: Nova relacija + label_relation_delete: Brisanje relacije + label_relates_to: u relaciji sa + label_duplicates: Duplira + label_duplicated_by: ponovljen kao + label_blocks: blokira + label_blocked_by: blokiran od strane + label_precedes: prethodi + label_follows: slijedi + label_end_to_start: od kraja do poÄetka + label_end_to_end: od kraja do kraja + label_end_to_start: od kraja do poÄetka + label_end_to_end: od kraja do kraja + label_stay_logged_in: Ostanite prijavljeni + label_disabled: IskljuÄen + label_show_completed_versions: Prikaži zavrÅ¡ene verzije + label_me: ja + label_board: Forum + label_board_new: Novi forum + label_board_plural: Forumi + label_topic_plural: Teme + label_message_plural: Poruke + label_message_last: Posljednja poruka + label_message_new: Nova poruka + label_message_posted: Poruka dodana + label_reply_plural: Odgovori + label_send_information: PoÅ¡alji korisniku informaciju o profilu + label_year: Godina + label_month: Mjesec + label_week: Tjedan + label_date_from: Od + label_date_to: Do + label_language_based: Zasnovano na jeziku + label_sort_by: "Uredi po %{value}" + label_send_test_email: PoÅ¡alji testno E-pismo + label_feeds_access_key: RSS access key + label_missing_feeds_access_key: Missing a RSS access key + label_feeds_access_key_created_on: "RSS kljuc za pristup je napravljen prije %{value}" + label_module_plural: Moduli + label_added_time_by: "Promijenio %{author} prije %{age}" + label_updated_time_by: "Dodao/la %{author} prije %{age}" + label_updated_time: "Promijenjeno prije %{value}" + label_jump_to_a_project: Prebaci se na projekt... + label_file_plural: Datoteke + label_changeset_plural: Promjene + label_default_columns: Zadani stupci + label_no_change_option: (Bez promjene) + label_bulk_edit_selected_issues: ZajedniÄka promjena izabranih predmeta + label_theme: Tema + label_default: Zadana + label_search_titles_only: Pretraživanje samo naslova + label_user_mail_option_all: "Za bilo koji dogaÄ‘aj na svim mojim projektima" + label_user_mail_option_selected: "Za bilo koji dogaÄ‘aj samo za izabrane projekte..." + label_user_mail_no_self_notified: "Ne želim primati obavijesti o promjenama koje sam napravim" + label_registration_activation_by_email: aktivacija putem e-poÅ¡te + label_registration_manual_activation: ruÄna aktivacija + label_registration_automatic_activation: automatska aktivacija + label_display_per_page: "Po stranici: %{value}" + label_age: Starost + label_change_properties: Promijeni svojstva + label_general: Općenito + label_more: JoÅ¡ + label_scm: SCM + label_plugins: Plugins + label_ldap_authentication: LDAP autentikacija + label_downloads_abbr: D/L + label_optional_description: Opcije + label_add_another_file: Dodaj joÅ¡ jednu datoteku + label_preferences: Preferences + label_chronological_order: U kronoloÅ¡kom redoslijedu + label_reverse_chronological_order: U obrnutom kronoloÅ¡kom redoslijedu + label_planning: Planiranje + label_incoming_emails: Dolazne poruke e-poÅ¡te + label_generate_key: Generiraj kljuÄ + label_issue_watchers: PromatraÄi + label_example: Primjer + label_display: Display + label_sort: Sort + label_ascending: Ascending + label_descending: Descending + label_date_from_to: From %{start} to %{end} + label_wiki_content_added: Wiki page added + label_wiki_content_updated: Wiki page updated + label_group: Group + label_group_plural: Grupe + label_group_new: Nova grupa + label_time_entry_plural: Spent time + label_version_sharing_none: Not shared + label_version_sharing_descendants: With subprojects + label_version_sharing_hierarchy: With project hierarchy + label_version_sharing_tree: With project tree + label_version_sharing_system: With all projects + label_update_issue_done_ratios: Update issue done ratios + label_copy_source: Source + label_copy_target: Target + label_copy_same_as_target: Same as target + label_display_used_statuses_only: Only display statuses that are used by this tracker + label_api_access_key: API access key + label_missing_api_access_key: Missing an API access key + label_api_access_key_created_on: "API access key created %{value} ago" + + button_login: Prijavi + button_submit: PoÅ¡alji + button_save: Spremi + button_check_all: OznaÄi sve + button_uncheck_all: IskljuÄi sve + button_delete: ObriÅ¡i + button_create: Napravi + button_create_and_continue: Napravi i nastavi + button_test: Test + button_edit: Uredi + button_add: Dodaj + button_change: Promijeni + button_apply: Primijeni + button_clear: Ukloni + button_lock: ZakljuÄaj + button_unlock: OtkljuÄaj + button_download: Preuzmi + button_list: Spisak + button_view: Pregled + button_move: Premjesti + button_move_and_follow: Move and follow + button_back: Nazad + button_cancel: Odustani + button_activate: Aktiviraj + button_sort: Redoslijed + button_log_time: ZapiÅ¡i vrijeme + button_rollback: IzvrÅ¡i rollback na ovu verziju + button_watch: Prati + button_unwatch: Prekini pracenje + button_reply: Odgovori + button_archive: Arhiviraj + button_rollback: Dearhiviraj + button_reset: PoniÅ¡ti + button_rename: Promijeni ime + button_change_password: Promjena zaporke + button_copy: Kopiraj + button_copy_and_follow: Copy and follow + button_annotate: Annotate + button_update: Promijeni + button_configure: Konfiguracija + button_quote: Navod + button_duplicate: Duplicate + button_show: Show + + status_active: aktivan + status_registered: Registriran + status_locked: zakljuÄan + + version_status_open: open + version_status_locked: locked + version_status_closed: closed + + field_active: Active + + text_select_mail_notifications: Izbor akcija za koje će biti poslana obavijest e-poÅ¡tom. + text_regexp_info: eg. ^[A-Z0-9]+$ + text_min_max_length_info: 0 znaÄi bez ograniÄenja + text_project_destroy_confirmation: Da li ste sigurni da želite izbrisati ovaj projekt i sve njegove podatke? + text_subprojects_destroy_warning: "Njegov(i) potprojekt(i): %{value} će takoÄ‘er biti obrisan." + text_workflow_edit: Select a role and a tracker to edit the workflow + text_are_you_sure: Da li ste sigurni? + text_journal_changed: "%{label} promijenjen iz %{old} u %{new}" + text_journal_set_to: "%{label} postavi na %{value}" + text_journal_deleted: "%{label} izbrisano (%{old})" + text_journal_added: "%{label} %{value} added" + text_tip_issue_begin_day: Zadaci koji poÄinju ovog dana + text_tip_issue_end_day: zadaci koji se zavrÅ¡avaju ovog dana + text_tip_issue_begin_end_day: Zadaci koji poÄinju i zavrÅ¡avaju se ovog dana + text_caracters_maximum: "NajviÅ¡e %{count} znakova." + text_caracters_minimum: "Mora biti dugaÄko najmanje %{count} znakova." + text_length_between: "Dužina izmedu %{min} i %{max} znakova." + text_tracker_no_workflow: Tijek rada nije definiran za ovaj tracker + text_unallowed_characters: Nedozvoljeni znakovi + text_comma_separated: ViÅ¡estruke vrijednosti su dozvoljene (razdvojene zarezom). + text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages + text_tracker_no_workflow: No workflow defined for this tracker + text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages + text_issue_added: "Predmet %{id} je prijavljen (prijavio %{author})." + text_issue_updated: "Predmet %{id} je promijenjen %{author})." + text_wiki_destroy_confirmation: Da li ste sigurni da želite izbrisati ovaj wiki i njegov sadržaj? + text_issue_category_destroy_question: "Neke predmeti (%{count}) su dodijeljeni ovoj kategoriji. Å to želite uraditi?" + text_issue_category_destroy_assignments: Ukloni dodjeljivanje kategorija + text_issue_category_reassign_to: Ponovo dodijeli predmete ovoj kategoriji + text_user_mail_option: "Za neizabrane projekte, primit ćete obavjesti samo o stvarima koje pratite ili u kojima sudjelujete (npr. predmete koje ste vi napravili ili koje su vama dodjeljeni)." + 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: UÄitaj poÄetnu konfiguraciju + text_status_changed_by_changeset: "Applied in changeset %{value}." + text_issues_destroy_confirmation: 'Jeste li sigurni da želite obrisati izabrani/e predmet(e)?' + text_select_project_modules: 'Odaberite module koji će biti omogućeni za ovaj projekt:' + text_default_administrator_account_changed: Default administrator account changed + text_file_repository_writable: Dozvoljeno pisanje u direktorij za privitke + text_plugin_assets_writable: Plugin assets directory writable + text_rmagick_available: RMagick dostupan (nije obavezno) + text_destroy_time_entries_question: "%{hours} sati je prijavljeno za predmete koje želite obrisati. Å to ćete uÄiniti?" + text_destroy_time_entries: ObriÅ¡i prijavljene sate + text_assign_time_entries_to_project: Pridruži prijavljene sate projektu + text_reassign_time_entries: 'Premjesti prijavljene sate ovom predmetu:' + text_user_wrote: "%{value} je napisao/la:" + text_enumeration_destroy_question: "%{count} objekata je pridruženo toj vrijednosti." + text_enumeration_category_reassign_to: 'Premjesti ih ovoj vrijednosti:' + text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/configuration.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: '... Ovaj diff je odrezan zato Å¡to prelazi maksimalnu veliÄinu koja može biti prikazana.' + text_custom_field_possible_values_info: 'One line for each value' + text_wiki_page_destroy_question: "This page has %{descendants} child page(s) and descendant(s). What do you want to do?" + text_wiki_page_nullify_children: "Keep child pages as root pages" + text_wiki_page_destroy_children: "Delete child pages and all their descendants" + text_wiki_page_reassign_children: "Reassign child pages to this parent page" + default_role_manager: Upravitelj + default_role_developer: Razvojni inženjer + default_role_reporter: Korisnik + default_tracker_bug: PogreÅ¡ka + default_tracker_feature: Funkcionalnost + default_tracker_support: PodrÅ¡ka + default_issue_status_new: Novo + default_issue_status_assigned: Dodijeljeno + default_issue_status_resolved: RijeÅ¡eno + default_issue_status_feedback: Povratna informacija + default_issue_status_closed: Zatvoreno + default_issue_status_rejected: Odbaceno + default_doc_category_user: KorisniÄka dokumentacija + default_doc_category_tech: TehniÄka dokumentacija + default_priority_low: Nizak + default_priority_normal: Redovan + default_priority_high: Visok + default_priority_urgent: Hitan + default_priority_immediate: Odmah + default_activity_design: Dizajn + default_activity_development: Razvoj + enumeration_issue_priorities: Prioriteti predmeta + enumeration_doc_categories: Kategorija dokumenata + enumeration_activities: Aktivnosti (po vremenu) + enumeration_system_activity: System Activity + field_sharing: Sharing + text_line_separated: Multiple values allowed (one line for each value). + label_close_versions: Close completed versions + button_unarchive: Unarchive + label_start_to_end: start to end + label_start_to_start: start to start + field_issue_to: Related issue + default_issue_status_in_progress: In Progress + text_own_membership_delete_confirmation: |- + You are about to remove some or all of your permissions and may no longer be able to edit this project after that. + Are you sure you want to continue? + label_board_sticky: Sticky + label_board_locked: Locked + permission_export_wiki_pages: Export wiki pages + setting_cache_formatted_text: Cache formatted text + permission_manage_project_activities: Manage project activities + error_unable_delete_issue_status: Unable to delete issue status + label_profile: Profile + permission_manage_subtasks: Manage subtasks + field_parent_issue: Parent task + label_subtask_plural: Subtasks + label_project_copy_notifications: Send email notifications during the project copy + error_can_not_delete_custom_field: Unable to delete custom field + error_unable_to_connect: Unable to connect (%{value}) + error_can_not_remove_role: This role is in use and can not be deleted. + error_can_not_delete_tracker: This tracker contains issues and can't be deleted. + field_principal: Principal + label_my_page_block: My page block + notice_failed_to_save_members: "Failed to save member(s): %{errors}." + text_zoom_out: Zoom out + text_zoom_in: Zoom in + notice_unable_delete_time_entry: Unable to delete time log entry. + label_overall_spent_time: Overall spent time + field_time_entries: Log time + project_module_gantt: Gantt + project_module_calendar: Calendar + button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" + field_text: Text field + label_user_mail_option_only_owner: Only for things I am the owner of + setting_default_notification_option: Default notification option + label_user_mail_option_only_my_events: Only for things I watch or I'm involved in + label_user_mail_option_only_assigned: Only for things I am assigned to + label_user_mail_option_none: No events + field_member_of_group: Assignee's group + field_assigned_to_role: Assignee's role + notice_not_authorized_archived_project: The project you're trying to access has been archived. + label_principal_search: "Search for user or group:" + label_user_search: "Search for user:" + field_visible: Visible + setting_commit_logtime_activity_id: Activity for logged time + text_time_logged_by_changeset: Applied in changeset %{value}. + setting_commit_logtime_enabled: Enable time logging + notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) + setting_gantt_items_limit: Maximum number of items displayed on the gantt chart + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. + label_my_queries: My custom queries + text_journal_changed_no_detail: "%{label} updated" + label_news_comment_added: Comment added to a news + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + label_bulk_edit_selected_time_entries: Bulk edit selected time entries + text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? + label_role_anonymous: Anonymous + label_role_non_member: Non member + label_issue_note_added: Note added + label_issue_status_updated: Status updated + label_issue_priority_updated: Priority updated + label_issues_visibility_own: Issues created by or assigned to the user + field_issues_visibility: Issues visibility + label_issues_visibility_all: All issues + permission_set_own_issues_private: Set own issues public or private + field_is_private: Private + permission_set_issues_private: Set issues public or private + label_issues_visibility_public: All non private issues + text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). + field_commit_logs_encoding: Commit messages encoding + field_scm_path_encoding: Path encoding + text_scm_path_encoding_note: "Default: UTF-8" + field_path_to_repository: Path to repository + field_root_directory: Root directory + field_cvs_module: Module + field_cvsroot: CVSROOT + text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) + text_scm_command: Command + text_scm_command_version: Version + label_git_report_last_commit: Report last commit for files and directories + notice_issue_successful_create: Issue %{id} created. + label_between: between + setting_issue_group_assignment: Allow issue assignment to groups + label_diff: diff + text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) + description_query_sort_criteria_direction: Sort direction + description_project_scope: Search scope + description_filter: Filter + description_user_mail_notification: Mail notification settings + description_date_from: Enter start date + description_message_content: Message content + description_available_columns: Available Columns + description_date_range_interval: Choose range by selecting start and end date + description_issue_category_reassign: Choose issue category + description_search: Searchfield + description_notes: Notes + description_date_range_list: Choose range from list + description_choose_project: Projects + description_date_to: Enter end date + description_query_sort_criteria_attribute: Sort attribute + description_wiki_subpages_reassign: Choose new parent page + description_selected_columns: Selected Columns + label_parent_revision: Parent + label_child_revision: Child + error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. + setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues + button_edit_section: Edit this section + setting_repositories_encodings: Attachments and repositories encodings + description_all_columns: All Columns + button_export: Export + label_export_options: "%{export_format} export options" + error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) + notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." + label_x_issues: + zero: 0 predmet + one: 1 predmet + other: "%{count} predmeti" + label_repository_new: New repository + field_repository_is_default: Main repository + label_copy_attachments: Copy attachments + label_item_position: "%{position}/%{count}" + label_completed_versions: Completed versions + text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. + field_multiple: Multiple values + setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed + text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes + text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) + notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. + text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} + permission_manage_related_issues: Manage related issues + field_auth_source_ldap_filter: LDAP filter + label_search_for_watchers: Search for watchers to add + notice_account_deleted: Your account has been permanently deleted. + setting_unsubscribe: Allow users to delete their own account + button_delete_my_account: Delete my account + text_account_destroy_confirmation: |- + Are you sure you want to proceed? + Your account will be permanently deleted, with no way to reactivate it. + error_session_expired: Your session has expired. Please login again. + text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." + setting_session_lifetime: Session maximum lifetime + setting_session_timeout: Session inactivity timeout + label_session_expiration: Session expiration + permission_close_project: Close / reopen the project + label_show_closed_projects: View closed projects + button_close: Close + button_reopen: Reopen + project_status_active: active + project_status_closed: closed + project_status_archived: archived + text_project_closed: This project is closed and read-only. + notice_user_successful_create: User %{id} created. + field_core_fields: Standard fields + field_timeout: Timeout (in seconds) + setting_thumbnails_enabled: Display attachment thumbnails + setting_thumbnails_size: Thumbnails size (in pixels) + label_status_transitions: Status transitions + label_fields_permissions: Fields permissions + label_readonly: Read-only + label_required: Required + text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. + field_board_parent: Parent forum + label_attribute_of_project: Project's %{name} + label_attribute_of_author: Author's %{name} + label_attribute_of_assigned_to: Assignee's %{name} + label_attribute_of_fixed_version: Target version's %{name} + label_copy_subtasks: Copy subtasks + label_copied_to: copied to + label_copied_from: copied from + label_any_issues_in_project: any issues in project + label_any_issues_not_in_project: any issues not in project + field_private_notes: Private notes + permission_view_private_notes: View private notes + permission_set_notes_private: Set notes as private + label_no_issues_in_project: no issues in project + label_any: Svi + label_last_n_weeks: last %{count} weeks + setting_cross_project_subtasks: Allow cross-project subtasks + label_cross_project_descendants: With subprojects + label_cross_project_tree: With project tree + label_cross_project_hierarchy: With project hierarchy + label_cross_project_system: With all projects + button_hide: Hide + setting_non_working_week_days: Non-working days + label_in_the_next_days: in the next + label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Ukupno + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/74/743a4dc7b2a0298d7100e21b5ae7b16566c99a77.svn-base --- a/.svn/pristine/74/743a4dc7b2a0298d7100e21b5ae7b16566c99a77.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,93 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class WatchersController < ApplicationController - before_filter :find_project - before_filter :require_login, :check_project_privacy, :only => [:watch, :unwatch] - before_filter :authorize, :only => [:new, :destroy] - - verify :method => :post, - :only => [ :watch, :unwatch ], - :render => { :nothing => true, :status => :method_not_allowed } - - def watch - if @watched.respond_to?(:visible?) && !@watched.visible?(User.current) - render_403 - else - set_watcher(User.current, true) - end - end - - def unwatch - set_watcher(User.current, false) - end - - def new - @watcher = Watcher.new(params[:watcher]) - @watcher.watchable = @watched - @watcher.save if request.post? - respond_to do |format| - format.html { redirect_to :back } - format.js do - render :update do |page| - page.replace_html 'watchers', :partial => 'watchers/watchers', :locals => {:watched => @watched} - end - end - end - rescue ::ActionController::RedirectBackError - render :text => 'Watcher added.', :layout => true - end - - def destroy - @watched.set_watcher(User.find(params[:user_id]), false) if request.post? - respond_to do |format| - format.html { redirect_to :back } - format.js do - render :update do |page| - page.replace_html 'watchers', :partial => 'watchers/watchers', :locals => {:watched => @watched} - end - end - end - end - -private - def find_project - klass = Object.const_get(params[:object_type].camelcase) - return false unless klass.respond_to?('watched_by') - @watched = klass.find(params[:object_id]) - @project = @watched.project - rescue - render_404 - end - - def set_watcher(user, watching) - @watched.set_watcher(user, watching) - respond_to do |format| - format.html { redirect_to :back } - format.js do - render(:update) do |page| - c = watcher_css(@watched) - page.select(".#{c}").each do |item| - page.replace_html item, watcher_link(@watched, user) - end - end - end - end - rescue ::ActionController::RedirectBackError - render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/74/7454b0c86e4f8493693e8238d3d4dafc54d562c2.svn-base --- a/.svn/pristine/74/7454b0c86e4f8493693e8238d3d4dafc54d562c2.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,64 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class CustomFieldsController < ApplicationController - layout 'admin' - - before_filter :require_admin - - def index - @custom_fields_by_type = CustomField.find(:all).group_by {|f| f.class.name } - @tab = params[:tab] || 'IssueCustomField' - end - - def new - @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'); return) unless @custom_field.is_a?(CustomField) - - if request.post? and @custom_field.save - flash[:notice] = l(:notice_successful_create) - call_hook(:controller_custom_fields_new_after_save, :params => params, :custom_field => @custom_field) - redirect_to :action => 'index', :tab => @custom_field.class.name - else - @trackers = Tracker.find(:all, :order => 'position') - end - end - - def edit - @custom_field = CustomField.find(params[:id]) - if request.post? and @custom_field.update_attributes(params[:custom_field]) - flash[:notice] = l(:notice_successful_update) - call_hook(:controller_custom_fields_edit_after_save, :params => params, :custom_field => @custom_field) - redirect_to :action => 'index', :tab => @custom_field.class.name - else - @trackers = Tracker.find(:all, :order => 'position') - end - end - - def destroy - @custom_field = CustomField.find(params[:id]).destroy - redirect_to :action => 'index', :tab => @custom_field.class.name - rescue - flash[:error] = l(:error_can_not_delete_custom_field) - redirect_to :action => 'index' - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/74/74646d64b0858dd62aa32241e9f5deb66f244cbc.svn-base --- a/.svn/pristine/74/74646d64b0858dd62aa32241e9f5deb66f244cbc.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,75 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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 'blankslate' - -module Redmine - module Views - module Builders - class Structure < BlankSlate - def initialize - @struct = [{}] - end - - def array(tag, options={}, &block) - @struct << [] - block.call(self) - ret = @struct.pop - @struct.last[tag] = ret - @struct.last.merge!(options) if options - end - - def method_missing(sym, *args, &block) - if args.any? - if args.first.is_a?(Hash) - if @struct.last.is_a?(Array) - @struct.last << args.first unless block - else - @struct.last[sym] = args.first - end - else - if @struct.last.is_a?(Array) - @struct.last << (args.last || {}).merge(:value => args.first) - else - @struct.last[sym] = args.first - end - end - end - - if block - @struct << (args.first.is_a?(Hash) ? args.first : {}) - block.call(self) - ret = @struct.pop - if @struct.last.is_a?(Array) - @struct.last << ret - else - if @struct.last.has_key?(sym) && @struct.last[sym].is_a?(Hash) - @struct.last[sym].merge! ret - else - @struct.last[sym] = ret - end - end - end - end - - def output - raise "Need to implement #{self.class.name}#output" - end - end - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/74/74659fa6cdf8fb6e2bcf14986b0b23d99872b7b3.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/74/74659fa6cdf8fb6e2bcf14986b0b23d99872b7b3.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,225 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../../../../test_helper', __FILE__) +begin + require 'mocha' + + class BazaarAdapterTest < ActiveSupport::TestCase + REPOSITORY_PATH = Rails.root.join('tmp/test/bazaar_repository').to_s + REPOSITORY_PATH.gsub!(/\/+/, '/') + + if File.directory?(REPOSITORY_PATH) + def setup + @adapter = Redmine::Scm::Adapters::BazaarAdapter.new( + File.join(REPOSITORY_PATH, "trunk") + ) + end + + def test_scm_version + to_test = { "Bazaar (bzr) 2.1.2\n" => [2,1,2], + "2.1.1\n1.7\n1.8" => [2,1,1], + "2.0.1\r\n1.8.1\r\n1.9.1" => [2,0,1]} + to_test.each do |s, v| + test_scm_version_for(s, v) + end + end + + def test_cat + cat = @adapter.cat('directory/document.txt') + assert cat =~ /Write the contents of a file as of a given revision to standard output/ + end + + def test_cat_path_invalid + assert_nil @adapter.cat('invalid') + end + + def test_cat_revision_invalid + assert_nil @adapter.cat('doc-mkdir.txt', '12345678') + end + + def test_diff + diff1 = @adapter.diff('doc-mkdir.txt', 3, 2) + assert_equal 21, diff1.size + buf = diff1[14].gsub(/\r\n|\r|\n/, "") + assert_equal "-Display more information.", buf + end + + def test_diff_path_invalid + assert_equal [], @adapter.diff('invalid', 1) + end + + def test_diff_revision_invalid + assert_equal [], @adapter.diff(nil, 12345678) + assert_equal [], @adapter.diff(nil, 12345678, 87654321) + end + + def test_annotate + annotate = @adapter.annotate('doc-mkdir.txt') + assert_equal 17, annotate.lines.size + assert_equal '1', annotate.revisions[0].identifier + assert_equal 'jsmith@', annotate.revisions[0].author + assert_equal 'mkdir', annotate.lines[0] + end + + def test_annotate_path_invalid + assert_nil @adapter.annotate('invalid') + end + + def test_annotate_revision_invalid + assert_nil @adapter.annotate('doc-mkdir.txt', '12345678') + end + + def test_branch_conf_path + p = "c:\\test\\test\\" + bcp = Redmine::Scm::Adapters::BazaarAdapter.branch_conf_path(p) + assert_equal File.join("c:\\test\\test", ".bzr", "branch", "branch.conf"), bcp + p = "c:\\test\\test\\.bzr" + bcp = Redmine::Scm::Adapters::BazaarAdapter.branch_conf_path(p) + assert_equal File.join("c:\\test\\test", ".bzr", "branch", "branch.conf"), bcp + p = "c:\\test\\test\\.bzr\\" + bcp = Redmine::Scm::Adapters::BazaarAdapter.branch_conf_path(p) + assert_equal File.join("c:\\test\\test", ".bzr", "branch", "branch.conf"), bcp + p = "c:\\test\\test" + bcp = Redmine::Scm::Adapters::BazaarAdapter.branch_conf_path(p) + assert_equal File.join("c:\\test\\test", ".bzr", "branch", "branch.conf"), bcp + p = "\\\\server\\test\\test\\" + bcp = Redmine::Scm::Adapters::BazaarAdapter.branch_conf_path(p) + assert_equal File.join("\\\\server\\test\\test", ".bzr", "branch", "branch.conf"), bcp + end + + def test_append_revisions_only_true + assert_equal true, @adapter.append_revisions_only + end + + def test_append_revisions_only_false + adpt = Redmine::Scm::Adapters::BazaarAdapter.new( + File.join(REPOSITORY_PATH, "empty-branch") + ) + assert_equal false, adpt.append_revisions_only + end + + def test_append_revisions_only_shared_repo + adpt = Redmine::Scm::Adapters::BazaarAdapter.new( + REPOSITORY_PATH + ) + assert_equal false, adpt.append_revisions_only + end + + def test_info_not_nil + assert_not_nil @adapter.info + end + + def test_info_nil + adpt = Redmine::Scm::Adapters::BazaarAdapter.new( + "/invalid/invalid/" + ) + assert_nil adpt.info + end + + def test_info + info = @adapter.info + assert_equal 4, info.lastrev.identifier.to_i + end + + def test_info_emtpy + adpt = Redmine::Scm::Adapters::BazaarAdapter.new( + File.join(REPOSITORY_PATH, "empty-branch") + ) + assert_equal 0, adpt.info.lastrev.identifier.to_i + end + + def test_entries_path_invalid + assert_equal [], @adapter.entries('invalid') + end + + def test_entries_revision_invalid + assert_nil @adapter.entries(nil, 12345678) + end + + def test_revisions + revisions = @adapter.revisions(nil, 4, 2) + assert_equal 3, revisions.size + assert_equal 2, revisions[2].identifier + assert_equal 'jsmith@foo.bar-20071203175224-v0eog5d5wrgdrshg', revisions[2].scmid + assert_equal 4, revisions[0].identifier + assert_equal 'jsmith@foo.bar-20071203175422-t40bf8li5zz0c4cg', revisions[0].scmid + assert_equal 2, revisions[0].paths.size + assert_equal 'D', revisions[0].paths[0][:action] + assert_equal '/doc-deleted.txt', revisions[0].paths[0][:path] + assert_equal 'docdeleted.txt-20071203175320-iwwj561ojuubs3gt-1', revisions[0].paths[0][:revision] + assert_equal 'M', revisions[0].paths[1][:action] + assert_equal '/directory/doc-ls.txt', revisions[0].paths[1][:path] + assert_equal 'docls.txt-20071203175005-a3hyc3mn0shl7cgu-1', revisions[0].paths[1][:revision] + end + + def test_revisions_path_invalid + assert_nil @adapter.revisions('invalid') + end + + def test_revisions_revision_invalid + assert_nil @adapter.revisions(nil, 12345678) + assert_nil @adapter.revisions(nil, 12345678, 87654321) + end + + def test_entry + entry = @adapter.entry() + assert_equal "", entry.path + assert_equal "dir", entry.kind + entry = @adapter.entry('') + assert_equal "", entry.path + assert_equal "dir", entry.kind + assert_nil @adapter.entry('invalid') + assert_nil @adapter.entry('/invalid') + assert_nil @adapter.entry('/invalid/') + assert_nil @adapter.entry('invalid/invalid') + assert_nil @adapter.entry('invalid/invalid/') + assert_nil @adapter.entry('/invalid/invalid') + assert_nil @adapter.entry('/invalid/invalid/') + ["doc-ls.txt", "/doc-ls.txt"].each do |path| + entry = @adapter.entry(path, 2) + assert_equal "doc-ls.txt", entry.path + assert_equal "file", entry.kind + end + ["directory", "/directory", "/directory/"].each do |path| + entry = @adapter.entry(path, 2) + assert_equal "directory", entry.path + assert_equal "dir", entry.kind + end + ["directory/document.txt", "/directory/document.txt"].each do |path| + entry = @adapter.entry(path, 2) + assert_equal "directory/document.txt", entry.path + assert_equal "file", entry.kind + end + end + + private + + def test_scm_version_for(scm_command_version, version) + @adapter.class.expects(:scm_version_from_command_line).returns(scm_command_version) + assert_equal version, @adapter.class.scm_command_version + end + else + puts "Bazaar test repository NOT FOUND. Skipping unit tests !!!" + def test_fake; assert true end + end + end +rescue LoadError + class BazaarMochaFake < ActiveSupport::TestCase + def test_fake; assert(false, "Requires mocha to run those tests") end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/74/746eb676a490baf5ce191dc6543fabe7fb71d84c.svn-base --- a/.svn/pristine/74/746eb676a490baf5ce191dc6543fabe7fb71d84c.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -module CodeRay -module Styles - - default :alpha - -end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/74/74c381bd74bb7f13d9d8695257e06bccd688238e.svn-base --- a/.svn/pristine/74/74c381bd74bb7f13d9d8695257e06bccd688238e.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -Signup template from application - -Here's a local variable set in the Mail object: <%= @name %>. - -And here's a method called in a mail helper: <%= do_something_helpful(@name) %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/74/74ced18dca894e05230dcd0fed4eb5324521d734.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/74/74ced18dca894e05230dcd0fed4eb5324521d734.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,51 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class Redmine::ApiTest::IssueStatusesTest < Redmine::ApiTest::Base + fixtures :issue_statuses + + def setup + Setting.rest_api_enabled = '1' + end + + context "/issue_statuses" do + context "GET" do + + should "return issue statuses" do + get '/issue_statuses.xml' + + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_tag :tag => 'issue_statuses', + :attributes => {:type => 'array'}, + :child => { + :tag => 'issue_status', + :child => { + :tag => 'id', + :content => '2', + :sibling => { + :tag => 'name', + :content => 'Assigned' + } + } + } + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/74/74d9194a2436a04f76d12b3254f21b2676662dd5.svn-base --- a/.svn/pristine/74/74d9194a2436a04f76d12b3254f21b2676662dd5.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,8 +0,0 @@ -class BuildProjectsTree < ActiveRecord::Migration - def self.up - Project.rebuild! - end - - def self.down - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/75/7537e9eba2daabb99d2e6fee47a3f35857d9e159.svn-base --- a/.svn/pristine/75/7537e9eba2daabb99d2e6fee47a3f35857d9e159.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +0,0 @@ -require File.dirname(__FILE__) + '/../test_helper' - -class ArbitraryCodeMixingTest < Test::Unit::TestCase - def setup - Engines.code_mixing_file_types = %w(controller helper) - end - - def test_should_allow_setting_of_different_code_mixing_file_types - assert_nothing_raised { - Engines.mix_code_from :things - } - end - - def test_should_add_new_types_to_existing_code_mixing_file_types - Engines.mix_code_from :things - assert_equal ["controller", "helper", "thing"], Engines.code_mixing_file_types - Engines.mix_code_from :other - assert_equal ["controller", "helper", "thing", "other"], Engines.code_mixing_file_types - end - - def test_should_allow_setting_of_multiple_types_at_once - Engines.mix_code_from :things, :other - assert_equal ["controller", "helper", "thing", "other"], Engines.code_mixing_file_types - end - - def test_should_singularize_elements_to_be_mixed - # this is the only test using mocha, so let's try to work around it - # also, this seems to be already tested with the :things in the tests above - # arg = stub(:to_s => stub(:singularize => "element")) - Engines.mix_code_from :elements - assert Engines.code_mixing_file_types.include?("element") - end - - # TODO doesn't seem to work as expected? - - # def test_should_successfully_mix_custom_types - # Engines.mix_code_from :things - # assert_equal 'Thing (from app)', Thing.from_app - # assert_equal 'Thing (from test_code_mixing)', Thing.from_plugin - # end -end \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/75/7564d8cc7d8f96cea467c64ec9f7ec7cf4ca759a.svn-base --- a/.svn/pristine/75/7564d8cc7d8f96cea467c64ec9f7ec7cf4ca759a.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,34 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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 IssuePriority < Enumeration - has_many :issues, :foreign_key => 'priority_id' - - OptionName = :enumeration_issue_priorities - - def option_name - OptionName - end - - def objects_count - issues.count - end - - def transfer_relations(to) - issues.update_all("priority_id = #{to.id}") - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/75/7564dff632a56de9feef3194aae6511189c0892c.svn-base --- a/.svn/pristine/75/7564dff632a56de9feef3194aae6511189c0892c.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ -<%= error_messages_for 'relation' %> - -

    <%= f.select :relation_type, collection_for_relation_type_select, {}, :onchange => "setPredecessorFieldsVisibility();" %> -<%= l(:label_issue) %> #<%= f.text_field :issue_to_id, :size => 10 %> -

    -<%= javascript_tag "observeRelatedIssueField('#{auto_complete_issues_path(:id => @issue, :project_id => @project) }')" %> - -<%= submit_tag l(:button_add) %> -<%= toggle_link l(:button_cancel), 'new-relation-form'%> -

    - -<%= javascript_tag "setPredecessorFieldsVisibility();" %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/75/756d2effd3b96f4e4b0f3695a706738e31fc6e53.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/75/756d2effd3b96f4e4b0f3695a706738e31fc6e53.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,268 @@ +--- +issues_001: + created_on: <%= 3.days.ago.to_s(:db) %> + project_id: 1 + updated_on: <%= 1.day.ago.to_s(:db) %> + priority_id: 4 + subject: Can't print recipes + id: 1 + fixed_version_id: + category_id: 1 + description: Unable to print recipes + tracker_id: 1 + assigned_to_id: + author_id: 2 + status_id: 1 + start_date: <%= 1.day.ago.to_date.to_s(:db) %> + due_date: <%= 10.day.from_now.to_date.to_s(:db) %> + root_id: 1 + lft: 1 + rgt: 2 + lock_version: 3 +issues_002: + created_on: 2006-07-19 21:04:21 +02:00 + project_id: 1 + updated_on: 2006-07-19 21:09:50 +02:00 + priority_id: 5 + subject: Add ingredients categories + id: 2 + fixed_version_id: 2 + category_id: + description: Ingredients of the recipe should be classified by categories + tracker_id: 2 + assigned_to_id: 3 + author_id: 2 + status_id: 2 + start_date: <%= 2.day.ago.to_date.to_s(:db) %> + due_date: + root_id: 2 + lft: 1 + rgt: 2 + lock_version: 3 + done_ratio: 30 +issues_003: + created_on: 2006-07-19 21:07:27 +02:00 + project_id: 1 + updated_on: 2006-07-19 21:07:27 +02:00 + priority_id: 4 + subject: Error 281 when updating a recipe + id: 3 + fixed_version_id: + category_id: + description: Error 281 is encountered when saving a recipe + tracker_id: 1 + assigned_to_id: 3 + author_id: 2 + status_id: 1 + start_date: <%= 15.day.ago.to_date.to_s(:db) %> + due_date: <%= 5.day.ago.to_date.to_s(:db) %> + root_id: 3 + lft: 1 + rgt: 2 +issues_004: + created_on: <%= 5.days.ago.to_s(:db) %> + project_id: 2 + updated_on: <%= 2.days.ago.to_s(:db) %> + priority_id: 4 + subject: Issue on project 2 + id: 4 + fixed_version_id: + category_id: + description: Issue on project 2 + tracker_id: 1 + assigned_to_id: 2 + author_id: 2 + status_id: 1 + root_id: 4 + lft: 1 + rgt: 2 +issues_005: + created_on: <%= 5.days.ago.to_s(:db) %> + project_id: 3 + updated_on: <%= 2.days.ago.to_s(:db) %> + priority_id: 4 + subject: Subproject issue + id: 5 + fixed_version_id: + category_id: + description: This is an issue on a cookbook subproject + tracker_id: 1 + assigned_to_id: + author_id: 2 + status_id: 1 + root_id: 5 + lft: 1 + rgt: 2 +issues_006: + created_on: <%= 1.minute.ago.to_s(:db) %> + project_id: 5 + updated_on: <%= 1.minute.ago.to_s(:db) %> + priority_id: 4 + subject: Issue of a private subproject + id: 6 + fixed_version_id: + category_id: + description: This is an issue of a private subproject of cookbook + tracker_id: 1 + assigned_to_id: + author_id: 2 + status_id: 1 + start_date: <%= Date.today.to_s(:db) %> + due_date: <%= 1.days.from_now.to_date.to_s(:db) %> + root_id: 6 + lft: 1 + rgt: 2 +issues_007: + created_on: <%= 10.days.ago.to_s(:db) %> + project_id: 1 + updated_on: <%= 10.days.ago.to_s(:db) %> + priority_id: 5 + 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 + root_id: 7 + lft: 1 + rgt: 2 +issues_008: + created_on: <%= 10.days.ago.to_s(:db) %> + project_id: 1 + updated_on: <%= 10.days.ago.to_s(:db) %> + priority_id: 5 + 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 + root_id: 8 + lft: 1 + rgt: 2 + closed_on: <%= 3.days.ago.to_s(:db) %> +issues_009: + created_on: <%= 1.minute.ago.to_s(:db) %> + project_id: 5 + updated_on: <%= 1.minute.ago.to_s(:db) %> + priority_id: 5 + subject: Blocked Issue + id: 9 + fixed_version_id: + category_id: + description: This is an issue that is blocked by issue #10 + tracker_id: 1 + assigned_to_id: + author_id: 2 + status_id: 1 + start_date: <%= Date.today.to_s(:db) %> + due_date: <%= 1.days.from_now.to_date.to_s(:db) %> + root_id: 9 + lft: 1 + rgt: 2 +issues_010: + created_on: <%= 1.minute.ago.to_s(:db) %> + project_id: 5 + updated_on: <%= 1.minute.ago.to_s(:db) %> + priority_id: 5 + subject: Issue Doing the Blocking + id: 10 + fixed_version_id: + category_id: + description: This is an issue that blocks issue #9 + tracker_id: 1 + assigned_to_id: + author_id: 2 + status_id: 1 + start_date: <%= Date.today.to_s(:db) %> + due_date: <%= 1.days.from_now.to_date.to_s(:db) %> + root_id: 10 + lft: 1 + rgt: 2 +issues_011: + created_on: <%= 3.days.ago.to_s(:db) %> + project_id: 1 + updated_on: <%= 1.day.ago.to_s(:db) %> + priority_id: 5 + subject: Closed issue on a closed version + id: 11 + fixed_version_id: 1 + category_id: 1 + description: + tracker_id: 1 + assigned_to_id: + author_id: 2 + status_id: 5 + start_date: <%= 1.day.ago.to_date.to_s(:db) %> + due_date: + root_id: 11 + lft: 1 + rgt: 2 + closed_on: <%= 1.day.ago.to_s(:db) %> +issues_012: + created_on: <%= 3.days.ago.to_s(:db) %> + project_id: 1 + updated_on: <%= 1.day.ago.to_s(:db) %> + priority_id: 5 + subject: Closed issue on a locked version + id: 12 + fixed_version_id: 2 + category_id: 1 + description: + tracker_id: 1 + assigned_to_id: + author_id: 3 + status_id: 5 + start_date: <%= 1.day.ago.to_date.to_s(:db) %> + due_date: + root_id: 12 + lft: 1 + rgt: 2 + closed_on: <%= 1.day.ago.to_s(:db) %> +issues_013: + created_on: <%= 5.days.ago.to_s(:db) %> + project_id: 3 + updated_on: <%= 2.days.ago.to_s(:db) %> + priority_id: 4 + subject: Subproject issue two + id: 13 + fixed_version_id: + category_id: + description: This is a second issue on a cookbook subproject + tracker_id: 1 + assigned_to_id: + author_id: 2 + status_id: 1 + root_id: 13 + lft: 1 + rgt: 2 +issues_014: + id: 14 + created_on: <%= 15.days.ago.to_s(:db) %> + project_id: 3 + updated_on: <%= 15.days.ago.to_s(:db) %> + priority_id: 5 + subject: Private issue on public project + fixed_version_id: + category_id: + description: This is a private issue + tracker_id: 1 + assigned_to_id: + author_id: 2 + status_id: 1 + is_private: true + root_id: 14 + lft: 1 + rgt: 2 diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/75/757babb19cbbfc71f08831b6bce5cad46e54487c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/75/757babb19cbbfc71f08831b6bce5cad46e54487c.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,268 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2007 Patrick Aljord patcito@Å‹mail.com +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require 'redmine/scm/adapters/git_adapter' + +class Repository::Git < Repository + attr_protected :root_url + validates_presence_of :url + + def self.human_attribute_name(attribute_key_name, *args) + attr_name = attribute_key_name.to_s + if attr_name == "url" + attr_name = "path_to_repository" + end + super(attr_name, *args) + end + + def self.scm_adapter_class + Redmine::Scm::Adapters::GitAdapter + end + + def self.scm_name + 'Git' + end + + def report_last_commit + extra_report_last_commit + end + + def extra_report_last_commit + return false if extra_info.nil? + v = extra_info["extra_report_last_commit"] + return false if v.nil? + v.to_s != '0' + end + + def supports_directory_revisions? + true + end + + def supports_revision_graph? + true + end + + def repo_log_encoding + 'UTF-8' + end + + # Returns the identifier for the given git changeset + def self.changeset_identifier(changeset) + changeset.scmid + end + + # Returns the readable identifier for the given git changeset + def self.format_changeset_identifier(changeset) + changeset.revision[0, 8] + end + + def branches + scm.branches + end + + def tags + scm.tags + end + + def default_branch + scm.default_branch + rescue Exception => e + logger.error "git: error during get default branch: #{e.message}" + nil + end + + def find_changeset_by_name(name) + if name.present? + changesets.where(:revision => name.to_s).first || + changesets.where('scmid LIKE ?', "#{name}%").first + end + end + + def entries(path=nil, identifier=nil) + entries = scm.entries(path, identifier, :report_last_commit => extra_report_last_commit) + load_entries_changesets(entries) + entries + end + + # With SCMs that have a sequential commit numbering, + # such as Subversion and Mercurial, + # Redmine is able to be clever and only fetch changesets + # going forward from the most recent one it knows about. + # + # However, Git does not have a sequential commit numbering. + # + # In order to fetch only new adding revisions, + # Redmine needs to save "heads". + # + # In Git and Mercurial, revisions are not in date order. + # Redmine Mercurial fixed issues. + # * Redmine Takes Too Long On Large Mercurial Repository + # http://www.redmine.org/issues/3449 + # * Sorting for changesets might go wrong on Mercurial repos + # http://www.redmine.org/issues/3567 + # + # Database revision column is text, so Redmine can not sort by revision. + # Mercurial has revision number, and revision number guarantees revision order. + # Redmine Mercurial model stored revisions ordered by database id to database. + # So, Redmine Mercurial model can use correct ordering revisions. + # + # Redmine Mercurial adapter uses "hg log -r 0:tip --limit 10" + # to get limited revisions from old to new. + # But, Git 1.7.3.4 does not support --reverse with -n or --skip. + # + # The repository can still be fully reloaded by calling #clear_changesets + # before fetching changesets (eg. for offline resync) + def fetch_changesets + scm_brs = branches + return if scm_brs.nil? || scm_brs.empty? + + h1 = extra_info || {} + h = h1.dup + repo_heads = scm_brs.map{ |br| br.scmid } + h["heads"] ||= [] + prev_db_heads = h["heads"].dup + if prev_db_heads.empty? + prev_db_heads += heads_from_branches_hash + end + return if prev_db_heads.sort == repo_heads.sort + + h["db_consistent"] ||= {} + if changesets.count == 0 + h["db_consistent"]["ordering"] = 1 + merge_extra_info(h) + self.save + elsif ! h["db_consistent"].has_key?("ordering") + h["db_consistent"]["ordering"] = 0 + merge_extra_info(h) + self.save + end + save_revisions(prev_db_heads, repo_heads) + end + + def save_revisions(prev_db_heads, repo_heads) + h = {} + opts = {} + opts[:reverse] = true + opts[:excludes] = prev_db_heads + opts[:includes] = repo_heads + + revisions = scm.revisions('', nil, nil, opts) + return if revisions.blank? + + # Make the search for existing revisions in the database in a more sufficient manner + # + # Git branch is the reference to the specific revision. + # Git can *delete* remote branch and *re-push* branch. + # + # $ git push remote :branch + # $ git push remote branch + # + # After deleting branch, revisions remain in repository until "git gc". + # On git 1.7.2.3, default pruning date is 2 weeks. + # So, "git log --not deleted_branch_head_revision" return code is 0. + # + # After re-pushing branch, "git log" returns revisions which are saved in database. + # So, Redmine needs to scan revisions and database every time. + # + # This is replacing the one-after-one queries. + # Find all revisions, that are in the database, and then remove them from the revision array. + # Then later we won't need any conditions for db existence. + # Query for several revisions at once, and remove them from the revisions array, if they are there. + # Do this in chunks, to avoid eventual memory problems (in case of tens of thousands of commits). + # If there are no revisions (because the original code's algorithm filtered them), + # then this part will be stepped over. + # We make queries, just if there is any revision. + limit = 100 + offset = 0 + revisions_copy = revisions.clone # revisions will change + while offset < revisions_copy.size + recent_changesets_slice = changesets.find( + :all, + :conditions => [ + 'scmid IN (?)', + revisions_copy.slice(offset, limit).map{|x| x.scmid} + ] + ) + # Subtract revisions that redmine already knows about + recent_revisions = recent_changesets_slice.map{|c| c.scmid} + revisions.reject!{|r| recent_revisions.include?(r.scmid)} + offset += limit + end + + revisions.each do |rev| + transaction do + # There is no search in the db for this revision, because above we ensured, + # that it's not in the db. + save_revision(rev) + end + end + h["heads"] = repo_heads.dup + merge_extra_info(h) + self.save + end + private :save_revisions + + def save_revision(rev) + parents = (rev.parents || []).collect{|rp| find_changeset_by_name(rp)}.compact + changeset = Changeset.create( + :repository => self, + :revision => rev.identifier, + :scmid => rev.scmid, + :committer => rev.author, + :committed_on => rev.time, + :comments => rev.message, + :parents => parents + ) + unless changeset.new_record? + rev.paths.each { |change| changeset.create_change(change) } + end + changeset + end + private :save_revision + + def heads_from_branches_hash + h1 = extra_info || {} + h = h1.dup + h["branches"] ||= {} + h['branches'].map{|br, hs| hs['last_scmid']} + end + + def latest_changesets(path,rev,limit=10) + revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false) + return [] if revisions.nil? || revisions.empty? + + changesets.find( + :all, + :conditions => [ + "scmid IN (?)", + revisions.map!{|c| c.scmid} + ] + ) + end + + def clear_extra_info_of_changesets + return if extra_info.nil? + v = extra_info["extra_report_last_commit"] + write_attribute(:extra_info, nil) + h = {} + h["extra_report_last_commit"] = v + merge_extra_info(h) + self.save + end + private :clear_extra_info_of_changesets +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/75/7582ac57d0446c5241d409e5e3670c3e4ce6c265.svn-base --- a/.svn/pristine/75/7582ac57d0446c5241d409e5e3670c3e4ce6c265.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1020 +0,0 @@ -# Galician (Spain) for Ruby on Rails -# by Marcos Arias Pena (markus@agil-e.com) - -gl: - number: - format: - separator: "," - delimiter: "." - precision: 3 - - currency: - format: - format: "%n %u" - unit: "€" - separator: "," - delimiter: "." - precision: 2 - - percentage: - format: - # separator: - delimiter: "" - # precision: - - precision: - format: - # separator: - delimiter: "" - # precision: - - human: - format: - # separator: - delimiter: "" - precision: 1 - storage_units: - format: "%n %u" - units: - byte: - one: "Byte" - other: "Bytes" - kb: "KB" - mb: "MB" - gb: "GB" - tb: "TB" - - - direction: ltr - date: - formats: - default: "%e/%m/%Y" - short: "%e %b" - long: "%A %e de %B de %Y" - day_names: [Domingo, Luns, Martes, Mércores, Xoves, Venres, Sábado] - abbr_day_names: [Dom, Lun, Mar, Mer, Xov, Ven, Sab] - month_names: [~, Xaneiro, Febreiro, Marzo, Abril, Maio, Xunio, Xullo, Agosto, Setembro, Outubro, Novembro, Decembro] - abbr_month_names: [~, Xan, Feb, Maz, Abr, Mai, Xun, Xul, Ago, Set, Out, Nov, Dec] - order: - - :day - - :month - - :year - - time: - formats: - default: "%A, %e de %B de %Y, %H:%M hs" - time: "%H:%M hs" - short: "%e/%m, %H:%M hs" - long: "%A %e de %B de %Y ás %H:%M horas" - - am: '' - pm: '' - - datetime: - distance_in_words: - half_a_minute: 'medio minuto' - less_than_x_seconds: - zero: 'menos dun segundo' - one: '1 segundo' - few: 'poucos segundos' - other: '%{count} segundos' - x_seconds: - one: '1 segundo' - other: '%{count} segundos' - less_than_x_minutes: - zero: 'menos dun minuto' - one: '1 minuto' - other: '%{count} minutos' - x_minutes: - one: '1 minuto' - other: '%{count} minuto' - about_x_hours: - one: 'aproximadamente unha hora' - other: '%{count} horas' - x_days: - one: '1 día' - other: '%{count} días' - x_weeks: - one: '1 semana' - other: '%{count} semanas' - about_x_months: - one: 'aproximadamente 1 mes' - other: '%{count} meses' - x_months: - one: '1 mes' - other: '%{count} meses' - about_x_years: - one: 'aproximadamente 1 ano' - other: '%{count} anos' - over_x_years: - one: 'máis dun ano' - other: '%{count} anos' - almost_x_years: - one: "almost 1 year" - other: "almost %{count} years" - now: 'agora' - today: 'hoxe' - tomorrow: 'mañá' - in: 'dentro de' - - support: - array: - sentence_connector: e - - activerecord: - models: - attributes: - errors: - template: - header: - one: "1 erro evitou que se poidese gardar o %{model}" - other: "%{count} erros evitaron que se poidese gardar o %{model}" - body: "Atopáronse os seguintes problemas:" - messages: - inclusion: "non está incluido na lista" - exclusion: "xa existe" - invalid: "non é válido" - confirmation: "non coincide coa confirmación" - accepted: "debe ser aceptado" - empty: "non pode estar valeiro" - blank: "non pode estar en blanco" - too_long: "é demasiado longo (non máis de %{count} carácteres)" - too_short: "é demasiado curto (non menos de %{count} carácteres)" - wrong_length: "non ten a lonxitude correcta (debe ser de %{count} carácteres)" - taken: "non está dispoñible" - not_a_number: "non é un número" - greater_than: "debe ser maior que %{count}" - greater_than_or_equal_to: "debe ser maior ou igual que %{count}" - equal_to: "debe ser igual a %{count}" - less_than: "debe ser menor que %{count}" - less_than_or_equal_to: "debe ser menor ou igual que %{count}" - odd: "debe ser par" - even: "debe ser impar" - greater_than_start_date: "debe ser posterior á data de comezo" - not_same_project: "non pertence ao mesmo proxecto" - circular_dependency: "Esta relación podería crear unha dependencia circular" - cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" - - actionview_instancetag_blank_option: Por favor seleccione - - 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_in_progress: In Progress - 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_developer: 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: %{value}" - 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: %{value}" - 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_roadmap: Consultar as peticións na planificación - field_is_public: Público - field_is_required: Obrigatorio - field_issue: Petición - field_issue_to: 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_first_day_of_week: '1' - general_lang_name: 'Galego' - general_pdf_encoding: UTF-8 - 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: "%{count} 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 %{author} fai %{age}" - 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: "%{value} 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_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_x_open_issues_abbr_on_total: - zero: 0 open / %{total} - one: 1 open / %{total} - other: "%{count} open / %{total}" - label_x_open_issues_abbr: - zero: 0 open - one: 1 open - other: "%{count} open" - label_x_closed_issues_abbr: - zero: 0 closed - one: 1 closed - other: "%{count} closed" - label_comment: Comentario - label_comment_add: Engadir un comentario - label_comment_added: Comentario engadido - label_comment_delete: Borrar comentarios - label_comment_plural: Comentarios - label_x_comments: - zero: no comments - one: 1 comment - other: "%{count} comments" - 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: %{value}" - label_document: Documento - label_document_added: Documento engadido - label_document_new: Novo documento - label_document_plural: Documentos - label_download: "%{count} Descarga" - label_download_plural: "%{count} 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: "%{value} hora" - label_f_hour_plural: "%{value} horas" - label_feed_plural: Feeds - label_feeds_access_key_created_on: "Clave de acceso por RSS creada fai %{value}" - 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 %{value}" - label_jump_to_a_project: Ir ao proxecto... - label_language_based: Baseado no idioma - label_last_changes: "últimos %{count} cambios" - label_last_login: Última conexión - label_last_month: último mes - label_last_n_days: "últimos %{count} días" - 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: "%{count} modificación" - label_modification_plural: "%{count} 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_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_x_projects: - zero: no projects - one: 1 project - other: "%{count} projects" - 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 %{value}" - label_roadmap_no_issues: Non hai peticións para esta versión - label_roadmap_overdue: "%{value} 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 %{value}" - 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 %{value}" - label_updated_time_by: "Actualizado por %{author} fai %{age}" - label_used_by: Utilizado por - label_user: Usuario - label_user_activity: "Actividade de %{value}" - 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_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 (%{value}). 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 %{value} 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: "%{count} petición(s) asignadas a ti rematan nos próximos %{days} días:" - mail_subject_account_activation_request: "Petición de activación de conta %{value}" - mail_subject_lost_password: "O teu contrasinal de %{value}" - mail_subject_register: "Activación da conta de %{value}" - mail_subject_reminder: "%{count} petición(s) rematarán nos próximos %{days} 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 (%{value})" - notice_email_sent: "Enviouse un correo a %{value}" - notice_failed_to_save_issues: "Imposible gravar %{count} petición(s) de %{total} seleccionada(s): %{ids}." - 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_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_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: "%{count} caracteres como máximo." - text_caracters_minimum: "%{count} 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 %{hours} 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/configuration.yml e reinicie a aplicación para activar os cambios." - text_enumeration_category_reassign_to: 'Reasignar ó seguinte valor:' - text_enumeration_destroy_question: "%{count} obxectos con este valor asignado." - text_file_repository_writable: Pódese escribir no repositorio - text_issue_added: "Petición %{id} engadida por %{author}." - text_issue_category_destroy_assignments: Deixar as peticións sen categoría - text_issue_category_destroy_question: "Algunhas peticións (%{count}) 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 %{id} actualizouse por %{author}." - 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_length_between: "Lonxitude entre %{min} e %{max} 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.
    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 %{value}" - text_subprojects_destroy_warning: "Os proxectos secundarios: %{value} tamén se eliminarán" - text_tip_issue_begin_day: tarefa que comeza este día - text_tip_issue_begin_end_day: tarefa que comeza e remata este día - text_tip_issue_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: "%{value} 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: "%{count} 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 - setting_file_max_size_displayed: Max size of text files displayed inline - field_watcher: Watcher - setting_openid: Allow OpenID login and registration - field_identity_url: OpenID URL - label_login_with_open_id_option: or login with OpenID - field_content: Content - label_descending: Descending - label_sort: Sort - label_ascending: Ascending - label_date_from_to: From %{start} to %{end} - label_greater_or_equal: ">=" - label_less_or_equal: <= - text_wiki_page_destroy_question: This page has %{descendants} child page(s) and descendant(s). What do you want to do? - text_wiki_page_reassign_children: Reassign child pages to this parent page - text_wiki_page_nullify_children: Keep child pages as root pages - text_wiki_page_destroy_children: Delete child pages and all their descendants - setting_password_min_length: Minimum password length - field_group_by: Group results by - mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated" - label_wiki_content_added: Wiki page added - mail_subject_wiki_content_added: "'%{id}' wiki page has been added" - mail_body_wiki_content_added: The '%{id}' wiki page has been added by %{author}. - label_wiki_content_updated: Wiki page updated - mail_body_wiki_content_updated: The '%{id}' wiki page has been updated by %{author}. - permission_add_project: Create project - setting_new_project_user_role_id: Role given to a non-admin user who creates a project - label_view_all_revisions: View all revisions - label_tag: Tag - label_branch: Branch - error_no_tracker_in_project: No tracker is associated to this project. Please check the Project settings. - error_no_default_issue_status: No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses"). - text_journal_changed: "%{label} changed from %{old} to %{new}" - text_journal_set_to: "%{label} set to %{value}" - text_journal_deleted: "%{label} deleted (%{old})" - label_group_plural: Groups - label_group: Group - label_group_new: New group - label_time_entry_plural: Spent time - text_journal_added: "%{label} %{value} added" - field_active: Active - enumeration_system_activity: System Activity - permission_delete_issue_watchers: Delete watchers - version_status_closed: closed - version_status_locked: locked - version_status_open: open - error_can_not_reopen_issue_on_closed_version: An issue assigned to a closed version can not be reopened - label_user_anonymous: Anonymous - button_move_and_follow: Move and follow - setting_default_projects_modules: Default enabled modules for new projects - setting_gravatar_default: Default Gravatar image - field_sharing: Sharing - label_version_sharing_hierarchy: With project hierarchy - label_version_sharing_system: With all projects - label_version_sharing_descendants: With subprojects - label_version_sharing_tree: With project tree - label_version_sharing_none: Not shared - error_can_not_archive_project: This project can not be archived - button_duplicate: Duplicate - button_copy_and_follow: Copy and follow - label_copy_source: Source - setting_issue_done_ratio: Calculate the issue done ratio with - setting_issue_done_ratio_issue_status: Use the issue status - error_issue_done_ratios_not_updated: Issue done ratios not updated. - error_workflow_copy_target: Please select target tracker(s) and role(s) - setting_issue_done_ratio_issue_field: Use the issue field - label_copy_same_as_target: Same as target - label_copy_target: Target - notice_issue_done_ratios_updated: Issue done ratios updated. - error_workflow_copy_source: Please select a source tracker or role - label_update_issue_done_ratios: Update issue done ratios - setting_start_of_week: Start calendars on - permission_view_issues: View Issues - label_display_used_statuses_only: Only display statuses that are used by this tracker - label_revision_id: Revision %{value} - label_api_access_key: API access key - label_api_access_key_created_on: API access key created %{value} ago - label_feeds_access_key: RSS access key - notice_api_access_key_reseted: Your API access key was reset. - setting_rest_api_enabled: Enable REST web service - label_missing_api_access_key: Missing an API access key - label_missing_feeds_access_key: Missing a RSS access key - button_show: Show - text_line_separated: Multiple values allowed (one line for each value). - setting_mail_handler_body_delimiters: Truncate emails after one of these lines - permission_add_subprojects: Create subprojects - label_subproject_new: New subproject - text_own_membership_delete_confirmation: |- - You are about to remove some or all of your permissions and may no longer be able to edit this project after that. - Are you sure you want to continue? - label_close_versions: Close completed versions - label_board_sticky: Sticky - label_board_locked: Locked - permission_export_wiki_pages: Export wiki pages - setting_cache_formatted_text: Cache formatted text - permission_manage_project_activities: Manage project activities - error_unable_delete_issue_status: Unable to delete issue status - label_profile: Profile - permission_manage_subtasks: Manage subtasks - field_parent_issue: Parent task - label_subtask_plural: Subtasks - label_project_copy_notifications: Send email notifications during the project copy - error_can_not_delete_custom_field: Unable to delete custom field - error_unable_to_connect: Unable to connect (%{value}) - error_can_not_remove_role: This role is in use and can not be deleted. - error_can_not_delete_tracker: This tracker contains issues and can't be deleted. - field_principal: Principal - label_my_page_block: My page block - notice_failed_to_save_members: "Failed to save member(s): %{errors}." - text_zoom_out: Zoom out - text_zoom_in: Zoom in - notice_unable_delete_time_entry: Unable to delete time log entry. - label_overall_spent_time: Overall spent time - field_time_entries: Log time - project_module_gantt: Gantt - project_module_calendar: Calendar - button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" - text_are_you_sure_with_children: Delete issue and all child issues? - field_text: Text field - label_user_mail_option_only_owner: Only for things I am the owner of - setting_default_notification_option: Default notification option - label_user_mail_option_only_my_events: Only for things I watch or I'm involved in - label_user_mail_option_only_assigned: Only for things I am assigned to - label_user_mail_option_none: No events - field_member_of_group: Assignee's group - field_assigned_to_role: Assignee's role - notice_not_authorized_archived_project: The project you're trying to access has been archived. - label_principal_search: "Search for user or group:" - label_user_search: "Search for user:" - field_visible: Visible - setting_emails_header: Emails header - setting_commit_logtime_activity_id: Activity for logged time - text_time_logged_by_changeset: Applied in changeset %{value}. - setting_commit_logtime_enabled: Enable time logging - notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) - setting_gantt_items_limit: Maximum number of items displayed on the gantt chart - field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text - text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. - label_my_queries: My custom queries - text_journal_changed_no_detail: "%{label} updated" - label_news_comment_added: Comment added to a news - button_expand_all: Expand all - button_collapse_all: Collapse all - label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee - label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author - label_bulk_edit_selected_time_entries: Bulk edit selected time entries - text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? - label_role_anonymous: Anonymous - label_role_non_member: Non member - label_issue_note_added: Note added - label_issue_status_updated: Status updated - label_issue_priority_updated: Priority updated - label_issues_visibility_own: Issues created by or assigned to the user - field_issues_visibility: Issues visibility - label_issues_visibility_all: All issues - permission_set_own_issues_private: Set own issues public or private - field_is_private: Private - permission_set_issues_private: Set issues public or private - label_issues_visibility_public: All non private issues - text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). - field_commit_logs_encoding: Codificación das mensaxes de commit - field_scm_path_encoding: Path encoding - text_scm_path_encoding_note: "Default: UTF-8" - field_path_to_repository: Path to repository - field_root_directory: Root directory - field_cvs_module: Module - field_cvsroot: CVSROOT - text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) - text_scm_command: Command - text_scm_command_version: Version - label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. - notice_issue_successful_create: Issue %{id} created. - label_between: between - setting_issue_group_assignment: Allow issue assignment to groups - label_diff: diff - text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) - description_query_sort_criteria_direction: Sort direction - description_project_scope: Search scope - description_filter: Filter - description_user_mail_notification: Mail notification settings - description_date_from: Enter start date - description_message_content: Message content - description_available_columns: Available Columns - description_date_range_interval: Choose range by selecting start and end date - description_issue_category_reassign: Choose issue category - description_search: Searchfield - description_notes: Notes - description_date_range_list: Choose range from list - description_choose_project: Projects - description_date_to: Enter end date - description_query_sort_criteria_attribute: Sort attribute - description_wiki_subpages_reassign: Choose new parent page - description_selected_columns: Selected Columns - label_parent_revision: Parent - label_child_revision: Child - error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. - setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues - button_edit_section: Edit this section - setting_repositories_encodings: Attachments and repositories encodings - description_all_columns: All Columns - button_export: Export - label_export_options: "%{export_format} export options" - error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/75/7589507f7ac535cf051469c3e0cd34a9aef60ed2.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/75/7589507f7ac535cf051469c3e0cd34a9aef60ed2.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,12 @@ +class PopulateMemberRoles < ActiveRecord::Migration + def self.up + MemberRole.delete_all + Member.all.each do |member| + MemberRole.create!(:member_id => member.id, :role_id => member.role_id) + end + end + + def self.down + MemberRole.delete_all + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/75/75f107e6669aa82c7a67c545bb8556cd17f911cf.svn-base --- a/.svn/pristine/75/75f107e6669aa82c7a67c545bb8556cd17f911cf.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +0,0 @@ -desc 'Load Redmine default configuration data. Language is chosen interactively or by setting REDMINE_LANG environment variable.' - -namespace :redmine do - task :load_default_data => :environment do - include Redmine::I18n - set_language_if_valid('en') - - envlang = ENV['REDMINE_LANG'] - if !envlang || !set_language_if_valid(envlang) - puts - while true - print "Select language: " - print valid_languages.collect(&:to_s).sort.join(", ") - print " [#{current_language}] " - STDOUT.flush - lang = STDIN.gets.chomp! - break if lang.empty? - break if set_language_if_valid(lang) - puts "Unknown language!" - end - STDOUT.flush - puts "====================================" - end - - begin - Redmine::DefaultData::Loader.load(current_language) - puts "Default configuration data loaded." - rescue Redmine::DefaultData::DataAlreadyLoaded => error - puts error - rescue => error - puts "Error: " + error - puts "Default configuration data was not loaded." - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/76/760f88c85efa4fcdc85da6f311991a1e4bd6de1d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/76/760f88c85efa4fcdc85da6f311991a1e4bd6de1d.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,1100 @@ +# Galician (Spain) for Ruby on Rails +# by Marcos Arias Pena (markus@agil-e.com) + +gl: + number: + format: + separator: "," + delimiter: "." + precision: 3 + + currency: + format: + format: "%n %u" + unit: "€" + separator: "," + delimiter: "." + precision: 2 + + percentage: + format: + # separator: + delimiter: "" + # precision: + + precision: + format: + # separator: + delimiter: "" + # precision: + + human: + format: + # separator: + delimiter: "" + precision: 3 + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + + direction: ltr + date: + formats: + default: "%e/%m/%Y" + short: "%e %b" + long: "%A %e de %B de %Y" + day_names: [Domingo, Luns, Martes, Mércores, Xoves, Venres, Sábado] + abbr_day_names: [Dom, Lun, Mar, Mer, Xov, Ven, Sab] + month_names: [~, Xaneiro, Febreiro, Marzo, Abril, Maio, Xunio, Xullo, Agosto, Setembro, Outubro, Novembro, Decembro] + abbr_month_names: [~, Xan, Feb, Maz, Abr, Mai, Xun, Xul, Ago, Set, Out, Nov, Dec] + order: + - :day + - :month + - :year + + time: + formats: + default: "%A, %e de %B de %Y, %H:%M hs" + time: "%H:%M hs" + short: "%e/%m, %H:%M hs" + long: "%A %e de %B de %Y ás %H:%M horas" + + am: '' + pm: '' + + datetime: + distance_in_words: + half_a_minute: 'medio minuto' + less_than_x_seconds: + zero: 'menos dun segundo' + one: '1 segundo' + few: 'poucos segundos' + other: '%{count} segundos' + x_seconds: + one: '1 segundo' + other: '%{count} segundos' + less_than_x_minutes: + zero: 'menos dun minuto' + one: '1 minuto' + other: '%{count} minutos' + x_minutes: + one: '1 minuto' + other: '%{count} minuto' + about_x_hours: + one: 'aproximadamente unha hora' + other: '%{count} horas' + x_hours: + one: "1 hora" + other: "%{count} horas" + x_days: + one: '1 día' + other: '%{count} días' + x_weeks: + one: '1 semana' + other: '%{count} semanas' + about_x_months: + one: 'aproximadamente 1 mes' + other: '%{count} meses' + x_months: + one: '1 mes' + other: '%{count} meses' + about_x_years: + one: 'aproximadamente 1 ano' + other: '%{count} anos' + over_x_years: + one: 'máis dun ano' + other: '%{count} anos' + almost_x_years: + one: "almost 1 year" + other: "almost %{count} years" + now: 'agora' + today: 'hoxe' + tomorrow: 'mañá' + in: 'dentro de' + + support: + array: + sentence_connector: e + + activerecord: + models: + attributes: + errors: + template: + header: + one: "1 erro evitou que se poidese gardar o %{model}" + other: "%{count} erros evitaron que se poidese gardar o %{model}" + body: "Atopáronse os seguintes problemas:" + messages: + inclusion: "non está incluido na lista" + exclusion: "xa existe" + invalid: "non é válido" + confirmation: "non coincide coa confirmación" + accepted: "debe ser aceptado" + empty: "non pode estar valeiro" + blank: "non pode estar en blanco" + too_long: "é demasiado longo (non máis de %{count} carácteres)" + too_short: "é demasiado curto (non menos de %{count} carácteres)" + wrong_length: "non ten a lonxitude correcta (debe ser de %{count} carácteres)" + taken: "non está dispoñible" + not_a_number: "non é un número" + greater_than: "debe ser maior que %{count}" + greater_than_or_equal_to: "debe ser maior ou igual que %{count}" + equal_to: "debe ser igual a %{count}" + less_than: "debe ser menor que %{count}" + less_than_or_equal_to: "debe ser menor ou igual que %{count}" + odd: "debe ser par" + even: "debe ser impar" + greater_than_start_date: "debe ser posterior á data de comezo" + not_same_project: "non pertence ao mesmo proxecto" + circular_dependency: "Esta relación podería crear unha dependencia circular" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" + + actionview_instancetag_blank_option: Por favor seleccione + + 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_in_progress: In Progress + 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_developer: 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: %{value}" + 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: %{value}" + 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_roadmap: Consultar as peticións na planificación + field_is_public: Público + field_is_required: Obrigatorio + field_issue: Petición + field_issue_to: 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_first_day_of_week: '1' + general_lang_name: 'Galego' + general_pdf_encoding: UTF-8 + general_text_No: 'Non' + general_text_Yes: 'Si' + general_text_no: 'non' + general_text_yes: 'si' + 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 %{author} fai %{age}" + 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: "%{value} 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_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_x_open_issues_abbr_on_total: + zero: 0 open / %{total} + one: 1 open / %{total} + other: "%{count} open / %{total}" + label_x_open_issues_abbr: + zero: 0 open + one: 1 open + other: "%{count} open" + label_x_closed_issues_abbr: + zero: 0 closed + one: 1 closed + other: "%{count} closed" + label_comment: Comentario + label_comment_add: Engadir un comentario + label_comment_added: Comentario engadido + label_comment_delete: Borrar comentarios + label_comment_plural: Comentarios + label_x_comments: + zero: no comments + one: 1 comment + other: "%{count} comments" + 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: %{value}" + label_document: Documento + label_document_added: Documento engadido + label_document_new: Novo documento + label_document_plural: Documentos + 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: "%{value} hora" + label_f_hour_plural: "%{value} horas" + label_feed_plural: Feeds + label_feeds_access_key_created_on: "Clave de acceso por RSS creada fai %{value}" + 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 %{value}" + label_jump_to_a_project: Ir ao proxecto... + label_language_based: Baseado no idioma + label_last_changes: "últimos %{count} cambios" + label_last_login: Última conexión + label_last_month: último mes + label_last_n_days: "últimos %{count} días" + 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_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_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_x_projects: + zero: no projects + one: 1 project + other: "%{count} projects" + 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 %{value}" + label_roadmap_no_issues: Non hai peticións para esta versión + label_roadmap_overdue: "%{value} 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 %{value}" + 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 %{value}" + label_updated_time_by: "Actualizado por %{author} fai %{age}" + label_used_by: Utilizado por + label_user: Usuario + label_user_activity: "Actividade de %{value}" + 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_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 (%{value}). 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 %{value} 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: "%{count} petición(s) asignadas a ti rematan nos próximos %{days} días:" + mail_subject_account_activation_request: "Petición de activación de conta %{value}" + mail_subject_lost_password: "O teu contrasinal de %{value}" + mail_subject_register: "Activación da conta de %{value}" + mail_subject_reminder: "%{count} petición(s) rematarán nos próximos %{days} 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 (%{value})" + notice_email_sent: "Enviouse un correo a %{value}" + notice_failed_to_save_issues: "Imposible gravar %{count} petición(s) de %{total} seleccionada(s): %{ids}." + 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_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_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_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: "%{count} caracteres como máximo." + text_caracters_minimum: "%{count} 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 %{hours} 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/configuration.yml e reinicie a aplicación para activar os cambios." + text_enumeration_category_reassign_to: 'Reasignar ó seguinte valor:' + text_enumeration_destroy_question: "%{count} obxectos con este valor asignado." + text_file_repository_writable: Pódese escribir no repositorio + text_issue_added: "Petición %{id} engadida por %{author}." + text_issue_category_destroy_assignments: Deixar as peticións sen categoría + text_issue_category_destroy_question: "Algunhas peticións (%{count}) 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 %{id} actualizouse por %{author}." + 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_length_between: "Lonxitude entre %{min} e %{max} 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_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 %{value}" + text_subprojects_destroy_warning: "Os proxectos secundarios: %{value} tamén se eliminarán" + text_tip_issue_begin_day: tarefa que comeza este día + text_tip_issue_begin_end_day: tarefa que comeza e remata este día + text_tip_issue_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: "%{value} 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: "%{count} 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 + setting_file_max_size_displayed: Max size of text files displayed inline + field_watcher: Watcher + setting_openid: Allow OpenID login and registration + field_identity_url: OpenID URL + label_login_with_open_id_option: or login with OpenID + field_content: Content + label_descending: Descending + label_sort: Sort + label_ascending: Ascending + label_date_from_to: From %{start} to %{end} + label_greater_or_equal: ">=" + label_less_or_equal: <= + text_wiki_page_destroy_question: This page has %{descendants} child page(s) and descendant(s). What do you want to do? + text_wiki_page_reassign_children: Reassign child pages to this parent page + text_wiki_page_nullify_children: Keep child pages as root pages + text_wiki_page_destroy_children: Delete child pages and all their descendants + setting_password_min_length: Minimum password length + field_group_by: Group results by + mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated" + label_wiki_content_added: Wiki page added + mail_subject_wiki_content_added: "'%{id}' wiki page has been added" + mail_body_wiki_content_added: The '%{id}' wiki page has been added by %{author}. + label_wiki_content_updated: Wiki page updated + mail_body_wiki_content_updated: The '%{id}' wiki page has been updated by %{author}. + permission_add_project: Create project + setting_new_project_user_role_id: Role given to a non-admin user who creates a project + label_view_all_revisions: View all revisions + label_tag: Tag + label_branch: Branch + error_no_tracker_in_project: No tracker is associated to this project. Please check the Project settings. + error_no_default_issue_status: No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses"). + text_journal_changed: "%{label} changed from %{old} to %{new}" + text_journal_set_to: "%{label} set to %{value}" + text_journal_deleted: "%{label} deleted (%{old})" + label_group_plural: Groups + label_group: Group + label_group_new: New group + label_time_entry_plural: Spent time + text_journal_added: "%{label} %{value} added" + field_active: Active + enumeration_system_activity: System Activity + permission_delete_issue_watchers: Delete watchers + version_status_closed: closed + version_status_locked: locked + version_status_open: open + error_can_not_reopen_issue_on_closed_version: An issue assigned to a closed version can not be reopened + label_user_anonymous: Anonymous + button_move_and_follow: Move and follow + setting_default_projects_modules: Default enabled modules for new projects + setting_gravatar_default: Default Gravatar image + field_sharing: Sharing + label_version_sharing_hierarchy: With project hierarchy + label_version_sharing_system: With all projects + label_version_sharing_descendants: With subprojects + label_version_sharing_tree: With project tree + label_version_sharing_none: Not shared + error_can_not_archive_project: This project can not be archived + button_duplicate: Duplicate + button_copy_and_follow: Copy and follow + label_copy_source: Source + setting_issue_done_ratio: Calculate the issue done ratio with + setting_issue_done_ratio_issue_status: Use the issue status + error_issue_done_ratios_not_updated: Issue done ratios not updated. + error_workflow_copy_target: Please select target tracker(s) and role(s) + setting_issue_done_ratio_issue_field: Use the issue field + label_copy_same_as_target: Same as target + label_copy_target: Target + notice_issue_done_ratios_updated: Issue done ratios updated. + error_workflow_copy_source: Please select a source tracker or role + label_update_issue_done_ratios: Update issue done ratios + setting_start_of_week: Start calendars on + permission_view_issues: View Issues + label_display_used_statuses_only: Only display statuses that are used by this tracker + label_revision_id: Revision %{value} + label_api_access_key: API access key + label_api_access_key_created_on: API access key created %{value} ago + label_feeds_access_key: RSS access key + notice_api_access_key_reseted: Your API access key was reset. + setting_rest_api_enabled: Enable REST web service + label_missing_api_access_key: Missing an API access key + label_missing_feeds_access_key: Missing a RSS access key + button_show: Show + text_line_separated: Multiple values allowed (one line for each value). + setting_mail_handler_body_delimiters: Truncate emails after one of these lines + permission_add_subprojects: Create subprojects + label_subproject_new: New subproject + text_own_membership_delete_confirmation: |- + You are about to remove some or all of your permissions and may no longer be able to edit this project after that. + Are you sure you want to continue? + label_close_versions: Close completed versions + label_board_sticky: Sticky + label_board_locked: Locked + permission_export_wiki_pages: Export wiki pages + setting_cache_formatted_text: Cache formatted text + permission_manage_project_activities: Manage project activities + error_unable_delete_issue_status: Unable to delete issue status + label_profile: Profile + permission_manage_subtasks: Manage subtasks + field_parent_issue: Parent task + label_subtask_plural: Subtasks + label_project_copy_notifications: Send email notifications during the project copy + error_can_not_delete_custom_field: Unable to delete custom field + error_unable_to_connect: Unable to connect (%{value}) + error_can_not_remove_role: This role is in use and can not be deleted. + error_can_not_delete_tracker: This tracker contains issues and can't be deleted. + field_principal: Principal + label_my_page_block: My page block + notice_failed_to_save_members: "Failed to save member(s): %{errors}." + text_zoom_out: Zoom out + text_zoom_in: Zoom in + notice_unable_delete_time_entry: Unable to delete time log entry. + label_overall_spent_time: Overall spent time + field_time_entries: Log time + project_module_gantt: Gantt + project_module_calendar: Calendar + button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" + field_text: Text field + label_user_mail_option_only_owner: Only for things I am the owner of + setting_default_notification_option: Default notification option + label_user_mail_option_only_my_events: Only for things I watch or I'm involved in + label_user_mail_option_only_assigned: Only for things I am assigned to + label_user_mail_option_none: No events + field_member_of_group: Assignee's group + field_assigned_to_role: Assignee's role + notice_not_authorized_archived_project: The project you're trying to access has been archived. + label_principal_search: "Search for user or group:" + label_user_search: "Search for user:" + field_visible: Visible + setting_commit_logtime_activity_id: Activity for logged time + text_time_logged_by_changeset: Applied in changeset %{value}. + setting_commit_logtime_enabled: Enable time logging + notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) + setting_gantt_items_limit: Maximum number of items displayed on the gantt chart + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. + label_my_queries: My custom queries + text_journal_changed_no_detail: "%{label} updated" + label_news_comment_added: Comment added to a news + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + label_bulk_edit_selected_time_entries: Bulk edit selected time entries + text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? + label_role_anonymous: Anonymous + label_role_non_member: Non member + label_issue_note_added: Note added + label_issue_status_updated: Status updated + label_issue_priority_updated: Priority updated + label_issues_visibility_own: Issues created by or assigned to the user + field_issues_visibility: Issues visibility + label_issues_visibility_all: All issues + permission_set_own_issues_private: Set own issues public or private + field_is_private: Private + permission_set_issues_private: Set issues public or private + label_issues_visibility_public: All non private issues + text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). + field_commit_logs_encoding: Codificación das mensaxes de commit + field_scm_path_encoding: Path encoding + text_scm_path_encoding_note: "Default: UTF-8" + field_path_to_repository: Path to repository + field_root_directory: Root directory + field_cvs_module: Module + field_cvsroot: CVSROOT + text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) + text_scm_command: Command + text_scm_command_version: Version + label_git_report_last_commit: Report last commit for files and directories + notice_issue_successful_create: Issue %{id} created. + label_between: between + setting_issue_group_assignment: Allow issue assignment to groups + label_diff: diff + text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) + description_query_sort_criteria_direction: Sort direction + description_project_scope: Search scope + description_filter: Filter + description_user_mail_notification: Mail notification settings + description_date_from: Enter start date + description_message_content: Message content + description_available_columns: Available Columns + description_date_range_interval: Choose range by selecting start and end date + description_issue_category_reassign: Choose issue category + description_search: Searchfield + description_notes: Notes + description_date_range_list: Choose range from list + description_choose_project: Projects + description_date_to: Enter end date + description_query_sort_criteria_attribute: Sort attribute + description_wiki_subpages_reassign: Choose new parent page + description_selected_columns: Selected Columns + label_parent_revision: Parent + label_child_revision: Child + error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. + setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues + button_edit_section: Edit this section + setting_repositories_encodings: Attachments and repositories encodings + description_all_columns: All Columns + button_export: Export + label_export_options: "%{export_format} export options" + error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) + notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." + label_x_issues: + zero: 0 petición + one: 1 petición + other: "%{count} peticións" + label_repository_new: New repository + field_repository_is_default: Main repository + label_copy_attachments: Copy attachments + label_item_position: "%{position}/%{count}" + label_completed_versions: Completed versions + text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. + field_multiple: Multiple values + setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed + text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes + text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) + notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. + text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} + permission_manage_related_issues: Manage related issues + field_auth_source_ldap_filter: LDAP filter + label_search_for_watchers: Search for watchers to add + notice_account_deleted: Your account has been permanently deleted. + setting_unsubscribe: Allow users to delete their own account + button_delete_my_account: Delete my account + text_account_destroy_confirmation: |- + Are you sure you want to proceed? + Your account will be permanently deleted, with no way to reactivate it. + error_session_expired: Your session has expired. Please login again. + text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." + setting_session_lifetime: Session maximum lifetime + setting_session_timeout: Session inactivity timeout + label_session_expiration: Session expiration + permission_close_project: Close / reopen the project + label_show_closed_projects: View closed projects + button_close: Close + button_reopen: Reopen + project_status_active: active + project_status_closed: closed + project_status_archived: archived + text_project_closed: This project is closed and read-only. + notice_user_successful_create: User %{id} created. + field_core_fields: Standard fields + field_timeout: Timeout (in seconds) + setting_thumbnails_enabled: Display attachment thumbnails + setting_thumbnails_size: Thumbnails size (in pixels) + label_status_transitions: Status transitions + label_fields_permissions: Fields permissions + label_readonly: Read-only + label_required: Required + text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. + field_board_parent: Parent forum + label_attribute_of_project: Project's %{name} + label_attribute_of_author: Author's %{name} + label_attribute_of_assigned_to: Assignee's %{name} + label_attribute_of_fixed_version: Target version's %{name} + label_copy_subtasks: Copy subtasks + label_copied_to: copied to + label_copied_from: copied from + label_any_issues_in_project: any issues in project + label_any_issues_not_in_project: any issues not in project + field_private_notes: Private notes + permission_view_private_notes: View private notes + permission_set_notes_private: Set notes as private + label_no_issues_in_project: no issues in project + label_any: todos + label_last_n_weeks: last %{count} weeks + setting_cross_project_subtasks: Allow cross-project subtasks + label_cross_project_descendants: With subprojects + label_cross_project_tree: With project tree + label_cross_project_hierarchy: With project hierarchy + label_cross_project_system: With all projects + button_hide: Hide + setting_non_working_week_days: Non-working days + label_in_the_next_days: in the next + label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Total + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/76/7613b919071a0b15c5315d48513bd037d7d92c4b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/76/7613b919071a0b15c5315d48513bd037d7d92c4b.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,85 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 Helpers + + # Simple class to compute the start and end dates of a calendar + class Calendar + include Redmine::I18n + attr_reader :startdt, :enddt + + def initialize(date, lang = current_language, period = :month) + @date = date + @events = [] + @ending_events_by_days = {} + @starting_events_by_days = {} + set_language_if_valid lang + case period + when :month + @startdt = Date.civil(date.year, date.month, 1) + @enddt = (@startdt >> 1)-1 + # starts from the first day of the week + @startdt = @startdt - (@startdt.cwday - first_wday)%7 + # ends on the last day of the week + @enddt = @enddt + (last_wday - @enddt.cwday)%7 + when :week + @startdt = date - (date.cwday - first_wday)%7 + @enddt = date + (last_wday - date.cwday)%7 + else + raise 'Invalid period' + end + end + + # Sets calendar events + def events=(events) + @events = events + @ending_events_by_days = @events.group_by {|event| event.due_date} + @starting_events_by_days = @events.group_by {|event| event.start_date} + end + + # Returns events for the given day + def events_on(day) + ((@ending_events_by_days[day] || []) + (@starting_events_by_days[day] || [])).uniq + end + + # Calendar current month + def month + @date.month + end + + # Return the first day of week + # 1 = Monday ... 7 = Sunday + def first_wday + case Setting.start_of_week.to_i + when 1 + @first_dow ||= (1 - 1)%7 + 1 + when 6 + @first_dow ||= (6 - 1)%7 + 1 + when 7 + @first_dow ||= (7 - 1)%7 + 1 + else + @first_dow ||= (l(:general_first_day_of_week).to_i - 1)%7 + 1 + end + end + + def last_wday + @last_dow ||= (first_wday + 5)%7 + 1 + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/76/76250042c24ef04826eb632546dd6627448cd0da.svn-base --- a/.svn/pristine/76/76250042c24ef04826eb632546dd6627448cd0da.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,127 +0,0 @@ -// ** I18N - -// Calendar EN language -// Author: Mihai Bazon, -// 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 -("Sunday", - "Monday", - "Tuesday", - "Wednesday", - "Thursday", - "Friday", - "Saturday", - "Sunday"); - -// 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 -("Sun", - "Mon", - "Tue", - "Wed", - "Thu", - "Fri", - "Sat", - "Sun"); - -// 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 -("January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December"); - -// short month names -Calendar._SMN = new Array -("Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec"); - -// tooltips -Calendar._TT = {}; -Calendar._TT["INFO"] = "About the calendar"; - -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"] = "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)"; - -// 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"; - -// 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"; - -// 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 -r 0a574315af3e -r 4f746d8966dd .svn/pristine/76/7646d2e1c72093f6ce9fbc03074e4564c3417f93.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/76/7646d2e1c72093f6ce9fbc03074e4564c3417f93.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,1091 @@ +# Serbian translations for Redmine +# by Vladimir Medarović (vlada@medarovic.com) +sr-YU: + direction: ltr + jquery: + locale: "sr" + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%d.%m.%Y." + short: "%e %b" + long: "%B %e, %Y" + + day_names: [nedelja, ponedeljak, utorak, sreda, Äetvrtak, petak, subota] + abbr_day_names: [ned, pon, uto, sre, Äet, pet, sub] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, januar, februar, mart, april, maj, jun, jul, avgust, septembar, oktobar, novembar, decembar] + abbr_month_names: [~, jan, feb, mar, apr, maj, jun, jul, avg, sep, okt, nov, dec] + # Used in date_select and datime_select. + order: + - :day + - :month + - :year + + time: + formats: + default: "%d.%m.%Y. u %H:%M" + time: "%H:%M" + short: "%d. %b u %H:%M" + long: "%d. %B %Y u %H:%M" + am: "am" + pm: "pm" + + datetime: + distance_in_words: + half_a_minute: "pola minuta" + less_than_x_seconds: + one: "manje od jedne sekunde" + other: "manje od %{count} sek." + x_seconds: + one: "jedna sekunda" + other: "%{count} sek." + less_than_x_minutes: + one: "manje od minuta" + other: "manje od %{count} min." + x_minutes: + one: "jedan minut" + other: "%{count} min." + about_x_hours: + one: "približno jedan sat" + other: "približno %{count} sati" + x_hours: + one: "1 sat" + other: "%{count} sati" + x_days: + one: "jedan dan" + other: "%{count} dana" + about_x_months: + one: "približno jedan mesec" + other: "približno %{count} meseci" + x_months: + one: "jedan mesec" + other: "%{count} meseci" + about_x_years: + one: "približno godinu dana" + other: "približno %{count} god." + over_x_years: + one: "preko godinu dana" + other: "preko %{count} god." + almost_x_years: + one: "skoro godinu dana" + other: "skoro %{count} god." + + number: + format: + separator: "," + delimiter: "" + precision: 3 + human: + format: + delimiter: "" + precision: 3 + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + + +# Used in array.to_sentence. + support: + array: + sentence_connector: "i" + skip_last_comma: false + + activerecord: + errors: + template: + header: + one: "1 error prohibited this %{model} from being saved" + other: "%{count} errors prohibited this %{model} from being saved" + messages: + inclusion: "nije ukljuÄen u spisak" + exclusion: "je rezervisan" + invalid: "je neispravan" + confirmation: "potvrda ne odgovara" + accepted: "mora biti prihvaćen" + empty: "ne može biti prazno" + blank: "ne može biti prazno" + too_long: "je predugaÄka (maksimum znakova je %{count})" + too_short: "je prekratka (minimum znakova je %{count})" + wrong_length: "je pogreÅ¡ne dužine (broj znakova mora biti %{count})" + taken: "je već u upotrebi" + not_a_number: "nije broj" + not_a_date: "nije ispravan datum" + greater_than: "mora biti veći od %{count}" + greater_than_or_equal_to: "mora biti veći ili jednak %{count}" + equal_to: "mora biti jednak %{count}" + less_than: "mora biti manji od %{count}" + less_than_or_equal_to: "mora biti manji ili jednak %{count}" + odd: "mora biti paran" + even: "mora biti neparan" + greater_than_start_date: "mora biti veći od poÄetnog datuma" + not_same_project: "ne pripada istom projektu" + circular_dependency: "Ova veza će stvoriti kružnu referencu" + cant_link_an_issue_with_a_descendant: "Problem ne može biti povezan sa jednim od svojih podzadataka" + + actionview_instancetag_blank_option: Molim odaberite + + general_text_No: 'Ne' + general_text_Yes: 'Da' + general_text_no: 'ne' + general_text_yes: 'da' + general_lang_name: 'Serbian (Srpski)' + general_csv_separator: ',' + general_csv_decimal_separator: '.' + general_csv_encoding: UTF-8 + general_pdf_encoding: UTF-8 + general_first_day_of_week: '1' + + notice_account_updated: Nalog je uspeÅ¡no ažuriran. + notice_account_invalid_creditentials: Neispravno korisniÄko ime ili lozinka. + notice_account_password_updated: Lozinka je uspeÅ¡no ažurirana. + notice_account_wrong_password: PogreÅ¡na lozinka + notice_account_register_done: KorisniÄki nalog je uspeÅ¡no kreiran. Kliknite na link koji ste dobili u e-poruci za aktivaciju. + notice_account_unknown_email: Nepoznat korisnik. + notice_can_t_change_password: Ovaj korisniÄki nalog za potvrdu identiteta koristi spoljni izvor. Nemoguće je promeniti lozinku. + notice_account_lost_email_sent: Poslata vam je e-poruka sa uputstvom za izbor nove lozinke + notice_account_activated: VaÅ¡ korisniÄki nalog je aktiviran. Sada se možete prijaviti. + notice_successful_create: UspeÅ¡no kreiranje. + notice_successful_update: UspeÅ¡no ažuriranje. + notice_successful_delete: UspeÅ¡no brisanje. + notice_successful_connection: UspeÅ¡no povezivanje. + notice_file_not_found: Strana kojoj želite pristupiti ne postoji ili je uklonjena. + notice_locking_conflict: Podatak je ažuriran od strane drugog korisnika. + notice_not_authorized: Niste ovlašćeni za pristup ovoj strani. + notice_email_sent: "E-poruka je poslata na %{value}" + notice_email_error: "Dogodila se greÅ¡ka prilikom slanja e-poruke (%{value})" + notice_feeds_access_key_reseted: VaÅ¡ RSS pristupni kljuÄ je poniÅ¡ten. + notice_api_access_key_reseted: VaÅ¡ API pristupni kljuÄ je poniÅ¡ten. + notice_failed_to_save_issues: "NeuspeÅ¡no snimanje %{count} problema od %{total} odabranih: %{ids}." + notice_failed_to_save_members: "NeuspeÅ¡no snimanje Älana(ova): %{errors}." + notice_no_issue_selected: "Ni jedan problem nije odabran! Molimo, odaberite problem koji želite da menjate." + notice_account_pending: "VaÅ¡ nalog je kreiran i Äeka na odobrenje administratora." + notice_default_data_loaded: Podrazumevano konfigurisanje je uspeÅ¡no uÄitano. + notice_unable_delete_version: Verziju je nemoguće izbrisati. + notice_unable_delete_time_entry: Stavku evidencije vremena je nemoguće izbrisati. + notice_issue_done_ratios_updated: Odnos reÅ¡enih problema je ažuriran. + + error_can_t_load_default_data: "Podrazumevano konfigurisanje je nemoguće uÄitati: %{value}" + error_scm_not_found: "Stavka ili ispravka nisu pronaÄ‘ene u spremiÅ¡tu." + error_scm_command_failed: "GreÅ¡ka se javila prilikom pokuÅ¡aja pristupa spremiÅ¡tu: %{value}" + error_scm_annotate: "Stavka ne postoji ili ne može biti oznaÄena." + error_issue_not_found_in_project: 'Problem nije pronaÄ‘en ili ne pripada ovom projektu.' + error_no_tracker_in_project: 'Ni jedno praćenje nije povezano sa ovim projektom. Molimo proverite podeÅ¡avanja projekta.' + error_no_default_issue_status: 'Podrazumevani status problema nije definisan. Molimo proverite vaÅ¡e konfigurisanje (idite na "Administracija -> Statusi problema").' + error_can_not_delete_custom_field: Nemoguće je izbrisati prilagoÄ‘eno polje + error_can_not_delete_tracker: "Ovo praćenje sadrži probleme i ne može biti obrisano." + error_can_not_remove_role: "Ova uloga je u upotrebi i ne može biti obrisana." + error_can_not_reopen_issue_on_closed_version: 'Problem dodeljen zatvorenoj verziji ne može biti ponovo otvoren' + error_can_not_archive_project: Ovaj projekat se ne može arhivirati + error_issue_done_ratios_not_updated: "Odnos reÅ¡enih problema nije ažuriran." + error_workflow_copy_source: 'Molimo odaberite izvorno praćenje ili ulogu' + error_workflow_copy_target: 'Molimo odaberite odrediÅ¡no praćenje i ulogu' + error_unable_delete_issue_status: 'Status problema je nemoguće obrisati' + error_unable_to_connect: "Povezivanje sa (%{value}) je nemoguće" + warning_attachments_not_saved: "%{count} datoteka ne može biti snimljena." + + mail_subject_lost_password: "VaÅ¡a %{value} lozinka" + mail_body_lost_password: 'Za promenu vaÅ¡e lozinke, kliknite na sledeći link:' + mail_subject_register: "Aktivacija vaÅ¡eg %{value} naloga" + mail_body_register: 'Za aktivaciju vaÅ¡eg naloga, kliknite na sledeći link:' + mail_body_account_information_external: "VaÅ¡ nalog %{value} možete koristiti za prijavu." + mail_body_account_information: Informacije o vaÅ¡em nalogu + mail_subject_account_activation_request: "Zahtev za aktivaciju naloga %{value}" + mail_body_account_activation_request: "Novi korisnik (%{value}) je registrovan. Nalog Äeka na vaÅ¡e odobrenje:" + mail_subject_reminder: "%{count} problema dospeva narednih %{days} dana" + mail_body_reminder: "%{count} problema dodeljenih vama dospeva u narednih %{days} dana:" + mail_subject_wiki_content_added: "Wiki stranica '%{id}' je dodata" + mail_body_wiki_content_added: "%{author} je dodao wiki stranicu '%{id}'." + mail_subject_wiki_content_updated: "Wiki stranica '%{id}' je ažurirana" + mail_body_wiki_content_updated: "%{author} je ažurirao wiki stranicu '%{id}'." + + + field_name: Naziv + field_description: Opis + field_summary: Rezime + field_is_required: Obavezno + field_firstname: Ime + field_lastname: Prezime + field_mail: E-adresa + field_filename: Datoteka + field_filesize: VeliÄina + field_downloads: Preuzimanja + field_author: Autor + field_created_on: Kreirano + field_updated_on: Ažurirano + field_field_format: Format + field_is_for_all: Za sve projekte + field_possible_values: Moguće vrednosti + field_regexp: Regularan izraz + field_min_length: Minimalna dužina + field_max_length: Maksimalna dužina + field_value: Vrednost + field_category: Kategorija + field_title: Naslov + field_project: Projekat + field_issue: Problem + field_status: Status + field_notes: BeleÅ¡ke + field_is_closed: Zatvoren problem + field_is_default: Podrazumevana vrednost + field_tracker: Praćenje + field_subject: Predmet + field_due_date: Krajnji rok + field_assigned_to: Dodeljeno + field_priority: Prioritet + field_fixed_version: OdrediÅ¡na verzija + field_user: Korisnik + field_principal: Glavni + field_role: Uloga + field_homepage: PoÄetna stranica + field_is_public: Javno objavljivanje + field_parent: Potprojekat od + field_is_in_roadmap: Problemi prikazani u planu rada + field_login: KorisniÄko ime + field_mail_notification: ObaveÅ¡tenja putem e-poÅ¡te + field_admin: Administrator + field_last_login_on: Poslednje povezivanje + field_language: Jezik + field_effective_date: Datum + field_password: Lozinka + field_new_password: Nova lozinka + field_password_confirmation: Potvrda lozinke + field_version: Verzija + field_type: Tip + field_host: Glavni raÄunar + field_port: Port + field_account: KorisniÄki nalog + field_base_dn: Bazni DN + field_attr_login: Atribut prijavljivanja + field_attr_firstname: Atribut imena + field_attr_lastname: Atribut prezimena + field_attr_mail: Atribut e-adrese + field_onthefly: Kreiranje korisnika u toku rada + field_start_date: PoÄetak + field_done_ratio: "% uraÄ‘eno" + field_auth_source: Režim potvrde identiteta + field_hide_mail: Sakrij moju e-adresu + field_comments: Komentar + field_url: URL + field_start_page: PoÄetna stranica + field_subproject: Potprojekat + field_hours: sati + field_activity: Aktivnost + field_spent_on: Datum + field_identifier: Identifikator + field_is_filter: Upotrebi kao filter + field_issue_to: Srodni problemi + field_delay: KaÅ¡njenje + field_assignable: Problem može biti dodeljen ovoj ulozi + field_redirect_existing_links: Preusmeri postojeće veze + field_estimated_hours: Procenjeno vreme + field_column_names: Kolone + field_time_zone: Vremenska zona + field_searchable: Može da se pretražuje + field_default_value: Podrazumevana vrednost + field_comments_sorting: Prikaži komentare + field_parent_title: MatiÄna stranica + field_editable: Izmenljivo + field_watcher: PosmatraÄ + field_identity_url: OpenID URL + field_content: Sadržaj + field_group_by: Grupisanje rezultata po + field_sharing: Deljenje + field_parent_issue: MatiÄni zadatak + + setting_app_title: Naslov aplikacije + setting_app_subtitle: Podnaslov aplikacije + setting_welcome_text: Tekst dobrodoÅ¡lice + setting_default_language: Podrazumevani jezik + setting_login_required: Obavezna potvrda identiteta + setting_self_registration: Samoregistracija + setting_attachment_max_size: Maks. veliÄina priložene datoteke + setting_issues_export_limit: OgraniÄenje izvoza „problema“ + setting_mail_from: E-adresa poÅ¡iljaoca + setting_bcc_recipients: Primaoci „Bcc“ kopije + setting_plain_text_mail: Poruka sa Äistim tekstom (bez HTML-a) + setting_host_name: Putanja i naziv glavnog raÄunara + setting_text_formatting: Oblikovanje teksta + setting_wiki_compression: Kompresija Wiki istorije + setting_feeds_limit: OgraniÄenje sadržaja izvora vesti + setting_default_projects_public: Podrazumeva se javno prikazivanje novih projekata + setting_autofetch_changesets: IzvrÅ¡avanje automatskog preuzimanja + setting_sys_api_enabled: Omogućavanje WS za upravljanje spremiÅ¡tem + setting_commit_ref_keywords: Referenciranje kljuÄnih reÄi + setting_commit_fix_keywords: Popravljanje kljuÄnih reÄi + setting_autologin: Automatska prijava + setting_date_format: Format datuma + setting_time_format: Format vremena + setting_cross_project_issue_relations: Dozvoli povezivanje problema iz unakrsnih projekata + setting_issue_list_default_columns: Podrazumevane kolone prikazane na spisku problema + setting_emails_footer: Podnožje stranice e-poruke + setting_protocol: Protokol + setting_per_page_options: Opcije prikaza objekata po stranici + setting_user_format: Format prikaza korisnika + setting_activity_days_default: Broj dana prikazanih na projektnoj aktivnosti + setting_display_subprojects_issues: Prikazuj probleme iz potprojekata na glavnom projektu, ukoliko nije drugaÄije navedeno + setting_enabled_scm: Omogućavanje SCM + setting_mail_handler_body_delimiters: "Skraćivanje e-poruke nakon jedne od ovih linija" + setting_mail_handler_api_enabled: Omogućavanje WS dolazne e-poruke + setting_mail_handler_api_key: API kljuÄ + setting_sequential_project_identifiers: Generisanje sekvencijalnog imena projekta + setting_gravatar_enabled: Koristi Gravatar korisniÄke ikone + setting_gravatar_default: Podrazumevana Gravatar slika + setting_diff_max_lines_displayed: Maks. broj prikazanih razliÄitih linija + setting_file_max_size_displayed: Maks. veliÄina tekst. datoteka prikazanih umetnuto + setting_repository_log_display_limit: Maks. broj revizija prikazanih u datoteci za evidenciju + setting_openid: Dozvoli OpenID prijavu i registraciju + setting_password_min_length: Minimalna dužina lozinke + setting_new_project_user_role_id: Kreatoru projekta (koji nije administrator) dodeljuje je uloga + setting_default_projects_modules: Podrazumevano omogućeni moduli za nove projekte + setting_issue_done_ratio: IzraÄunaj odnos reÅ¡enih problema + setting_issue_done_ratio_issue_field: koristeći polje problema + setting_issue_done_ratio_issue_status: koristeći status problema + setting_start_of_week: Prvi dan u sedmici + setting_rest_api_enabled: Omogući REST web usluge + setting_cache_formatted_text: KeÅ¡iranje obraÄ‘enog teksta + + permission_add_project: Kreiranje projekta + permission_add_subprojects: Kreiranje potpojekta + permission_edit_project: Izmena projekata + permission_select_project_modules: Odabiranje modula projekta + permission_manage_members: Upravljanje Älanovima + permission_manage_project_activities: Upravljanje projektnim aktivnostima + permission_manage_versions: Upravljanje verzijama + permission_manage_categories: Upravljanje kategorijama problema + permission_view_issues: Pregled problema + permission_add_issues: Dodavanje problema + permission_edit_issues: Izmena problema + permission_manage_issue_relations: Upravljanje vezama izmeÄ‘u problema + permission_add_issue_notes: Dodavanje beleÅ¡ki + permission_edit_issue_notes: Izmena beleÅ¡ki + permission_edit_own_issue_notes: Izmena sopstvenih beleÅ¡ki + permission_move_issues: Pomeranje problema + permission_delete_issues: Brisanje problema + permission_manage_public_queries: Upravljanje javnim upitima + permission_save_queries: Snimanje upita + permission_view_gantt: Pregledanje Gantovog dijagrama + permission_view_calendar: Pregledanje kalendara + permission_view_issue_watchers: Pregledanje spiska posmatraÄa + permission_add_issue_watchers: Dodavanje posmatraÄa + permission_delete_issue_watchers: Brisanje posmatraÄa + permission_log_time: Beleženje utroÅ¡enog vremena + permission_view_time_entries: Pregledanje utroÅ¡enog vremena + permission_edit_time_entries: Izmena utroÅ¡enog vremena + permission_edit_own_time_entries: Izmena sopstvenog utroÅ¡enog vremena + permission_manage_news: Upravljanje vestima + permission_comment_news: Komentarisanje vesti + permission_view_documents: Pregledanje dokumenata + permission_manage_files: Upravljanje datotekama + permission_view_files: Pregledanje datoteka + permission_manage_wiki: Upravljanje wiki stranicama + permission_rename_wiki_pages: Promena imena wiki stranicama + permission_delete_wiki_pages: Brisanje wiki stranica + permission_view_wiki_pages: Pregledanje wiki stranica + permission_view_wiki_edits: Pregledanje wiki istorije + permission_edit_wiki_pages: Izmena wiki stranica + permission_delete_wiki_pages_attachments: Brisanje priloženih datoteka + permission_protect_wiki_pages: ZaÅ¡tita wiki stranica + permission_manage_repository: Upravljanje spremiÅ¡tem + permission_browse_repository: Pregledanje spremiÅ¡ta + permission_view_changesets: Pregledanje skupa promena + permission_commit_access: Potvrda pristupa + permission_manage_boards: Upravljanje forumima + permission_view_messages: Pregledanje poruka + permission_add_messages: Slanje poruka + permission_edit_messages: Izmena poruka + permission_edit_own_messages: Izmena sopstvenih poruka + permission_delete_messages: Brisanje poruka + permission_delete_own_messages: Brisanje sopstvenih poruka + permission_export_wiki_pages: Izvoz wiki stranica + permission_manage_subtasks: Upravljanje podzadacima + + project_module_issue_tracking: Praćenje problema + project_module_time_tracking: Praćenje vremena + project_module_news: Vesti + project_module_documents: Dokumenti + project_module_files: Datoteke + project_module_wiki: Wiki + project_module_repository: SpremiÅ¡te + project_module_boards: Forumi + + label_user: Korisnik + label_user_plural: Korisnici + label_user_new: Novi korisnik + label_user_anonymous: Anoniman + label_project: Projekat + label_project_new: Novi projekat + label_project_plural: Projekti + label_x_projects: + zero: nema projekata + one: jedan projekat + other: "%{count} projekata" + label_project_all: Svi projekti + label_project_latest: Poslednji projekti + label_issue: Problem + label_issue_new: Novi problem + label_issue_plural: Problemi + label_issue_view_all: Prikaz svih problema + label_issues_by: "Problemi (%{value})" + label_issue_added: Problem je dodat + label_issue_updated: Problem je ažuriran + label_document: Dokument + label_document_new: Novi dokument + label_document_plural: Dokumenti + label_document_added: Dokument je dodat + label_role: Uloga + label_role_plural: Uloge + label_role_new: Nova uloga + label_role_and_permissions: Uloge i dozvole + label_member: ÄŒlan + label_member_new: Novi Älan + label_member_plural: ÄŒlanovi + label_tracker: Praćenje + label_tracker_plural: Praćenja + label_tracker_new: Novo praćenje + label_workflow: Tok posla + label_issue_status: Status problema + label_issue_status_plural: Statusi problema + label_issue_status_new: Novi status + label_issue_category: Kategorija problema + label_issue_category_plural: Kategorije problema + label_issue_category_new: Nova kategorija + label_custom_field: PrilagoÄ‘eno polje + label_custom_field_plural: PrilagoÄ‘ena polja + label_custom_field_new: Novo prilagoÄ‘eno polje + label_enumerations: Nabrojiva lista + label_enumeration_new: Nova vrednost + label_information: Informacija + label_information_plural: Informacije + label_please_login: Molimo, prijavite se + label_register: Registracija + label_login_with_open_id_option: ili prijava sa OpenID + label_password_lost: Izgubljena lozinka + label_home: PoÄetak + label_my_page: Moja stranica + label_my_account: Moj nalog + label_my_projects: Moji projekti + label_my_page_block: My page block + label_administration: Administracija + label_login: Prijava + label_logout: Odjava + label_help: Pomoć + label_reported_issues: Prijavljeni problemi + label_assigned_to_me_issues: Problemi dodeljeni meni + label_last_login: Poslednje povezivanje + label_registered_on: Registrovan + label_activity: Aktivnost + label_overall_activity: Celokupna aktivnost + label_user_activity: "Aktivnost korisnika %{value}" + label_new: Novo + label_logged_as: Prijavljeni ste kao + label_environment: Okruženje + label_authentication: Potvrda identiteta + label_auth_source: Režim potvrde identiteta + label_auth_source_new: Novi režim potvrde identiteta + label_auth_source_plural: Režimi potvrde identiteta + label_subproject_plural: Potprojekti + label_subproject_new: Novi potprojekat + label_and_its_subprojects: "%{value} i njegovi potprojekti" + label_min_max_length: Min. - Maks. dužina + label_list: Spisak + label_date: Datum + label_integer: Ceo broj + label_float: Sa pokretnim zarezom + label_boolean: LogiÄki operator + label_string: Tekst + label_text: Dugi tekst + label_attribute: Osobina + label_attribute_plural: Osobine + label_no_data: Nema podataka za prikazivanje + label_change_status: Promena statusa + label_history: Istorija + label_attachment: Datoteka + label_attachment_new: Nova datoteka + label_attachment_delete: Brisanje datoteke + label_attachment_plural: Datoteke + label_file_added: Datoteka je dodata + label_report: IzveÅ¡taj + label_report_plural: IzveÅ¡taji + label_news: Vesti + label_news_new: Dodavanje vesti + label_news_plural: Vesti + label_news_latest: Poslednje vesti + label_news_view_all: Prikaz svih vesti + label_news_added: Vesti su dodate + label_settings: PodeÅ¡avanja + label_overview: Pregled + label_version: Verzija + label_version_new: Nova verzija + label_version_plural: Verzije + label_close_versions: Zatvori zavrÅ¡ene verzije + label_confirmation: Potvrda + label_export_to: 'TakoÄ‘e dostupno i u varijanti:' + label_read: ÄŒitanje... + label_public_projects: Javni projekti + label_open_issues: otvoren + label_open_issues_plural: otvorenih + label_closed_issues: zatvoren + label_closed_issues_plural: zatvorenih + label_x_open_issues_abbr_on_total: + zero: 0 otvorenih / %{total} + one: 1 otvoren / %{total} + other: "%{count} otvorenih / %{total}" + label_x_open_issues_abbr: + zero: 0 otvorenih + one: 1 otvoren + other: "%{count} otvorenih" + label_x_closed_issues_abbr: + zero: 0 zatvorenih + one: 1 zatvoren + other: "%{count} zatvorenih" + label_total: Ukupno + label_permissions: Dozvole + label_current_status: Trenutni status + label_new_statuses_allowed: Novi statusi dozvoljeni + label_all: svi + label_none: nijedan + label_nobody: nikome + label_next: Sledeće + label_previous: Prethodno + label_used_by: Koristio + label_details: Detalji + label_add_note: Dodaj beleÅ¡ku + label_per_page: Po strani + label_calendar: Kalendar + label_months_from: meseci od + label_gantt: Gantov dijagram + label_internal: UnutraÅ¡nji + label_last_changes: "poslednjih %{count} promena" + label_change_view_all: Prikaži sve promene + label_personalize_page: Personalizuj ovu stranu + label_comment: Komentar + label_comment_plural: Komentari + label_x_comments: + zero: bez komentara + one: jedan komentar + other: "%{count} komentara" + label_comment_add: Dodaj komentar + label_comment_added: Komentar dodat + label_comment_delete: ObriÅ¡i komentare + label_query: PrilagoÄ‘en upit + label_query_plural: PrilagoÄ‘eni upiti + label_query_new: Novi upit + label_filter_add: Dodavanje filtera + label_filter_plural: Filteri + label_equals: je + label_not_equals: nije + label_in_less_than: manje od + label_in_more_than: viÅ¡e od + label_greater_or_equal: '>=' + label_less_or_equal: '<=' + label_in: u + label_today: danas + label_all_time: sve vreme + label_yesterday: juÄe + label_this_week: ove sedmice + label_last_week: poslednje sedmice + label_last_n_days: "poslednjih %{count} dana" + label_this_month: ovog meseca + label_last_month: poslednjeg meseca + label_this_year: ove godine + label_date_range: Vremenski period + label_less_than_ago: pre manje od nekoliko dana + label_more_than_ago: pre viÅ¡e od nekoliko dana + label_ago: pre nekoliko dana + label_contains: sadrži + label_not_contains: ne sadrži + label_day_plural: dana + label_repository: SpremiÅ¡te + label_repository_plural: SpremiÅ¡ta + label_browse: Pregledanje + label_branch: Grana + label_tag: Oznaka + label_revision: Revizija + label_revision_plural: Revizije + label_revision_id: "Revizija %{value}" + label_associated_revisions: Pridružene revizije + label_added: dodato + label_modified: promenjeno + label_copied: kopirano + label_renamed: preimenovano + label_deleted: izbrisano + label_latest_revision: Poslednja revizija + label_latest_revision_plural: Poslednje revizije + label_view_revisions: Pregled revizija + label_view_all_revisions: Pregled svih revizija + label_max_size: Maksimalna veliÄina + label_sort_highest: PremeÅ¡tanje na vrh + label_sort_higher: PremeÅ¡tanje na gore + label_sort_lower: PremeÅ¡tanje na dole + label_sort_lowest: PremeÅ¡tanje na dno + label_roadmap: Plan rada + label_roadmap_due_in: "Dospeva %{value}" + label_roadmap_overdue: "%{value} najkasnije" + label_roadmap_no_issues: Nema problema za ovu verziju + label_search: Pretraga + 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_page: Wiki stranica + label_wiki_page_plural: Wiki stranice + label_index_by_title: Indeksiranje po naslovu + label_index_by_date: Indeksiranje po datumu + label_current_version: Trenutna verzija + label_preview: Pregled + label_feed_plural: Izvori vesti + label_changes_details: Detalji svih promena + label_issue_tracking: Praćenje problema + label_spent_time: UtroÅ¡eno vreme + label_overall_spent_time: Celokupno utroÅ¡eno vreme + label_f_hour: "%{value} sat" + label_f_hour_plural: "%{value} sati" + label_time_tracking: Praćenje vremena + label_change_plural: Promene + label_statistics: Statistika + label_commits_per_month: IzvrÅ¡enja meseÄno + label_commits_per_author: IzvrÅ¡enja po autoru + label_view_diff: Pogledaj razlike + label_diff_inline: unutra + label_diff_side_by_side: uporedo + label_options: Opcije + label_copy_workflow_from: Kopiranje toka posla od + label_permissions_report: IzveÅ¡taj o dozvolama + label_watched_issues: Posmatrani problemi + label_related_issues: Srodni problemi + label_applied_status: Primenjeni statusi + label_loading: UÄitavanje... + label_relation_new: Nova relacija + label_relation_delete: Brisanje relacije + label_relates_to: srodnih sa + label_duplicates: dupliranih + label_duplicated_by: dupliranih od + label_blocks: odbijenih + label_blocked_by: odbijenih od + label_precedes: prethodi + label_follows: praćenih + label_end_to_start: od kraja do poÄetka + label_end_to_end: od kraja do kraja + label_start_to_start: od poÄetka do poÄetka + label_start_to_end: od poÄetka do kraja + label_stay_logged_in: Ostanite prijavljeni + label_disabled: onemogućeno + label_show_completed_versions: Prikazivanje zavrÅ¡ene verzije + label_me: meni + label_board: Forum + label_board_new: Novi forum + label_board_plural: Forumi + label_board_locked: ZakljuÄana + label_board_sticky: Lepljiva + label_topic_plural: Teme + label_message_plural: Poruke + label_message_last: Poslednja poruka + label_message_new: Nova poruka + label_message_posted: Poruka je dodata + label_reply_plural: Odgovori + label_send_information: PoÅ¡alji korisniku detalje naloga + label_year: Godina + label_month: Mesec + label_week: Sedmica + label_date_from: Å alje + label_date_to: Prima + label_language_based: Bazirano na jeziku korisnika + label_sort_by: "Sortirano po %{value}" + label_send_test_email: Slanje probne e-poruke + label_feeds_access_key: RSS pristupni kljuÄ + label_missing_feeds_access_key: RSS pristupni kljuÄ nedostaje + label_feeds_access_key_created_on: "RSS pristupni kljuÄ je napravljen pre %{value}" + label_module_plural: Moduli + label_added_time_by: "Dodao %{author} pre %{age}" + label_updated_time_by: "Ažurirao %{author} pre %{age}" + label_updated_time: "Ažurirano pre %{value}" + label_jump_to_a_project: Skok na projekat... + label_file_plural: Datoteke + label_changeset_plural: Skupovi promena + label_default_columns: Podrazumevane kolone + label_no_change_option: (Bez promena) + label_bulk_edit_selected_issues: Grupna izmena odabranih problema + label_theme: Tema + label_default: Podrazumevano + label_search_titles_only: Pretražuj samo naslove + label_user_mail_option_all: "Za bilo koji dogaÄ‘aj na svim mojim projektima" + label_user_mail_option_selected: "Za bilo koji dogaÄ‘aj na samo odabranim projektima..." + label_user_mail_no_self_notified: "Ne želim biti obaveÅ¡tavan za promene koje sam pravim" + label_registration_activation_by_email: aktivacija naloga putem e-poruke + label_registration_manual_activation: ruÄna aktivacija naloga + label_registration_automatic_activation: automatska aktivacija naloga + label_display_per_page: "Broj stavki po stranici: %{value}" + label_age: Starost + label_change_properties: Promeni svojstva + label_general: OpÅ¡ti + label_more: ViÅ¡e + label_scm: SCM + label_plugins: Dodatne komponente + label_ldap_authentication: LDAP potvrda identiteta + label_downloads_abbr: D/L + label_optional_description: Opciono opis + label_add_another_file: Dodaj joÅ¡ jednu datoteku + label_preferences: PodeÅ¡avanja + label_chronological_order: po hronoloÅ¡kom redosledu + label_reverse_chronological_order: po obrnutom hronoloÅ¡kom redosledu + label_planning: Planiranje + label_incoming_emails: Dolazne e-poruke + label_generate_key: Generisanje kljuÄa + label_issue_watchers: PosmatraÄi + label_example: Primer + label_display: Prikaz + label_sort: Sortiranje + label_ascending: Rastući niz + label_descending: Opadajući niz + label_date_from_to: Od %{start} do %{end} + label_wiki_content_added: Wiki stranica je dodata + label_wiki_content_updated: Wiki stranica je ažurirana + label_group: Grupa + label_group_plural: Grupe + label_group_new: Nova grupa + label_time_entry_plural: UtroÅ¡eno vreme + label_version_sharing_none: Nije deljeno + label_version_sharing_descendants: Sa potprojektima + label_version_sharing_hierarchy: Sa hijerarhijom projekta + label_version_sharing_tree: Sa stablom projekta + label_version_sharing_system: Sa svim projektima + label_update_issue_done_ratios: Ažuriraj odnos reÅ¡enih problema + label_copy_source: Izvor + label_copy_target: OdrediÅ¡te + label_copy_same_as_target: Isto kao odrediÅ¡te + label_display_used_statuses_only: Prikazuj statuse korišćene samo od strane ovog praćenja + label_api_access_key: API pristupni kljuÄ + label_missing_api_access_key: Nedostaje API pristupni kljuÄ + label_api_access_key_created_on: "API pristupni kljuÄ je kreiran pre %{value}" + label_profile: Profil + label_subtask_plural: Podzadatak + label_project_copy_notifications: PoÅ¡alji e-poruku sa obaveÅ¡tenjem prilikom kopiranja projekta + + button_login: Prijava + button_submit: PoÅ¡alji + button_save: Snimi + button_check_all: UkljuÄi sve + button_uncheck_all: IskljuÄi sve + button_delete: IzbriÅ¡i + button_create: Kreiraj + button_create_and_continue: Kreiraj i nastavi + button_test: Test + button_edit: Izmeni + button_add: Dodaj + button_change: Promeni + button_apply: Primeni + button_clear: ObriÅ¡i + button_lock: ZakljuÄaj + button_unlock: OtkljuÄaj + button_download: Preuzmi + button_list: Spisak + button_view: Prikaži + button_move: Pomeri + button_move_and_follow: Pomeri i prati + button_back: Nazad + button_cancel: PoniÅ¡ti + button_activate: Aktiviraj + button_sort: Sortiraj + button_log_time: Evidentiraj vreme + button_rollback: Povratak na ovu verziju + button_watch: Prati + button_unwatch: Ne prati viÅ¡e + button_reply: Odgovori + button_archive: Arhiviraj + button_unarchive: Vrati iz arhive + button_reset: PoniÅ¡ti + button_rename: Preimenuj + button_change_password: Promeni lozinku + button_copy: Kopiraj + button_copy_and_follow: Kopiraj i prati + button_annotate: Pribeleži + button_update: Ažuriraj + button_configure: Podesi + button_quote: Pod navodnicima + button_duplicate: Dupliraj + button_show: Prikaži + + status_active: aktivni + status_registered: registrovani + status_locked: zakljuÄani + + version_status_open: otvoren + version_status_locked: zakljuÄan + version_status_closed: zatvoren + + field_active: Aktivan + + text_select_mail_notifications: Odaberi akcije za koje će obaveÅ¡tenje biti poslato putem e-poÅ¡te. + text_regexp_info: npr. ^[A-Z0-9]+$ + text_min_max_length_info: 0 znaÄi bez ograniÄenja + text_project_destroy_confirmation: Jeste li sigurni da želite da izbriÅ¡ete ovaj projekat i sve pripadajuće podatke? + text_subprojects_destroy_warning: "Potprojekti: %{value} će takoÄ‘e biti izbrisan." + text_workflow_edit: Odaberite ulogu i praćenje za izmenu toka posla + text_are_you_sure: Jeste li sigurni? + text_journal_changed: "%{label} promenjen od %{old} u %{new}" + text_journal_set_to: "%{label} postavljen u %{value}" + text_journal_deleted: "%{label} izbrisano (%{old})" + text_journal_added: "%{label} %{value} dodato" + text_tip_issue_begin_day: zadatak poÄinje ovog dana + text_tip_issue_end_day: zadatak se zavrÅ¡ava ovog dana + text_tip_issue_begin_end_day: zadatak poÄinje i zavrÅ¡ava ovog dana + text_caracters_maximum: "NajviÅ¡e %{count} znak(ova)." + text_caracters_minimum: "Broj znakova mora biti najmanje %{count}." + text_length_between: "Broj znakova mora biti izmeÄ‘u %{min} i %{max}." + text_tracker_no_workflow: Ovo praćenje nema definisan tok posla + text_unallowed_characters: Nedozvoljeni znakovi + text_comma_separated: Dozvoljene su viÅ¡estruke vrednosti (odvojene zarezom). + text_line_separated: Dozvoljene su viÅ¡estruke vrednosti (jedan red za svaku vrednost). + text_issues_ref_in_commit_messages: Referenciranje i popravljanje problema u izvrÅ¡nim porukama + text_issue_added: "%{author} je prijavio problem %{id}." + text_issue_updated: "%{author} je ažurirao problem %{id}." + text_wiki_destroy_confirmation: Jeste li sigurni da želite da obriÅ¡ete wiki i sav sadržaj? + text_issue_category_destroy_question: "Nekoliko problema (%{count}) je dodeljeno ovoj kategoriji. Å ta želite da uradite?" + text_issue_category_destroy_assignments: Ukloni dodeljene kategorije + text_issue_category_reassign_to: Dodeli ponovo probleme ovoj kategoriji + text_user_mail_option: "Za neizabrane projekte, dobićete samo obaveÅ¡tenje o stvarima koje pratite ili ste ukljuÄeni (npr. problemi Äiji ste vi autor ili zastupnik)." + text_no_configuration_data: "Uloge, praćenja, statusi problema i toka posla joÅ¡ uvek nisu podeÅ¡eni.\nPreporuÄljivo je da uÄitate podrazumevano konfigurisanje. Izmena je moguća nakon prvog uÄitavanja." + text_load_default_configuration: UÄitaj podrazumevano konfigurisanje + text_status_changed_by_changeset: "Primenjeno u skupu sa promenama %{value}." + text_issues_destroy_confirmation: 'Jeste li sigurni da želite da izbriÅ¡ete odabrane probleme?' + text_select_project_modules: 'Odaberite module koje želite omogućiti za ovaj projekat:' + text_default_administrator_account_changed: Podrazumevani administratorski nalog je promenjen + text_file_repository_writable: Fascikla priloženih datoteka je upisiva + text_plugin_assets_writable: Fascikla elemenata dodatnih komponenti je upisiva + text_rmagick_available: RMagick je dostupan (opciono) + text_destroy_time_entries_question: "%{hours} sati je prijavljeno za ovaj problem koji želite izbrisati. Å ta želite da uradite?" + text_destroy_time_entries: IzbriÅ¡i prijavljene sate + text_assign_time_entries_to_project: Dodeli prijavljene sate projektu + text_reassign_time_entries: 'Dodeli ponovo prijavljene sate ovom problemu:' + text_user_wrote: "%{value} je napisao:" + text_enumeration_destroy_question: "%{count} objekat(a) je dodeljeno ovoj vrednosti." + text_enumeration_category_reassign_to: 'Dodeli ih ponovo ovoj vrednosti:' + text_email_delivery_not_configured: "Isporuka e-poruka nije konfigurisana i obaveÅ¡tenja su onemogućena.\nPodesite vaÅ¡ SMTP server u config/configuration.yml i pokrenite ponovo aplikaciju za njihovo omogućavanje." + text_repository_usernames_mapping: "Odaberite ili ažurirajte Redmine korisnike mapiranjem svakog korisniÄkog imena pronaÄ‘enog u evidenciji spremiÅ¡ta.\nKorisnici sa istim Redmine imenom i imenom spremiÅ¡ta ili e-adresom su automatski mapirani." + text_diff_truncated: '... Ova razlika je iseÄena jer je dostignuta maksimalna veliÄina prikaza.' + text_custom_field_possible_values_info: 'Jedan red za svaku vrednost' + text_wiki_page_destroy_question: "Ova stranica ima %{descendants} podreÄ‘enih stranica i podstranica. Å ta želite da uradite?" + text_wiki_page_nullify_children: "Zadrži podreÄ‘ene stranice kao korene stranice" + text_wiki_page_destroy_children: "IzbriÅ¡i podreÄ‘ene stranice i sve njihove podstranice" + text_wiki_page_reassign_children: "Dodeli ponovo podreÄ‘ene stranice ovoj matiÄnoj stranici" + text_own_membership_delete_confirmation: "Nakon uklanjanja pojedinih ili svih vaÅ¡ih dozvola nećete viÅ¡e moći da ureÄ‘ujete ovaj projekat.\nŽelite li da nastavite?" + text_zoom_in: Uvećaj + text_zoom_out: Umanji + + default_role_manager: Menadžer + default_role_developer: Programer + default_role_reporter: IzveÅ¡taÄ + default_tracker_bug: GreÅ¡ka + default_tracker_feature: Funkcionalnost + default_tracker_support: PodrÅ¡ka + default_issue_status_new: Novo + default_issue_status_in_progress: U toku + default_issue_status_resolved: ReÅ¡eno + default_issue_status_feedback: Povratna informacija + default_issue_status_closed: Zatvoreno + default_issue_status_rejected: Odbijeno + default_doc_category_user: KorisniÄka dokumentacija + default_doc_category_tech: TehniÄka dokumentacija + default_priority_low: Nizak + default_priority_normal: Normalan + default_priority_high: Visok + default_priority_urgent: Hitno + default_priority_immediate: Neposredno + default_activity_design: Dizajn + default_activity_development: Razvoj + + enumeration_issue_priorities: Prioriteti problema + enumeration_doc_categories: Kategorije dokumenta + enumeration_activities: Aktivnosti (praćenje vremena) + enumeration_system_activity: Sistemska aktivnost + + field_time_entries: Vreme evidencije + project_module_gantt: Gantov dijagram + project_module_calendar: Kalendar + button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" + field_text: Text field + label_user_mail_option_only_owner: Samo za stvari koje posedujem + setting_default_notification_option: Podrazumevana opcija za notifikaciju + label_user_mail_option_only_my_events: Za dogadjaje koje pratim ili sam u njih ukljuÄen + label_user_mail_option_only_assigned: Za dogadjaje koji su mi dodeljeni liÄno + label_user_mail_option_none: Bez obaveÅ¡tenja + field_member_of_group: Assignee's group + field_assigned_to_role: Assignee's role + notice_not_authorized_archived_project: Projekat kome pokuÅ¡avate da pristupite je arhiviran + label_principal_search: "Traži korisnike ili grupe:" + label_user_search: "Traži korisnike:" + field_visible: Vidljivo + setting_emails_header: Email zaglavlje + setting_commit_logtime_activity_id: Activity for logged time + text_time_logged_by_changeset: Applied in changeset %{value}. + setting_commit_logtime_enabled: Omogući praćenje vremena + notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) + setting_gantt_items_limit: Maksimalan broj stavki na gant grafiku + field_warn_on_leaving_unsaved: Upozori me ako napuÅ¡tam stranu sa tekstom koji nije snimljen + text_warn_on_leaving_unsaved: Strana sadrži tekst koji nije snimljen i biće izgubljen ako je napustite. + label_my_queries: My custom queries + text_journal_changed_no_detail: "%{label} ažuriran" + label_news_comment_added: Komentar dodat u novosti + button_expand_all: ProÅ¡iri sve + button_collapse_all: Zatvori sve + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + label_bulk_edit_selected_time_entries: Bulk edit selected time entries + text_time_entries_destroy_confirmation: Da li ste sigurni da želite da obriÅ¡ete selektovane stavke ? + label_role_anonymous: Anonimus + label_role_non_member: Nije Älan + label_issue_note_added: Nota dodana + label_issue_status_updated: Status ažuriran + label_issue_priority_updated: Prioritet ažuriran + label_issues_visibility_own: Problem kreiran od strane ili je dodeljen korisniku + field_issues_visibility: Vidljivost problema + label_issues_visibility_all: Svi problemi + permission_set_own_issues_private: Podesi sopstveni problem kao privatan ili javan + field_is_private: Privatno + permission_set_issues_private: Podesi problem kao privatan ili javan + label_issues_visibility_public: Svi javni problemi + text_issues_destroy_descendants_confirmation: Ova operacija će takoÄ‘e obrisati %{count} podzadataka. + field_commit_logs_encoding: Kodiranje izvrÅ¡nih poruka + field_scm_path_encoding: Path encoding + text_scm_path_encoding_note: "Default: UTF-8" + field_path_to_repository: Path to repository + field_root_directory: Root directory + field_cvs_module: Module + field_cvsroot: CVSROOT + text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) + text_scm_command: Command + text_scm_command_version: Version + label_git_report_last_commit: Report last commit for files and directories + notice_issue_successful_create: Issue %{id} created. + label_between: between + setting_issue_group_assignment: Allow issue assignment to groups + label_diff: diff + text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) + description_query_sort_criteria_direction: Sort direction + description_project_scope: Search scope + description_filter: Filter + description_user_mail_notification: Mail notification settings + description_date_from: Enter start date + description_message_content: Message content + description_available_columns: Available Columns + description_date_range_interval: Choose range by selecting start and end date + description_issue_category_reassign: Choose issue category + description_search: Searchfield + description_notes: Notes + description_date_range_list: Choose range from list + description_choose_project: Projects + description_date_to: Enter end date + description_query_sort_criteria_attribute: Sort attribute + description_wiki_subpages_reassign: Choose new parent page + description_selected_columns: Selected Columns + label_parent_revision: Parent + label_child_revision: Child + error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. + setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues + button_edit_section: Edit this section + setting_repositories_encodings: Attachments and repositories encodings + description_all_columns: All Columns + button_export: Export + label_export_options: "%{export_format} export options" + error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) + notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." + label_x_issues: + zero: 0 problem + one: 1 problem + other: "%{count} problemi" + label_repository_new: New repository + field_repository_is_default: Main repository + label_copy_attachments: Copy attachments + label_item_position: "%{position}/%{count}" + label_completed_versions: Completed versions + text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. + field_multiple: Multiple values + setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed + text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes + text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) + notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. + text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} + permission_manage_related_issues: Manage related issues + field_auth_source_ldap_filter: LDAP filter + label_search_for_watchers: Search for watchers to add + notice_account_deleted: Your account has been permanently deleted. + setting_unsubscribe: Allow users to delete their own account + button_delete_my_account: Delete my account + text_account_destroy_confirmation: |- + Are you sure you want to proceed? + Your account will be permanently deleted, with no way to reactivate it. + error_session_expired: Your session has expired. Please login again. + text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." + setting_session_lifetime: Session maximum lifetime + setting_session_timeout: Session inactivity timeout + label_session_expiration: Session expiration + permission_close_project: Close / reopen the project + label_show_closed_projects: View closed projects + button_close: Close + button_reopen: Reopen + project_status_active: active + project_status_closed: closed + project_status_archived: archived + text_project_closed: This project is closed and read-only. + notice_user_successful_create: User %{id} created. + field_core_fields: Standard fields + field_timeout: Timeout (in seconds) + setting_thumbnails_enabled: Display attachment thumbnails + setting_thumbnails_size: Thumbnails size (in pixels) + label_status_transitions: Status transitions + label_fields_permissions: Fields permissions + label_readonly: Read-only + label_required: Required + text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. + field_board_parent: Parent forum + label_attribute_of_project: Project's %{name} + label_attribute_of_author: Author's %{name} + label_attribute_of_assigned_to: Assignee's %{name} + label_attribute_of_fixed_version: Target version's %{name} + label_copy_subtasks: Copy subtasks + label_copied_to: copied to + label_copied_from: copied from + label_any_issues_in_project: any issues in project + label_any_issues_not_in_project: any issues not in project + field_private_notes: Private notes + permission_view_private_notes: View private notes + permission_set_notes_private: Set notes as private + label_no_issues_in_project: no issues in project + label_any: svi + label_last_n_weeks: last %{count} weeks + setting_cross_project_subtasks: Allow cross-project subtasks + label_cross_project_descendants: Sa potprojektima + label_cross_project_tree: Sa stablom projekta + label_cross_project_hierarchy: Sa hijerarhijom projekta + label_cross_project_system: Sa svim projektima + button_hide: Hide + setting_non_working_week_days: Non-working days + label_in_the_next_days: in the next + label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Ukupno + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/76/764f20578b33c5ef0c99adc3a136111ff27ce87f.svn-base --- a/.svn/pristine/76/764f20578b33c5ef0c99adc3a136111ff27ce87f.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,65 +0,0 @@ -module CodeRay -module Encoders - - class HTML - class CSS # :nodoc: - - attr :stylesheet - - def CSS.load_stylesheet style = nil - CodeRay::Styles[style] - end - - def initialize style = :default - @classes = Hash.new - style = CSS.load_stylesheet style - @stylesheet = [ - style::CSS_MAIN_STYLES, - style::TOKEN_COLORS.gsub(/^(?!$)/, '.CodeRay ') - ].join("\n") - parse style::TOKEN_COLORS - end - - def get_style styles - cl = @classes[styles.first] - return '' unless cl - style = '' - 1.upto styles.size do |offset| - break if style = cl[styles[offset .. -1]] - end - # warn 'Style not found: %p' % [styles] if style.empty? - return style - end - - private - - CSS_CLASS_PATTERN = / - ( # $1 = selectors - (?: - (?: \s* \. [-\w]+ )+ - \s* ,? - )+ - ) - \s* \{ \s* - ( [^\}]+ )? # $2 = style - \s* \} \s* - | - ( [^\n]+ ) # $3 = error - /mx - def parse stylesheet - stylesheet.scan CSS_CLASS_PATTERN do |selectors, style, error| - raise "CSS parse error: '#{error.inspect}' not recognized" if error - for selector in selectors.split(',') - classes = selector.scan(/[-\w]+/) - cl = classes.pop - @classes[cl] ||= Hash.new - @classes[cl][classes] = style.to_s.strip.delete(' ').chomp(';') - end - end - end - - end - end - -end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/76/7653b9d7574c0a4c89892ba03eb88b9209dbb271.svn-base --- a/.svn/pristine/76/7653b9d7574c0a4c89892ba03eb88b9209dbb271.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,66 +0,0 @@ -module CodeRay -module Scanners - - # = Debug Scanner - # - # Parses the output of the Encoders::Debug encoder. - class Raydebug < Scanner - - register_for :raydebug - file_extension 'raydebug' - title 'CodeRay Token Dump' - - protected - - def scan_tokens encoder, options - - opened_tokens = [] - - until eos? - - if match = scan(/\s+/) - encoder.text_token match, :space - - elsif match = scan(/ (\w+) \( ( [^\)\\]* ( \\. [^\)\\]* )* ) /x) - kind = self[1] - encoder.text_token kind, :class - encoder.text_token '(', :operator - match = self[2] - encoder.text_token match, kind.to_sym - encoder.text_token match, :operator if match = scan(/\)/) - - elsif match = scan(/ (\w+) ([<\[]) /x) - kind = self[1] - case self[2] - when '<' - encoder.text_token kind, :class - when '[' - encoder.text_token kind, :class - else - raise 'CodeRay bug: This case should not be reached.' - end - kind = kind.to_sym - opened_tokens << kind - encoder.begin_group kind - encoder.text_token self[2], :operator - - elsif !opened_tokens.empty? && match = scan(/ [>\]] /x) - encoder.text_token match, :operator - encoder.end_group opened_tokens.pop - - else - encoder.text_token getch, :space - - end - - end - - encoder.end_group opened_tokens.pop until opened_tokens.empty? - - encoder - end - - end - -end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/76/765c4d02e29c8c91040d1cddac7513edae190213.svn-base --- a/.svn/pristine/76/765c4d02e29c8c91040d1cddac7513edae190213.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,18 +0,0 @@ -

    <%= link_to h(@board.name), :controller => 'boards', :action => 'show', :project_id => @project, :id => @board %> » <%=h @message.subject %>

    - -<% form_for :message, @message, :url => {:action => 'edit'}, :html => {:multipart => true, :id => 'message-form'} do |f| %> - <%= render :partial => 'form', :locals => {:f => f, :replying => !@message.parent.nil?} %> - <%= submit_tag l(:button_save) %> - <%= link_to_remote l(:label_preview), - { :url => { :controller => 'messages', :action => 'preview', :board_id => @board }, - :method => 'post', - :update => 'preview', - :with => "Form.serialize('message-form')", - :complete => "Element.scrollTo('preview')" - }, :accesskey => accesskey(:preview) %> -<% end %> -
    - -<% content_for :header_tags do %> - <%= stylesheet_link_tag 'scm' %> -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/76/7692f8f9ec64d588c08e89635d4d3bb3b5c0c1d4.svn-base --- a/.svn/pristine/76/7692f8f9ec64d588c08e89635d4d3bb3b5c0c1d4.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,44 +0,0 @@ ---- -projects_trackers_001: - project_id: 4 - tracker_id: 3 -projects_trackers_002: - project_id: 1 - tracker_id: 1 -projects_trackers_003: - project_id: 5 - tracker_id: 1 -projects_trackers_004: - project_id: 1 - tracker_id: 2 -projects_trackers_005: - project_id: 5 - tracker_id: 2 -projects_trackers_006: - project_id: 5 - tracker_id: 3 -projects_trackers_007: - project_id: 2 - tracker_id: 1 -projects_trackers_008: - project_id: 2 - tracker_id: 2 -projects_trackers_009: - project_id: 2 - tracker_id: 3 -projects_trackers_010: - project_id: 3 - tracker_id: 2 -projects_trackers_011: - project_id: 3 - tracker_id: 3 -projects_trackers_012: - project_id: 4 - tracker_id: 1 -projects_trackers_013: - project_id: 4 - tracker_id: 2 -projects_trackers_014: - project_id: 1 - tracker_id: 3 - \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/76/76b52953c1403e5158177a89f2ffc5c4d36f9088.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/76/76b52953c1403e5158177a89f2ffc5c4d36f9088.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,48 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class SearchHelperTest < ActionView::TestCase + include SearchHelper + include ERB::Util + + def test_highlight_single_token + assert_equal 'This is a token.', + highlight_tokens('This is a token.', %w(token)) + end + + def test_highlight_multiple_tokens + assert_equal 'This is a token and another token.', + highlight_tokens('This is a token and another token.', %w(token another)) + end + + def test_highlight_should_not_exceed_maximum_length + s = (('1234567890' * 100) + ' token ') * 100 + r = highlight_tokens(s, %w(token)) + assert r.include?('token') + assert r.length <= 1300 + end + + def test_highlight_multibyte + s = ('й' * 200) + ' token ' + ('й' * 200) + r = highlight_tokens(s, %w(token)) + assert_equal ('й' * 45) + ' ... ' + ('й' * 44) + ' token ' + ('й' * 44) + ' ... ' + ('й' * 45), r + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/76/76f5b1657c4e0eb2717b1dfffe6b49bb8cd094b8.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/76/76f5b1657c4e0eb2717b1dfffe6b49bb8cd094b8.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,22 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 VersionCustomField < CustomField + def type_name + :label_version_plural + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/77/7718e8a0efeaa5f26e8369285e5ddf3e72a45738.svn-base --- a/.svn/pristine/77/7718e8a0efeaa5f26e8369285e5ddf3e72a45738.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,26 +0,0 @@ -class PluginMail < ActionMailer::Base - def mail_from_plugin(note=nil) - body(:note => note) - end - - def mail_from_plugin_with_application_template(note=nil) - body(:note => note) - end - - def multipart_from_plugin - content_type 'multipart/alternative' - part :content_type => "text/html", :body => render_message("multipart_from_plugin_html", {}) - part "text/plain" do |p| - p.body = render_message("multipart_from_plugin_plain", {}) - end - end - - def multipart_from_plugin_with_application_template - content_type 'multipart/alternative' - part :content_type => "text/html", :body => render_message("multipart_from_plugin_with_application_template_html", {}) - part "text/plain" do |p| - p.body = render_message("multipart_from_plugin_with_application_template_plain", {}) - end - end - -end \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/77/7727bb04d0abb3e8e4031848fb82a82cb998f0af.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/77/7727bb04d0abb3e8e4031848fb82a82cb998f0af.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,115 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 ASC, #{Attachment.table_name}.id ASC", + :dependent => :destroy) + send :include, Redmine::Acts::Attachable::InstanceMethods + before_save :attach_saved_attachments + end + end + + module InstanceMethods + def self.included(base) + base.extend ClassMethods + end + + def attachments_visible?(user=User.current) + (respond_to?(:visible?) ? visible?(user) : true) && + user.allowed_to?(self.class.attachable_options[:view_permission], self.project) + end + + def attachments_deletable?(user=User.current) + (respond_to?(:visible?) ? visible?(user) : true) && + user.allowed_to?(self.class.attachable_options[:delete_permission], self.project) + end + + def saved_attachments + @saved_attachments ||= [] + end + + def unsaved_attachments + @unsaved_attachments ||= [] + end + + def save_attachments(attachments, author=User.current) + if attachments.is_a?(Hash) + attachments = attachments.stringify_keys + attachments = attachments.to_a.sort {|a, b| + if a.first.to_i > 0 && b.first.to_i > 0 + a.first.to_i <=> b.first.to_i + elsif a.first.to_i > 0 + 1 + elsif b.first.to_i > 0 + -1 + else + a.first <=> b.first + end + } + attachments = attachments.map(&:last) + end + if attachments.is_a?(Array) + attachments.each do |attachment| + next unless attachment.is_a?(Hash) + a = nil + if file = attachment['file'] + next unless file.size > 0 + a = Attachment.create(:file => file, :author => author) + elsif token = attachment['token'] + a = Attachment.find_by_token(token) + next unless a + a.filename = attachment['filename'] unless attachment['filename'].blank? + a.content_type = attachment['content_type'] + end + next unless a + a.description = attachment['description'].to_s.strip + if a.new_record? + unsaved_attachments << a + else + saved_attachments << a + end + end + end + {:files => saved_attachments, :unsaved => unsaved_attachments} + end + + def attach_saved_attachments + saved_attachments.each do |attachment| + self.attachments << attachment + end + end + + module ClassMethods + end + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/77/77535eb0610e7a795a3a338ad92c9bcb966e9ab9.svn-base --- a/.svn/pristine/77/77535eb0610e7a795a3a338ad92c9bcb966e9ab9.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,63 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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 MemberRole < ActiveRecord::Base - belongs_to :member - belongs_to :role - - after_destroy :remove_member_if_empty - - after_create :add_role_to_group_users - after_destroy :remove_role_from_group_users - - validates_presence_of :role - - def validate - errors.add :role_id, :invalid if role && !role.member? - end - - def inherited? - !inherited_from.nil? - end - - private - - def remove_member_if_empty - if member.roles.empty? - member.destroy - end - end - - def add_role_to_group_users - if member.principal.is_a?(Group) - member.principal.users.each do |user| - user_member = Member.find_by_project_id_and_user_id(member.project_id, user.id) || Member.new(:project_id => member.project_id, :user_id => user.id) - user_member.member_roles << MemberRole.new(:role => role, :inherited_from => id) - user_member.save! - end - end - end - - def remove_role_from_group_users - MemberRole.find(:all, :conditions => { :inherited_from => id }).group_by(&:member).each do |member, member_roles| - member_roles.each(&:destroy) - if member && member.user - Watcher.prune(:user => member.user, :project => member.project) - end - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/77/77b5f4fec79f0d6eb61a76e965e02cb7504641fe.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/77/77b5f4fec79f0d6eb61a76e965e02cb7504641fe.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,306 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class RepositoryBazaarTest < ActiveSupport::TestCase + fixtures :projects + + include Redmine::I18n + + REPOSITORY_PATH = Rails.root.join('tmp/test/bazaar_repository').to_s + REPOSITORY_PATH_TRUNK = File.join(REPOSITORY_PATH, "trunk") + NUM_REV = 4 + + REPOSITORY_PATH_NON_ASCII = Rails.root.join(REPOSITORY_PATH + '/' + 'non_ascii').to_s + + # Bazaar core does not support xml output such as Subversion and Mercurial. + # "bzr" command output and command line parameter depend on locale. + # So, non ASCII path tests cannot run independent locale. + # + # If you want to run Bazaar non ASCII path tests on Linux *Ruby 1.9*, + # you need to set locale character set "ISO-8859-1". + # E.g. "LANG=en_US.ISO-8859-1". + # On Linux other platforms (e.g. Ruby 1.8, JRuby), + # you need to set "RUN_LATIN1_OUTPUT_TEST = true" manually. + # + # On Windows, because it is too hard to change system locale, + # you cannot run Bazaar non ASCII path tests. + # + RUN_LATIN1_OUTPUT_TEST = (RUBY_PLATFORM != 'java' && + REPOSITORY_PATH.respond_to?(:force_encoding) && + Encoding.locale_charmap == "ISO-8859-1") + + CHAR_1_UTF8_HEX = "\xc3\x9c" + CHAR_1_LATIN1_HEX = "\xdc" + + def setup + @project = Project.find(3) + @repository = Repository::Bazaar.create( + :project => @project, :url => REPOSITORY_PATH_TRUNK, + :log_encoding => 'UTF-8') + assert @repository + @char_1_utf8 = CHAR_1_UTF8_HEX.dup + @char_1_ascii8bit = CHAR_1_LATIN1_HEX.dup + if @char_1_utf8.respond_to?(:force_encoding) + @char_1_utf8.force_encoding('UTF-8') + @char_1_ascii8bit.force_encoding('ASCII-8BIT') + end + end + + def test_blank_path_to_repository_error_message + set_language_if_valid 'en' + repo = Repository::Bazaar.new( + :project => @project, + :identifier => 'test', + :log_encoding => 'UTF-8' + ) + assert !repo.save + assert_include "Path to repository can't be blank", + repo.errors.full_messages + end + + def test_blank_path_to_repository_error_message_fr + set_language_if_valid 'fr' + str = "Chemin du d\xc3\xa9p\xc3\xb4t doit \xc3\xaatre renseign\xc3\xa9(e)" + str.force_encoding('UTF-8') if str.respond_to?(:force_encoding) + repo = Repository::Bazaar.new( + :project => @project, + :url => "", + :identifier => 'test', + :log_encoding => 'UTF-8' + ) + assert !repo.save + assert_include str, repo.errors.full_messages + end + + if File.directory?(REPOSITORY_PATH_TRUNK) + def test_fetch_changesets_from_scratch + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + + assert_equal NUM_REV, @repository.changesets.count + assert_equal 9, @repository.filechanges.count + assert_equal 'Initial import', @repository.changesets.find_by_revision('1').comments + end + + def test_fetch_changesets_incremental + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + # Remove changesets with revision > 5 + @repository.changesets.all.each {|c| c.destroy if c.revision.to_i > 2} + @project.reload + assert_equal 2, @repository.changesets.count + + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + end + + def test_entries + entries = @repository.entries + assert_kind_of Redmine::Scm::Adapters::Entries, entries + assert_equal 2, entries.size + + assert_equal 'dir', entries[0].kind + assert_equal 'directory', entries[0].name + assert_equal 'directory', entries[0].path + + assert_equal 'file', entries[1].kind + assert_equal 'doc-mkdir.txt', entries[1].name + assert_equal 'doc-mkdir.txt', entries[1].path + end + + def test_entries_in_subdirectory + entries = @repository.entries('directory') + assert_equal 3, entries.size + + assert_equal 'file', entries.last.kind + assert_equal 'edit.png', entries.last.name + assert_equal 'directory/edit.png', entries.last.path + end + + def test_previous + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + changeset = @repository.find_changeset_by_name('3') + assert_equal @repository.find_changeset_by_name('2'), changeset.previous + end + + def test_previous_nil + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + changeset = @repository.find_changeset_by_name('1') + assert_nil changeset.previous + end + + def test_next + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + changeset = @repository.find_changeset_by_name('2') + assert_equal @repository.find_changeset_by_name('3'), changeset.next + end + + def test_next_nil + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + changeset = @repository.find_changeset_by_name('4') + assert_nil changeset.next + end + + if File.directory?(REPOSITORY_PATH_NON_ASCII) && RUN_LATIN1_OUTPUT_TEST + def test_cat_latin1_path + latin1_repo = create_latin1_repo + buf = latin1_repo.cat( + "test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}-2.txt", 2) + assert buf + lines = buf.split("\n") + assert_equal 2, lines.length + assert_equal 'It is written in Python.', lines[1] + + buf = latin1_repo.cat( + "test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}-1.txt", 2) + assert buf + lines = buf.split("\n") + assert_equal 1, lines.length + assert_equal "test-#{@char_1_ascii8bit}.txt", lines[0] + end + + def test_annotate_latin1_path + latin1_repo = create_latin1_repo + ann1 = latin1_repo.annotate( + "test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}-2.txt", 2) + assert_equal 2, ann1.lines.size + assert_equal '2', ann1.revisions[0].identifier + assert_equal 'test00@', ann1.revisions[0].author + assert_equal 'It is written in Python.', ann1.lines[1] + ann2 = latin1_repo.annotate( + "test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}-1.txt", 2) + assert_equal 1, ann2.lines.size + assert_equal '2', ann2.revisions[0].identifier + assert_equal 'test00@', ann2.revisions[0].author + assert_equal "test-#{@char_1_ascii8bit}.txt", ann2.lines[0] + end + + def test_diff_latin1_path + latin1_repo = create_latin1_repo + diff1 = latin1_repo.diff( + "test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}-1.txt", 2, 1) + assert_equal 7, diff1.size + buf = diff1[5].gsub(/\r\n|\r|\n/, "") + assert_equal "+test-#{@char_1_ascii8bit}.txt", buf + end + + def test_entries_latin1_path + latin1_repo = create_latin1_repo + entries = latin1_repo.entries("test-#{@char_1_utf8}-dir", 2) + assert_kind_of Redmine::Scm::Adapters::Entries, entries + assert_equal 3, entries.size + assert_equal 'file', entries[1].kind + assert_equal "test-#{@char_1_utf8}-1.txt", entries[0].name + assert_equal "test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}-1.txt", entries[0].path + end + + def test_entry_latin1_path + latin1_repo = create_latin1_repo + ["test-#{@char_1_utf8}-dir", + "/test-#{@char_1_utf8}-dir", + "/test-#{@char_1_utf8}-dir/" + ].each do |path| + entry = latin1_repo.entry(path, 2) + assert_equal "test-#{@char_1_utf8}-dir", entry.path + assert_equal "dir", entry.kind + end + ["test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}-1.txt", + "/test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}-1.txt" + ].each do |path| + entry = latin1_repo.entry(path, 2) + assert_equal "test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}-1.txt", + entry.path + assert_equal "file", entry.kind + end + end + + def test_changeset_latin1_path + latin1_repo = create_latin1_repo + assert_equal 0, latin1_repo.changesets.count + latin1_repo.fetch_changesets + @project.reload + assert_equal 3, latin1_repo.changesets.count + + cs2 = latin1_repo.changesets.find_by_revision('2') + assert_not_nil cs2 + assert_equal "test-#{@char_1_utf8}", cs2.comments + c2 = cs2.filechanges.sort_by(&:path) + assert_equal 4, c2.size + assert_equal 'A', c2[0].action + assert_equal "/test-#{@char_1_utf8}-dir/", c2[0].path + assert_equal 'A', c2[1].action + assert_equal "/test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}-1.txt", c2[1].path + assert_equal 'A', c2[2].action + assert_equal "/test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}-2.txt", c2[2].path + assert_equal 'A', c2[3].action + assert_equal "/test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}.txt", c2[3].path + + cs3 = latin1_repo.changesets.find_by_revision('3') + assert_not_nil cs3 + assert_equal "modify, move and delete #{@char_1_utf8} files", cs3.comments + c3 = cs3.filechanges.sort_by(&:path) + assert_equal 3, c3.size + assert_equal 'M', c3[0].action + assert_equal "/test-#{@char_1_utf8}-1.txt", c3[0].path + assert_equal 'D', c3[1].action + assert_equal "/test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}-2.txt", c3[1].path + assert_equal 'M', c3[2].action + assert_equal "/test-#{@char_1_utf8}-dir/test-#{@char_1_utf8}.txt", c3[2].path + end + else + msg = "Bazaar non ASCII output test cannot run this environment." + "\n" + if msg.respond_to?(:force_encoding) + msg += "Encoding.locale_charmap: " + Encoding.locale_charmap + "\n" + end + puts msg + end + + private + + def create_latin1_repo + repo = Repository::Bazaar.create( + :project => @project, + :identifier => 'latin1', + :url => REPOSITORY_PATH_NON_ASCII, + :log_encoding => 'ISO-8859-1' + ) + assert repo + repo + end + else + puts "Bazaar test repository NOT FOUND. Skipping unit tests !!!" + def test_fake; assert true end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/77/77d58783aae2a4640c9d8514879fb6e9b163a2b5.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/77/77d58783aae2a4640c9d8514879fb6e9b163a2b5.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,1087 @@ +# Italian translations for Ruby on Rails +# by Claudio Poli (masterkain@gmail.com) +# by Diego Pierotto (ita.translations@tiscali.it) +# by Emidio Stani (emidiostani@gmail.com) + +it: + direction: ltr + date: + formats: + default: "%d-%m-%Y" + short: "%d %b" + long: "%d %B %Y" + only_day: "%e" + + day_names: [Domenica, Lunedì, Martedì, Mercoledì, Giovedì, Venerdì, Sabato] + abbr_day_names: [Dom, Lun, Mar, Mer, Gio, Ven, Sab] + month_names: [~, Gennaio, Febbraio, Marzo, Aprile, Maggio, Giugno, Luglio, Agosto, Settembre, Ottobre, Novembre, Dicembre] + abbr_month_names: [~, Gen, Feb, Mar, Apr, Mag, Giu, Lug, Ago, Set, Ott, Nov, Dic] + order: + - :day + - :month + - :year + + time: + formats: + default: "%a %d %b %Y, %H:%M:%S %z" + time: "%H:%M" + short: "%d %b %H:%M" + long: "%d %B %Y %H:%M" + only_second: "%S" + + datetime: + formats: + default: "%d-%m-%YT%H:%M:%S%Z" + + am: 'am' + pm: 'pm' + + datetime: + distance_in_words: + half_a_minute: "mezzo minuto" + less_than_x_seconds: + one: "meno di un secondo" + other: "meno di %{count} secondi" + x_seconds: + one: "1 secondo" + other: "%{count} secondi" + less_than_x_minutes: + one: "meno di un minuto" + other: "meno di %{count} minuti" + x_minutes: + one: "1 minuto" + other: "%{count} minuti" + about_x_hours: + one: "circa un'ora" + other: "circa %{count} ore" + x_hours: + one: "1 ora" + other: "%{count} ore" + x_days: + one: "1 giorno" + other: "%{count} giorni" + about_x_months: + one: "circa un mese" + other: "circa %{count} mesi" + x_months: + one: "1 mese" + other: "%{count} mesi" + about_x_years: + one: "circa un anno" + other: "circa %{count} anni" + over_x_years: + one: "oltre un anno" + other: "oltre %{count} anni" + almost_x_years: + one: "quasi 1 anno" + other: "quasi %{count} anni" + + number: + format: + precision: 3 + separator: ',' + delimiter: '.' + currency: + format: + unit: '€' + precision: 2 + format: '%n %u' + human: + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + + support: + array: + sentence_connector: "e" + skip_last_comma: false + + activerecord: + errors: + template: + header: + one: "Non posso salvare questo %{model}: 1 errore" + other: "Non posso salvare questo %{model}: %{count} errori." + body: "Per favore ricontrolla i seguenti campi:" + messages: + inclusion: "non è incluso nella lista" + exclusion: "è riservato" + invalid: "non è valido" + confirmation: "non coincide con la conferma" + accepted: "deve essere accettata" + empty: "non può essere vuoto" + blank: "non può essere lasciato in bianco" + too_long: "è troppo lungo (il massimo è %{count} lettere)" + too_short: "è troppo corto (il minimo è %{count} lettere)" + wrong_length: "è della lunghezza sbagliata (deve essere di %{count} lettere)" + taken: "è già in uso" + not_a_number: "non è un numero" + greater_than: "deve essere superiore a %{count}" + greater_than_or_equal_to: "deve essere superiore o uguale a %{count}" + equal_to: "deve essere uguale a %{count}" + less_than: "deve essere meno di %{count}" + less_than_or_equal_to: "deve essere meno o uguale a %{count}" + odd: "deve essere dispari" + even: "deve essere pari" + greater_than_start_date: "deve essere maggiore della data di partenza" + not_same_project: "non appartiene allo stesso progetto" + circular_dependency: "Questa relazione creerebbe una dipendenza circolare" + cant_link_an_issue_with_a_descendant: "Una segnalazione non può essere collegata a una delle sue discendenti" + + actionview_instancetag_blank_option: Scegli + + general_text_No: 'No' + general_text_Yes: 'Sì' + general_text_no: 'no' + general_text_yes: 'sì' + general_lang_name: 'Italiano' + general_csv_separator: ';' + general_csv_decimal_separator: ',' + general_csv_encoding: ISO-8859-1 + general_pdf_encoding: UTF-8 + general_first_day_of_week: '1' + + notice_account_updated: L'utente è stato aggiornato. + notice_account_invalid_creditentials: Nome utente o password non validi. + notice_account_password_updated: La password è stata aggiornata. + notice_account_wrong_password: Password errata + notice_account_register_done: L'utente è stato creato. + notice_account_unknown_email: Utente sconosciuto. + notice_can_t_change_password: Questo utente utilizza un metodo di autenticazione esterno. Impossibile cambiare la password. + notice_account_lost_email_sent: Ti è stata spedita una email con le istruzioni per cambiare la password. + notice_account_activated: Il tuo account è stato attivato. Ora puoi effettuare l'accesso. + notice_successful_create: Creazione effettuata. + notice_successful_update: Modifica effettuata. + notice_successful_delete: Eliminazione effettuata. + notice_successful_connection: Connessione effettuata. + notice_file_not_found: La pagina desiderata non esiste o è stata rimossa. + notice_locking_conflict: Le informazioni sono state modificate da un altro utente. + notice_not_authorized: Non sei autorizzato ad accedere a questa pagina. + notice_email_sent: "Una email è stata spedita a %{value}" + notice_email_error: "Si è verificato un errore durante l'invio di una email (%{value})" + notice_feeds_access_key_reseted: La tua chiave di accesso RSS è stata reimpostata. + + error_scm_not_found: "La risorsa e/o la versione non esistono nel repository." + error_scm_command_failed: "Si è verificato un errore durante l'accesso al repository: %{value}" + + mail_subject_lost_password: "Password %{value}" + mail_body_lost_password: 'Per cambiare la password, usa il seguente collegamento:' + mail_subject_register: "Attivazione utente %{value}" + mail_body_register: "Per attivare l'utente, usa il seguente collegamento:" + + + field_name: Nome + field_description: Descrizione + field_summary: Sommario + field_is_required: Richiesto + field_firstname: Nome + field_lastname: Cognome + field_mail: Email + field_filename: File + field_filesize: Dimensione + field_downloads: Download + field_author: Autore + field_created_on: Creato + field_updated_on: Aggiornato + field_field_format: Formato + field_is_for_all: Per tutti i progetti + field_possible_values: Valori possibili + field_regexp: Espressione regolare + field_min_length: Lunghezza minima + field_max_length: Lunghezza massima + field_value: Valore + field_category: Categoria + field_title: Titolo + field_project: Progetto + field_issue: Segnalazione + field_status: Stato + field_notes: Note + field_is_closed: Chiudi la segnalazione + field_is_default: Stato predefinito + field_tracker: Tracker + field_subject: Oggetto + field_due_date: Scadenza + field_assigned_to: Assegnato a + field_priority: Priorità + field_fixed_version: Versione prevista + field_user: Utente + field_role: Ruolo + field_homepage: Homepage + field_is_public: Pubblico + field_parent: Sottoprogetto di + field_is_in_roadmap: Segnalazioni mostrate nella roadmap + field_login: Utente + field_mail_notification: Notifiche via email + field_admin: Amministratore + field_last_login_on: Ultima connessione + field_language: Lingua + field_effective_date: Data + field_password: Password + field_new_password: Nuova password + field_password_confirmation: Conferma + field_version: Versione + field_type: Tipo + field_host: Host + field_port: Porta + field_account: Utente + field_base_dn: DN base + field_attr_login: Attributo connessione + field_attr_firstname: Attributo nome + field_attr_lastname: Attributo cognome + field_attr_mail: Attributo email + field_onthefly: Creazione utente "al volo" + field_start_date: Inizio + field_done_ratio: "% completato" + field_auth_source: Modalità di autenticazione + field_hide_mail: Nascondi il mio indirizzo email + field_comments: Commento + field_url: URL + field_start_page: Pagina principale + field_subproject: Sottoprogetto + field_hours: Ore + field_activity: Attività + field_spent_on: Data + field_identifier: Identificativo + field_is_filter: Usato come filtro + field_issue_to: Segnalazioni correlate + field_delay: Ritardo + field_assignable: E' possibile assegnare segnalazioni a questo ruolo + field_redirect_existing_links: Redirige i collegamenti esistenti + field_estimated_hours: Tempo stimato + field_default_value: Stato predefinito + + setting_app_title: Titolo applicazione + setting_app_subtitle: Sottotitolo applicazione + setting_welcome_text: Testo di benvenuto + setting_default_language: Lingua predefinita + setting_login_required: Autenticazione richiesta + setting_self_registration: Auto-registrazione abilitata + setting_attachment_max_size: Dimensione massima allegati + setting_issues_export_limit: Limite esportazione segnalazioni + setting_mail_from: Indirizzo sorgente email + setting_host_name: Nome host + setting_text_formatting: Formattazione testo + 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: Parole chiave riferimento + setting_commit_fix_keywords: Parole chiave chiusura + setting_autologin: Connessione automatica + setting_date_format: Formato data + setting_cross_project_issue_relations: Consenti la creazione di relazioni tra segnalazioni in progetti differenti + + label_user: Utente + label_user_plural: Utenti + label_user_new: Nuovo utente + label_project: Progetto + label_project_new: Nuovo progetto + label_project_plural: Progetti + label_x_projects: + zero: nessun progetto + one: 1 progetto + other: "%{count} progetti" + label_project_all: Tutti i progetti + label_project_latest: Ultimi progetti registrati + label_issue: Segnalazione + label_issue_new: Nuova segnalazione + label_issue_plural: Segnalazioni + label_issue_view_all: Mostra tutte le segnalazioni + label_document: Documento + label_document_new: Nuovo documento + label_document_plural: Documenti + label_role: Ruolo + label_role_plural: Ruoli + label_role_new: Nuovo ruolo + label_role_and_permissions: Ruoli e permessi + label_member: Membro + label_member_new: Nuovo membro + label_member_plural: Membri + label_tracker: Tracker + label_tracker_plural: Tracker + label_tracker_new: Nuovo tracker + label_workflow: Workflow + label_issue_status: Stato segnalazione + label_issue_status_plural: Stati segnalazioni + label_issue_status_new: Nuovo stato + label_issue_category: Categoria segnalazione + label_issue_category_plural: Categorie segnalazioni + label_issue_category_new: Nuova categoria + label_custom_field: Campo personalizzato + label_custom_field_plural: Campi personalizzati + label_custom_field_new: Nuovo campo personalizzato + label_enumerations: Enumerazioni + label_enumeration_new: Nuovo valore + label_information: Informazione + label_information_plural: Informazioni + label_please_login: Entra + label_register: Registrati + label_password_lost: Password dimenticata + label_home: Home + label_my_page: Pagina personale + label_my_account: Il mio utente + label_my_projects: I miei progetti + label_administration: Amministrazione + label_login: Entra + label_logout: Esci + label_help: Aiuto + label_reported_issues: Segnalazioni + label_assigned_to_me_issues: Le mie segnalazioni + label_last_login: Ultimo collegamento + label_registered_on: Registrato il + label_activity: Attività + label_new: Nuovo + label_logged_as: Collegato come + label_environment: Ambiente + label_authentication: Autenticazione + label_auth_source: Modalità di autenticazione + label_auth_source_new: Nuova modalità di autenticazione + label_auth_source_plural: Modalità di autenticazione + label_subproject_plural: Sottoprogetti + label_min_max_length: Lunghezza minima - massima + label_list: Elenco + label_date: Data + label_integer: Intero + label_boolean: Booleano + label_string: Testo + label_text: Testo esteso + label_attribute: Attributo + label_attribute_plural: Attributi + label_no_data: Nessun dato disponibile + label_change_status: Cambia stato + label_history: Cronologia + label_attachment: File + label_attachment_new: Nuovo file + label_attachment_delete: Elimina file + label_attachment_plural: File + label_report: Report + label_report_plural: Report + label_news: Notizia + label_news_new: Aggiungi notizia + label_news_plural: Notizie + label_news_latest: Utime notizie + label_news_view_all: Tutte le notizie + label_settings: Impostazioni + label_overview: Panoramica + label_version: Versione + label_version_new: Nuova versione + label_version_plural: Versioni + label_confirmation: Conferma + label_export_to: Esporta su + label_read: Leggi... + label_public_projects: Progetti pubblici + label_open_issues: aperta + label_open_issues_plural: aperte + label_closed_issues: chiusa + label_closed_issues_plural: chiuse + label_x_open_issues_abbr_on_total: + zero: 0 aperte / %{total} + one: 1 aperta / %{total} + other: "%{count} aperte / %{total}" + label_x_open_issues_abbr: + zero: 0 aperte + one: 1 aperta + other: "%{count} aperte" + label_x_closed_issues_abbr: + zero: 0 chiuse + one: 1 chiusa + other: "%{count} chiuse" + label_total: Totale + label_permissions: Permessi + label_current_status: Stato attuale + label_new_statuses_allowed: Nuovi stati possibili + label_all: tutti + label_none: nessuno + label_next: Successivo + label_previous: Precedente + label_used_by: Usato da + label_details: Dettagli + label_add_note: Aggiungi una nota + label_per_page: Per pagina + label_calendar: Calendario + label_months_from: mesi da + label_gantt: Gantt + label_internal: Interno + label_last_changes: "ultime %{count} modifiche" + label_change_view_all: Tutte le modifiche + label_personalize_page: Personalizza la pagina + label_comment: Commento + label_comment_plural: Commenti + label_x_comments: + zero: nessun commento + one: 1 commento + other: "%{count} commenti" + label_comment_add: Aggiungi un commento + label_comment_added: Commento aggiunto + label_comment_delete: Elimina commenti + label_query: Query personalizzata + label_query_plural: Query personalizzate + label_query_new: Nuova query + label_filter_add: Aggiungi filtro + label_filter_plural: Filtri + label_equals: è + label_not_equals: non è + label_in_less_than: è minore di + label_in_more_than: è maggiore di + label_in: in + label_today: oggi + label_this_week: questa settimana + label_less_than_ago: meno di giorni fa + label_more_than_ago: più di giorni fa + label_ago: giorni fa + label_contains: contiene + label_not_contains: non contiene + label_day_plural: giorni + label_repository: Repository + label_browse: Sfoglia + label_revision: Versione + label_revision_plural: Versioni + label_added: aggiunto + label_modified: modificato + label_deleted: eliminato + label_latest_revision: Ultima versione + label_latest_revision_plural: Ultime versioni + label_view_revisions: Mostra versioni + label_max_size: Dimensione massima + label_sort_highest: Sposta in cima + label_sort_higher: Su + label_sort_lower: Giù + label_sort_lowest: Sposta in fondo + label_roadmap: Roadmap + label_roadmap_due_in: "Da ultimare in %{value}" + label_roadmap_overdue: "%{value} di ritardo" + label_roadmap_no_issues: Nessuna segnalazione per questa versione + label_search: Ricerca + label_result_plural: Risultati + label_all_words: Tutte le parole + label_wiki: Wiki + label_wiki_edit: Modifica wiki + label_wiki_edit_plural: Modifiche wiki + label_wiki_page: Pagina Wiki + label_wiki_page_plural: Pagine wiki + label_index_by_title: Ordina per titolo + label_index_by_date: Ordina per data + label_current_version: Versione corrente + label_preview: Anteprima + label_feed_plural: Feed + label_changes_details: Particolari di tutti i cambiamenti + label_issue_tracking: Tracking delle segnalazioni + label_spent_time: Tempo impiegato + label_f_hour: "%{value} ora" + label_f_hour_plural: "%{value} ore" + label_time_tracking: Tracking del tempo + label_change_plural: Modifiche + label_statistics: Statistiche + label_commits_per_month: Commit per mese + label_commits_per_author: Commit per autore + label_view_diff: mostra differenze + label_diff_inline: in linea + label_diff_side_by_side: fianco a fianco + label_options: Opzioni + label_copy_workflow_from: Copia workflow da + label_permissions_report: Report permessi + label_watched_issues: Segnalazioni osservate + label_related_issues: Segnalazioni correlate + label_applied_status: Stato applicato + label_loading: Caricamento... + label_relation_new: Nuova relazione + label_relation_delete: Elimina relazione + label_relates_to: correlato a + label_duplicates: duplicati + label_blocks: blocchi + label_blocked_by: bloccato da + label_precedes: precede + label_follows: segue + label_end_to_start: fine a inizio + label_end_to_end: fine a fine + label_start_to_start: inizio a inizio + label_start_to_end: inizio a fine + label_stay_logged_in: Rimani collegato + label_disabled: disabilitato + label_show_completed_versions: Mostra versioni completate + label_me: me + label_board: Forum + label_board_new: Nuovo forum + label_board_plural: Forum + label_topic_plural: Argomenti + label_message_plural: Messaggi + label_message_last: Ultimo messaggio + label_message_new: Nuovo messaggio + label_reply_plural: Risposte + label_send_information: Invia all'utente le informazioni relative all'account + label_year: Anno + label_month: Mese + label_week: Settimana + label_date_from: Da + label_date_to: A + label_language_based: Basato sul linguaggio + label_sort_by: "Ordina per %{value}" + label_send_test_email: Invia una email di prova + label_feeds_access_key_created_on: "chiave di accesso RSS creata %{value} fa" + label_module_plural: Moduli + label_added_time_by: "Aggiunto da %{author} %{age} fa" + label_updated_time: "Aggiornato %{value} fa" + label_jump_to_a_project: Vai al progetto... + + button_login: Entra + button_submit: Invia + button_save: Salva + button_check_all: Seleziona tutti + button_uncheck_all: Deseleziona tutti + button_delete: Elimina + button_create: Crea + button_test: Prova + button_edit: Modifica + button_add: Aggiungi + button_change: Cambia + button_apply: Applica + button_clear: Pulisci + button_lock: Blocca + button_unlock: Sblocca + button_download: Scarica + button_list: Elenca + button_view: Mostra + button_move: Sposta + button_back: Indietro + button_cancel: Annulla + button_activate: Attiva + button_sort: Ordina + button_log_time: Registra tempo + button_rollback: Ripristina questa versione + button_watch: Osserva + button_unwatch: Dimentica + button_reply: Rispondi + button_archive: Archivia + button_unarchive: Ripristina + button_reset: Reimposta + button_rename: Rinomina + + status_active: attivo + status_registered: registrato + status_locked: bloccato + + text_select_mail_notifications: Seleziona le azioni per cui deve essere inviata una notifica. + text_regexp_info: es. ^[A-Z0-9]+$ + text_min_max_length_info: 0 significa nessuna restrizione + text_project_destroy_confirmation: Sei sicuro di voler eliminare il progetto e tutti i dati ad esso collegati? + text_workflow_edit: Seleziona un ruolo ed un tracker per modificare il workflow + text_are_you_sure: Sei sicuro ? + text_tip_issue_begin_day: attività che iniziano in questa giornata + text_tip_issue_end_day: attività che terminano in questa giornata + text_tip_issue_begin_end_day: attività che iniziano e terminano in questa giornata + text_caracters_maximum: "massimo %{count} caratteri." + text_length_between: "Lunghezza compresa tra %{min} e %{max} 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 virgole). + text_issues_ref_in_commit_messages: Segnalazioni di riferimento e chiusura nei messaggi di commit + text_issue_added: "%{author} ha aggiunto la segnalazione %{id}." + text_issue_updated: "La segnalazione %{id} è stata aggiornata da %{author}." + text_wiki_destroy_confirmation: Sicuro di voler eliminare questo wiki e tutti i suoi contenuti? + text_issue_category_destroy_question: "Alcune segnalazioni (%{count}) risultano assegnate a questa categoria. Cosa vuoi fare ?" + text_issue_category_destroy_assignments: Rimuovi le assegnazioni a questa categoria + text_issue_category_reassign_to: Riassegna segnalazioni a questa categoria + + default_role_manager: Gestore + default_role_developer: Sviluppatore + default_role_reporter: Segnalatore + default_tracker_bug: Segnalazione + default_tracker_feature: Funzione + default_tracker_support: Supporto + default_issue_status_new: Nuovo + default_issue_status_in_progress: In elaborazione + default_issue_status_resolved: Risolto + default_issue_status_feedback: Commenti + default_issue_status_closed: Chiuso + default_issue_status_rejected: Rifiutato + default_doc_category_user: Documentazione utente + default_doc_category_tech: Documentazione tecnica + default_priority_low: Bassa + default_priority_normal: Normale + default_priority_high: Alta + default_priority_urgent: Urgente + default_priority_immediate: Immediata + default_activity_design: Progettazione + default_activity_development: Sviluppo + + enumeration_issue_priorities: Priorità segnalazioni + enumeration_doc_categories: Categorie di documenti + enumeration_activities: Attività (time tracking) + label_file_plural: File + label_changeset_plural: Changeset + field_column_names: Colonne + label_default_columns: Colonne predefinite + setting_issue_list_default_columns: Colonne predefinite mostrate nell'elenco segnalazioni + notice_no_issue_selected: "Nessuna segnalazione selezionata! Seleziona le segnalazioni che intendi modificare." + label_bulk_edit_selected_issues: Modifica massiva delle segnalazioni selezionate + label_no_change_option: (Nessuna modifica) + notice_failed_to_save_issues: "Impossibile salvare %{count} segnalazioni su %{total} selezionate: %{ids}." + label_theme: Tema + label_default: Predefinito + label_search_titles_only: Cerca solo nei titoli + label_nobody: nessuno + button_change_password: Modifica password + text_user_mail_option: "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" + setting_emails_footer: Piè di pagina email + label_float: Decimale + button_copy: Copia + mail_body_account_information_external: "Puoi utilizzare il tuo account %{value} per accedere al sistema." + mail_body_account_information: Le informazioni riguardanti il tuo account + setting_protocol: Protocollo + label_user_mail_no_self_notified: "Non voglio notifiche riguardanti modifiche da me apportate" + setting_time_format: Formato ora + label_registration_activation_by_email: attivazione account via email + mail_subject_account_activation_request: "%{value} richiesta attivazione account" + mail_body_account_activation_request: "Un nuovo utente (%{value}) ha effettuato la registrazione. Il suo account è in attesa di abilitazione da parte tua:" + label_registration_automatic_activation: attivazione account automatica + label_registration_manual_activation: attivazione account manuale + notice_account_pending: "Il tuo account è stato creato ed è in attesa di attivazione da parte dell'amministratore." + field_time_zone: Fuso orario + text_caracters_minimum: "Deve essere lungo almeno %{count} caratteri." + setting_bcc_recipients: Destinatari in copia nascosta (bcc) + button_annotate: Annota + label_issues_by: "Segnalazioni di %{value}" + field_searchable: Ricercabile + label_display_per_page: "Per pagina: %{value}" + setting_per_page_options: Opzioni oggetti per pagina + label_age: Età + 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.\nE' vivamente consigliato caricare la configurazione predefinita. Potrai modificarla una volta caricata." + error_can_t_load_default_data: "Non è stato possibile caricare la configurazione predefinita : %{value}" + button_update: Aggiorna + label_change_properties: Modifica le proprietà + label_general: Generale + label_repository_plural: Repository + label_associated_revisions: Revisioni associate + setting_user_format: Formato visualizzazione utenti + text_status_changed_by_changeset: "Applicata nel changeset %{value}." + label_more: Altro + text_issues_destroy_confirmation: 'Sei sicuro di voler eliminare le segnalazioni selezionate?' + label_scm: SCM + text_select_project_modules: 'Seleziona i moduli abilitati per questo progetto:' + label_issue_added: Segnalazioni aggiunte + label_issue_updated: Segnalazioni aggiornate + label_document_added: Documenti aggiunti + label_message_posted: Messaggi aggiunti + label_file_added: File aggiunti + label_news_added: Notizie aggiunte + project_module_boards: Forum + project_module_issue_tracking: Tracking delle segnalazioni + project_module_wiki: Wiki + project_module_files: File + project_module_documents: Documenti + 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 predefinito è stato modificato + text_rmagick_available: RMagick disponibile (opzionale) + button_configure: Configura + label_plugins: Plugin + label_ldap_authentication: Autenticazione LDAP + label_downloads_abbr: D/L + label_this_month: questo mese + label_last_n_days: "ultimi %{count} giorni" + label_all_time: sempre + label_this_year: quest'anno + label_date_range: Intervallo di date + label_last_week: ultima settimana + label_yesterday: ieri + label_last_month: ultimo mese + label_add_another_file: Aggiungi un altro file + label_optional_description: Descrizione opzionale + text_destroy_time_entries_question: "%{hours} ore risultano spese sulle segnalazioni che stai per eliminare. Cosa vuoi fare ?" + error_issue_not_found_in_project: 'La segnalazione non è stata trovata o non appartiene al progetto' + text_assign_time_entries_to_project: Assegna le ore segnalate al progetto + text_destroy_time_entries: Elimina le ore segnalate + text_reassign_time_entries: 'Riassegna le ore a questa segnalazione:' + setting_activity_days_default: Giorni mostrati sulle attività di progetto + label_chronological_order: In ordine cronologico + field_comments_sorting: Mostra commenti + label_reverse_chronological_order: In ordine cronologico inverso + label_preferences: Preferenze + setting_display_subprojects_issues: Mostra le segnalazioni dei sottoprogetti nel progetto principale in modo predefinito + label_overall_activity: Attività generale + setting_default_projects_public: I nuovi progetti sono pubblici in modo predefinito + error_scm_annotate: "L'oggetto non esiste o non può essere annotato." + label_planning: Pianificazione + text_subprojects_destroy_warning: "Anche i suoi sottoprogetti: %{value} verranno eliminati." + label_and_its_subprojects: "%{value} ed i suoi sottoprogetti" + mail_body_reminder: "%{count} segnalazioni che ti sono state assegnate scadranno nei prossimi %{days} giorni:" + mail_subject_reminder: "%{count} segnalazioni in scadenza nei prossimi %{days} giorni" + text_user_wrote: "%{value} ha scritto:" + label_duplicated_by: duplicato da + setting_enabled_scm: SCM abilitato + text_enumeration_category_reassign_to: 'Riassegnale a questo valore:' + text_enumeration_destroy_question: "%{count} oggetti hanno un assegnamento su questo valore." + label_incoming_emails: Email in arrivo + label_generate_key: Genera una chiave + setting_mail_handler_api_enabled: Abilita WS per le email in arrivo + setting_mail_handler_api_key: Chiave API + text_email_delivery_not_configured: "La consegna via email non è configurata e le notifiche sono disabilitate.\nConfigura il tuo server SMTP in config/configuration.yml e riavvia l'applicazione per abilitarle." + field_parent_title: Pagina principale + label_issue_watchers: Osservatori + button_quote: Quota + setting_sequential_project_identifiers: Genera progetti con identificativi in sequenza + notice_unable_delete_version: Impossibile eliminare la versione + label_renamed: rinominato + label_copied: copiato + 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 segnalazioni + 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_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 note utente o email sono mappati automaticamente." + permission_edit_own_messages: Modifica propri messaggi + permission_delete_own_messages: Elimina propri messaggi + label_user_activity: "attività di %{value}" + label_updated_time_by: "Aggiornato da %{author} %{age} 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: Directory attività dei plugins scrivibile + warning_attachments_not_saved: "%{count} file non possono essere salvati." + button_create_and_continue: Crea e continua + text_custom_field_possible_values_info: 'Un valore per ogni riga' + label_display: Mostra + field_editable: Modificabile + setting_repository_log_display_limit: Numero massimo di revisioni elencate nella cronologia file + setting_file_max_size_displayed: Dimensione massima dei contenuti testuali visualizzati + field_watcher: Osservatore + setting_openid: Accetta connessione e registrazione con OpenID + field_identity_url: URL OpenID + label_login_with_open_id_option: oppure autenticati usando OpenID + field_content: Contenuto + label_descending: Discendente + label_sort: Ordina + label_ascending: Ascendente + label_date_from_to: Da %{start} a %{end} + label_greater_or_equal: ">=" + label_less_or_equal: <= + text_wiki_page_destroy_question: Questa pagina ha %{descendants} pagine figlie. Cosa ne vuoi fare? + text_wiki_page_reassign_children: Riassegna le pagine figlie al padre di questa pagina + text_wiki_page_nullify_children: Mantieni le pagine figlie come pagine radice + text_wiki_page_destroy_children: Elimina le pagine figlie e tutta la discendenza + setting_password_min_length: Lunghezza minima password + field_group_by: Raggruppa risultati per + mail_subject_wiki_content_updated: "La pagina wiki '%{id}' è stata aggiornata" + label_wiki_content_added: Aggiunta pagina al wiki + mail_subject_wiki_content_added: "La pagina '%{id}' è stata aggiunta al wiki" + mail_body_wiki_content_added: La pagina '%{id}' è stata aggiunta al wiki da %{author}. + label_wiki_content_updated: Aggiornata pagina wiki + mail_body_wiki_content_updated: La pagina '%{id}' wiki è stata aggiornata da%{author}. + permission_add_project: Crea progetto + setting_new_project_user_role_id: Ruolo assegnato agli utenti non amministratori che creano un progetto + label_view_all_revisions: Mostra tutte le revisioni + label_tag: Tag + label_branch: Branch + error_no_tracker_in_project: Nessun tracker è associato a questo progetto. Per favore verifica le impostazioni del Progetto. + error_no_default_issue_status: Nessuno stato predefinito delle segnalazioni è configurato. Per favore verifica le impostazioni (Vai in "Amministrazione -> Stati segnalazioni"). + text_journal_changed: "%{label} modificata da %{old} a %{new}" + text_journal_set_to: "%{label} impostata a %{value}" + text_journal_deleted: "%{label} eliminata (%{old})" + label_group_plural: Gruppi + label_group: Gruppo + label_group_new: Nuovo gruppo + label_time_entry_plural: Tempo impiegato + text_journal_added: "%{value} %{label} aggiunto" + field_active: Attivo + enumeration_system_activity: Attività di sistema + permission_delete_issue_watchers: Elimina osservatori + version_status_closed: chiusa + version_status_locked: bloccata + version_status_open: aperta + error_can_not_reopen_issue_on_closed_version: Una segnalazione assegnata ad una versione chiusa non può essere riaperta + label_user_anonymous: Anonimo + button_move_and_follow: Sposta e segui + setting_default_projects_modules: Moduli predefiniti abilitati per i nuovi progetti + setting_gravatar_default: Immagine Gravatar predefinita + field_sharing: Condivisione + label_version_sharing_hierarchy: Con gerarchia progetto + label_version_sharing_system: Con tutti i progetti + label_version_sharing_descendants: Con sottoprogetti + label_version_sharing_tree: Con progetto padre + label_version_sharing_none: Nessuna condivisione + error_can_not_archive_project: Questo progetto non può essere archiviato + button_duplicate: Duplica + button_copy_and_follow: Copia e segui + label_copy_source: Sorgente + setting_issue_done_ratio: Calcola la percentuale di segnalazioni completate con + setting_issue_done_ratio_issue_status: Usa lo stato segnalazioni + error_issue_done_ratios_not_updated: La percentuale delle segnalazioni completate non è aggiornata. + error_workflow_copy_target: Per favore seleziona trackers finali e ruolo(i) + setting_issue_done_ratio_issue_field: Usa il campo segnalazioni + label_copy_same_as_target: Uguale a destinazione + label_copy_target: Destinazione + notice_issue_done_ratios_updated: La percentuale delle segnalazioni completate è aggiornata. + error_workflow_copy_source: Per favore seleziona un tracker sorgente o ruolo + label_update_issue_done_ratios: Aggiorna la percentuale delle segnalazioni completate + setting_start_of_week: Avvia calendari il + permission_view_issues: Mostra segnalazioni + label_display_used_statuses_only: Mostra solo stati che vengono usati per questo tracker + label_revision_id: Revisione %{value} + label_api_access_key: Chiave di accesso API + label_api_access_key_created_on: Chiave di accesso API creata %{value} fa + label_feeds_access_key: Chiave di accesso RSS + notice_api_access_key_reseted: La chiave di accesso API è stata reimpostata. + setting_rest_api_enabled: Abilita il servizio web REST + label_missing_api_access_key: Chiave di accesso API mancante + label_missing_feeds_access_key: Chiave di accesso RSS mancante + button_show: Mostra + text_line_separated: Valori multipli permessi (un valore per ogni riga). + setting_mail_handler_body_delimiters: Tronca email dopo una di queste righe + permission_add_subprojects: Crea sottoprogetti + label_subproject_new: Nuovo sottoprogetto + text_own_membership_delete_confirmation: |- + Stai per eliminare alcuni o tutti i permessi e non sarai più in grado di modificare questo progetto dopo tale azione. + Sei sicuro di voler continuare? + label_close_versions: Versioni completate chiuse + label_board_sticky: Annunci + label_board_locked: Bloccato + permission_export_wiki_pages: Esporta pagine wiki + setting_cache_formatted_text: Cache testo formattato + permission_manage_project_activities: Gestisci attività progetti + error_unable_delete_issue_status: Impossibile eliminare lo stato segnalazioni + label_profile: Profilo + permission_manage_subtasks: Gestisci sottoattività + field_parent_issue: Attività principale + label_subtask_plural: Sottoattività + label_project_copy_notifications: Invia notifiche email durante la copia del progetto + error_can_not_delete_custom_field: Impossibile eliminare il campo personalizzato + error_unable_to_connect: Impossibile connettersi (%{value}) + error_can_not_remove_role: Questo ruolo è in uso e non può essere eliminato. + error_can_not_delete_tracker: Questo tracker contiene segnalazioni e non può essere eliminato. + field_principal: Principale + label_my_page_block: La mia pagina di blocco + notice_failed_to_save_members: "Impossibile salvare il membro(i): %{errors}." + text_zoom_out: Riduci ingrandimento + text_zoom_in: Aumenta ingrandimento + notice_unable_delete_time_entry: Impossibile eliminare il valore time log. + label_overall_spent_time: Totale tempo impiegato + field_time_entries: Tempo di collegamento + project_module_gantt: Gantt + project_module_calendar: Calendario + button_edit_associated_wikipage: "Modifica la pagina wiki associata: %{page_title}" + field_text: Campo di testo + label_user_mail_option_only_owner: Solo se io sono il proprietario + setting_default_notification_option: Opzione di notifica predefinita + label_user_mail_option_only_my_events: Solo se sono un osservatore o sono coinvolto + label_user_mail_option_only_assigned: Solo quando mi assegnano attività + label_user_mail_option_none: Nessun evento + field_member_of_group: Gruppo dell'assegnatario + field_assigned_to_role: Ruolo dell'assegnatario + notice_not_authorized_archived_project: Il progetto a cui stai accedendo è stato archiviato. + label_principal_search: "Cerca utente o gruppo:" + label_user_search: "Cerca utente:" + field_visible: Visibile + setting_emails_header: Intestazione email + setting_commit_logtime_activity_id: Attività per il tempo di collegamento + text_time_logged_by_changeset: Usato nel changeset %{value}. + setting_commit_logtime_enabled: Abilita registrazione del tempo di collegamento + notice_gantt_chart_truncated: Il grafico è stato troncato perchè eccede il numero di oggetti (%{max}) da visualizzare + setting_gantt_items_limit: Massimo numero di oggetti da visualizzare sul diagramma di gantt + field_warn_on_leaving_unsaved: Avvisami quando lascio una pagina con testo non salvato + text_warn_on_leaving_unsaved: La pagina corrente contiene del testo non salvato che verrà perso se lasci questa pagina. + label_my_queries: Le mie queries personalizzate + text_journal_changed_no_detail: "%{label} aggiornato" + label_news_comment_added: Commento aggiunto a una notizia + button_expand_all: Espandi tutto + button_collapse_all: Comprimi tutto + label_additional_workflow_transitions_for_assignee: Transizioni supplementari consentite quando l'utente è l'assegnatario + label_additional_workflow_transitions_for_author: Transizioni supplementari consentite quando l'utente è l'autore + label_bulk_edit_selected_time_entries: Modifica massiva delle ore segnalate selezionate + text_time_entries_destroy_confirmation: Sei sicuro di voler eliminare l'ora\e selezionata\e? + label_role_anonymous: Anonimo + label_role_non_member: Non membro + label_issue_note_added: Nota aggiunta + label_issue_status_updated: Stato aggiornato + label_issue_priority_updated: Priorità aggiornata + label_issues_visibility_own: Segnalazioni create o assegnate all'utente + field_issues_visibility: Visibilità segnalazioni + label_issues_visibility_all: Tutte le segnalazioni + permission_set_own_issues_private: Imposta le proprie segnalazioni pubbliche o private + field_is_private: Privato + permission_set_issues_private: Imposta le segnalazioni pubbliche o private + label_issues_visibility_public: Tutte le segnalazioni non private + text_issues_destroy_descendants_confirmation: Questo eliminerà anche %{count} sottoattività. + field_commit_logs_encoding: Codifica dei messaggi di commit + field_scm_path_encoding: Codifica del percorso + text_scm_path_encoding_note: "Predefinito: UTF-8" + field_path_to_repository: Percorso del repository + field_root_directory: Directory radice + field_cvs_module: Modulo + field_cvsroot: CVSROOT + text_mercurial_repository_note: Repository locale (es. /hgrepo, c:\hgrepo) + text_scm_command: Comando + text_scm_command_version: Versione + label_git_report_last_commit: Riporta l'ultimo commit per files e directories + text_scm_config: Puoi configurare i comandi scm nel file config/configuration.yml. E' necessario riavviare l'applicazione dopo averlo modificato. + text_scm_command_not_available: Il comando scm non è disponibile. Controllare le impostazioni nel pannello di amministrazione. + notice_issue_successful_create: Segnalazione %{id} creata. + label_between: tra + setting_issue_group_assignment: Permetti di assegnare una segnalazione a gruppi + label_diff: diff + text_git_repository_note: Il repository è spoglio e locale (e.g. /gitrepo, c:\gitrepo) + description_query_sort_criteria_direction: Ordinamento + description_project_scope: Search scope + description_filter: Filtro + description_user_mail_notification: Impostazioni notifica via mail + description_date_from: Inserisci la data d'inizio + description_message_content: Contenuto del messaggio + description_available_columns: Colonne disponibili + description_date_range_interval: Scegli l'intervallo selezionando la data di inizio e di fine + description_issue_category_reassign: Scegli la categoria della segnalazione + description_search: Campo di ricerca + description_notes: Note + description_date_range_list: Scegli l'intervallo dalla lista + description_choose_project: Progetti + description_date_to: Inserisci la data di fine + description_query_sort_criteria_attribute: Attributo di ordinamento + description_wiki_subpages_reassign: Scegli la nuova pagina padre + description_selected_columns: Colonne selezionate + label_parent_revision: Padre + label_child_revision: Figlio + error_scm_annotate_big_text_file: La nota non può essere salvata, supera la dimensiona massima del campo di testo. + setting_default_issue_start_date_to_creation_date: Usa la data corrente come data d'inizio per le nuove segnalazioni + button_edit_section: Modifica questa sezione + setting_repositories_encodings: Codifica degli allegati e dei repository + description_all_columns: Tutte le colonne + button_export: Esporta + label_export_options: "%{export_format} opzioni per l'export" + error_attachment_too_big: Questo file non può essere caricato in quanto la sua dimensione supera la massima consentita (%{max_size}) + notice_failed_to_save_time_entries: "Non ho potuto salvare %{count} registrazioni di tempo impiegato su %{total} selezionate: %{ids}." + label_x_issues: + zero: 0 segnalazione + one: 1 segnalazione + other: "%{count} segnalazioni" + label_repository_new: Nuovo repository + field_repository_is_default: Repository principale + label_copy_attachments: Copia allegati + label_item_position: "%{position}/%{count}" + label_completed_versions: Completed versions + text_project_identifier_info: Consentiti solo lettere minuscole (a-z), numeri, trattini e trattini bassi.
    Una volta salvato, l'identificatore non può essere modificato. + field_multiple: Valori multipli + setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed + text_issue_conflict_resolution_add_notes: Aggiunge le mie note e non salvare le mie ulteriori modifiche + text_issue_conflict_resolution_overwrite: Applica comunque le mie modifiche (le note precedenti verranno mantenute ma alcuni cambiamenti potrebbero essere sovrascritti) + notice_issue_update_conflict: La segnalazione è stata aggiornata da un altro utente mentre la stavi editando. + text_issue_conflict_resolution_cancel: Cancella ogni modifica e rivisualizza %{link} + permission_manage_related_issues: Gestisci relative segnalazioni + field_auth_source_ldap_filter: Filtro LDAP + label_search_for_watchers: Cerca osservatori da aggiungere + notice_account_deleted: Il tuo account sarà definitivamente rimosso. + setting_unsubscribe: Consentire agli utenti di cancellare il proprio account + button_delete_my_account: Cancella il mio account + text_account_destroy_confirmation: "Sei sicuro di voler procedere?\nIl tuo account sarà definitivamente cancellato, senza alcuna possibilità di ripristino." + error_session_expired: "La tua sessione è scaduta. Effettua nuovamente il login." + text_session_expiration_settings: "Attenzione: la modifica di queste impostazioni può far scadere le sessioni correnti, compresa la tua." + setting_session_lifetime: Massima durata di una sessione + setting_session_timeout: Timeout di inattività di una sessione + label_session_expiration: Scadenza sessione + permission_close_project: Chiusura / riapertura progetto + label_show_closed_projects: Vedi progetti chiusi + button_close: Chiudi + button_reopen: Riapri + project_status_active: attivo + project_status_closed: chiuso + project_status_archived: archiviato + text_project_closed: Questo progetto è chiuso e in sola lettura. + notice_user_successful_create: Creato utente %{id}. + field_core_fields: Campi standard + field_timeout: Timeout (in secondi) + setting_thumbnails_enabled: Mostra miniature degli allegati + setting_thumbnails_size: Dimensioni delle miniature (in pixels) + label_status_transitions: Transizioni di stato + label_fields_permissions: Permessi sui campi + label_readonly: Sola lettura + label_required: Richiesto + text_repository_identifier_info: Consentiti solo lettere minuscole (a-z), numeri, trattini e trattini bassi.
    Una volta salvato, ll'identificatore non può essere modificato. + field_board_parent: Parent forum + label_attribute_of_project: Project's %{name} + label_attribute_of_author: Author's %{name} + label_attribute_of_assigned_to: Assegnatari %{name} + label_attribute_of_fixed_version: Target version's %{name} + label_copy_subtasks: Copia sottoattività + label_copied_to: copia a + label_copied_from: copia da + label_any_issues_in_project: ogni segnalazione del progetto + label_any_issues_not_in_project: ogni segnalazione non nel progetto + field_private_notes: Note private + permission_view_private_notes: Visualizza note private + permission_set_notes_private: Imposta note come private + label_no_issues_in_project: progetto privo di segnalazioni + label_any: tutti + label_last_n_weeks: ultime %{count} settimane + setting_cross_project_subtasks: Consenti sottoattività cross-project + label_cross_project_descendants: Con sottoprogetti + label_cross_project_tree: Con progetto padre + label_cross_project_hierarchy: Con gerarchia progetto + label_cross_project_system: Con tutti i progetti + button_hide: Nascondi + setting_non_working_week_days: Giorni non lavorativi + label_in_the_next_days: nei prossimi + label_in_the_past_days: nei passati + label_attribute_of_user: Utente %{name} + text_turning_multiple_off: Disabilitando valori multipli, i valori multipli verranno rimossi, in modo da mantenere un solo valore per item. + label_attribute_of_issue: Segnalazione %{name} + permission_add_documents: Aggiungi documenti + permission_edit_documents: Edita documenti + permission_delete_documents: Cancella documenti + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Abilita supporto a JSONP + field_inherit_members: Eredita membri + field_closed_on: Chiuso + setting_default_projects_tracker_ids: Trackers di default per nuovi progetti + label_total_time: Totale diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/77/77dddf9e601d2a13642541b4fb2f841096013486.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/77/77dddf9e601d2a13642541b4fb2f841096013486.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,165 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class WelcomeControllerTest < ActionController::TestCase + fixtures :projects, :news, :users, :members + + def setup + User.current = nil + end + + def test_index + get :index + assert_response :success + assert_template 'index' + assert_not_nil assigns(:news) + assert_not_nil assigns(:projects) + assert !assigns(:projects).include?(Project.where(:is_public => false).first) + end + + def test_browser_language + Setting.default_language = 'en' + @request.env['HTTP_ACCEPT_LANGUAGE'] = 'fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3' + get :index + assert_equal :fr, @controller.current_language + end + + def test_browser_language_alternate + Setting.default_language = 'en' + @request.env['HTTP_ACCEPT_LANGUAGE'] = 'zh-TW' + get :index + assert_equal :"zh-TW", @controller.current_language + end + + def test_browser_language_alternate_not_valid + Setting.default_language = 'en' + @request.env['HTTP_ACCEPT_LANGUAGE'] = 'fr-CA' + 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\r?$}) + end + + def test_warn_on_leaving_unsaved_turn_on + user = User.find(2) + user.pref.warn_on_leaving_unsaved = '1' + user.pref.save! + @request.session[:user_id] = 2 + + get :index + assert_tag 'script', + :attributes => {:type => "text/javascript"}, + :content => %r{warnLeavingUnsaved} + end + + def test_warn_on_leaving_unsaved_turn_off + user = User.find(2) + user.pref.warn_on_leaving_unsaved = '0' + user.pref.save! + @request.session[:user_id] = 2 + + get :index + assert_no_tag 'script', + :attributes => {:type => "text/javascript"}, + :content => %r{warnLeavingUnsaved} + end + + def test_logout_link_should_post + @request.session[:user_id] = 2 + + get :index + assert_select 'a[href=/logout][data-method=post]', :text => 'Sign out' + end + + def test_call_hook_mixed_in + assert @controller.respond_to?(:call_hook) + end + + def test_project_jump_box_should_escape_names_once + Project.find(1).update_attribute :name, 'Foo & Bar' + @request.session[:user_id] = 2 + + get :index + assert_select "#header select" do + assert_select "option", :text => 'Foo & Bar' + end + end + + context "test_api_offset_and_limit" do + context "without params" do + should "return 0, 25" do + assert_equal [0, 25], @controller.api_offset_and_limit({}) + end + end + + context "with limit" do + should "return 0, limit" do + assert_equal [0, 30], @controller.api_offset_and_limit({:limit => 30}) + end + + should "not exceed 100" do + assert_equal [0, 100], @controller.api_offset_and_limit({:limit => 120}) + end + + should "not be negative" do + assert_equal [0, 25], @controller.api_offset_and_limit({:limit => -10}) + end + end + + context "with offset" do + should "return offset, 25" do + assert_equal [10, 25], @controller.api_offset_and_limit({:offset => 10}) + end + + should "not be negative" do + assert_equal [0, 25], @controller.api_offset_and_limit({:offset => -10}) + end + + context "and limit" do + should "return offset, limit" do + assert_equal [10, 50], @controller.api_offset_and_limit({:offset => 10, :limit => 50}) + end + end + end + + context "with page" do + should "return offset, 25" do + assert_equal [0, 25], @controller.api_offset_and_limit({:page => 1}) + assert_equal [50, 25], @controller.api_offset_and_limit({:page => 3}) + end + + should "not be negative" do + assert_equal [0, 25], @controller.api_offset_and_limit({:page => 0}) + assert_equal [0, 25], @controller.api_offset_and_limit({:page => -2}) + end + + context "and limit" do + should "return offset, limit" do + assert_equal [0, 100], @controller.api_offset_and_limit({:page => 1, :limit => 100}) + assert_equal [200, 100], @controller.api_offset_and_limit({:page => 3, :limit => 100}) + end + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/77/77edc6851d3d16ead6a951e08fc89080d2584af3.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/77/77edc6851d3d16ead6a951e08fc89080d2584af3.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,178 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class Setting < ActiveRecord::Base + + DATE_FORMATS = [ + '%Y-%m-%d', + '%d/%m/%Y', + '%d.%m.%Y', + '%d-%m-%Y', + '%m/%d/%Y', + '%d %b %Y', + '%d %B %Y', + '%b %d, %Y', + '%B %d, %Y' + ] + + TIME_FORMATS = [ + '%H:%M', + '%I:%M %p' + ] + + ENCODINGS = %w(US-ASCII + windows-1250 + windows-1251 + windows-1252 + windows-1253 + windows-1254 + windows-1255 + windows-1256 + windows-1257 + windows-1258 + windows-31j + ISO-2022-JP + ISO-2022-KR + ISO-8859-1 + ISO-8859-2 + ISO-8859-3 + ISO-8859-4 + ISO-8859-5 + ISO-8859-6 + ISO-8859-7 + ISO-8859-8 + ISO-8859-9 + ISO-8859-13 + ISO-8859-15 + KOI8-R + UTF-8 + UTF-16 + UTF-16BE + UTF-16LE + EUC-JP + Shift_JIS + CP932 + GB18030 + GBK + ISCII91 + EUC-KR + Big5 + Big5-HKSCS + TIS-620) + + cattr_accessor :available_settings + @@available_settings = YAML::load(File.open("#{Rails.root}/config/settings.yml")) + Redmine::Plugin.all.each do |plugin| + next unless plugin.settings + @@available_settings["plugin_#{plugin.id}"] = {'default' => plugin.settings[:default], 'serialized' => true} + end + + validates_uniqueness_of :name + validates_inclusion_of :name, :in => @@available_settings.keys + validates_numericality_of :value, :only_integer => true, :if => Proc.new { |setting| @@available_settings[setting.name]['format'] == 'int' } + + # Hash used to cache setting values + @cached_settings = {} + @cached_cleared_on = Time.now + + def value + v = read_attribute(:value) + # Unserialize serialized settings + v = YAML::load(v) if @@available_settings[name]['serialized'] && v.is_a?(String) + v = v.to_sym if @@available_settings[name]['format'] == 'symbol' && !v.blank? + v + end + + def value=(v) + v = v.to_yaml if v && @@available_settings[name] && @@available_settings[name]['serialized'] + write_attribute(:value, v.to_s) + end + + # Returns the value of the setting named name + def self.[](name) + v = @cached_settings[name] + v ? v : (@cached_settings[name] = find_or_default(name).value) + end + + def self.[]=(name, v) + setting = find_or_default(name) + setting.value = (v ? v : "") + @cached_settings[name] = nil + setting.save + setting.value + end + + # Defines getter and setter for each setting + # Then setting values can be read using: Setting.some_setting_name + # or set using Setting.some_setting_name = "some value" + @@available_settings.each do |name, params| + src = <<-END_SRC + def self.#{name} + self[:#{name}] + end + + def self.#{name}? + self[:#{name}].to_i > 0 + end + + def self.#{name}=(value) + self[:#{name}] = value + end + END_SRC + class_eval src, __FILE__, __LINE__ + end + + # Helper that returns an array based on per_page_options setting + def self.per_page_options_array + 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_i > 0 + end + + # Checks if settings have changed since the values were read + # and clears the cache hash if it's the case + # Called once per request + def self.check_cache + settings_updated_on = Setting.maximum(:updated_on) + if settings_updated_on && @cached_cleared_on <= settings_updated_on + clear_cache + end + end + + # Clears the settings cache + def self.clear_cache + @cached_settings.clear + @cached_cleared_on = Time.now + logger.info "Settings cache cleared." if logger + end + +private + # Returns the Setting instance for the setting named name + # (record found in database or new record with default value) + def self.find_or_default(name) + name = name.to_s + raise "There's no setting named #{name}" unless @@available_settings.has_key?(name) + setting = find_by_name(name) + unless setting + setting = new(:name => name) + setting.value = @@available_settings[name]['default'] + end + setting + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/78/7828cc0008d3db898fec05425bedf18d84ff5098.svn-base --- a/.svn/pristine/78/7828cc0008d3db898fec05425bedf18d84ff5098.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,29 +0,0 @@ -<% if issues && issues.any? %> -<% form_tag({}) do %> - - - - - - - - - <% for issue in issues %> - - - - - - - <% end %> - -
    #<%=l(:field_project)%><%=l(:field_tracker)%><%=l(:field_subject)%>
    - <%= check_box_tag("ids[]", issue.id, false, :style => 'display:none;') %> - <%= link_to(h(issue.id), :controller => 'issues', :action => 'show', :id => issue) %> - <%= link_to_project(issue.project) %><%=h issue.tracker %> - <%= link_to h(truncate(issue.subject, :length => 60)), :controller => 'issues', :action => 'show', :id => issue %> (<%=h issue.status %>) -
    -<% end %> -<% else %> -

    <%= l(:label_no_data) %>

    -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/78/782c6f591e4e955b6c072dd5cc852035f16ba97d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/78/782c6f591e4e955b6c072dd5cc852035f16ba97d.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,17 @@ +

    <%= l(:label_issue_plural) %>

    +<%= link_to l(:label_issue_view_all), _project_issues_path(@project, :set_filter => 1) %>
    +<% if @project %> +<%= link_to l(:field_summary), project_issues_report_path(@project) %>
    +<% end %> +<%= call_hook(:view_issues_sidebar_issues_bottom) %> + +<% if User.current.allowed_to?(:view_calendar, @project, :global => true) %> + <%= link_to l(:label_calendar), _project_calendar_path(@project) %>
    +<% end %> +<% if User.current.allowed_to?(:view_gantt, @project, :global => true) %> + <%= link_to l(:label_gantt), _project_gantt_path(@project) %>
    +<% end %> +<%= call_hook(:view_issues_sidebar_planning_bottom) %> + +<%= render_sidebar_queries %> +<%= call_hook(:view_issues_sidebar_queries_bottom) %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/78/78376d3215166c5e884949655b3cf6e5caa6e30e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/78/78376d3215166c5e884949655b3cf6e5caa6e30e.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,129 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class ActivityTest < ActiveSupport::TestCase + fixtures :projects, :versions, :attachments, :users, :roles, :members, :member_roles, :issues, :journals, :journal_details, + :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages, :time_entries, + :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions + + def setup + @project = Project.find(1) + end + + def test_activity_without_subprojects + events = find_events(User.anonymous, :project => @project) + assert_not_nil events + + assert events.include?(Issue.find(1)) + assert !events.include?(Issue.find(4)) + # subproject issue + assert !events.include?(Issue.find(5)) + end + + def test_activity_with_subprojects + events = find_events(User.anonymous, :project => @project, :with_subprojects => 1) + assert_not_nil events + + assert events.include?(Issue.find(1)) + # subproject issue + assert events.include?(Issue.find(5)) + end + + def test_global_activity_anonymous + events = find_events(User.anonymous) + assert_not_nil events + + assert events.include?(Issue.find(1)) + assert events.include?(Message.find(5)) + # Issue of a private project + assert !events.include?(Issue.find(4)) + # Private issue and comment + assert !events.include?(Issue.find(14)) + assert !events.include?(Journal.find(5)) + end + + def test_global_activity_logged_user + events = find_events(User.find(2)) # manager + assert_not_nil events + + assert events.include?(Issue.find(1)) + # Issue of a private project the user belongs to + 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 + + def test_files_activity + f = Redmine::Activity::Fetcher.new(User.anonymous, :project => Project.find(1)) + f.scope = ['files'] + events = f.events + + assert_kind_of Array, events + assert events.include?(Attachment.find_by_container_type_and_container_id('Project', 1)) + assert events.include?(Attachment.find_by_container_type_and_container_id('Version', 1)) + assert_equal [Attachment], events.collect(&:class).uniq + assert_equal %w(Project Version), events.collect(&:container_type).uniq.sort + end + + def test_event_group_for_issue + issue = Issue.find(1) + assert_equal issue, issue.event_group + end + + def test_event_group_for_journal + issue = Issue.find(1) + journal = issue.journals.first + assert_equal issue, journal.event_group + end + + def test_event_group_for_issue_time_entry + time = TimeEntry.where(:issue_id => 1).first + assert_equal time.issue, time.event_group + end + + def test_event_group_for_project_time_entry + time = TimeEntry.where(:issue_id => nil).first + assert_equal time, time.event_group + end + + def test_event_group_for_message + message = Message.find(1) + reply = message.children.first + assert_equal message, message.event_group + assert_equal message, reply.event_group + end + + def test_event_group_for_wiki_content_version + content = WikiContent::Version.find(1) + assert_equal content.page, content.event_group + end + + private + + def find_events(user, options={}) + Redmine::Activity::Fetcher.new(user, options).events(Date.today - 30, Date.today + 1) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/78/784c9b0d2d8855db8c97b7e039fb2ee4a682fc0e.svn-base --- a/.svn/pristine/78/784c9b0d2d8855db8c97b7e039fb2ee4a682fc0e.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,19 +0,0 @@ -require File.dirname(__FILE__) + '/../test_helper' - -class TestingTest < Test::Unit::TestCase - def setup - Engines::Testing.set_fixture_path - @filename = File.join(Engines::Testing.temporary_fixtures_directory, 'testing_fixtures.yml') - File.delete(@filename) if File.exists?(@filename) - end - - def teardown - File.delete(@filename) if File.exists?(@filename) - end - - def test_should_copy_fixtures_files_to_tmp_directory - assert !File.exists?(@filename) - Engines::Testing.setup_plugin_fixtures - assert File.exists?(@filename) - end -end \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/78/78656abccdc6e881919c3599b07b9c04e13170fc.svn-base --- a/.svn/pristine/78/78656abccdc6e881919c3599b07b9c04e13170fc.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,87 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) -require 'reports_controller' - -# Re-raise errors caught by the controller. -class ReportsController; def rescue_action(e) raise e end; end - - -class ReportsControllerTest < ActionController::TestCase - fixtures :projects, :trackers, :issue_statuses, :issues, - :enumerations, :users, :issue_categories, - :projects_trackers, - :roles, - :member_roles, - :members, - :enabled_modules, - :workflows, - :versions - - def setup - @controller = ReportsController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - User.current = nil - end - - context "GET :issue_report without details" do - setup do - get :issue_report, :id => 1 - end - - should_respond_with :success - should_render_template :issue_report - - [:issues_by_tracker, :issues_by_version, :issues_by_category, :issues_by_assigned_to, - :issues_by_author, :issues_by_subproject].each do |ivar| - should_assign_to ivar - should "set a value for #{ivar}" do - assert assigns[ivar.to_s].present? - end - end - end - - context "GET :issue_report_details" do - %w(tracker version priority category assigned_to author subproject).each do |detail| - context "for #{detail}" do - setup do - get :issue_report_details, :id => 1, :detail => detail - end - - should_respond_with :success - should_render_template :issue_report_details - should_assign_to :field - should_assign_to :rows - should_assign_to :data - should_assign_to :report_title - end - end - - context "with an invalid detail" do - setup do - get :issue_report_details, :id => 1, :detail => 'invalid' - end - - should_respond_with :redirect - should_redirect_to('the issue report') {{:controller => 'reports', :action => 'issue_report', :id => 'ecookbook'}} - end - - end - -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/78/787bda5f0568c9fa3603342f51af7f230d71bae6.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/78/787bda5f0568c9fa3603342f51af7f230d71bae6.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,236 @@ +var contextMenuObserving; +var contextMenuUrl; + +function contextMenuRightClick(event) { + var target = $(event.target); + if (target.is('a')) {return;} + var tr = target.parents('tr').first(); + if (!tr.hasClass('hascontextmenu')) {return;} + event.preventDefault(); + if (!contextMenuIsSelected(tr)) { + contextMenuUnselectAll(); + contextMenuAddSelection(tr); + contextMenuSetLastSelected(tr); + } + contextMenuShow(event); +} + +function contextMenuClick(event) { + var target = $(event.target); + var lastSelected; + + if (target.is('a') && target.hasClass('submenu')) { + event.preventDefault(); + return; + } + contextMenuHide(); + if (target.is('a') || target.is('img')) { return; } + if (event.which == 1 || (navigator.appVersion.match(/\bMSIE\b/))) { + var tr = target.parents('tr').first(); + if (tr.length && tr.hasClass('hascontextmenu')) { + // a row was clicked, check if the click was on checkbox + if (target.is('input')) { + // a checkbox may be clicked + if (target.attr('checked')) { + tr.addClass('context-menu-selection'); + } else { + tr.removeClass('context-menu-selection'); + } + } else { + if (event.ctrlKey || event.metaKey) { + contextMenuToggleSelection(tr); + } else if (event.shiftKey) { + lastSelected = contextMenuLastSelected(); + if (lastSelected.length) { + var toggling = false; + $('.hascontextmenu').each(function(){ + if (toggling || $(this).is(tr)) { + contextMenuAddSelection($(this)); + } + if ($(this).is(tr) || $(this).is(lastSelected)) { + toggling = !toggling; + } + }); + } else { + contextMenuAddSelection(tr); + } + } else { + contextMenuUnselectAll(); + contextMenuAddSelection(tr); + } + contextMenuSetLastSelected(tr); + } + } else { + // click is outside the rows + if (target.is('a') && (target.hasClass('disabled') || target.hasClass('submenu'))) { + event.preventDefault(); + } else { + contextMenuUnselectAll(); + } + } + } +} + +function contextMenuCreate() { + if ($('#context-menu').length < 1) { + var menu = document.createElement("div"); + menu.setAttribute("id", "context-menu"); + menu.setAttribute("style", "display:none;"); + document.getElementById("content").appendChild(menu); + } +} + +function contextMenuShow(event) { + var mouse_x = event.pageX; + var mouse_y = event.pageY; + var render_x = mouse_x; + var render_y = mouse_y; + var dims; + var menu_width; + var menu_height; + var window_width; + var window_height; + var max_width; + var max_height; + + $('#context-menu').css('left', (render_x + 'px')); + $('#context-menu').css('top', (render_y + 'px')); + $('#context-menu').html(''); + + $.ajax({ + url: contextMenuUrl, + data: $(event.target).parents('form').first().serialize(), + success: function(data, textStatus, jqXHR) { + $('#context-menu').html(data); + menu_width = $('#context-menu').width(); + menu_height = $('#context-menu').height(); + max_width = mouse_x + 2*menu_width; + max_height = mouse_y + menu_height; + + var ws = window_size(); + window_width = ws.width; + window_height = ws.height; + + /* display the menu above and/or to the left of the click if needed */ + if (max_width > window_width) { + render_x -= menu_width; + $('#context-menu').addClass('reverse-x'); + } else { + $('#context-menu').removeClass('reverse-x'); + } + if (max_height > window_height) { + render_y -= menu_height; + $('#context-menu').addClass('reverse-y'); + } else { + $('#context-menu').removeClass('reverse-y'); + } + if (render_x <= 0) render_x = 1; + if (render_y <= 0) render_y = 1; + $('#context-menu').css('left', (render_x + 'px')); + $('#context-menu').css('top', (render_y + 'px')); + $('#context-menu').show(); + + //if (window.parseStylesheets) { window.parseStylesheets(); } // IE + + } + }); +} + +function contextMenuSetLastSelected(tr) { + $('.cm-last').removeClass('cm-last'); + tr.addClass('cm-last'); +} + +function contextMenuLastSelected() { + return $('.cm-last').first(); +} + +function contextMenuUnselectAll() { + $('.hascontextmenu').each(function(){ + contextMenuRemoveSelection($(this)); + }); + $('.cm-last').removeClass('cm-last'); +} + +function contextMenuHide() { + $('#context-menu').hide(); +} + +function contextMenuToggleSelection(tr) { + if (contextMenuIsSelected(tr)) { + contextMenuRemoveSelection(tr); + } else { + contextMenuAddSelection(tr); + } +} + +function contextMenuAddSelection(tr) { + tr.addClass('context-menu-selection'); + contextMenuCheckSelectionBox(tr, true); + contextMenuClearDocumentSelection(); +} + +function contextMenuRemoveSelection(tr) { + tr.removeClass('context-menu-selection'); + contextMenuCheckSelectionBox(tr, false); +} + +function contextMenuIsSelected(tr) { + return tr.hasClass('context-menu-selection'); +} + +function contextMenuCheckSelectionBox(tr, checked) { + tr.find('input[type=checkbox]').attr('checked', checked); +} + +function contextMenuClearDocumentSelection() { + // TODO + if (document.selection) { + document.selection.empty(); // IE + } else { + window.getSelection().removeAllRanges(); + } +} + +function contextMenuInit(url) { + contextMenuUrl = url; + contextMenuCreate(); + contextMenuUnselectAll(); + + if (!contextMenuObserving) { + $(document).click(contextMenuClick); + $(document).contextmenu(contextMenuRightClick); + contextMenuObserving = true; + } +} + +function toggleIssuesSelection(el) { + var boxes = $(el).parents('form').find('input[type=checkbox]'); + var all_checked = true; + boxes.each(function(){ if (!$(this).attr('checked')) { all_checked = false; } }); + boxes.each(function(){ + if (all_checked) { + $(this).removeAttr('checked'); + $(this).parents('tr').removeClass('context-menu-selection'); + } else if (!$(this).attr('checked')) { + $(this).attr('checked', true); + $(this).parents('tr').addClass('context-menu-selection'); + } + }); +} + +function window_size() { + var w; + var h; + if (window.innerWidth) { + w = window.innerWidth; + h = window.innerHeight; + } else if (document.documentElement) { + w = document.documentElement.clientWidth; + h = document.documentElement.clientHeight; + } else { + w = document.body.clientWidth; + h = document.body.clientHeight; + } + return {width: w, height: h}; +} diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/78/7884d1a52f46dcfb203fc57885fb084b78063ba1.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/78/7884d1a52f46dcfb203fc57885fb084b78063ba1.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,242 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require 'redmine/scm/adapters/abstract_adapter' +require 'rexml/document' + +module Redmine + module Scm + module Adapters + class DarcsAdapter < AbstractAdapter + # Darcs executable name + DARCS_BIN = Redmine::Configuration['scm_darcs_command'] || "darcs" + + class << self + def client_command + @@bin ||= DARCS_BIN + end + + def sq_bin + @@sq_bin ||= shell_quote_command + end + + def client_version + @@client_version ||= (darcs_binary_version || []) + end + + def client_available + !client_version.empty? + end + + def darcs_binary_version + darcsversion = darcs_binary_version_from_command_line.dup + if darcsversion.respond_to?(:force_encoding) + darcsversion.force_encoding('ASCII-8BIT') + end + if m = darcsversion.match(%r{\A(.*?)((\d+\.)+\d+)}) + m[2].scan(%r{\d+}).collect(&:to_i) + end + end + + def darcs_binary_version_from_command_line + shellout("#{sq_bin} --version") { |io| io.read }.to_s + end + end + + def initialize(url, root_url=nil, login=nil, password=nil, + path_encoding=nil) + @url = url + @root_url = url + end + + def supports_cat? + # cat supported in darcs 2.0.0 and higher + self.class.client_version_above?([2, 0, 0]) + end + + # Get info about the darcs repository + def info + rev = revisions(nil,nil,nil,{:limit => 1}) + rev ? Info.new({:root_url => @url, :lastrev => rev.last}) : nil + end + + # Returns an Entries collection + # or nil if the given path doesn't exist in the repository + def entries(path=nil, identifier=nil, options={}) + path_prefix = (path.blank? ? '' : "#{path}/") + if path.blank? + path = ( self.class.client_version_above?([2, 2, 0]) ? @url : '.' ) + end + entries = Entries.new + cmd = "#{self.class.sq_bin} annotate --repodir #{shell_quote @url} --xml-output" + cmd << " --match #{shell_quote("hash #{identifier}")}" if identifier + cmd << " #{shell_quote path}" + shellout(cmd) do |io| + begin + doc = REXML::Document.new(io) + if doc.root.name == 'directory' + doc.elements.each('directory/*') do |element| + next unless ['file', 'directory'].include? element.name + entries << entry_from_xml(element, path_prefix) + end + elsif doc.root.name == 'file' + entries << entry_from_xml(doc.root, path_prefix) + end + rescue + end + end + return nil if $? && $?.exitstatus != 0 + entries.compact! + entries.sort_by_name + end + + def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) + path = '.' if path.blank? + revisions = Revisions.new + cmd = "#{self.class.sq_bin} changes --repodir #{shell_quote @url} --xml-output" + 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 + doc = REXML::Document.new(io) + doc.elements.each("changelog/patch") do |patch| + message = patch.elements['name'].text + message << "\n" + patch.elements['comment'].text.gsub(/\*\*\*END OF DESCRIPTION\*\*\*.*\z/m, '') if patch.elements['comment'] + revisions << Revision.new({:identifier => nil, + :author => patch.attributes['author'], + :scmid => patch.attributes['hash'], + :time => Time.parse(patch.attributes['local_date']), + :message => message, + :paths => (options[:with_path] ? get_paths_for_patch(patch.attributes['hash']) : nil) + }) + end + rescue + end + end + return nil if $? && $?.exitstatus != 0 + revisions + end + + def diff(path, identifier_from, identifier_to=nil) + path = '*' if path.blank? + cmd = "#{self.class.sq_bin} diff --repodir #{shell_quote @url}" + if identifier_to.nil? + cmd << " --match #{shell_quote("hash #{identifier_from}")}" + else + cmd << " --to-match #{shell_quote("hash #{identifier_from}")}" + cmd << " --from-match #{shell_quote("hash #{identifier_to}")}" + end + cmd << " -u #{shell_quote path}" + diff = [] + shellout(cmd) do |io| + io.each_line do |line| + diff << line + end + end + return nil if $? && $?.exitstatus != 0 + diff + end + + def cat(path, identifier=nil) + cmd = "#{self.class.sq_bin} show content --repodir #{shell_quote @url}" + cmd << " --match #{shell_quote("hash #{identifier}")}" if identifier + cmd << " #{shell_quote path}" + cat = nil + shellout(cmd) do |io| + io.binmode + cat = io.read + end + return nil if $? && $?.exitstatus != 0 + cat + end + + private + + # 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 => modified_element.elements['patch'].attributes['hash'] + }) + }) + end + + def get_paths_for_patch(hash) + paths = get_paths_for_patch_raw(hash) + if self.class.client_version_above?([2, 4]) + orig_paths = paths + paths = [] + add_paths = [] + add_paths_name = [] + mod_paths = [] + other_paths = [] + orig_paths.each do |path| + if path[:action] == 'A' + add_paths << path + add_paths_name << path[:path] + elsif path[:action] == 'M' + mod_paths << path + else + other_paths << path + end + end + add_paths_name.each do |add_path| + mod_paths.delete_if { |m| m[:path] == add_path } + end + paths.concat add_paths + paths.concat mod_paths + paths.concat other_paths + end + paths + end + + # Retrieve changed paths for a single patch + def get_paths_for_patch_raw(hash) + cmd = "#{self.class.sq_bin} annotate --repodir #{shell_quote @url} --summary --xml-output" + cmd << " --match #{shell_quote("hash #{hash}")} " + paths = [] + shellout(cmd) do |io| + begin + # Darcs xml output has multiple root elements in this case (tested with darcs 1.0.7) + # A root element is added so that REXML doesn't raise an error + doc = REXML::Document.new("" + io.read + "") + doc.elements.each('fake_root/summary/*') do |modif| + paths << {:action => modif.name[0,1].upcase, + :path => "/" + modif.text.chomp.gsub(/^\s*/, '') + } + end + rescue + end + end + paths + rescue CommandFailed + paths + end + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/78/7887ac0add3adb74049da4eb63c9169b67eaf06b.svn-base --- a/.svn/pristine/78/7887ac0add3adb74049da4eb63c9169b67eaf06b.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,226 +0,0 @@ -require 'rake' -require 'rake/rdoctask' -require 'tmpdir' - -task :default => :doc - -desc 'Generate documentation for the engines plugin.' -Rake::RDocTask.new(:doc) do |doc| - doc.rdoc_dir = 'doc' - doc.title = 'Engines' - doc.main = "README" - doc.rdoc_files.include("README", "CHANGELOG", "MIT-LICENSE") - doc.rdoc_files.include('lib/**/*.rb') - doc.options << '--line-numbers' << '--inline-source' -end - -desc 'Run the engine plugin tests within their test harness' -task :cruise do - # checkout the project into a temporary directory - version = "rails_2.0" - test_dir = "#{Dir.tmpdir}/engines_plugin_#{version}_test" - puts "Checking out test harness for #{version} into #{test_dir}" - `svn co http://svn.rails-engines.org/test/engines/#{version} #{test_dir}` - - # run all the tests in this project - Dir.chdir(test_dir) - load 'Rakefile' - puts "Running all tests in test harness" - ['db:migrate', 'test', 'test:plugins'].each do |t| - Rake::Task[t].invoke - end -end - -task :clean => [:clobber_doc, "test:clean"] - -namespace :test do - - # Yields a block with STDOUT and STDERR silenced. If you *really* want - # to output something, the block is yielded with the original output - # streams, i.e. - # - # silence do |o, e| - # puts 'hello!' # no output produced - # o.puts 'hello!' # output on STDOUT - # end - # - # (based on silence_stream in ActiveSupport.) - def silence - yield(STDOUT, STDERR) if ENV['VERBOSE'] - streams = [STDOUT, STDERR] - actual_stdout = STDOUT.dup - actual_stderr = STDERR.dup - streams.each do |s| - s.reopen(RUBY_PLATFORM =~ /mswin/ ? 'NUL:' : '/dev/null') - s.sync = true - end - yield actual_stdout, actual_stderr - ensure - STDOUT.reopen(actual_stdout) - STDERR.reopen(actual_stderr) - end - - def test_app_dir - File.join(File.dirname(__FILE__), 'test_app') - end - - def run(cmd) - cmd = cmd.join(" && ") if cmd.is_a?(Array) - system(cmd) || raise("failed running '#{cmd}'") - end - - desc 'Remove the test application' - task :clean do - FileUtils.rm_r(test_app_dir) if File.exist?(test_app_dir) - end - - desc 'Build the test rails application (use RAILS=[edge,] to test against specific version)' - task :generate_app do - silence do |out, err| - out.puts "> Creating test application at #{test_app_dir}" - - if ENV['RAILS'] - vendor_dir = File.join(test_app_dir, 'vendor') - FileUtils.mkdir_p vendor_dir - - if ENV['RAILS'] == 'edge' - out.puts " Cloning Edge Rails from GitHub" - run "cd #{vendor_dir} && git clone --depth 1 git://github.com/rails/rails.git" - elsif ENV['RAILS'] =~ /\d\.\d\.\d/ - if ENV['CURL'] - out.puts " Cloning Rails Tag #{ENV['RAILS']} from GitHub using curl and tar" - run ["cd #{vendor_dir}", - "mkdir rails", - "cd rails", - "curl -s -L http://github.com/rails/rails/tarball/#{ENV['RAILS']} | tar xzv --strip-components 1"] - else - out.puts " Cloning Rails Tag #{ENV['RAILS']} from GitHub (can be slow - set CURL=true to use curl)" - run ["cd #{vendor_dir}", - "git clone git://github.com/rails/rails.git", - "cd rails", - "git pull", - "git checkout v#{ENV['RAILS']}"] - end - elsif File.exist?(ENV['RAILS']) - out.puts " Linking rails from #{ENV['RAILS']}" - run "cd #{vendor_dir} && ln -s #{ENV['RAILS']} rails" - else - raise "Couldn't build test application from '#{ENV['RAILS']}'" - end - - out.puts " generating rails default directory structure" - run "ruby #{File.join(vendor_dir, 'rails', 'railties', 'bin', 'rails')} #{test_app_dir}" - else - version = `rails --version`.chomp.split.last - out.puts " building rails using the 'rails' command (rails version: #{version})" - run "rails #{test_app_dir}" - end - - # get the database config and schema in place - out.puts " writing database.yml" - require 'yaml' - File.open(File.join(test_app_dir, 'config', 'database.yml'), 'w') do |f| - f.write(%w(development test).inject({}) do |h, env| - h[env] = {"adapter" => "sqlite3", "database" => "engines_#{env}.sqlite3"} ; h - end.to_yaml) - end - out.puts " installing exception_notification plugin" - run "cd #{test_app_dir} && ./script/plugin install git://github.com/rails/exception_notification.git" - end - end - - # We can't link the plugin, as it needs to be present for script/generate to find - # the plugin generator. - # TODO: find and +1/create issue for loading generators from symlinked plugins - desc 'Mirror the engines plugin into the test application' - task :copy_engines_plugin do - puts "> Copying engines plugin into test application" - engines_plugin = File.join(test_app_dir, "vendor", "plugins", "engines") - FileUtils.rm_r(engines_plugin) if File.exist?(engines_plugin) - FileUtils.mkdir_p(engines_plugin) - FileList["*"].exclude("test_app").each do |file| - FileUtils.cp_r(file, engines_plugin) - end - end - - def insert_line(line, options) - line = line + "\n" - target_file = File.join(test_app_dir, options[:into]) - lines = File.readlines(target_file) - return if lines.include?(line) - - if options[:after] - if options[:after].is_a?(String) - after_line = options[:after] + "\n" - else - after_line = lines.find { |l| l =~ options[:after] } - raise "couldn't find a line matching #{options[:after].inspect} in #{target_file}" unless after_line - end - index = lines.index(after_line) - raise "couldn't find line '#{after_line}' in #{target_file}" unless index - lines.insert(index + 1, line) - else - lines << line - end - File.open(target_file, 'w') { |f| f.write lines.join } - end - - def mirror_test_files(src, dest=nil) - destination_dir = File.join(*([test_app_dir, dest].compact)) - FileUtils.cp_r(File.join(File.dirname(__FILE__), 'test', src), destination_dir) - end - - desc 'Update the plugin and tests files in the test application from the plugin' - task :mirror_engine_files => [:test_app, :copy_engines_plugin] do - puts "> Tweaking generated application to be suitable for testing" - - # Replace the Rails plugin loader with the engines one. - insert_line("require File.join(File.dirname(__FILE__), '../vendor/plugins/engines/boot')", - :into => 'config/environment.rb', - :after => "require File.join(File.dirname(__FILE__), 'boot')") - - # Add the engines test helper to handle fixtures & stuff. - insert_line("require 'engines_test_helper'", :into => 'test/test_helper.rb') - - # Run engine plugin tests when running the application - insert_line("task :test => ['test:engines:all']", :into => 'Rakefile') - - # We want exceptions to be raised - insert_line("def rescue_action(e) raise e end;", - :into => "app/controllers/application_controller.rb", - :after => "class ApplicationController < ActionController::Base") - - # We need this method to test where actions are being rendered from. - insert_line("include RenderInformation", - :into => "app/controllers/application_controller.rb", - :after => "class ApplicationController < ActionController::Base") - - puts "> Mirroring test application files into #{test_app_dir}" - mirror_test_files('app') - mirror_test_files('lib') - mirror_test_files('plugins', 'vendor') - mirror_test_files('unit', 'test') - mirror_test_files('functional', 'test') - end - - desc 'Prepare the engines test environment' - task :test_app do - version_tag = File.join(test_app_dir, 'RAILS_VERSION') - existing_version = File.read(version_tag).chomp rescue 'unknown' - if existing_version == ENV['RAILS'] - puts "> Reusing existing test application (#{ENV['RAILS']})" - else - puts "> Recreating test application" - Rake::Task["test:clean"].invoke - Rake::Task["test:generate_app"].invoke - - File.open(version_tag, "w") { |f| f.write ENV['RAILS'] } - end - end -end - -task :test => "test:mirror_engine_files" do - puts "> Loading the test application environment and running tests" - # We use exec here to replace the current running rake process - exec("cd #{test_app_dir} && rake db:migrate && rake") -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/78/78a1de16a7f51fffc3afacfeda93104b299506dc.svn-base --- a/.svn/pristine/78/78a1de16a7f51fffc3afacfeda93104b299506dc.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,323 +0,0 @@ -# encoding: utf-8 -require 'strscan' - -module CodeRay - - autoload :WordList, 'coderay/helpers/word_list' - - # = Scanners - # - # This module holds the Scanner class and its subclasses. - # For example, the Ruby scanner is named CodeRay::Scanners::Ruby - # can be found in coderay/scanners/ruby. - # - # Scanner also provides methods and constants for the register - # mechanism and the [] method that returns the Scanner class - # belonging to the given lang. - # - # See PluginHost. - module Scanners - extend PluginHost - plugin_path File.dirname(__FILE__), 'scanners' - - - # = Scanner - # - # The base class for all Scanners. - # - # It is a subclass of Ruby's great +StringScanner+, which - # makes it easy to access the scanning methods inside. - # - # It is also +Enumerable+, so you can use it like an Array of - # Tokens: - # - # require 'coderay' - # - # c_scanner = CodeRay::Scanners[:c].new "if (*p == '{') nest++;" - # - # for text, kind in c_scanner - # puts text if kind == :operator - # end - # - # # prints: (*==)++; - # - # OK, this is a very simple example :) - # You can also use +map+, +any?+, +find+ and even +sort_by+, - # if you want. - class Scanner < StringScanner - - extend Plugin - plugin_host Scanners - - # Raised if a Scanner fails while scanning - ScanError = Class.new StandardError - - # The default options for all scanner classes. - # - # Define @default_options for subclasses. - DEFAULT_OPTIONS = { } - - KINDS_NOT_LOC = [:comment, :doctype, :docstring] - - attr_accessor :state - - class << self - - # Normalizes the given code into a string with UNIX newlines, in the - # scanner's internal encoding, with invalid and undefined charachters - # replaced by placeholders. Always returns a new object. - def normalize code - # original = code - code = code.to_s unless code.is_a? ::String - return code if code.empty? - - if code.respond_to? :encoding - code = encode_with_encoding code, self.encoding - else - code = to_unix code - end - # code = code.dup if code.eql? original - code - end - - # The typical filename suffix for this scanner's language. - def file_extension extension = lang - @file_extension ||= extension.to_s - end - - # The encoding used internally by this scanner. - def encoding name = 'UTF-8' - @encoding ||= defined?(Encoding.find) && Encoding.find(name) - end - - # The lang of this Scanner class, which is equal to its Plugin ID. - def lang - @plugin_id - end - - protected - - def encode_with_encoding code, target_encoding - if code.encoding == target_encoding - if code.valid_encoding? - return to_unix(code) - else - source_encoding = guess_encoding code - end - else - source_encoding = code.encoding - end - # print "encode_with_encoding from #{source_encoding} to #{target_encoding}" - code.encode target_encoding, source_encoding, :universal_newline => true, :undef => :replace, :invalid => :replace - end - - def to_unix code - code.index(?\r) ? code.gsub(/\r\n?/, "\n") : code - end - - def guess_encoding s - #:nocov: - IO.popen("file -b --mime -", "w+") do |file| - file.write s[0, 1024] - file.close_write - begin - Encoding.find file.gets[/charset=([-\w]+)/, 1] - rescue ArgumentError - Encoding::BINARY - end - end - #:nocov: - end - - end - - # Create a new Scanner. - # - # * +code+ is the input String and is handled by the superclass - # StringScanner. - # * +options+ is a Hash with Symbols as keys. - # It is merged with the default options of the class (you can - # overwrite default options here.) - # - # Else, a Tokens object is used. - def initialize code = '', options = {} - if self.class == Scanner - raise NotImplementedError, "I am only the basic Scanner class. I can't scan anything. :( Use my subclasses." - end - - @options = self.class::DEFAULT_OPTIONS.merge options - - super self.class.normalize(code) - - @tokens = options[:tokens] || Tokens.new - @tokens.scanner = self if @tokens.respond_to? :scanner= - - setup - end - - # Sets back the scanner. Subclasses should redefine the reset_instance - # method instead of this one. - def reset - super - reset_instance - end - - # Set a new string to be scanned. - def string= code - code = self.class.normalize(code) - super code - reset_instance - end - - # the Plugin ID for this scanner - def lang - self.class.lang - end - - # the default file extension for this scanner - def file_extension - self.class.file_extension - end - - # Scan the code and returns all tokens in a Tokens object. - def tokenize source = nil, options = {} - options = @options.merge(options) - @tokens = options[:tokens] || @tokens || Tokens.new - @tokens.scanner = self if @tokens.respond_to? :scanner= - case source - when Array - self.string = self.class.normalize(source.join) - when nil - reset - else - self.string = self.class.normalize(source) - end - - begin - scan_tokens @tokens, options - rescue => e - message = "Error in %s#scan_tokens, initial state was: %p" % [self.class, defined?(state) && state] - raise_inspect e.message, @tokens, message, 30, e.backtrace - end - - @cached_tokens = @tokens - if source.is_a? Array - @tokens.split_into_parts(*source.map { |part| part.size }) - else - @tokens - end - end - - # Cache the result of tokenize. - def tokens - @cached_tokens ||= tokenize - end - - # Traverse the tokens. - def each &block - tokens.each(&block) - end - include Enumerable - - # The current line position of the scanner, starting with 1. - # See also: #column. - # - # Beware, this is implemented inefficiently. It should be used - # for debugging only. - def line pos = self.pos - return 1 if pos <= 0 - binary_string[0...pos].count("\n") + 1 - end - - # The current column position of the scanner, starting with 1. - # See also: #line. - def column pos = self.pos - return 1 if pos <= 0 - pos - (binary_string.rindex(?\n, pos - 1) || -1) - end - - # The string in binary encoding. - # - # To be used with #pos, which is the index of the byte the scanner - # will scan next. - def binary_string - @binary_string ||= - if string.respond_to?(:bytesize) && string.bytesize != string.size - #:nocov: - string.dup.force_encoding('binary') - #:nocov: - else - string - end - end - - protected - - # Can be implemented by subclasses to do some initialization - # that has to be done once per instance. - # - # Use reset for initialization that has to be done once per - # scan. - def setup # :doc: - end - - # This is the central method, and commonly the only one a - # subclass implements. - # - # Subclasses must implement this method; it must return +tokens+ - # and must only use Tokens#<< for storing scanned tokens! - def scan_tokens tokens, options # :doc: - raise NotImplementedError, "#{self.class}#scan_tokens not implemented." - end - - # Resets the scanner. - def reset_instance - @tokens.clear if @tokens.respond_to?(:clear) && !@options[:keep_tokens] - @cached_tokens = nil - @binary_string = nil if defined? @binary_string - end - - # Scanner error with additional status information - def raise_inspect msg, tokens, state = self.state || 'No state given!', ambit = 30, backtrace = caller - raise ScanError, <<-EOE % [ - - -***ERROR in %s: %s (after %d tokens) - -tokens: -%s - -current line: %d column: %d pos: %d -matched: %p state: %p -bol? = %p, eos? = %p - -surrounding code: -%p ~~ %p - - -***ERROR*** - - EOE - File.basename(caller[0]), - msg, - tokens.respond_to?(:size) ? tokens.size : 0, - tokens.respond_to?(:last) ? tokens.last(10).map { |t| t.inspect }.join("\n") : '', - line, column, pos, - matched, state, bol?, eos?, - binary_string[pos - ambit, ambit], - binary_string[pos, ambit], - ], backtrace - end - - # Shorthand for scan_until(/\z/). - # This method also avoids a JRuby 1.9 mode bug. - def scan_rest - rest = self.rest - terminate - rest - end - - end - - end -end \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/78/78ae413198b7dc2195c980bf2f03968e4d50e683.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/78/78ae413198b7dc2195c980bf2f03968e4d50e683.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,3898 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class IssuesControllerTest < ActionController::TestCase + fixtures :projects, + :users, + :roles, + :members, + :member_roles, + :issues, + :issue_statuses, + :versions, + :trackers, + :projects_trackers, + :issue_categories, + :enabled_modules, + :enumerations, + :attachments, + :workflows, + :custom_fields, + :custom_values, + :custom_fields_projects, + :custom_fields_trackers, + :time_entries, + :journals, + :journal_details, + :queries, + :repositories, + :changesets + + include Redmine::I18n + + def setup + User.current = nil + end + + def test_index + with_settings :default_language => "en" do + get :index + assert_response :success + assert_template 'index' + assert_not_nil assigns(:issues) + assert_nil assigns(:project) + + # links to visible issues + assert_select 'a[href=/issues/1]', :text => /Can't print recipes/ + assert_select 'a[href=/issues/5]', :text => /Subproject issue/ + # private projects hidden + assert_select 'a[href=/issues/6]', 0 + assert_select 'a[href=/issues/4]', 0 + # project column + assert_select 'th', :text => /Project/ + end + 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' + assert_not_nil assigns(:issues) + assert_nil assigns(:project) + + assert_select 'a[href=/issues/1]', 0 + assert_select 'a[href=/issues/5]', :text => /Subproject issue/ + end + + def test_index_should_list_visible_issues_only + get :index, :per_page => 100 + assert_response :success + assert_not_nil assigns(:issues) + assert_nil assigns(:issues).detect {|issue| !issue.visible?} + end + + def test_index_with_project + Setting.display_subprojects_issues = 0 + get :index, :project_id => 1 + assert_response :success + assert_template 'index' + assert_not_nil assigns(:issues) + + assert_select 'a[href=/issues/1]', :text => /Can't print recipes/ + assert_select 'a[href=/issues/5]', 0 + end + + def test_index_with_project_and_subprojects + Setting.display_subprojects_issues = 1 + get :index, :project_id => 1 + assert_response :success + assert_template 'index' + assert_not_nil assigns(:issues) + + assert_select 'a[href=/issues/1]', :text => /Can't print recipes/ + assert_select 'a[href=/issues/5]', :text => /Subproject issue/ + assert_select 'a[href=/issues/6]', 0 + end + + def test_index_with_project_and_subprojects_should_show_private_subprojects_with_permission + @request.session[:user_id] = 2 + Setting.display_subprojects_issues = 1 + get :index, :project_id => 1 + assert_response :success + assert_template 'index' + assert_not_nil assigns(:issues) + + assert_select 'a[href=/issues/1]', :text => /Can't print recipes/ + assert_select 'a[href=/issues/5]', :text => /Subproject issue/ + assert_select 'a[href=/issues/6]', :text => /Issue of a private subproject/ + end + + def test_index_with_project_and_default_filter + get :index, :project_id => 1, :set_filter => 1 + assert_response :success + assert_template 'index' + assert_not_nil assigns(:issues) + + query = assigns(:query) + assert_not_nil query + # default filter + assert_equal({'status_id' => {:operator => 'o', :values => ['']}}, query.filters) + end + + def test_index_with_project_and_filter + get :index, :project_id => 1, :set_filter => 1, + :f => ['tracker_id'], + :op => {'tracker_id' => '='}, + :v => {'tracker_id' => ['1']} + assert_response :success + assert_template 'index' + assert_not_nil assigns(:issues) + + query = assigns(:query) + assert_not_nil query + assert_equal({'tracker_id' => {:operator => '=', :values => ['1']}}, query.filters) + end + + def test_index_with_short_filters + to_test = { + 'status_id' => { + 'o' => { :op => 'o', :values => [''] }, + 'c' => { :op => 'c', :values => [''] }, + '7' => { :op => '=', :values => ['7'] }, + '7|3|4' => { :op => '=', :values => ['7', '3', '4'] }, + '=7' => { :op => '=', :values => ['7'] }, + '!3' => { :op => '!', :values => ['3'] }, + '!7|3|4' => { :op => '!', :values => ['7', '3', '4'] }}, + 'subject' => { + 'This is a subject' => { :op => '=', :values => ['This is a subject'] }, + 'o' => { :op => '=', :values => ['o'] }, + '~This is part of a subject' => { :op => '~', :values => ['This is part of a subject'] }, + '!~This is part of a subject' => { :op => '!~', :values => ['This is part of a subject'] }}, + 'tracker_id' => { + '3' => { :op => '=', :values => ['3'] }, + '=3' => { :op => '=', :values => ['3'] }}, + 'start_date' => { + '2011-10-12' => { :op => '=', :values => ['2011-10-12'] }, + '=2011-10-12' => { :op => '=', :values => ['2011-10-12'] }, + '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] }, + '<=2011-10-12' => { :op => '<=', :values => ['2011-10-12'] }, + '><2011-10-01|2011-10-30' => { :op => '><', :values => ['2011-10-01', '2011-10-30'] }, + ' { :op => ' ['2'] }, + '>t+2' => { :op => '>t+', :values => ['2'] }, + 't+2' => { :op => 't+', :values => ['2'] }, + 't' => { :op => 't', :values => [''] }, + 'w' => { :op => 'w', :values => [''] }, + '>t-2' => { :op => '>t-', :values => ['2'] }, + ' { :op => ' ['2'] }, + 't-2' => { :op => 't-', :values => ['2'] }}, + 'created_on' => { + '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] }, + ' { :op => ' ['2'] }, + '>t-2' => { :op => '>t-', :values => ['2'] }, + 't-2' => { :op => 't-', :values => ['2'] }}, + 'cf_1' => { + 'c' => { :op => '=', :values => ['c'] }, + '!c' => { :op => '!', :values => ['c'] }, + '!*' => { :op => '!*', :values => [''] }, + '*' => { :op => '*', :values => [''] }}, + 'estimated_hours' => { + '=13.4' => { :op => '=', :values => ['13.4'] }, + '>=45' => { :op => '>=', :values => ['45'] }, + '<=125' => { :op => '<=', :values => ['125'] }, + '><10.5|20.5' => { :op => '><', :values => ['10.5', '20.5'] }, + '!*' => { :op => '!*', :values => [''] }, + '*' => { :op => '*', :values => [''] }} + } + + default_filter = { 'status_id' => {:operator => 'o', :values => [''] }} + + to_test.each do |field, expression_and_expected| + expression_and_expected.each do |filter_expression, expected| + + get :index, :set_filter => 1, field => filter_expression + + assert_response :success + assert_template 'index' + assert_not_nil assigns(:issues) + + query = assigns(:query) + assert_not_nil query + assert query.has_filter?(field) + assert_equal(default_filter.merge({field => {:operator => expected[:op], :values => expected[:values]}}), query.filters) + end + end + end + + def test_index_with_project_and_empty_filters + get :index, :project_id => 1, :set_filter => 1, :fields => [''] + assert_response :success + assert_template 'index' + assert_not_nil assigns(:issues) + + query = assigns(:query) + assert_not_nil query + # no filter + assert_equal({}, query.filters) + end + + def test_index_with_project_custom_field_filter + field = ProjectCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string') + CustomValue.create!(:custom_field => field, :customized => Project.find(3), :value => 'Foo') + CustomValue.create!(:custom_field => field, :customized => Project.find(5), :value => 'Foo') + filter_name = "project.cf_#{field.id}" + @request.session[:user_id] = 1 + + get :index, :set_filter => 1, + :f => [filter_name], + :op => {filter_name => '='}, + :v => {filter_name => ['Foo']} + assert_response :success + assert_template 'index' + assert_equal [3, 5], assigns(:issues).map(&:project_id).uniq.sort + end + + def test_index_with_query + get :index, :project_id => 1, :query_id => 5 + assert_response :success + assert_template 'index' + assert_not_nil assigns(:issues) + assert_nil assigns(:issue_count_by_group) + end + + def test_index_with_query_grouped_by_tracker + get :index, :project_id => 1, :query_id => 6 + assert_response :success + assert_template 'index' + assert_not_nil assigns(:issues) + assert_not_nil assigns(:issue_count_by_group) + end + + def test_index_with_query_grouped_by_list_custom_field + get :index, :project_id => 1, :query_id => 9 + assert_response :success + assert_template 'index' + assert_not_nil assigns(:issues) + assert_not_nil assigns(:issue_count_by_group) + end + + def test_index_with_query_grouped_by_user_custom_field + cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'user') + CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '2') + CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '3') + CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '3') + CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '') + + get :index, :project_id => 1, :set_filter => 1, :group_by => "cf_#{cf.id}" + assert_response :success + + assert_select 'tr.group', 3 + assert_select 'tr.group' do + assert_select 'a', :text => 'John Smith' + assert_select 'span.count', :text => '1' + end + assert_select 'tr.group' do + assert_select 'a', :text => 'Dave Lopper' + assert_select 'span.count', :text => '2' + end + end + + def test_index_with_query_grouped_by_tracker + 3.times {|i| Issue.generate!(:tracker_id => (i + 1))} + + get :index, :set_filter => 1, :group_by => 'tracker', :sort => 'id:desc' + assert_response :success + + trackers = assigns(:issues).map(&:tracker).uniq + assert_equal [1, 2, 3], trackers.map(&:id) + end + + def test_index_with_query_grouped_by_tracker_in_reverse_order + 3.times {|i| Issue.generate!(:tracker_id => (i + 1))} + + get :index, :set_filter => 1, :group_by => 'tracker', :sort => 'id:desc,tracker:desc' + assert_response :success + + trackers = assigns(:issues).map(&:tracker).uniq + assert_equal [3, 2, 1], trackers.map(&:id) + end + + def test_index_with_query_id_and_project_id_should_set_session_query + get :index, :project_id => 1, :query_id => 4 + assert_response :success + assert_kind_of Hash, session[:query] + assert_equal 4, session[:query][:id] + assert_equal 1, session[:query][:project_id] + end + + def test_index_with_invalid_query_id_should_respond_404 + get :index, :project_id => 1, :query_id => 999 + assert_response 404 + end + + def test_index_with_cross_project_query_in_session_should_show_project_issues + q = IssueQuery.create!(:name => "test", :user_id => 2, :is_public => false, :project => nil) + @request.session[:query] = {:id => q.id, :project_id => 1} + + with_settings :display_subprojects_issues => '0' do + get :index, :project_id => 1 + end + assert_response :success + assert_not_nil assigns(:query) + assert_equal q.id, assigns(:query).id + assert_equal 1, assigns(:query).project_id + assert_equal [1], assigns(:issues).map(&:project_id).uniq + end + + def test_private_query_should_not_be_available_to_other_users + q = IssueQuery.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil) + @request.session[:user_id] = 3 + + get :index, :query_id => q.id + assert_response 403 + end + + def test_private_query_should_be_available_to_its_user + q = IssueQuery.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil) + @request.session[:user_id] = 2 + + get :index, :query_id => q.id + assert_response :success + end + + def test_public_query_should_be_available_to_other_users + q = IssueQuery.create!(:name => "private", :user => User.find(2), :is_public => true, :project => nil) + @request.session[:user_id] = 3 + + get :index, :query_id => q.id + assert_response :success + end + + def test_index_should_omit_page_param_in_export_links + get :index, :page => 2 + assert_response :success + assert_select 'a.atom[href=/issues.atom]' + assert_select 'a.csv[href=/issues.csv]' + assert_select 'a.pdf[href=/issues.pdf]' + assert_select 'form#csv-export-form[action=/issues.csv]' + end + + def test_index_csv + get :index, :format => 'csv' + assert_response :success + assert_not_nil assigns(:issues) + assert_equal 'text/csv; header=present', @response.content_type + assert @response.body.starts_with?("#,") + lines = @response.body.chomp.split("\n") + assert_equal assigns(:query).columns.size, lines[0].split(',').size + end + + def test_index_csv_with_project + get :index, :project_id => 1, :format => 'csv' + assert_response :success + assert_not_nil assigns(:issues) + assert_equal 'text/csv; header=present', @response.content_type + end + + def test_index_csv_with_description + Issue.generate!(:description => 'test_index_csv_with_description') + + with_settings :default_language => 'en' do + get :index, :format => 'csv', :description => '1' + assert_response :success + assert_not_nil assigns(:issues) + end + + assert_equal 'text/csv; header=present', response.content_type + headers = response.body.chomp.split("\n").first.split(',') + assert_include 'Description', headers + assert_include 'test_index_csv_with_description', response.body + end + + def test_index_csv_with_spent_time_column + issue = Issue.create!(:project_id => 1, :tracker_id => 1, :subject => 'test_index_csv_with_spent_time_column', :author_id => 2) + TimeEntry.create!(:project => issue.project, :issue => issue, :hours => 7.33, :user => User.find(2), :spent_on => Date.today) + + get :index, :format => 'csv', :set_filter => '1', :c => %w(subject spent_hours) + assert_response :success + assert_equal 'text/csv; header=present', @response.content_type + lines = @response.body.chomp.split("\n") + assert_include "#{issue.id},#{issue.subject},7.33", lines + end + + def test_index_csv_with_all_columns + get :index, :format => 'csv', :columns => 'all' + assert_response :success + assert_not_nil assigns(:issues) + assert_equal 'text/csv; header=present', @response.content_type + assert_match /\A#,/, response.body + lines = response.body.chomp.split("\n") + assert_equal assigns(:query).available_inline_columns.size, lines[0].split(',').size + end + + def test_index_csv_with_multi_column_field + CustomField.find(1).update_attribute :multiple, true + issue = Issue.find(1) + issue.custom_field_values = {1 => ['MySQL', 'Oracle']} + issue.save! + + get :index, :format => 'csv', :columns => 'all' + assert_response :success + lines = @response.body.chomp.split("\n") + assert lines.detect {|line| line.include?('"MySQL, Oracle"')} + end + + def test_index_csv_should_format_float_custom_fields_with_csv_decimal_separator + field = IssueCustomField.create!(:name => 'Float', :is_for_all => true, :tracker_ids => [1], :field_format => 'float') + issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id => '185.6'}) + + with_settings :default_language => 'fr' do + get :index, :format => 'csv', :columns => 'all' + assert_response :success + issue_line = response.body.chomp.split("\n").map {|line| line.split(';')}.detect {|line| line[0]==issue.id.to_s} + assert_include '185,60', issue_line + end + + with_settings :default_language => 'en' do + get :index, :format => 'csv', :columns => 'all' + assert_response :success + issue_line = response.body.chomp.split("\n").map {|line| line.split(',')}.detect {|line| line[0]==issue.id.to_s} + assert_include '185.60', issue_line + end + end + + def test_index_csv_big_5 + with_settings :default_language => "zh-TW" do + str_utf8 = "\xe4\xb8\x80\xe6\x9c\x88" + str_big5 = "\xa4@\xa4\xeb" + if str_utf8.respond_to?(:force_encoding) + str_utf8.force_encoding('UTF-8') + str_big5.force_encoding('Big5') + end + issue = Issue.generate!(:subject => str_utf8) + + get :index, :project_id => 1, + :f => ['subject'], + :op => '=', :values => [str_utf8], + :format => 'csv' + assert_equal 'text/csv; header=present', @response.content_type + lines = @response.body.chomp.split("\n") + s1 = "\xaa\xac\xbaA" + if str_utf8.respond_to?(:force_encoding) + s1.force_encoding('Big5') + end + assert_include s1, lines[0] + assert_include str_big5, lines[1] + end + end + + def test_index_csv_cannot_convert_should_be_replaced_big_5 + with_settings :default_language => "zh-TW" do + str_utf8 = "\xe4\xbb\xa5\xe5\x86\x85" + if str_utf8.respond_to?(:force_encoding) + str_utf8.force_encoding('UTF-8') + end + issue = Issue.generate!(:subject => str_utf8) + + get :index, :project_id => 1, + :f => ['subject'], + :op => '=', :values => [str_utf8], + :c => ['status', 'subject'], + :format => 'csv', + :set_filter => 1 + assert_equal 'text/csv; header=present', @response.content_type + lines = @response.body.chomp.split("\n") + s1 = "\xaa\xac\xbaA" # status + if str_utf8.respond_to?(:force_encoding) + s1.force_encoding('Big5') + end + assert lines[0].include?(s1) + s2 = lines[1].split(",")[2] + if s1.respond_to?(:force_encoding) + s3 = "\xa5H?" # subject + s3.force_encoding('Big5') + assert_equal s3, s2 + elsif RUBY_PLATFORM == 'java' + assert_equal "??", s2 + else + assert_equal "\xa5H???", s2 + end + end + end + + def test_index_csv_tw + with_settings :default_language => "zh-TW" do + str1 = "test_index_csv_tw" + issue = Issue.generate!(:subject => str1, :estimated_hours => '1234.5') + + get :index, :project_id => 1, + :f => ['subject'], + :op => '=', :values => [str1], + :c => ['estimated_hours', 'subject'], + :format => 'csv', + :set_filter => 1 + assert_equal 'text/csv; header=present', @response.content_type + lines = @response.body.chomp.split("\n") + assert_equal "#{issue.id},1234.50,#{str1}", lines[1] + end + end + + def test_index_csv_fr + with_settings :default_language => "fr" do + str1 = "test_index_csv_fr" + issue = Issue.generate!(:subject => str1, :estimated_hours => '1234.5') + + get :index, :project_id => 1, + :f => ['subject'], + :op => '=', :values => [str1], + :c => ['estimated_hours', 'subject'], + :format => 'csv', + :set_filter => 1 + assert_equal 'text/csv; header=present', @response.content_type + lines = @response.body.chomp.split("\n") + assert_equal "#{issue.id};1234,50;#{str1}", lines[1] + end + end + + def test_index_pdf + ["en", "zh", "zh-TW", "ja", "ko"].each do |lang| + with_settings :default_language => lang do + + get :index + assert_response :success + assert_template 'index' + + if lang == "ja" + if RUBY_PLATFORM != 'java' + assert_equal "CP932", l(:general_pdf_encoding) + end + if RUBY_PLATFORM == 'java' && l(:general_pdf_encoding) == "CP932" + next + end + end + + get :index, :format => 'pdf' + assert_response :success + assert_not_nil assigns(:issues) + assert_equal 'application/pdf', @response.content_type + + get :index, :project_id => 1, :format => 'pdf' + assert_response :success + assert_not_nil assigns(:issues) + assert_equal 'application/pdf', @response.content_type + + get :index, :project_id => 1, :query_id => 6, :format => 'pdf' + assert_response :success + assert_not_nil assigns(:issues) + assert_equal 'application/pdf', @response.content_type + end + end + end + + def test_index_pdf_with_query_grouped_by_list_custom_field + get :index, :project_id => 1, :query_id => 9, :format => 'pdf' + assert_response :success + assert_not_nil assigns(:issues) + assert_not_nil assigns(:issue_count_by_group) + assert_equal 'application/pdf', @response.content_type + end + + def test_index_atom + get :index, :project_id => 'ecookbook', :format => 'atom' + assert_response :success + assert_template 'common/feed' + assert_equal 'application/atom+xml', response.content_type + + assert_select 'feed' do + assert_select 'link[rel=self][href=?]', 'http://test.host/projects/ecookbook/issues.atom' + assert_select 'link[rel=alternate][href=?]', 'http://test.host/projects/ecookbook/issues' + assert_select 'entry link[href=?]', 'http://test.host/issues/1' + end + end + + def test_index_sort + get :index, :sort => 'tracker,id:desc' + assert_response :success + + sort_params = @request.session['issues_index_sort'] + assert sort_params.is_a?(String) + assert_equal 'tracker,id:desc', sort_params + + issues = assigns(:issues) + assert_not_nil issues + assert !issues.empty? + assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id) + end + + def test_index_sort_by_field_not_included_in_columns + Setting.issue_list_default_columns = %w(subject author) + get :index, :sort => 'tracker' + end + + def test_index_sort_by_assigned_to + get :index, :sort => 'assigned_to' + assert_response :success + assignees = assigns(:issues).collect(&:assigned_to).compact + assert_equal assignees.sort, assignees + end + + def test_index_sort_by_assigned_to_desc + get :index, :sort => 'assigned_to:desc' + assert_response :success + assignees = assigns(:issues).collect(&:assigned_to).compact + assert_equal assignees.sort.reverse, assignees + end + + def test_index_group_by_assigned_to + get :index, :group_by => 'assigned_to', :sort => 'priority' + assert_response :success + end + + def test_index_sort_by_author + get :index, :sort => 'author' + assert_response :success + authors = assigns(:issues).collect(&:author) + assert_equal authors.sort, authors + end + + def test_index_sort_by_author_desc + get :index, :sort => 'author:desc' + assert_response :success + authors = assigns(:issues).collect(&:author) + assert_equal authors.sort.reverse, authors + end + + def test_index_group_by_author + get :index, :group_by => 'author', :sort => 'priority' + assert_response :success + end + + def test_index_sort_by_spent_hours + get :index, :sort => 'spent_hours:desc' + assert_response :success + hours = assigns(:issues).collect(&:spent_hours) + assert_equal hours.sort.reverse, hours + end + + def test_index_sort_by_user_custom_field + cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'user') + CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '2') + CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '3') + CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '3') + CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '') + + get :index, :project_id => 1, :set_filter => 1, :sort => "cf_#{cf.id},id" + assert_response :success + + assert_equal [2, 3, 1], assigns(:issues).select {|issue| issue.custom_field_value(cf).present?}.map(&:id) + end + + def test_index_with_columns + columns = ['tracker', 'subject', 'assigned_to'] + get :index, :set_filter => 1, :c => columns + assert_response :success + + # query should use specified columns + query = assigns(:query) + assert_kind_of IssueQuery, query + assert_equal columns, query.column_names.map(&:to_s) + + # columns should be stored in session + assert_kind_of Hash, session[:query] + assert_kind_of Array, session[:query][:column_names] + assert_equal columns, session[:query][:column_names].map(&:to_s) + + # ensure only these columns are kept in the selected columns list + assert_select 'select#selected_columns option' do + assert_select 'option', 3 + assert_select 'option[value=tracker]' + assert_select 'option[value=project]', 0 + end + end + + def test_index_without_project_should_implicitly_add_project_column_to_default_columns + Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to'] + get :index, :set_filter => 1 + + # query should use specified columns + query = assigns(:query) + assert_kind_of IssueQuery, query + assert_equal [:id, :project, :tracker, :subject, :assigned_to], query.columns.map(&:name) + end + + def test_index_without_project_and_explicit_default_columns_should_not_add_project_column + Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to'] + columns = ['id', 'tracker', 'subject', 'assigned_to'] + get :index, :set_filter => 1, :c => columns + + # query should use specified columns + query = assigns(:query) + assert_kind_of IssueQuery, query + assert_equal columns.map(&:to_sym), query.columns.map(&:name) + end + + def test_index_with_custom_field_column + columns = %w(tracker subject cf_2) + get :index, :set_filter => 1, :c => columns + assert_response :success + + # query should use specified columns + query = assigns(:query) + assert_kind_of IssueQuery, query + assert_equal columns, query.column_names.map(&:to_s) + + assert_select 'table.issues td.cf_2.string' + end + + def test_index_with_multi_custom_field_column + field = CustomField.find(1) + field.update_attribute :multiple, true + issue = Issue.find(1) + issue.custom_field_values = {1 => ['MySQL', 'Oracle']} + issue.save! + + get :index, :set_filter => 1, :c => %w(tracker subject cf_1) + assert_response :success + + assert_select 'table.issues td.cf_1', :text => 'MySQL, Oracle' + end + + def test_index_with_multi_user_custom_field_column + field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true, + :tracker_ids => [1], :is_for_all => true) + issue = Issue.find(1) + issue.custom_field_values = {field.id => ['2', '3']} + issue.save! + + get :index, :set_filter => 1, :c => ['tracker', 'subject', "cf_#{field.id}"] + assert_response :success + + assert_select "table.issues td.cf_#{field.id}" do + assert_select 'a', 2 + assert_select 'a[href=?]', '/users/2', :text => 'John Smith' + assert_select 'a[href=?]', '/users/3', :text => 'Dave Lopper' + end + end + + def test_index_with_date_column + with_settings :date_format => '%d/%m/%Y' do + Issue.find(1).update_attribute :start_date, '1987-08-24' + + get :index, :set_filter => 1, :c => %w(start_date) + + assert_select "table.issues td.start_date", :text => '24/08/1987' + end + end + + def test_index_with_done_ratio_column + Issue.find(1).update_attribute :done_ratio, 40 + + get :index, :set_filter => 1, :c => %w(done_ratio) + + assert_select 'table.issues td.done_ratio' do + assert_select 'table.progress' do + assert_select 'td.closed[style=?]', 'width: 40%;' + end + end + end + + def test_index_with_spent_hours_column + get :index, :set_filter => 1, :c => %w(subject spent_hours) + + assert_select 'table.issues tr#issue-3 td.spent_hours', :text => '1.00' + end + + def test_index_should_not_show_spent_hours_column_without_permission + Role.anonymous.remove_permission! :view_time_entries + get :index, :set_filter => 1, :c => %w(subject spent_hours) + + assert_select 'td.spent_hours', 0 + end + + def test_index_with_fixed_version_column + get :index, :set_filter => 1, :c => %w(fixed_version) + + assert_select 'table.issues td.fixed_version' do + assert_select 'a[href=?]', '/versions/2', :text => '1.0' + end + end + + def test_index_with_relations_column + IssueRelation.delete_all + IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(7)) + IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(8), :issue_to => Issue.find(1)) + IssueRelation.create!(:relation_type => "blocks", :issue_from => Issue.find(1), :issue_to => Issue.find(11)) + IssueRelation.create!(:relation_type => "blocks", :issue_from => Issue.find(12), :issue_to => Issue.find(2)) + + get :index, :set_filter => 1, :c => %w(subject relations) + assert_response :success + assert_select "tr#issue-1 td.relations" do + assert_select "span", 3 + assert_select "span", :text => "Related to #7" + assert_select "span", :text => "Related to #8" + assert_select "span", :text => "Blocks #11" + end + assert_select "tr#issue-2 td.relations" do + assert_select "span", 1 + assert_select "span", :text => "Blocked by #12" + end + assert_select "tr#issue-3 td.relations" do + assert_select "span", 0 + end + + get :index, :set_filter => 1, :c => %w(relations), :format => 'csv' + assert_response :success + assert_equal 'text/csv; header=present', response.content_type + lines = response.body.chomp.split("\n") + assert_include '1,"Related to #7, Related to #8, Blocks #11"', lines + assert_include '2,Blocked by #12', lines + assert_include '3,""', lines + + get :index, :set_filter => 1, :c => %w(subject relations), :format => 'pdf' + assert_response :success + assert_equal 'application/pdf', response.content_type + end + + def test_index_with_description_column + get :index, :set_filter => 1, :c => %w(subject description) + + assert_select 'table.issues thead th', 3 # columns: chekbox + id + subject + assert_select 'td.description[colspan=3]', :text => 'Unable to print recipes' + + get :index, :set_filter => 1, :c => %w(subject description), :format => 'pdf' + assert_response :success + assert_equal 'application/pdf', response.content_type + end + + def test_index_send_html_if_query_is_invalid + get :index, :f => ['start_date'], :op => {:start_date => '='} + assert_equal 'text/html', @response.content_type + assert_template 'index' + end + + def test_index_send_nothing_if_query_is_invalid + get :index, :f => ['start_date'], :op => {:start_date => '='}, :format => 'csv' + assert_equal 'text/csv', @response.content_type + assert @response.body.blank? + end + + def test_show_by_anonymous + get :show, :id => 1 + assert_response :success + assert_template 'show' + assert_equal Issue.find(1), assigns(:issue) + + assert_select 'div.issue div.description', :text => /Unable to print recipes/ + + # anonymous role is allowed to add a note + assert_select 'form#issue-form' do + assert_select 'fieldset' do + assert_select 'legend', :text => 'Notes' + assert_select 'textarea[name=?]', 'issue[notes]' + end + end + + assert_select 'title', :text => "Bug #1: Can't print recipes - eCookbook - Redmine" + end + + def test_show_by_manager + @request.session[:user_id] = 2 + get :show, :id => 1 + assert_response :success + + assert_select 'a', :text => /Quote/ + + assert_select 'form#issue-form' do + assert_select 'fieldset' do + assert_select 'legend', :text => 'Change properties' + assert_select 'input[name=?]', 'issue[subject]' + end + assert_select 'fieldset' do + assert_select 'legend', :text => 'Log time' + assert_select 'input[name=?]', 'time_entry[hours]' + end + assert_select 'fieldset' do + assert_select 'legend', :text => 'Notes' + assert_select 'textarea[name=?]', 'issue[notes]' + end + end + end + + def test_show_should_display_update_form + @request.session[:user_id] = 2 + get :show, :id => 1 + assert_response :success + + assert_select 'form#issue-form' do + assert_select 'input[name=?]', 'issue[is_private]' + assert_select 'select[name=?]', 'issue[project_id]' + assert_select 'select[name=?]', 'issue[tracker_id]' + assert_select 'input[name=?]', 'issue[subject]' + assert_select 'textarea[name=?]', 'issue[description]' + assert_select 'select[name=?]', 'issue[status_id]' + assert_select 'select[name=?]', 'issue[priority_id]' + assert_select 'select[name=?]', 'issue[assigned_to_id]' + assert_select 'select[name=?]', 'issue[category_id]' + assert_select 'select[name=?]', 'issue[fixed_version_id]' + assert_select 'input[name=?]', 'issue[parent_issue_id]' + assert_select 'input[name=?]', 'issue[start_date]' + assert_select 'input[name=?]', 'issue[due_date]' + assert_select 'select[name=?]', 'issue[done_ratio]' + assert_select 'input[name=?]', 'issue[custom_field_values][2]' + assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0 + assert_select 'textarea[name=?]', 'issue[notes]' + end + end + + def test_show_should_display_update_form_with_minimal_permissions + Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes] + WorkflowTransition.delete_all :role_id => 1 + + @request.session[:user_id] = 2 + get :show, :id => 1 + assert_response :success + + assert_select 'form#issue-form' do + assert_select 'input[name=?]', 'issue[is_private]', 0 + assert_select 'select[name=?]', 'issue[project_id]', 0 + assert_select 'select[name=?]', 'issue[tracker_id]', 0 + assert_select 'input[name=?]', 'issue[subject]', 0 + assert_select 'textarea[name=?]', 'issue[description]', 0 + assert_select 'select[name=?]', 'issue[status_id]', 0 + assert_select 'select[name=?]', 'issue[priority_id]', 0 + assert_select 'select[name=?]', 'issue[assigned_to_id]', 0 + assert_select 'select[name=?]', 'issue[category_id]', 0 + assert_select 'select[name=?]', 'issue[fixed_version_id]', 0 + assert_select 'input[name=?]', 'issue[parent_issue_id]', 0 + assert_select 'input[name=?]', 'issue[start_date]', 0 + assert_select 'input[name=?]', 'issue[due_date]', 0 + assert_select 'select[name=?]', 'issue[done_ratio]', 0 + assert_select 'input[name=?]', 'issue[custom_field_values][2]', 0 + assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0 + assert_select 'textarea[name=?]', 'issue[notes]' + end + end + + def test_show_should_display_update_form_with_workflow_permissions + Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes] + + @request.session[:user_id] = 2 + get :show, :id => 1 + assert_response :success + + assert_select 'form#issue-form' do + assert_select 'input[name=?]', 'issue[is_private]', 0 + assert_select 'select[name=?]', 'issue[project_id]', 0 + assert_select 'select[name=?]', 'issue[tracker_id]', 0 + assert_select 'input[name=?]', 'issue[subject]', 0 + assert_select 'textarea[name=?]', 'issue[description]', 0 + assert_select 'select[name=?]', 'issue[status_id]' + assert_select 'select[name=?]', 'issue[priority_id]', 0 + assert_select 'select[name=?]', 'issue[assigned_to_id]' + assert_select 'select[name=?]', 'issue[category_id]', 0 + assert_select 'select[name=?]', 'issue[fixed_version_id]' + assert_select 'input[name=?]', 'issue[parent_issue_id]', 0 + assert_select 'input[name=?]', 'issue[start_date]', 0 + assert_select 'input[name=?]', 'issue[due_date]', 0 + assert_select 'select[name=?]', 'issue[done_ratio]' + assert_select 'input[name=?]', 'issue[custom_field_values][2]', 0 + assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0 + assert_select 'textarea[name=?]', 'issue[notes]' + end + end + + def test_show_should_not_display_update_form_without_permissions + Role.find(1).update_attribute :permissions, [:view_issues] + + @request.session[:user_id] = 2 + get :show, :id => 1 + assert_response :success + + assert_select 'form#issue-form', 0 + end + + def test_update_form_should_not_display_inactive_enumerations + assert !IssuePriority.find(15).active? + + @request.session[:user_id] = 2 + get :show, :id => 1 + assert_response :success + + assert_select 'form#issue-form' do + assert_select 'select[name=?]', 'issue[priority_id]' do + assert_select 'option[value=4]' + assert_select 'option[value=15]', 0 + end + end + end + + def test_update_form_should_allow_attachment_upload + @request.session[:user_id] = 2 + get :show, :id => 1 + + assert_select 'form#issue-form[method=post][enctype=multipart/form-data]' do + assert_select 'input[type=file][name=?]', 'attachments[dummy][file]' + end + end + + def test_show_should_deny_anonymous_access_without_permission + Role.anonymous.remove_permission!(:view_issues) + get :show, :id => 1 + assert_response :redirect + end + + def test_show_should_deny_anonymous_access_to_private_issue + Issue.update_all(["is_private = ?", true], "id = 1") + get :show, :id => 1 + assert_response :redirect + end + + def test_show_should_deny_non_member_access_without_permission + Role.non_member.remove_permission!(:view_issues) + @request.session[:user_id] = 9 + get :show, :id => 1 + assert_response 403 + end + + def test_show_should_deny_non_member_access_to_private_issue + Issue.update_all(["is_private = ?", true], "id = 1") + @request.session[:user_id] = 9 + get :show, :id => 1 + assert_response 403 + end + + def test_show_should_deny_member_access_without_permission + Role.find(1).remove_permission!(:view_issues) + @request.session[:user_id] = 2 + get :show, :id => 1 + assert_response 403 + end + + def test_show_should_deny_member_access_to_private_issue_without_permission + Issue.update_all(["is_private = ?", true], "id = 1") + @request.session[:user_id] = 3 + get :show, :id => 1 + assert_response 403 + end + + def test_show_should_allow_author_access_to_private_issue + Issue.update_all(["is_private = ?, author_id = 3", true], "id = 1") + @request.session[:user_id] = 3 + get :show, :id => 1 + assert_response :success + end + + def test_show_should_allow_assignee_access_to_private_issue + Issue.update_all(["is_private = ?, assigned_to_id = 3", true], "id = 1") + @request.session[:user_id] = 3 + get :show, :id => 1 + assert_response :success + end + + def test_show_should_allow_member_access_to_private_issue_with_permission + Issue.update_all(["is_private = ?", true], "id = 1") + User.find(3).roles_for_project(Project.find(1)).first.update_attribute :issues_visibility, 'all' + @request.session[:user_id] = 3 + get :show, :id => 1 + assert_response :success + 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_select 'div#relations' do + assert_select 'a', :text => /#2$/ + assert_select 'a', :text => /#4$/, :count => 0 + end + end + + def test_show_should_list_subtasks + Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue') + + get :show, :id => 1 + assert_response :success + + assert_select 'div#issue_tree' do + assert_select 'td.subject', :text => /Child Issue/ + end + end + + def test_show_should_list_parents + issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue') + + get :show, :id => issue.id + assert_response :success + + assert_select 'div.subject' do + assert_select 'h3', 'Child Issue' + assert_select 'a[href=/issues/1]' + end + end + + def test_show_should_not_display_prev_next_links_without_query_in_session + get :show, :id => 1 + assert_response :success + assert_nil assigns(:prev_issue_id) + assert_nil assigns(:next_issue_id) + + assert_select 'div.next-prev-links', 0 + end + + def test_show_should_display_prev_next_links_with_query_in_session + @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => nil} + @request.session['issues_index_sort'] = 'id' + + with_settings :display_subprojects_issues => '0' do + get :show, :id => 3 + end + + assert_response :success + # Previous and next issues for all projects + assert_equal 2, assigns(:prev_issue_id) + assert_equal 5, assigns(:next_issue_id) + + count = Issue.open.visible.count + + assert_select 'div.next-prev-links' do + assert_select 'a[href=/issues/2]', :text => /Previous/ + assert_select 'a[href=/issues/5]', :text => /Next/ + assert_select 'span.position', :text => "3 of #{count}" + end + end + + def test_show_should_display_prev_next_links_with_saved_query_in_session + query = IssueQuery.create!(:name => 'test', :is_public => true, :user_id => 1, + :filters => {'status_id' => {:values => ['5'], :operator => '='}}, + :sort_criteria => [['id', 'asc']]) + @request.session[:query] = {:id => query.id, :project_id => nil} + + get :show, :id => 11 + + assert_response :success + assert_equal query, assigns(:query) + # Previous and next issues for all projects + assert_equal 8, assigns(:prev_issue_id) + assert_equal 12, assigns(:next_issue_id) + + assert_select 'div.next-prev-links' do + assert_select 'a[href=/issues/8]', :text => /Previous/ + assert_select 'a[href=/issues/12]', :text => /Next/ + end + end + + def test_show_should_display_prev_next_links_with_query_and_sort_on_association + @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => nil} + + %w(project tracker status priority author assigned_to category fixed_version).each do |assoc_sort| + @request.session['issues_index_sort'] = assoc_sort + + get :show, :id => 3 + assert_response :success, "Wrong response status for #{assoc_sort} sort" + + assert_select 'div.next-prev-links' do + assert_select 'a', :text => /(Previous|Next)/ + end + end + end + + def test_show_should_display_prev_next_links_with_project_query_in_session + @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => 1} + @request.session['issues_index_sort'] = 'id' + + with_settings :display_subprojects_issues => '0' do + get :show, :id => 3 + end + + assert_response :success + # Previous and next issues inside project + assert_equal 2, assigns(:prev_issue_id) + assert_equal 7, assigns(:next_issue_id) + + assert_select 'div.next-prev-links' do + assert_select 'a[href=/issues/2]', :text => /Previous/ + assert_select 'a[href=/issues/7]', :text => /Next/ + end + end + + def test_show_should_not_display_prev_link_for_first_issue + @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => 1} + @request.session['issues_index_sort'] = 'id' + + with_settings :display_subprojects_issues => '0' do + get :show, :id => 1 + end + + assert_response :success + assert_nil assigns(:prev_issue_id) + assert_equal 2, assigns(:next_issue_id) + + assert_select 'div.next-prev-links' do + assert_select 'a', :text => /Previous/, :count => 0 + assert_select 'a[href=/issues/2]', :text => /Next/ + end + end + + def test_show_should_not_display_prev_next_links_for_issue_not_in_query_results + @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'c'}}, :project_id => 1} + @request.session['issues_index_sort'] = 'id' + + get :show, :id => 1 + + assert_response :success + assert_nil assigns(:prev_issue_id) + assert_nil assigns(:next_issue_id) + + assert_select 'a', :text => /Previous/, :count => 0 + assert_select 'a', :text => /Next/, :count => 0 + end + + def test_show_show_should_display_prev_next_links_with_query_sort_by_user_custom_field + cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'user') + CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '2') + CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '3') + CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '3') + CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '') + + query = IssueQuery.create!(:name => 'test', :is_public => true, :user_id => 1, :filters => {}, + :sort_criteria => [["cf_#{cf.id}", 'asc'], ['id', 'asc']]) + @request.session[:query] = {:id => query.id, :project_id => nil} + + get :show, :id => 3 + assert_response :success + + assert_equal 2, assigns(:prev_issue_id) + assert_equal 1, assigns(:next_issue_id) + + assert_select 'div.next-prev-links' do + assert_select 'a[href=/issues/2]', :text => /Previous/ + assert_select 'a[href=/issues/1]', :text => /Next/ + end + end + + def test_show_should_display_link_to_the_assignee + get :show, :id => 2 + assert_response :success + assert_select '.assigned-to' do + assert_select 'a[href=/users/3]' + end + end + + def test_show_should_display_visible_changesets_from_other_projects + project = Project.find(2) + issue = project.issues.first + issue.changeset_ids = [102] + issue.save! + # changesets from other projects should be displayed even if repository + # is disabled on issue's project + project.disable_module! :repository + + @request.session[:user_id] = 2 + get :show, :id => issue.id + + assert_select 'a[href=?]', '/projects/ecookbook/repository/revisions/3' + end + + def test_show_should_display_watchers + @request.session[:user_id] = 2 + Issue.find(1).add_watcher User.find(2) + + get :show, :id => 1 + assert_select 'div#watchers ul' do + assert_select 'li' do + assert_select 'a[href=/users/2]' + assert_select 'a img[alt=Delete]' + end + end + end + + def test_show_should_display_watchers_with_gravatars + @request.session[:user_id] = 2 + Issue.find(1).add_watcher User.find(2) + + with_settings :gravatar_enabled => '1' do + get :show, :id => 1 + end + + assert_select 'div#watchers ul' do + assert_select 'li' do + assert_select 'img.gravatar' + assert_select 'a[href=/users/2]' + assert_select 'a img[alt=Delete]' + end + end + end + + def test_show_with_thumbnails_enabled_should_display_thumbnails + @request.session[:user_id] = 2 + + with_settings :thumbnails_enabled => '1' do + get :show, :id => 14 + assert_response :success + end + + assert_select 'div.thumbnails' do + assert_select 'a[href=/attachments/16/testfile.png]' do + assert_select 'img[src=/attachments/thumbnail/16]' + end + end + end + + def test_show_with_thumbnails_disabled_should_not_display_thumbnails + @request.session[:user_id] = 2 + + with_settings :thumbnails_enabled => '0' do + get :show, :id => 14 + assert_response :success + end + + assert_select 'div.thumbnails', 0 + end + + def test_show_with_multi_custom_field + field = CustomField.find(1) + field.update_attribute :multiple, true + issue = Issue.find(1) + issue.custom_field_values = {1 => ['MySQL', 'Oracle']} + issue.save! + + get :show, :id => 1 + assert_response :success + + assert_select 'td', :text => 'MySQL, Oracle' + end + + def test_show_with_multi_user_custom_field + field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true, + :tracker_ids => [1], :is_for_all => true) + issue = Issue.find(1) + issue.custom_field_values = {field.id => ['2', '3']} + issue.save! + + get :show, :id => 1 + assert_response :success + + # TODO: should display links + assert_select 'td', :text => 'Dave Lopper, John Smith' + end + + def test_show_should_display_private_notes_with_permission_only + journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Privates notes', :private_notes => true, :user_id => 1) + @request.session[:user_id] = 2 + + get :show, :id => 2 + assert_response :success + assert_include journal, assigns(:journals) + + Role.find(1).remove_permission! :view_private_notes + get :show, :id => 2 + assert_response :success + assert_not_include journal, assigns(:journals) + end + + def test_show_atom + get :show, :id => 2, :format => 'atom' + assert_response :success + assert_template 'journals/index' + # Inline image + assert_select 'content', :text => Regexp.new(Regexp.quote('http://test.host/attachments/download/10')) + 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_show_export_to_pdf_with_ancestors + issue = Issue.generate!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => 1) + + get :show, :id => issue.id, :format => 'pdf' + assert_response :success + assert_equal 'application/pdf', @response.content_type + assert @response.body.starts_with?('%PDF') + end + + def test_show_export_to_pdf_with_descendants + c1 = Issue.generate!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => 1) + c2 = Issue.generate!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => 1) + c3 = Issue.generate!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => c1.id) + + get :show, :id => 1, :format => 'pdf' + assert_response :success + assert_equal 'application/pdf', @response.content_type + assert @response.body.starts_with?('%PDF') + end + + def test_show_export_to_pdf_with_journals + get :show, :id => 1, :format => 'pdf' + assert_response :success + assert_equal 'application/pdf', @response.content_type + assert @response.body.starts_with?('%PDF') + end + + def test_show_export_to_pdf_with_changesets + Issue.find(3).changesets = Changeset.find_all_by_id(100, 101, 102) + + get :show, :id => 3, :format => 'pdf' + assert_response :success + assert_equal 'application/pdf', @response.content_type + assert @response.body.starts_with?('%PDF') + end + + def test_show_invalid_should_respond_with_404 + get :show, :id => 999 + assert_response 404 + end + + def test_get_new + @request.session[:user_id] = 2 + get :new, :project_id => 1, :tracker_id => 1 + assert_response :success + assert_template 'new' + + assert_select 'form#issue-form' do + assert_select 'input[name=?]', 'issue[is_private]' + assert_select 'select[name=?]', 'issue[project_id]', 0 + assert_select 'select[name=?]', 'issue[tracker_id]' + assert_select 'input[name=?]', 'issue[subject]' + assert_select 'textarea[name=?]', 'issue[description]' + assert_select 'select[name=?]', 'issue[status_id]' + assert_select 'select[name=?]', 'issue[priority_id]' + assert_select 'select[name=?]', 'issue[assigned_to_id]' + assert_select 'select[name=?]', 'issue[category_id]' + assert_select 'select[name=?]', 'issue[fixed_version_id]' + assert_select 'input[name=?]', 'issue[parent_issue_id]' + assert_select 'input[name=?]', 'issue[start_date]' + assert_select 'input[name=?]', 'issue[due_date]' + assert_select 'select[name=?]', 'issue[done_ratio]' + assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Default string' + assert_select 'input[name=?]', 'issue[watcher_user_ids][]' + end + + # Be sure we don't display inactive IssuePriorities + assert ! IssuePriority.find(15).active? + assert_select 'select[name=?]', 'issue[priority_id]' do + assert_select 'option[value=15]', 0 + end + end + + def test_get_new_with_minimal_permissions + Role.find(1).update_attribute :permissions, [:add_issues] + WorkflowTransition.delete_all :role_id => 1 + + @request.session[:user_id] = 2 + get :new, :project_id => 1, :tracker_id => 1 + assert_response :success + assert_template 'new' + + assert_select 'form#issue-form' do + assert_select 'input[name=?]', 'issue[is_private]', 0 + assert_select 'select[name=?]', 'issue[project_id]', 0 + assert_select 'select[name=?]', 'issue[tracker_id]' + assert_select 'input[name=?]', 'issue[subject]' + assert_select 'textarea[name=?]', 'issue[description]' + assert_select 'select[name=?]', 'issue[status_id]' + assert_select 'select[name=?]', 'issue[priority_id]' + assert_select 'select[name=?]', 'issue[assigned_to_id]' + assert_select 'select[name=?]', 'issue[category_id]' + assert_select 'select[name=?]', 'issue[fixed_version_id]' + assert_select 'input[name=?]', 'issue[parent_issue_id]', 0 + assert_select 'input[name=?]', 'issue[start_date]' + assert_select 'input[name=?]', 'issue[due_date]' + assert_select 'select[name=?]', 'issue[done_ratio]' + assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Default string' + assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0 + end + end + + def test_get_new_with_list_custom_field + @request.session[:user_id] = 2 + get :new, :project_id => 1, :tracker_id => 1 + assert_response :success + assert_template 'new' + + assert_select 'select.list_cf[name=?]', 'issue[custom_field_values][1]' do + assert_select 'option', 4 + assert_select 'option[value=MySQL]', :text => 'MySQL' + end + end + + def test_get_new_with_multi_custom_field + field = IssueCustomField.find(1) + field.update_attribute :multiple, true + + @request.session[:user_id] = 2 + get :new, :project_id => 1, :tracker_id => 1 + assert_response :success + assert_template 'new' + + assert_select 'select[name=?][multiple=multiple]', 'issue[custom_field_values][1][]' do + assert_select 'option', 3 + assert_select 'option[value=MySQL]', :text => 'MySQL' + end + assert_select 'input[name=?][type=hidden][value=?]', 'issue[custom_field_values][1][]', '' + end + + def test_get_new_with_multi_user_custom_field + field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true, + :tracker_ids => [1], :is_for_all => true) + + @request.session[:user_id] = 2 + get :new, :project_id => 1, :tracker_id => 1 + assert_response :success + assert_template 'new' + + assert_select 'select[name=?][multiple=multiple]', "issue[custom_field_values][#{field.id}][]" do + assert_select 'option', Project.find(1).users.count + assert_select 'option[value=2]', :text => 'John Smith' + end + assert_select 'input[name=?][type=hidden][value=?]', "issue[custom_field_values][#{field.id}][]", '' + end + + def test_get_new_with_date_custom_field + field = IssueCustomField.create!(:name => 'Date', :field_format => 'date', :tracker_ids => [1], :is_for_all => true) + + @request.session[:user_id] = 2 + get :new, :project_id => 1, :tracker_id => 1 + assert_response :success + + assert_select 'input[name=?]', "issue[custom_field_values][#{field.id}]" + end + + def test_get_new_with_text_custom_field + field = IssueCustomField.create!(:name => 'Text', :field_format => 'text', :tracker_ids => [1], :is_for_all => true) + + @request.session[:user_id] = 2 + get :new, :project_id => 1, :tracker_id => 1 + assert_response :success + + assert_select 'textarea[name=?]', "issue[custom_field_values][#{field.id}]" + end + + def test_get_new_without_default_start_date_is_creation_date + Setting.default_issue_start_date_to_creation_date = 0 + + @request.session[:user_id] = 2 + get :new, :project_id => 1, :tracker_id => 1 + assert_response :success + assert_template 'new' + + assert_select 'input[name=?]', 'issue[start_date]' + assert_select 'input[name=?][value]', 'issue[start_date]', 0 + end + + def test_get_new_with_default_start_date_is_creation_date + Setting.default_issue_start_date_to_creation_date = 1 + + @request.session[:user_id] = 2 + get :new, :project_id => 1, :tracker_id => 1 + assert_response :success + assert_template 'new' + + assert_select 'input[name=?][value=?]', 'issue[start_date]', Date.today.to_s + end + + def test_get_new_form_should_allow_attachment_upload + @request.session[:user_id] = 2 + get :new, :project_id => 1, :tracker_id => 1 + + assert_select 'form[id=issue-form][method=post][enctype=multipart/form-data]' do + assert_select 'input[name=?][type=file]', 'attachments[dummy][file]' + end + end + + def test_get_new_should_prefill_the_form_from_params + @request.session[:user_id] = 2 + get :new, :project_id => 1, + :issue => {:tracker_id => 3, :description => 'Prefilled', :custom_field_values => {'2' => 'Custom field value'}} + + issue = assigns(:issue) + assert_equal 3, issue.tracker_id + assert_equal 'Prefilled', issue.description + assert_equal 'Custom field value', issue.custom_field_value(2) + + assert_select 'select[name=?]', 'issue[tracker_id]' do + assert_select 'option[value=3][selected=selected]' + end + assert_select 'textarea[name=?]', 'issue[description]', :text => /Prefilled/ + assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Custom field value' + end + + def test_get_new_should_mark_required_fields + cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2]) + cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2]) + WorkflowPermission.delete_all + WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'due_date', :rule => 'required') + WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'required') + @request.session[:user_id] = 2 + + get :new, :project_id => 1 + assert_response :success + assert_template 'new' + + assert_select 'label[for=issue_start_date]' do + assert_select 'span[class=required]', 0 + end + assert_select 'label[for=issue_due_date]' do + assert_select 'span[class=required]' + end + assert_select 'label[for=?]', "issue_custom_field_values_#{cf1.id}" do + assert_select 'span[class=required]', 0 + end + assert_select 'label[for=?]', "issue_custom_field_values_#{cf2.id}" do + assert_select 'span[class=required]' + end + end + + def test_get_new_should_not_display_readonly_fields + cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2]) + cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2]) + WorkflowPermission.delete_all + WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'due_date', :rule => 'readonly') + WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'readonly') + @request.session[:user_id] = 2 + + get :new, :project_id => 1 + assert_response :success + assert_template 'new' + + assert_select 'input[name=?]', 'issue[start_date]' + assert_select 'input[name=?]', 'issue[due_date]', 0 + assert_select 'input[name=?]', "issue[custom_field_values][#{cf1.id}]" + assert_select 'input[name=?]', "issue[custom_field_values][#{cf2.id}]", 0 + end + + def test_get_new_without_tracker_id + @request.session[:user_id] = 2 + get :new, :project_id => 1 + assert_response :success + assert_template 'new' + + issue = assigns(:issue) + assert_not_nil issue + assert_equal Project.find(1).trackers.first, issue.tracker + end + + def test_get_new_with_no_default_status_should_display_an_error + @request.session[:user_id] = 2 + IssueStatus.delete_all + + get :new, :project_id => 1 + assert_response 500 + assert_error_tag :content => /No default issue/ + end + + def test_get_new_with_no_tracker_should_display_an_error + @request.session[:user_id] = 2 + Tracker.delete_all + + get :new, :project_id => 1 + assert_response 500 + assert_error_tag :content => /No tracker/ + end + + def test_update_form_for_new_issue + @request.session[:user_id] = 2 + xhr :post, :update_form, :project_id => 1, + :issue => {:tracker_id => 2, + :subject => 'This is the test_new issue', + :description => 'This is the description', + :priority_id => 5} + assert_response :success + assert_template 'update_form' + assert_template 'form' + assert_equal 'text/javascript', response.content_type + + issue = assigns(:issue) + assert_kind_of Issue, issue + assert_equal 1, issue.project_id + assert_equal 2, issue.tracker_id + assert_equal 'This is the test_new issue', issue.subject + end + + def test_update_form_for_new_issue_should_propose_transitions_based_on_initial_status + @request.session[:user_id] = 2 + WorkflowTransition.delete_all + WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2) + WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 5) + WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 5, :new_status_id => 4) + + xhr :post, :update_form, :project_id => 1, + :issue => {:tracker_id => 1, + :status_id => 5, + :subject => 'This is an issue'} + + assert_equal 5, assigns(:issue).status_id + assert_equal [1,2,5], assigns(:allowed_statuses).map(&:id).sort + end + + def test_post_create + @request.session[:user_id] = 2 + assert_difference 'Issue.count' do + post :create, :project_id => 1, + :issue => {:tracker_id => 3, + :status_id => 2, + :subject => 'This is the test_new issue', + :description => 'This is the description', + :priority_id => 5, + :start_date => '2010-11-07', + :estimated_hours => '', + :custom_field_values => {'2' => 'Value for field 2'}} + end + assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id + + issue = Issue.find_by_subject('This is the test_new issue') + assert_not_nil issue + assert_equal 2, issue.author_id + assert_equal 3, issue.tracker_id + assert_equal 2, issue.status_id + assert_equal Date.parse('2010-11-07'), issue.start_date + assert_nil issue.estimated_hours + v = issue.custom_values.where(:custom_field_id => 2).first + assert_not_nil v + assert_equal 'Value for field 2', v.value + end + + def test_post_new_with_group_assignment + group = Group.find(11) + project = Project.find(1) + project.members << Member.new(:principal => group, :roles => [Role.givable.first]) + + with_settings :issue_group_assignment => '1' do + @request.session[:user_id] = 2 + assert_difference 'Issue.count' do + post :create, :project_id => project.id, + :issue => {:tracker_id => 3, + :status_id => 1, + :subject => 'This is the test_new_with_group_assignment issue', + :assigned_to_id => group.id} + end + end + assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id + + issue = Issue.find_by_subject('This is the test_new_with_group_assignment issue') + assert_not_nil issue + assert_equal group, issue.assigned_to + end + + def test_post_create_without_start_date_and_default_start_date_is_not_creation_date + Setting.default_issue_start_date_to_creation_date = 0 + + @request.session[:user_id] = 2 + assert_difference 'Issue.count' do + post :create, :project_id => 1, + :issue => {:tracker_id => 3, + :status_id => 2, + :subject => 'This is the test_new issue', + :description => 'This is the description', + :priority_id => 5, + :estimated_hours => '', + :custom_field_values => {'2' => 'Value for field 2'}} + end + assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id + + issue = Issue.find_by_subject('This is the test_new issue') + assert_not_nil issue + assert_nil issue.start_date + end + + def test_post_create_without_start_date_and_default_start_date_is_creation_date + Setting.default_issue_start_date_to_creation_date = 1 + + @request.session[:user_id] = 2 + assert_difference 'Issue.count' do + post :create, :project_id => 1, + :issue => {:tracker_id => 3, + :status_id => 2, + :subject => 'This is the test_new issue', + :description => 'This is the description', + :priority_id => 5, + :estimated_hours => '', + :custom_field_values => {'2' => 'Value for field 2'}} + end + assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id + + issue = Issue.find_by_subject('This is the test_new issue') + assert_not_nil issue + assert_equal Date.today, issue.start_date + end + + def test_post_create_and_continue + @request.session[:user_id] = 2 + assert_difference 'Issue.count' do + post :create, :project_id => 1, + :issue => {:tracker_id => 3, :subject => 'This is first issue', :priority_id => 5}, + :continue => '' + end + + issue = Issue.first(:order => 'id DESC') + assert_redirected_to :controller => 'issues', :action => 'new', :project_id => 'ecookbook', :issue => {:tracker_id => 3} + assert_not_nil flash[:notice], "flash was not set" + assert_include %|##{issue.id}|, flash[:notice], "issue link not found in the flash message" + end + + def test_post_create_without_custom_fields_param + @request.session[:user_id] = 2 + assert_difference 'Issue.count' do + post :create, :project_id => 1, + :issue => {:tracker_id => 1, + :subject => 'This is the test_new issue', + :description => 'This is the description', + :priority_id => 5} + end + assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id + end + + def test_post_create_with_multi_custom_field + field = IssueCustomField.find_by_name('Database') + field.update_attribute(:multiple, true) + + @request.session[:user_id] = 2 + assert_difference 'Issue.count' do + post :create, :project_id => 1, + :issue => {:tracker_id => 1, + :subject => 'This is the test_new issue', + :description => 'This is the description', + :priority_id => 5, + :custom_field_values => {'1' => ['', 'MySQL', 'Oracle']}} + end + assert_response 302 + issue = Issue.first(:order => 'id DESC') + assert_equal ['MySQL', 'Oracle'], issue.custom_field_value(1).sort + end + + def test_post_create_with_empty_multi_custom_field + field = IssueCustomField.find_by_name('Database') + field.update_attribute(:multiple, true) + + @request.session[:user_id] = 2 + assert_difference 'Issue.count' do + post :create, :project_id => 1, + :issue => {:tracker_id => 1, + :subject => 'This is the test_new issue', + :description => 'This is the description', + :priority_id => 5, + :custom_field_values => {'1' => ['']}} + end + assert_response 302 + issue = Issue.first(:order => 'id DESC') + assert_equal [''], issue.custom_field_value(1).sort + end + + def test_post_create_with_multi_user_custom_field + field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true, + :tracker_ids => [1], :is_for_all => true) + + @request.session[:user_id] = 2 + assert_difference 'Issue.count' do + post :create, :project_id => 1, + :issue => {:tracker_id => 1, + :subject => 'This is the test_new issue', + :description => 'This is the description', + :priority_id => 5, + :custom_field_values => {field.id.to_s => ['', '2', '3']}} + end + assert_response 302 + issue = Issue.first(:order => 'id DESC') + assert_equal ['2', '3'], issue.custom_field_value(field).sort + end + + def test_post_create_with_required_custom_field_and_without_custom_fields_param + field = IssueCustomField.find_by_name('Database') + field.update_attribute(:is_required, true) + + @request.session[:user_id] = 2 + assert_no_difference 'Issue.count' do + post :create, :project_id => 1, + :issue => {:tracker_id => 1, + :subject => 'This is the test_new issue', + :description => 'This is the description', + :priority_id => 5} + end + assert_response :success + assert_template 'new' + issue = assigns(:issue) + assert_not_nil issue + assert_error_tag :content => /Database can't be blank/ + end + + def test_create_should_validate_required_fields + cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2]) + cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2]) + WorkflowPermission.delete_all + WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => 'due_date', :rule => 'required') + WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'required') + @request.session[:user_id] = 2 + + assert_no_difference 'Issue.count' do + post :create, :project_id => 1, :issue => { + :tracker_id => 2, + :status_id => 1, + :subject => 'Test', + :start_date => '', + :due_date => '', + :custom_field_values => {cf1.id.to_s => '', cf2.id.to_s => ''} + } + assert_response :success + assert_template 'new' + end + + assert_error_tag :content => /Due date can't be blank/i + assert_error_tag :content => /Bar can't be blank/i + end + + def test_create_should_ignore_readonly_fields + cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2]) + cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2]) + WorkflowPermission.delete_all + WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => 'due_date', :rule => 'readonly') + WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'readonly') + @request.session[:user_id] = 2 + + assert_difference 'Issue.count' do + post :create, :project_id => 1, :issue => { + :tracker_id => 2, + :status_id => 1, + :subject => 'Test', + :start_date => '2012-07-14', + :due_date => '2012-07-16', + :custom_field_values => {cf1.id.to_s => 'value1', cf2.id.to_s => 'value2'} + } + assert_response 302 + end + + issue = Issue.first(:order => 'id DESC') + assert_equal Date.parse('2012-07-14'), issue.start_date + assert_nil issue.due_date + assert_equal 'value1', issue.custom_field_value(cf1) + assert_nil issue.custom_field_value(cf2) + end + + def test_post_create_with_watchers + @request.session[:user_id] = 2 + ActionMailer::Base.deliveries.clear + + assert_difference 'Watcher.count', 2 do + post :create, :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_not_nil mail + assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail) + end + + def test_post_create_subissue + @request.session[:user_id] = 2 + + assert_difference 'Issue.count' do + post :create, :project_id => 1, + :issue => {:tracker_id => 1, + :subject => 'This is a child issue', + :parent_issue_id => '2'} + assert_response 302 + end + issue = Issue.order('id DESC').first + assert_equal Issue.find(2), issue.parent + end + + def test_post_create_subissue_with_sharp_parent_id + @request.session[:user_id] = 2 + + assert_difference 'Issue.count' do + post :create, :project_id => 1, + :issue => {:tracker_id => 1, + :subject => 'This is a child issue', + :parent_issue_id => '#2'} + assert_response 302 + end + issue = Issue.order('id DESC').first + assert_equal Issue.find(2), issue.parent + end + + def test_post_create_subissue_with_non_visible_parent_id_should_not_validate + @request.session[:user_id] = 2 + + assert_no_difference 'Issue.count' do + post :create, :project_id => 1, + :issue => {:tracker_id => 1, + :subject => 'This is a child issue', + :parent_issue_id => '4'} + + assert_response :success + assert_select 'input[name=?][value=?]', 'issue[parent_issue_id]', '4' + assert_error_tag :content => /Parent task is invalid/i + end + end + + def test_post_create_subissue_with_non_numeric_parent_id_should_not_validate + @request.session[:user_id] = 2 + + assert_no_difference 'Issue.count' do + post :create, :project_id => 1, + :issue => {:tracker_id => 1, + :subject => 'This is a child issue', + :parent_issue_id => '01ABC'} + + assert_response :success + assert_select 'input[name=?][value=?]', 'issue[parent_issue_id]', '01ABC' + assert_error_tag :content => /Parent task is invalid/i + end + end + + def test_post_create_private + @request.session[:user_id] = 2 + + assert_difference 'Issue.count' do + post :create, :project_id => 1, + :issue => {:tracker_id => 1, + :subject => 'This is a private issue', + :is_private => '1'} + end + issue = Issue.first(:order => 'id DESC') + assert issue.is_private? + end + + def test_post_create_private_with_set_own_issues_private_permission + role = Role.find(1) + role.remove_permission! :set_issues_private + role.add_permission! :set_own_issues_private + + @request.session[:user_id] = 2 + + assert_difference 'Issue.count' do + post :create, :project_id => 1, + :issue => {:tracker_id => 1, + :subject => 'This is a private issue', + :is_private => '1'} + end + issue = Issue.first(:order => 'id DESC') + assert issue.is_private? + end + + def test_post_create_should_send_a_notification + ActionMailer::Base.deliveries.clear + @request.session[:user_id] = 2 + assert_difference 'Issue.count' do + post :create, :project_id => 1, + :issue => {:tracker_id => 3, + :subject => 'This is the test_new issue', + :description => 'This is the description', + :priority_id => 5, + :estimated_hours => '', + :custom_field_values => {'2' => 'Value for field 2'}} + end + assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id + + assert_equal 1, ActionMailer::Base.deliveries.size + end + + def test_post_create_should_preserve_fields_values_on_validation_failure + @request.session[:user_id] = 2 + post :create, :project_id => 1, + :issue => {:tracker_id => 1, + # 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_select 'textarea[name=?]', 'issue[description]', :text => 'This is a description' + assert_select 'select[name=?]', 'issue[priority_id]' do + assert_select 'option[value=6][selected=selected]', :text => 'High' + end + # Custom fields + assert_select 'select[name=?]', 'issue[custom_field_values][1]' do + assert_select 'option[value=Oracle][selected=selected]', :text => 'Oracle' + end + assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Value for field 2' + end + + def test_post_create_with_failure_should_preserve_watchers + assert !User.find(8).member_of?(Project.find(1)) + + @request.session[:user_id] = 2 + post :create, :project_id => 1, + :issue => {:tracker_id => 1, + :watcher_user_ids => ['3', '8']} + assert_response :success + assert_template 'new' + + assert_select 'input[name=?][value=2]:not(checked)', 'issue[watcher_user_ids][]' + assert_select 'input[name=?][value=3][checked=checked]', 'issue[watcher_user_ids][]' + assert_select 'input[name=?][value=8][checked=checked]', 'issue[watcher_user_ids][]' + end + + def test_post_create_should_ignore_non_safe_attributes + @request.session[:user_id] = 2 + assert_nothing_raised do + post :create, :project_id => 1, :issue => { :tracker => "A param can not be a Tracker" } + end + end + + def test_post_create_with_attachment + set_tmp_attachments_directory + @request.session[:user_id] = 2 + + assert_difference 'Issue.count' do + assert_difference 'Attachment.count' do + post :create, :project_id => 1, + :issue => { :tracker_id => '1', :subject => 'With attachment' }, + :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}} + end + end + + issue = Issue.first(:order => 'id DESC') + attachment = Attachment.first(:order => 'id DESC') + + assert_equal issue, attachment.container + assert_equal 2, attachment.author_id + assert_equal 'testfile.txt', attachment.filename + assert_equal 'text/plain', attachment.content_type + assert_equal 'test file', attachment.description + assert_equal 59, attachment.filesize + assert File.exists?(attachment.diskfile) + assert_equal 59, File.size(attachment.diskfile) + end + + def test_post_create_with_failure_should_save_attachments + set_tmp_attachments_directory + @request.session[:user_id] = 2 + + assert_no_difference 'Issue.count' do + assert_difference 'Attachment.count' do + post :create, :project_id => 1, + :issue => { :tracker_id => '1', :subject => '' }, + :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}} + assert_response :success + assert_template 'new' + end + end + + attachment = Attachment.first(:order => 'id DESC') + assert_equal 'testfile.txt', attachment.filename + assert File.exists?(attachment.diskfile) + assert_nil attachment.container + + assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token + assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt' + end + + def test_post_create_with_failure_should_keep_saved_attachments + set_tmp_attachments_directory + attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2) + @request.session[:user_id] = 2 + + assert_no_difference 'Issue.count' do + assert_no_difference 'Attachment.count' do + post :create, :project_id => 1, + :issue => { :tracker_id => '1', :subject => '' }, + :attachments => {'p0' => {'token' => attachment.token}} + assert_response :success + assert_template 'new' + end + end + + assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token + assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt' + end + + def test_post_create_should_attach_saved_attachments + set_tmp_attachments_directory + attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2) + @request.session[:user_id] = 2 + + assert_difference 'Issue.count' do + assert_no_difference 'Attachment.count' do + post :create, :project_id => 1, + :issue => { :tracker_id => '1', :subject => 'Saved attachments' }, + :attachments => {'p0' => {'token' => attachment.token}} + assert_response 302 + end + end + + issue = Issue.first(:order => 'id DESC') + assert_equal 1, issue.attachments.count + + attachment.reload + assert_equal issue, attachment.container + end + + context "without workflow privilege" do + setup do + WorkflowTransition.delete_all(["role_id = ?", Role.anonymous.id]) + Role.anonymous.add_permission! :add_issues, :add_issue_notes + end + + context "#new" do + should "propose default status only" do + get :new, :project_id => 1 + assert_response :success + assert_template 'new' + assert_select 'select[name=?]', 'issue[status_id]' do + assert_select 'option', 1 + assert_select 'option[value=?]', IssueStatus.default.id.to_s + end + end + + should "accept default status" do + assert_difference 'Issue.count' do + post :create, :project_id => 1, + :issue => {:tracker_id => 1, + :subject => 'This is an issue', + :status_id => 1} + end + issue = Issue.last(:order => 'id') + assert_equal IssueStatus.default, issue.status + end + + should "ignore unauthorized status" do + assert_difference 'Issue.count' do + post :create, :project_id => 1, + :issue => {:tracker_id => 1, + :subject => 'This is an issue', + :status_id => 3} + end + issue = Issue.last(:order => 'id') + assert_equal IssueStatus.default, issue.status + end + end + + context "#update" do + should "ignore status change" do + assert_difference 'Journal.count' do + put :update, :id => 1, :issue => {:status_id => 3, :notes => 'just trying'} + end + assert_equal 1, Issue.find(1).status_id + end + + should "ignore attributes changes" do + assert_difference 'Journal.count' do + put :update, :id => 1, :issue => {:subject => 'changed', :assigned_to_id => 2, :notes => 'just trying'} + end + issue = Issue.find(1) + assert_equal "Can't print recipes", issue.subject + assert_nil issue.assigned_to + end + end + end + + context "with workflow privilege" do + setup do + WorkflowTransition.delete_all(["role_id = ?", Role.anonymous.id]) + WorkflowTransition.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3) + WorkflowTransition.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4) + Role.anonymous.add_permission! :add_issues, :add_issue_notes + end + + context "#update" do + should "accept authorized status" do + assert_difference 'Journal.count' do + put :update, :id => 1, :issue => {:status_id => 3, :notes => 'just trying'} + end + assert_equal 3, Issue.find(1).status_id + end + + should "ignore unauthorized status" do + assert_difference 'Journal.count' do + put :update, :id => 1, :issue => {:status_id => 2, :notes => 'just trying'} + end + assert_equal 1, Issue.find(1).status_id + end + + should "accept authorized attributes changes" do + assert_difference 'Journal.count' do + put :update, :id => 1, :issue => {:assigned_to_id => 2, :notes => 'just trying'} + end + issue = Issue.find(1) + assert_equal 2, issue.assigned_to_id + end + + should "ignore unauthorized attributes changes" do + assert_difference 'Journal.count' do + put :update, :id => 1, :issue => {:subject => 'changed', :notes => 'just trying'} + end + issue = Issue.find(1) + assert_equal "Can't print recipes", issue.subject + end + end + + context "and :edit_issues permission" do + setup do + Role.anonymous.add_permission! :add_issues, :edit_issues + end + + should "accept authorized status" do + assert_difference 'Journal.count' do + put :update, :id => 1, :issue => {:status_id => 3, :notes => 'just trying'} + end + assert_equal 3, Issue.find(1).status_id + end + + should "ignore unauthorized status" do + assert_difference 'Journal.count' do + put :update, :id => 1, :issue => {:status_id => 2, :notes => 'just trying'} + end + assert_equal 1, Issue.find(1).status_id + end + + should "accept authorized attributes changes" do + assert_difference 'Journal.count' do + put :update, :id => 1, :issue => {:subject => 'changed', :assigned_to_id => 2, :notes => 'just trying'} + end + issue = Issue.find(1) + assert_equal "changed", issue.subject + assert_equal 2, issue.assigned_to_id + end + end + end + + def test_new_as_copy + @request.session[:user_id] = 2 + get :new, :project_id => 1, :copy_from => 1 + + assert_response :success + assert_template 'new' + + assert_not_nil assigns(:issue) + orig = Issue.find(1) + assert_equal 1, assigns(:issue).project_id + assert_equal orig.subject, assigns(:issue).subject + assert assigns(:issue).copy? + + assert_select 'form[id=issue-form][action=/projects/ecookbook/issues]' do + assert_select 'select[name=?]', 'issue[project_id]' do + assert_select 'option[value=1][selected=selected]', :text => 'eCookbook' + assert_select 'option[value=2]:not([selected])', :text => 'OnlineStore' + end + assert_select 'input[name=copy_from][value=1]' + end + + # "New issue" menu item should not link to copy + assert_select '#main-menu a.new-issue[href=/projects/ecookbook/issues/new]' + end + + def test_new_as_copy_with_attachments_should_show_copy_attachments_checkbox + @request.session[:user_id] = 2 + issue = Issue.find(3) + assert issue.attachments.count > 0 + get :new, :project_id => 1, :copy_from => 3 + + assert_select 'input[name=copy_attachments][type=checkbox][checked=checked][value=1]' + end + + def test_new_as_copy_without_attachments_should_not_show_copy_attachments_checkbox + @request.session[:user_id] = 2 + issue = Issue.find(3) + issue.attachments.delete_all + get :new, :project_id => 1, :copy_from => 3 + + assert_select 'input[name=copy_attachments]', 0 + end + + def test_new_as_copy_with_subtasks_should_show_copy_subtasks_checkbox + @request.session[:user_id] = 2 + issue = Issue.generate_with_descendants! + get :new, :project_id => 1, :copy_from => issue.id + + assert_select 'input[type=checkbox][name=copy_subtasks][checked=checked][value=1]' + end + + def test_new_as_copy_with_invalid_issue_should_respond_with_404 + @request.session[:user_id] = 2 + get :new, :project_id => 1, :copy_from => 99999 + assert_response 404 + end + + def test_create_as_copy_on_different_project + @request.session[:user_id] = 2 + assert_difference 'Issue.count' do + post :create, :project_id => 1, :copy_from => 1, + :issue => {:project_id => '2', :tracker_id => '3', :status_id => '1', :subject => 'Copy'} + + assert_not_nil assigns(:issue) + assert assigns(:issue).copy? + end + issue = Issue.first(:order => 'id DESC') + assert_redirected_to "/issues/#{issue.id}" + + assert_equal 2, issue.project_id + assert_equal 3, issue.tracker_id + assert_equal 'Copy', issue.subject + end + + def test_create_as_copy_should_copy_attachments + @request.session[:user_id] = 2 + issue = Issue.find(3) + count = issue.attachments.count + assert count > 0 + + assert_difference 'Issue.count' do + assert_difference 'Attachment.count', count do + assert_no_difference 'Journal.count' do + post :create, :project_id => 1, :copy_from => 3, + :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with attachments'}, + :copy_attachments => '1' + end + end + end + copy = Issue.first(:order => 'id DESC') + assert_equal count, copy.attachments.count + assert_equal issue.attachments.map(&:filename).sort, copy.attachments.map(&:filename).sort + end + + def test_create_as_copy_without_copy_attachments_option_should_not_copy_attachments + @request.session[:user_id] = 2 + issue = Issue.find(3) + count = issue.attachments.count + assert count > 0 + + assert_difference 'Issue.count' do + assert_no_difference 'Attachment.count' do + assert_no_difference 'Journal.count' do + post :create, :project_id => 1, :copy_from => 3, + :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with attachments'} + end + end + end + copy = Issue.first(:order => 'id DESC') + assert_equal 0, copy.attachments.count + end + + def test_create_as_copy_with_attachments_should_add_new_files + @request.session[:user_id] = 2 + issue = Issue.find(3) + count = issue.attachments.count + assert count > 0 + + assert_difference 'Issue.count' do + assert_difference 'Attachment.count', count + 1 do + assert_no_difference 'Journal.count' do + post :create, :project_id => 1, :copy_from => 3, + :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with attachments'}, + :copy_attachments => '1', + :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}} + end + end + end + copy = Issue.first(:order => 'id DESC') + assert_equal count + 1, copy.attachments.count + end + + def test_create_as_copy_should_add_relation_with_copied_issue + @request.session[:user_id] = 2 + + assert_difference 'Issue.count' do + assert_difference 'IssueRelation.count' do + post :create, :project_id => 1, :copy_from => 1, + :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy'} + end + end + copy = Issue.first(:order => 'id DESC') + assert_equal 1, copy.relations.size + end + + def test_create_as_copy_should_copy_subtasks + @request.session[:user_id] = 2 + issue = Issue.generate_with_descendants! + count = issue.descendants.count + + assert_difference 'Issue.count', count+1 do + assert_no_difference 'Journal.count' do + post :create, :project_id => 1, :copy_from => issue.id, + :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with subtasks'}, + :copy_subtasks => '1' + end + end + copy = Issue.where(:parent_id => nil).first(:order => 'id DESC') + assert_equal count, copy.descendants.count + assert_equal issue.descendants.map(&:subject).sort, copy.descendants.map(&:subject).sort + end + + def test_create_as_copy_without_copy_subtasks_option_should_not_copy_subtasks + @request.session[:user_id] = 2 + issue = Issue.generate_with_descendants! + + assert_difference 'Issue.count', 1 do + assert_no_difference 'Journal.count' do + post :create, :project_id => 1, :copy_from => 3, + :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with subtasks'} + end + end + copy = Issue.where(:parent_id => nil).first(:order => 'id DESC') + assert_equal 0, copy.descendants.count + end + + def test_create_as_copy_with_failure + @request.session[:user_id] = 2 + post :create, :project_id => 1, :copy_from => 1, + :issue => {:project_id => '2', :tracker_id => '3', :status_id => '1', :subject => ''} + + assert_response :success + assert_template 'new' + + assert_not_nil assigns(:issue) + assert assigns(:issue).copy? + + assert_select 'form#issue-form[action=/projects/ecookbook/issues]' do + assert_select 'select[name=?]', 'issue[project_id]' do + assert_select 'option[value=1]:not([selected])', :text => 'eCookbook' + assert_select 'option[value=2][selected=selected]', :text => 'OnlineStore' + end + assert_select 'input[name=copy_from][value=1]' + end + end + + def test_create_as_copy_on_project_without_permission_should_ignore_target_project + @request.session[:user_id] = 2 + assert !User.find(2).member_of?(Project.find(4)) + + assert_difference 'Issue.count' do + post :create, :project_id => 1, :copy_from => 1, + :issue => {:project_id => '4', :tracker_id => '3', :status_id => '1', :subject => 'Copy'} + end + issue = Issue.first(:order => 'id DESC') + assert_equal 1, issue.project_id + end + + def test_get_edit + @request.session[:user_id] = 2 + get :edit, :id => 1 + assert_response :success + assert_template 'edit' + assert_not_nil assigns(:issue) + assert_equal Issue.find(1), assigns(:issue) + + # Be sure we don't display inactive IssuePriorities + assert ! IssuePriority.find(15).active? + assert_select 'select[name=?]', 'issue[priority_id]' do + assert_select 'option[value=15]', 0 + end + end + + def test_get_edit_should_display_the_time_entry_form_with_log_time_permission + @request.session[:user_id] = 2 + Role.find_by_name('Manager').update_attribute :permissions, [:view_issues, :edit_issues, :log_time] + + get :edit, :id => 1 + assert_select 'input[name=?]', 'time_entry[hours]' + end + + def test_get_edit_should_not_display_the_time_entry_form_without_log_time_permission + @request.session[:user_id] = 2 + Role.find_by_name('Manager').remove_permission! :log_time + + get :edit, :id => 1 + assert_select 'input[name=?]', 'time_entry[hours]', 0 + end + + def test_get_edit_with_params + @request.session[:user_id] = 2 + get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 }, + :time_entry => { :hours => '2.5', :comments => 'test_get_edit_with_params', :activity_id => 10 } + assert_response :success + assert_template 'edit' + + issue = assigns(:issue) + assert_not_nil issue + + assert_equal 5, issue.status_id + assert_select 'select[name=?]', 'issue[status_id]' do + assert_select 'option[value=5][selected=selected]', :text => 'Closed' + end + + assert_equal 7, issue.priority_id + assert_select 'select[name=?]', 'issue[priority_id]' do + assert_select 'option[value=7][selected=selected]', :text => 'Urgent' + end + + assert_select 'input[name=?][value=2.5]', 'time_entry[hours]' + assert_select 'select[name=?]', 'time_entry[activity_id]' do + assert_select 'option[value=10][selected=selected]', :text => 'Development' + end + assert_select 'input[name=?][value=test_get_edit_with_params]', 'time_entry[comments]' + end + + def test_get_edit_with_multi_custom_field + field = CustomField.find(1) + field.update_attribute :multiple, true + issue = Issue.find(1) + issue.custom_field_values = {1 => ['MySQL', 'Oracle']} + issue.save! + + @request.session[:user_id] = 2 + get :edit, :id => 1 + assert_response :success + assert_template 'edit' + + assert_select 'select[name=?][multiple=multiple]', 'issue[custom_field_values][1][]' do + assert_select 'option', 3 + assert_select 'option[value=MySQL][selected=selected]' + assert_select 'option[value=Oracle][selected=selected]' + assert_select 'option[value=PostgreSQL]:not([selected])' + end + end + + def test_update_form_for_existing_issue + @request.session[:user_id] = 2 + xhr :put, :update_form, :project_id => 1, + :id => 1, + :issue => {:tracker_id => 2, + :subject => 'This is the test_new issue', + :description => 'This is the description', + :priority_id => 5} + assert_response :success + assert_equal 'text/javascript', response.content_type + assert_template 'update_form' + assert_template 'form' + + issue = assigns(:issue) + assert_kind_of Issue, issue + assert_equal 1, issue.id + assert_equal 1, issue.project_id + assert_equal 2, issue.tracker_id + assert_equal 'This is the test_new issue', issue.subject + end + + def test_update_form_for_existing_issue_should_keep_issue_author + @request.session[:user_id] = 3 + xhr :put, :update_form, :project_id => 1, :id => 1, :issue => {:subject => 'Changed'} + assert_response :success + assert_equal 'text/javascript', response.content_type + + issue = assigns(:issue) + assert_equal User.find(2), issue.author + assert_equal 2, issue.author_id + assert_not_equal User.current, issue.author + end + + def test_update_form_for_existing_issue_should_propose_transitions_based_on_initial_status + @request.session[:user_id] = 2 + WorkflowTransition.delete_all + WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 1) + WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 5) + WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 5, :new_status_id => 4) + + xhr :put, :update_form, :project_id => 1, + :id => 2, + :issue => {:tracker_id => 2, + :status_id => 5, + :subject => 'This is an issue'} + + assert_equal 5, assigns(:issue).status_id + assert_equal [1,2,5], assigns(:allowed_statuses).map(&:id).sort + end + + def test_update_form_for_existing_issue_with_project_change + @request.session[:user_id] = 2 + xhr :put, :update_form, :project_id => 1, + :id => 1, + :issue => {:project_id => 2, + :tracker_id => 2, + :subject => 'This is the test_new issue', + :description => 'This is the description', + :priority_id => 5} + assert_response :success + assert_template 'form' + + issue = assigns(:issue) + assert_kind_of Issue, issue + assert_equal 1, issue.id + assert_equal 2, issue.project_id + assert_equal 2, issue.tracker_id + assert_equal 'This is the test_new issue', issue.subject + end + + def test_put_update_without_custom_fields_param + @request.session[:user_id] = 2 + ActionMailer::Base.deliveries.clear + + issue = Issue.find(1) + assert_equal '125', issue.custom_value_for(2).value + old_subject = issue.subject + new_subject = 'Subject modified by IssuesControllerTest#test_post_edit' + + assert_difference('Journal.count') do + assert_difference('JournalDetail.count', 2) do + put :update, :id => 1, :issue => {:subject => new_subject, + :priority_id => '6', + :category_id => '1' # no change + } + end + end + assert_redirected_to :action => 'show', :id => '1' + issue.reload + assert_equal new_subject, issue.subject + # Make sure custom fields were not cleared + assert_equal '125', issue.custom_value_for(2).value + + mail = ActionMailer::Base.deliveries.last + assert_not_nil mail + assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]") + assert_mail_body_match "Subject changed from #{old_subject} to #{new_subject}", mail + end + + def test_put_update_with_project_change + @request.session[:user_id] = 2 + ActionMailer::Base.deliveries.clear + + assert_difference('Journal.count') do + assert_difference('JournalDetail.count', 3) do + put :update, :id => 1, :issue => {:project_id => '2', + :tracker_id => '1', # no change + :priority_id => '6', + :category_id => '3' + } + end + end + assert_redirected_to :action => 'show', :id => '1' + issue = Issue.find(1) + assert_equal 2, issue.project_id + assert_equal 1, issue.tracker_id + assert_equal 6, issue.priority_id + assert_equal 3, issue.category_id + + mail = ActionMailer::Base.deliveries.last + assert_not_nil mail + assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]") + assert_mail_body_match "Project changed from eCookbook to OnlineStore", mail + end + + def test_put_update_with_tracker_change + @request.session[:user_id] = 2 + ActionMailer::Base.deliveries.clear + + assert_difference('Journal.count') do + assert_difference('JournalDetail.count', 2) do + put :update, :id => 1, :issue => {:project_id => '1', + :tracker_id => '2', + :priority_id => '6' + } + end + end + assert_redirected_to :action => 'show', :id => '1' + issue = Issue.find(1) + assert_equal 1, issue.project_id + assert_equal 2, issue.tracker_id + assert_equal 6, issue.priority_id + assert_equal 1, issue.category_id + + mail = ActionMailer::Base.deliveries.last + assert_not_nil mail + assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]") + assert_mail_body_match "Tracker changed from Bug to Feature request", mail + end + + def test_put_update_with_custom_field_change + @request.session[:user_id] = 2 + issue = Issue.find(1) + assert_equal '125', issue.custom_value_for(2).value + + assert_difference('Journal.count') do + assert_difference('JournalDetail.count', 3) do + put :update, :id => 1, :issue => {:subject => 'Custom field change', + :priority_id => '6', + :category_id => '1', # no change + :custom_field_values => { '2' => 'New custom value' } + } + end + end + assert_redirected_to :action => 'show', :id => '1' + issue.reload + assert_equal 'New custom value', issue.custom_value_for(2).value + + mail = ActionMailer::Base.deliveries.last + assert_not_nil mail + assert_mail_body_match "Searchable field changed from 125 to New custom value", mail + end + + def test_put_update_with_multi_custom_field_change + field = CustomField.find(1) + field.update_attribute :multiple, true + issue = Issue.find(1) + issue.custom_field_values = {1 => ['MySQL', 'Oracle']} + issue.save! + + @request.session[:user_id] = 2 + assert_difference('Journal.count') do + assert_difference('JournalDetail.count', 3) do + put :update, :id => 1, + :issue => { + :subject => 'Custom field change', + :custom_field_values => { '1' => ['', 'Oracle', 'PostgreSQL'] } + } + end + end + assert_redirected_to :action => 'show', :id => '1' + assert_equal ['Oracle', 'PostgreSQL'], Issue.find(1).custom_field_value(1).sort + end + + def test_put_update_with_status_and_assignee_change + issue = Issue.find(1) + assert_equal 1, issue.status_id + @request.session[:user_id] = 2 + assert_difference('TimeEntry.count', 0) do + put :update, + :id => 1, + :issue => { :status_id => 2, :assigned_to_id => 3, :notes => 'Assigned to dlopper' }, + :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first } + end + assert_redirected_to :action => 'show', :id => '1' + issue.reload + assert_equal 2, issue.status_id + j = Journal.order('id DESC').first + assert_equal 'Assigned to dlopper', j.notes + assert_equal 2, j.details.size + + mail = ActionMailer::Base.deliveries.last + assert_mail_body_match "Status changed from New to Assigned", mail + # subject should contain the new status + assert mail.subject.include?("(#{ IssueStatus.find(2).name })") + end + + def test_put_update_with_note_only + notes = 'Note added by IssuesControllerTest#test_update_with_note_only' + # anonymous user + put :update, + :id => 1, + :issue => { :notes => notes } + assert_redirected_to :action => 'show', :id => '1' + j = Journal.order('id DESC').first + assert_equal notes, j.notes + assert_equal 0, j.details.size + assert_equal User.anonymous, j.user + + mail = ActionMailer::Base.deliveries.last + assert_mail_body_match notes, mail + end + + def test_put_update_with_private_note_only + notes = 'Private note' + @request.session[:user_id] = 2 + + assert_difference 'Journal.count' do + put :update, :id => 1, :issue => {:notes => notes, :private_notes => '1'} + assert_redirected_to :action => 'show', :id => '1' + end + + j = Journal.order('id DESC').first + assert_equal notes, j.notes + assert_equal true, j.private_notes + end + + def test_put_update_with_private_note_and_changes + notes = 'Private note' + @request.session[:user_id] = 2 + + assert_difference 'Journal.count', 2 do + put :update, :id => 1, :issue => {:subject => 'New subject', :notes => notes, :private_notes => '1'} + assert_redirected_to :action => 'show', :id => '1' + end + + j = Journal.order('id DESC').first + assert_equal notes, j.notes + assert_equal true, j.private_notes + assert_equal 0, j.details.count + + j = Journal.order('id DESC').offset(1).first + assert_nil j.notes + assert_equal false, j.private_notes + assert_equal 1, j.details.count + end + + def test_put_update_with_note_and_spent_time + @request.session[:user_id] = 2 + spent_hours_before = Issue.find(1).spent_hours + assert_difference('TimeEntry.count') do + put :update, + :id => 1, + :issue => { :notes => '2.5 hours added' }, + :time_entry => { :hours => '2.5', :comments => 'test_put_update_with_note_and_spent_time', :activity_id => TimeEntryActivity.first.id } + end + assert_redirected_to :action => 'show', :id => '1' + + issue = Issue.find(1) + + j = Journal.order('id DESC').first + assert_equal '2.5 hours added', j.notes + assert_equal 0, j.details.size + + t = issue.time_entries.find_by_comments('test_put_update_with_note_and_spent_time') + assert_not_nil t + assert_equal 2.5, t.hours + assert_equal spent_hours_before + 2.5, issue.spent_hours + end + + def test_put_update_should_preserve_parent_issue_even_if_not_visible + parent = Issue.generate!(:project_id => 1, :is_private => true) + issue = Issue.generate!(:parent_issue_id => parent.id) + assert !parent.visible?(User.find(3)) + @request.session[:user_id] = 3 + + get :edit, :id => issue.id + assert_select 'input[name=?][value=?]', 'issue[parent_issue_id]', parent.id.to_s + + put :update, :id => issue.id, :issue => {:subject => 'New subject', :parent_issue_id => parent.id.to_s} + assert_response 302 + assert_equal parent, issue.parent + end + + def test_put_update_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 + assert_difference 'Attachment.count' do + put :update, :id => 1, + :issue => {:notes => ''}, + :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}} + end + + assert_redirected_to :action => 'show', :id => '1' + j = Issue.find(1).journals.reorder('id DESC').first + assert j.notes.blank? + assert_equal 1, j.details.size + assert_equal 'testfile.txt', j.details.first.value + assert_equal User.anonymous, j.user + + attachment = Attachment.first(:order => 'id DESC') + assert_equal Issue.find(1), attachment.container + assert_equal User.anonymous, attachment.author + assert_equal 'testfile.txt', attachment.filename + assert_equal 'text/plain', attachment.content_type + assert_equal 'test file', attachment.description + assert_equal 59, attachment.filesize + assert File.exists?(attachment.diskfile) + assert_equal 59, File.size(attachment.diskfile) + + mail = ActionMailer::Base.deliveries.last + assert_mail_body_match 'testfile.txt', mail + end + + def test_put_update_with_failure_should_save_attachments + set_tmp_attachments_directory + @request.session[:user_id] = 2 + + assert_no_difference 'Journal.count' do + assert_difference 'Attachment.count' do + put :update, :id => 1, + :issue => { :subject => '' }, + :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}} + assert_response :success + assert_template 'edit' + end + end + + attachment = Attachment.first(:order => 'id DESC') + assert_equal 'testfile.txt', attachment.filename + assert File.exists?(attachment.diskfile) + assert_nil attachment.container + + assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token + assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt' + end + + def test_put_update_with_failure_should_keep_saved_attachments + set_tmp_attachments_directory + attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2) + @request.session[:user_id] = 2 + + assert_no_difference 'Journal.count' do + assert_no_difference 'Attachment.count' do + put :update, :id => 1, + :issue => { :subject => '' }, + :attachments => {'p0' => {'token' => attachment.token}} + assert_response :success + assert_template 'edit' + end + end + + assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token + assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt' + end + + def test_put_update_should_attach_saved_attachments + set_tmp_attachments_directory + attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2) + @request.session[:user_id] = 2 + + assert_difference 'Journal.count' do + assert_difference 'JournalDetail.count' do + assert_no_difference 'Attachment.count' do + put :update, :id => 1, + :issue => {:notes => 'Attachment added'}, + :attachments => {'p0' => {'token' => attachment.token}} + assert_redirected_to '/issues/1' + end + end + end + + attachment.reload + assert_equal Issue.find(1), attachment.container + + journal = Journal.first(:order => 'id DESC') + assert_equal 1, journal.details.size + assert_equal 'testfile.txt', journal.details.first.value + end + + def test_put_update_with_attachment_that_fails_to_save + 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 + + # Mock out the unsaved attachment + Attachment.any_instance.stubs(:create).returns(Attachment.new) + + # anonymous user + put :update, + :id => 1, + :issue => {:notes => ''}, + :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}} + assert_redirected_to :action => 'show', :id => '1' + assert_equal '1 file(s) could not be saved.', flash[:warning] + end + + def test_put_update_with_no_change + issue = Issue.find(1) + issue.journals.clear + ActionMailer::Base.deliveries.clear + + put :update, + :id => 1, + :issue => {:notes => ''} + 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_put_update_should_send_a_notification + @request.session[:user_id] = 2 + ActionMailer::Base.deliveries.clear + issue = Issue.find(1) + old_subject = issue.subject + new_subject = 'Subject modified by IssuesControllerTest#test_post_edit' + + put :update, :id => 1, :issue => {:subject => new_subject, + :priority_id => '6', + :category_id => '1' # no change + } + assert_equal 1, ActionMailer::Base.deliveries.size + end + + def test_put_update_with_invalid_spent_time_hours_only + @request.session[:user_id] = 2 + notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time' + + assert_no_difference('Journal.count') do + put :update, + :id => 1, + :issue => {:notes => notes}, + :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"} + end + assert_response :success + assert_template 'edit' + + assert_error_tag :descendant => {:content => /Activity can't be blank/} + assert_select 'textarea[name=?]', 'issue[notes]', :text => notes + assert_select 'input[name=?][value=?]', 'time_entry[hours]', '2z' + end + + def test_put_update_with_invalid_spent_time_comments_only + @request.session[:user_id] = 2 + notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time' + + assert_no_difference('Journal.count') do + put :update, + :id => 1, + :issue => {:notes => notes}, + :time_entry => {"comments"=>"this is my comment", "activity_id"=>"", "hours"=>""} + end + assert_response :success + assert_template 'edit' + + assert_error_tag :descendant => {:content => /Activity can't be blank/} + assert_error_tag :descendant => {:content => /Hours can't be blank/} + assert_select 'textarea[name=?]', 'issue[notes]', :text => notes + assert_select 'input[name=?][value=?]', 'time_entry[comments]', 'this is my comment' + end + + def test_put_update_should_allow_fixed_version_to_be_set_to_a_subproject + issue = Issue.find(2) + @request.session[:user_id] = 2 + + put :update, + :id => issue.id, + :issue => { + :fixed_version_id => 4 + } + + assert_response :redirect + issue.reload + assert_equal 4, issue.fixed_version_id + assert_not_equal issue.project_id, issue.fixed_version.project_id + end + + def test_put_update_should_redirect_back_using_the_back_url_parameter + issue = Issue.find(2) + @request.session[:user_id] = 2 + + put :update, + :id => issue.id, + :issue => { + :fixed_version_id => 4 + }, + :back_url => '/issues' + + assert_response :redirect + assert_redirected_to '/issues' + end + + def test_put_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host + issue = Issue.find(2) + @request.session[:user_id] = 2 + + put :update, + :id => issue.id, + :issue => { + :fixed_version_id => 4 + }, + :back_url => 'http://google.com' + + assert_response :redirect + assert_redirected_to :controller => 'issues', :action => 'show', :id => issue.id + end + + def test_get_bulk_edit + @request.session[:user_id] = 2 + get :bulk_edit, :ids => [1, 2] + assert_response :success + assert_template 'bulk_edit' + + assert_select 'ul#bulk-selection' do + assert_select 'li', 2 + assert_select 'li a', :text => 'Bug #1' + end + + assert_select 'form#bulk_edit_form[action=?]', '/issues/bulk_update' do + assert_select 'input[name=?]', 'ids[]', 2 + assert_select 'input[name=?][value=1][type=hidden]', 'ids[]' + + assert_select 'select[name=?]', 'issue[project_id]' + assert_select 'input[name=?]', 'issue[parent_issue_id]' + + # Project specific custom field, date type + field = CustomField.find(9) + assert !field.is_for_all? + assert_equal 'date', field.field_format + assert_select 'input[name=?]', 'issue[custom_field_values][9]' + + # System wide custom field + assert CustomField.find(1).is_for_all? + assert_select 'select[name=?]', 'issue[custom_field_values][1]' + + # Be sure we don't display inactive IssuePriorities + assert ! IssuePriority.find(15).active? + assert_select 'select[name=?]', 'issue[priority_id]' do + assert_select 'option[value=15]', 0 + end + end + end + + def test_get_bulk_edit_on_different_projects + @request.session[:user_id] = 2 + get :bulk_edit, :ids => [1, 2, 6] + assert_response :success + assert_template 'bulk_edit' + + # Can not set issues from different projects as children of an issue + assert_select 'input[name=?]', 'issue[parent_issue_id]', 0 + + # Project specific custom field, date type + field = CustomField.find(9) + assert !field.is_for_all? + assert !field.project_ids.include?(Issue.find(6).project_id) + assert_select 'input[name=?]', 'issue[custom_field_values][9]', 0 + end + + def test_get_bulk_edit_with_user_custom_field + field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user', :is_for_all => true) + + @request.session[:user_id] = 2 + get :bulk_edit, :ids => [1, 2] + assert_response :success + assert_template 'bulk_edit' + + assert_select 'select.user_cf[name=?]', "issue[custom_field_values][#{field.id}]" do + assert_select 'option', Project.find(1).users.count + 2 # "no change" + "none" options + end + end + + def test_get_bulk_edit_with_version_custom_field + field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true) + + @request.session[:user_id] = 2 + get :bulk_edit, :ids => [1, 2] + assert_response :success + assert_template 'bulk_edit' + + assert_select 'select.version_cf[name=?]', "issue[custom_field_values][#{field.id}]" do + assert_select 'option', Project.find(1).shared_versions.count + 2 # "no change" + "none" options + end + end + + def test_get_bulk_edit_with_multi_custom_field + field = CustomField.find(1) + field.update_attribute :multiple, true + + @request.session[:user_id] = 2 + get :bulk_edit, :ids => [1, 2] + assert_response :success + assert_template 'bulk_edit' + + assert_select 'select[name=?]', 'issue[custom_field_values][1][]' do + assert_select 'option', field.possible_values.size + 1 # "none" options + end + end + + def test_bulk_edit_should_only_propose_statuses_allowed_for_all_issues + WorkflowTransition.delete_all + WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 1) + WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3) + WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4) + WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 1) + WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 3) + WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 5) + @request.session[:user_id] = 2 + get :bulk_edit, :ids => [1, 2] + + assert_response :success + statuses = assigns(:available_statuses) + assert_not_nil statuses + assert_equal [1, 3], statuses.map(&:id).sort + + assert_select 'select[name=?]', 'issue[status_id]' do + assert_select 'option', 3 # 2 statuses + "no change" option + end + end + + def test_bulk_edit_should_propose_target_project_open_shared_versions + @request.session[:user_id] = 2 + post :bulk_edit, :ids => [1, 2, 6], :issue => {:project_id => 1} + assert_response :success + assert_template 'bulk_edit' + assert_equal Project.find(1).shared_versions.open.all.sort, assigns(:versions).sort + + assert_select 'select[name=?]', 'issue[fixed_version_id]' do + assert_select 'option', :text => '2.0' + end + end + + def test_bulk_edit_should_propose_target_project_categories + @request.session[:user_id] = 2 + post :bulk_edit, :ids => [1, 2, 6], :issue => {:project_id => 1} + assert_response :success + assert_template 'bulk_edit' + assert_equal Project.find(1).issue_categories.sort, assigns(:categories).sort + + assert_select 'select[name=?]', 'issue[category_id]' do + assert_select 'option', :text => 'Recipes' + end + end + + def test_bulk_update + @request.session[:user_id] = 2 + # update issues priority + post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing', + :issue => {:priority_id => 7, + :assigned_to_id => '', + :custom_field_values => {'2' => ''}} + + 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} + + issue = Issue.find(1) + journal = issue.journals.reorder('created_on DESC').first + 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_update_with_group_assignee + group = Group.find(11) + project = Project.find(1) + project.members << Member.new(:principal => group, :roles => [Role.givable.first]) + + @request.session[:user_id] = 2 + # update issues assignee + post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing', + :issue => {:priority_id => '', + :assigned_to_id => group.id, + :custom_field_values => {'2' => ''}} + + assert_response 302 + assert_equal [group, group], Issue.find_all_by_id([1, 2]).collect {|i| i.assigned_to} + end + + def test_bulk_update_on_different_projects + @request.session[:user_id] = 2 + # update issues priority + post :bulk_update, :ids => [1, 2, 6], :notes => 'Bulk editing', + :issue => {:priority_id => 7, + :assigned_to_id => '', + :custom_field_values => {'2' => ''}} + + assert_response 302 + # check that the issues were updated + assert_equal [7, 7, 7], Issue.find([1,2,6]).map(&:priority_id) + + issue = Issue.find(1) + journal = issue.journals.reorder('created_on DESC').first + 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_update_on_different_projects_without_rights + @request.session[:user_id] = 3 + user = User.find(3) + action = { :controller => "issues", :action => "bulk_update" } + assert user.allowed_to?(action, Issue.find(1).project) + assert ! user.allowed_to?(action, Issue.find(6).project) + post :bulk_update, :ids => [1, 6], :notes => 'Bulk should fail', + :issue => {:priority_id => 7, + :assigned_to_id => '', + :custom_field_values => {'2' => ''}} + assert_response 403 + assert_not_equal "Bulk should fail", Journal.last.notes + end + + def test_bullk_update_should_send_a_notification + @request.session[:user_id] = 2 + ActionMailer::Base.deliveries.clear + post(:bulk_update, + { + :ids => [1, 2], + :notes => 'Bulk editing', + :issue => { + :priority_id => 7, + :assigned_to_id => '', + :custom_field_values => {'2' => ''} + } + }) + + assert_response 302 + assert_equal 2, ActionMailer::Base.deliveries.size + end + + def test_bulk_update_project + @request.session[:user_id] = 2 + post :bulk_update, :ids => [1, 2], :issue => {:project_id => '2'} + assert_redirected_to :controller => 'issues', :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 + # No tracker change + assert_equal 1, Issue.find(1).tracker_id + assert_equal 2, Issue.find(2).tracker_id + end + + def test_bulk_update_project_on_single_issue_should_follow_when_needed + @request.session[:user_id] = 2 + post :bulk_update, :id => 1, :issue => {:project_id => '2'}, :follow => '1' + assert_redirected_to '/issues/1' + end + + def test_bulk_update_project_on_multiple_issues_should_follow_when_needed + @request.session[:user_id] = 2 + post :bulk_update, :id => [1, 2], :issue => {:project_id => '2'}, :follow => '1' + assert_redirected_to '/projects/onlinestore/issues' + end + + def test_bulk_update_tracker + @request.session[:user_id] = 2 + post :bulk_update, :ids => [1, 2], :issue => {:tracker_id => '2'} + assert_redirected_to :controller => 'issues', :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_update_status + @request.session[:user_id] = 2 + # update issues priority + post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing status', + :issue => {:priority_id => '', + :assigned_to_id => '', + :status_id => '5'} + + assert_response 302 + issue = Issue.find(1) + assert issue.closed? + end + + def test_bulk_update_priority + @request.session[:user_id] = 2 + post :bulk_update, :ids => [1, 2], :issue => {:priority_id => 6} + + assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook' + assert_equal 6, Issue.find(1).priority_id + assert_equal 6, Issue.find(2).priority_id + end + + def test_bulk_update_with_notes + @request.session[:user_id] = 2 + post :bulk_update, :ids => [1, 2], :notes => 'Moving two issues' + + assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook' + assert_equal 'Moving two issues', Issue.find(1).journals.sort_by(&:id).last.notes + assert_equal 'Moving two issues', Issue.find(2).journals.sort_by(&:id).last.notes + end + + def test_bulk_update_parent_id + IssueRelation.delete_all + + @request.session[:user_id] = 2 + post :bulk_update, :ids => [1, 3], + :notes => 'Bulk editing parent', + :issue => {:priority_id => '', :assigned_to_id => '', :status_id => '', :parent_issue_id => '2'} + + assert_response 302 + parent = Issue.find(2) + assert_equal parent.id, Issue.find(1).parent_id + assert_equal parent.id, Issue.find(3).parent_id + assert_equal [1, 3], parent.children.collect(&:id).sort + end + + def test_bulk_update_custom_field + @request.session[:user_id] = 2 + # update issues priority + post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing custom field', + :issue => {:priority_id => '', + :assigned_to_id => '', + :custom_field_values => {'2' => '777'}} + + assert_response 302 + + issue = Issue.find(1) + journal = issue.journals.reorder('created_on DESC').first + 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_update_custom_field_to_blank + @request.session[:user_id] = 2 + post :bulk_update, :ids => [1, 3], :notes => 'Bulk editing custom field', + :issue => {:priority_id => '', + :assigned_to_id => '', + :custom_field_values => {'1' => '__none__'}} + assert_response 302 + assert_equal '', Issue.find(1).custom_field_value(1) + assert_equal '', Issue.find(3).custom_field_value(1) + end + + def test_bulk_update_multi_custom_field + field = CustomField.find(1) + field.update_attribute :multiple, true + + @request.session[:user_id] = 2 + post :bulk_update, :ids => [1, 2, 3], :notes => 'Bulk editing multi custom field', + :issue => {:priority_id => '', + :assigned_to_id => '', + :custom_field_values => {'1' => ['MySQL', 'Oracle']}} + + assert_response 302 + + assert_equal ['MySQL', 'Oracle'], Issue.find(1).custom_field_value(1).sort + assert_equal ['MySQL', 'Oracle'], Issue.find(3).custom_field_value(1).sort + # the custom field is not associated with the issue tracker + assert_nil Issue.find(2).custom_field_value(1) + end + + def test_bulk_update_multi_custom_field_to_blank + field = CustomField.find(1) + field.update_attribute :multiple, true + + @request.session[:user_id] = 2 + post :bulk_update, :ids => [1, 3], :notes => 'Bulk editing multi custom field', + :issue => {:priority_id => '', + :assigned_to_id => '', + :custom_field_values => {'1' => ['__none__']}} + assert_response 302 + assert_equal [''], Issue.find(1).custom_field_value(1) + assert_equal [''], Issue.find(3).custom_field_value(1) + end + + def test_bulk_update_unassign + assert_not_nil Issue.find(2).assigned_to + @request.session[:user_id] = 2 + # unassign issues + post :bulk_update, :ids => [1, 2], :notes => 'Bulk unassigning', :issue => {:assigned_to_id => 'none'} + assert_response 302 + # check that the issues were updated + assert_nil Issue.find(2).assigned_to + end + + def test_post_bulk_update_should_allow_fixed_version_to_be_set_to_a_subproject + @request.session[:user_id] = 2 + + post :bulk_update, :ids => [1,2], :issue => {:fixed_version_id => 4} + + assert_response :redirect + issues = Issue.find([1,2]) + issues.each do |issue| + assert_equal 4, issue.fixed_version_id + assert_not_equal issue.project_id, issue.fixed_version.project_id + end + end + + def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter + @request.session[:user_id] = 2 + post :bulk_update, :ids => [1,2], :back_url => '/issues' + + assert_response :redirect + assert_redirected_to '/issues' + end + + def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host + @request.session[:user_id] = 2 + post :bulk_update, :ids => [1,2], :back_url => 'http://google.com' + + assert_response :redirect + assert_redirected_to :controller => 'issues', :action => 'index', :project_id => Project.find(1).identifier + end + + def test_bulk_update_with_failure_should_set_flash + @request.session[:user_id] = 2 + Issue.update_all("subject = ''", "id = 2") # Make it invalid + post :bulk_update, :ids => [1, 2], :issue => {:priority_id => 6} + + assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook' + assert_equal 'Failed to save 1 issue(s) on 2 selected: #2.', flash[:error] + end + + def test_get_bulk_copy + @request.session[:user_id] = 2 + get :bulk_edit, :ids => [1, 2, 3], :copy => '1' + assert_response :success + assert_template 'bulk_edit' + + issues = assigns(:issues) + assert_not_nil issues + assert_equal [1, 2, 3], issues.map(&:id).sort + + assert_select 'input[name=copy_attachments]' + end + + def test_bulk_copy_to_another_project + @request.session[:user_id] = 2 + assert_difference 'Issue.count', 2 do + assert_no_difference 'Project.find(1).issues.count' do + post :bulk_update, :ids => [1, 2], :issue => {:project_id => '2'}, :copy => '1' + end + end + assert_redirected_to '/projects/ecookbook/issues' + + copies = Issue.all(:order => 'id DESC', :limit => issues.size) + copies.each do |copy| + assert_equal 2, copy.project_id + end + end + + def test_bulk_copy_should_allow_not_changing_the_issue_attributes + @request.session[:user_id] = 2 + issues = [ + Issue.create!(:project_id => 1, :tracker_id => 1, :status_id => 1, :priority_id => 2, :subject => 'issue 1', :author_id => 1, :assigned_to_id => nil), + Issue.create!(:project_id => 2, :tracker_id => 3, :status_id => 2, :priority_id => 1, :subject => 'issue 2', :author_id => 2, :assigned_to_id => 3) + ] + + assert_difference 'Issue.count', issues.size do + post :bulk_update, :ids => issues.map(&:id), :copy => '1', + :issue => { + :project_id => '', :tracker_id => '', :assigned_to_id => '', + :status_id => '', :start_date => '', :due_date => '' + } + end + + copies = Issue.all(:order => 'id DESC', :limit => issues.size) + issues.each do |orig| + copy = copies.detect {|c| c.subject == orig.subject} + assert_not_nil copy + assert_equal orig.project_id, copy.project_id + assert_equal orig.tracker_id, copy.tracker_id + assert_equal orig.status_id, copy.status_id + assert_equal orig.assigned_to_id, copy.assigned_to_id + assert_equal orig.priority_id, copy.priority_id + end + end + + def test_bulk_copy_should_allow_changing_the_issue_attributes + # Fixes random test failure with Mysql + # where Issue.all(:limit => 2, :order => 'id desc', :conditions => {:project_id => 2}) + # doesn't return the expected results + Issue.delete_all("project_id=2") + + @request.session[:user_id] = 2 + assert_difference 'Issue.count', 2 do + assert_no_difference 'Project.find(1).issues.count' do + post :bulk_update, :ids => [1, 2], :copy => '1', + :issue => { + :project_id => '2', :tracker_id => '', :assigned_to_id => '4', + :status_id => '1', :start_date => '2009-12-01', :due_date => '2009-12-31' + } + end + end + + copied_issues = Issue.all(:limit => 2, :order => 'id desc', :conditions => {:project_id => 2}) + assert_equal 2, copied_issues.size + copied_issues.each do |issue| + assert_equal 2, issue.project_id, "Project is incorrect" + assert_equal 4, issue.assigned_to_id, "Assigned to is incorrect" + assert_equal 1, issue.status_id, "Status is incorrect" + assert_equal '2009-12-01', issue.start_date.to_s, "Start date is incorrect" + assert_equal '2009-12-31', issue.due_date.to_s, "Due date is incorrect" + end + end + + def test_bulk_copy_should_allow_adding_a_note + @request.session[:user_id] = 2 + assert_difference 'Issue.count', 1 do + post :bulk_update, :ids => [1], :copy => '1', + :notes => 'Copying one issue', + :issue => { + :project_id => '', :tracker_id => '', :assigned_to_id => '4', + :status_id => '3', :start_date => '2009-12-01', :due_date => '2009-12-31' + } + end + + issue = Issue.first(:order => 'id DESC') + assert_equal 1, issue.journals.size + journal = issue.journals.first + assert_equal 0, journal.details.size + assert_equal 'Copying one issue', journal.notes + end + + def test_bulk_copy_should_allow_not_copying_the_attachments + attachment_count = Issue.find(3).attachments.size + assert attachment_count > 0 + @request.session[:user_id] = 2 + + assert_difference 'Issue.count', 1 do + assert_no_difference 'Attachment.count' do + post :bulk_update, :ids => [3], :copy => '1', + :issue => { + :project_id => '' + } + end + end + end + + def test_bulk_copy_should_allow_copying_the_attachments + attachment_count = Issue.find(3).attachments.size + assert attachment_count > 0 + @request.session[:user_id] = 2 + + assert_difference 'Issue.count', 1 do + assert_difference 'Attachment.count', attachment_count do + post :bulk_update, :ids => [3], :copy => '1', :copy_attachments => '1', + :issue => { + :project_id => '' + } + end + end + end + + def test_bulk_copy_should_add_relations_with_copied_issues + @request.session[:user_id] = 2 + + assert_difference 'Issue.count', 2 do + assert_difference 'IssueRelation.count', 2 do + post :bulk_update, :ids => [1, 3], :copy => '1', + :issue => { + :project_id => '1' + } + end + end + end + + def test_bulk_copy_should_allow_not_copying_the_subtasks + issue = Issue.generate_with_descendants! + @request.session[:user_id] = 2 + + assert_difference 'Issue.count', 1 do + post :bulk_update, :ids => [issue.id], :copy => '1', + :issue => { + :project_id => '' + } + end + end + + def test_bulk_copy_should_allow_copying_the_subtasks + issue = Issue.generate_with_descendants! + count = issue.descendants.count + @request.session[:user_id] = 2 + + assert_difference 'Issue.count', count+1 do + post :bulk_update, :ids => [issue.id], :copy => '1', :copy_subtasks => '1', + :issue => { + :project_id => '' + } + end + copy = Issue.where(:parent_id => nil).order("id DESC").first + assert_equal count, copy.descendants.count + end + + def test_bulk_copy_should_not_copy_selected_subtasks_twice + issue = Issue.generate_with_descendants! + count = issue.descendants.count + @request.session[:user_id] = 2 + + assert_difference 'Issue.count', count+1 do + post :bulk_update, :ids => issue.self_and_descendants.map(&:id), :copy => '1', :copy_subtasks => '1', + :issue => { + :project_id => '' + } + end + copy = Issue.where(:parent_id => nil).order("id DESC").first + assert_equal count, copy.descendants.count + end + + def test_bulk_copy_to_another_project_should_follow_when_needed + @request.session[:user_id] = 2 + post :bulk_update, :ids => [1], :copy => '1', :issue => {:project_id => 2}, :follow => '1' + issue = Issue.first(:order => 'id DESC') + assert_redirected_to :controller => 'issues', :action => 'show', :id => issue + end + + def test_destroy_issue_with_no_time_entries + assert_nil TimeEntry.find_by_issue_id(2) + @request.session[:user_id] = 2 + + assert_difference 'Issue.count', -1 do + delete :destroy, :id => 2 + end + assert_redirected_to :action => 'index', :project_id => 'ecookbook' + assert_nil Issue.find_by_id(2) + end + + def test_destroy_issues_with_time_entries + @request.session[:user_id] = 2 + + assert_no_difference 'Issue.count' do + delete :destroy, :ids => [1, 3] + end + assert_response :success + assert_template 'destroy' + assert_not_nil assigns(:hours) + assert Issue.find_by_id(1) && Issue.find_by_id(3) + + assert_select 'form' do + assert_select 'input[name=_method][value=delete]' + end + end + + def test_destroy_issues_and_destroy_time_entries + @request.session[:user_id] = 2 + + assert_difference 'Issue.count', -2 do + assert_difference 'TimeEntry.count', -3 do + delete :destroy, :ids => [1, 3], :todo => 'destroy' + end + end + 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 + + def test_destroy_issues_and_assign_time_entries_to_project + @request.session[:user_id] = 2 + + assert_difference 'Issue.count', -2 do + assert_no_difference 'TimeEntry.count' do + delete :destroy, :ids => [1, 3], :todo => 'nullify' + end + end + 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 + end + + def test_destroy_issues_and_reassign_time_entries_to_another_issue + @request.session[:user_id] = 2 + + assert_difference 'Issue.count', -2 do + assert_no_difference 'TimeEntry.count' do + delete :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2 + end + end + 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_issues_from_different_projects + @request.session[:user_id] = 2 + + assert_difference 'Issue.count', -3 do + delete :destroy, :ids => [1, 2, 6], :todo => 'destroy' + end + assert_redirected_to :controller => 'issues', :action => 'index' + assert !(Issue.find_by_id(1) || Issue.find_by_id(2) || Issue.find_by_id(6)) + end + + def test_destroy_parent_and_child_issues + parent = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'Parent Issue') + child = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'Child Issue', :parent_issue_id => parent.id) + assert child.is_descendant_of?(parent.reload) + + @request.session[:user_id] = 2 + assert_difference 'Issue.count', -2 do + delete :destroy, :ids => [parent.id, child.id], :todo => 'destroy' + end + assert_response 302 + end + + def test_destroy_invalid_should_respond_with_404 + @request.session[:user_id] = 2 + assert_no_difference 'Issue.count' do + delete :destroy, :id => 999 + end + assert_response 404 + end + + def test_default_search_scope + get :index + + assert_select 'div#quick-search form' do + assert_select 'input[name=issues][value=1][type=hidden]' + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/78/78cbb9f26be10a59d967769f645d28516059e6e0.svn-base --- a/.svn/pristine/78/78cbb9f26be10a59d967769f645d28516059e6e0.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,126 +0,0 @@ -// ** I18N - -// Calendar EN language -// Author: Mihai Bazon, -// 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 -r 0a574315af3e -r 4f746d8966dd .svn/pristine/78/78d4b11a09b045fdd7cd2b6a434ac47f6be2b36b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/78/78d4b11a09b045fdd7cd2b6a434ac47f6be2b36b.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,38 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class RoutingContextMenusTest < ActionController::IntegrationTest + def test_context_menus_time_entries + ["get", "post"].each do |method| + assert_routing( + { :method => method, :path => "/time_entries/context_menu" }, + { :controller => 'context_menus', :action => 'time_entries' } + ) + end + end + + def test_context_menus_issues + ["get", "post"].each do |method| + assert_routing( + { :method => method, :path => "/issues/context_menu" }, + { :controller => 'context_menus', :action => 'issues' } + ) + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/78/78dfa12979bae9d902d8a61dfe74ca64de496f0f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/78/78dfa12979bae9d902d8a61dfe74ca64de496f0f.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,810 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2013 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 'tcpdf' +require 'fpdf/chinese' +require 'fpdf/japanese' +require 'fpdf/korean' + +if RUBY_VERSION < '1.9' + require 'iconv' +end + +module Redmine + module Export + module PDF + include ActionView::Helpers::TextHelper + include ActionView::Helpers::NumberHelper + include IssuesHelper + + class ITCPDF < TCPDF + include Redmine::I18n + attr_accessor :footer_date + + def initialize(lang, orientation='P') + @@k_path_cache = Rails.root.join('tmp', 'pdf') + FileUtils.mkdir_p @@k_path_cache unless File::exist?(@@k_path_cache) + set_language_if_valid lang + pdf_encoding = l(:general_pdf_encoding).upcase + super(orientation, 'mm', 'A4', (pdf_encoding == 'UTF-8'), pdf_encoding) + case current_language.to_s.downcase + when 'vi' + @font_for_content = 'DejaVuSans' + @font_for_footer = 'DejaVuSans' + else + case pdf_encoding + when 'UTF-8' + @font_for_content = 'FreeSans' + @font_for_footer = 'FreeSans' + when 'CP949' + extend(PDF_Korean) + AddUHCFont() + @font_for_content = 'UHC' + @font_for_footer = 'UHC' + when 'CP932', 'SJIS', 'SHIFT_JIS' + extend(PDF_Japanese) + AddSJISFont() + @font_for_content = 'SJIS' + @font_for_footer = 'SJIS' + when 'GB18030' + extend(PDF_Chinese) + AddGBFont() + @font_for_content = 'GB' + @font_for_footer = 'GB' + when 'BIG5' + extend(PDF_Chinese) + AddBig5Font() + @font_for_content = 'Big5' + @font_for_footer = 'Big5' + else + @font_for_content = 'Arial' + @font_for_footer = 'Helvetica' + end + end + SetCreator(Redmine::Info.app_name) + SetFont(@font_for_content) + @outlines = [] + @outlineRoot = nil + end + + def SetFontStyle(style, size) + SetFont(@font_for_content, style, size) + end + + def SetTitle(txt) + txt = begin + utf16txt = to_utf16(txt) + hextxt = "" + rescue + txt + end || '' + super(txt) + end + + def textstring(s) + # Format a text string + if s =~ /^\{\{([<>]?)toc\}\}<\/p>/i, '') + html + end + + # Encodes an UTF-8 string to UTF-16BE + def to_utf16(str) + if str.respond_to?(:encode) + str.encode('UTF-16BE') + else + Iconv.conv('UTF-16BE', 'UTF-8', str) + end + end + + def RDMCell(w ,h=0, txt='', border=0, ln=0, align='', fill=0, link='') + Cell(w, h, fix_text_encoding(txt), border, ln, align, fill, link) + end + + def RDMMultiCell(w, h=0, txt='', border=0, align='', fill=0, ln=1) + MultiCell(w, h, fix_text_encoding(txt), border, align, fill, ln) + end + + def RDMwriteHTMLCell(w, h, x, y, txt='', attachments=[], border=0, ln=1, fill=0) + @attachments = attachments + writeHTMLCell(w, h, x, y, + fix_text_encoding(formatted_text(txt)), + border, ln, fill) + end + + def getImageFilename(attrname) + # attrname: general_pdf_encoding string file/uri name + atta = RDMPdfEncoding.attach(@attachments, attrname, l(:general_pdf_encoding)) + if atta + return atta.diskfile + else + return nil + end + end + + def Footer + SetFont(@font_for_footer, 'I', 8) + SetY(-15) + SetX(15) + RDMCell(0, 5, @footer_date, 0, 0, 'L') + SetY(-15) + SetX(-30) + RDMCell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C') + end + + def Bookmark(txt, level=0, y=0) + if (y == -1) + y = GetY() + end + @outlines << {:t => txt, :l => level, :p => PageNo(), :y => (@h - y)*@k} + end + + def bookmark_title(txt) + txt = begin + utf16txt = to_utf16(txt) + hextxt = "" + rescue + txt + end || '' + end + + def putbookmarks + nb=@outlines.size + return if (nb==0) + lru=[] + level=0 + @outlines.each_with_index do |o, i| + if(o[:l]>0) + parent=lru[o[:l]-1] + #Set parent and last pointers + @outlines[i][:parent]=parent + @outlines[parent][:last]=i + if (o[:l]>level) + #Level increasing: set first pointer + @outlines[parent][:first]=i + end + else + @outlines[i][:parent]=nb + end + if (o[:l]<=level && i>0) + #Set prev and next pointers + prev=lru[o[:l]] + @outlines[prev][:next]=i + @outlines[i][:prev]=prev + end + lru[o[:l]]=i + level=o[:l] + end + #Outline items + n=self.n+1 + @outlines.each_with_index do |o, i| + newobj() + out('<>') + out('endobj') + end + #Outline root + newobj() + @outlineRoot=self.n + out("<>"); + out('endobj'); + end + + def putresources() + super + putbookmarks() + end + + def putcatalog() + super + if(@outlines.size > 0) + out("/Outlines #{@outlineRoot} 0 R"); + out('/PageMode /UseOutlines'); + end + end + end + + # fetch row values + def fetch_row_values(issue, query, level) + query.inline_columns.collect do |column| + s = if column.is_a?(QueryCustomFieldColumn) + cv = issue.custom_field_values.detect {|v| v.custom_field_id == column.custom_field.id} + show_value(cv) + else + value = issue.send(column.name) + if column.name == :subject + value = " " * level + value + end + if value.is_a?(Date) + format_date(value) + elsif value.is_a?(Time) + format_time(value) + else + value + end + end + s.to_s + end + end + + # calculate columns width + def calc_col_width(issues, query, table_width, pdf) + # calculate statistics + # by captions + pdf.SetFontStyle('B',8) + col_padding = pdf.GetStringWidth('OO') + col_width_min = query.inline_columns.map {|v| pdf.GetStringWidth(v.caption) + col_padding} + col_width_max = Array.new(col_width_min) + col_width_avg = Array.new(col_width_min) + word_width_max = query.inline_columns.map {|c| + n = 10 + c.caption.split.each {|w| + x = pdf.GetStringWidth(w) + col_padding + n = x if n < x + } + n + } + + # by properties of issues + pdf.SetFontStyle('',8) + col_padding = pdf.GetStringWidth('OO') + k = 1 + issue_list(issues) {|issue, level| + k += 1 + values = fetch_row_values(issue, query, level) + values.each_with_index {|v,i| + n = pdf.GetStringWidth(v) + col_padding + col_width_max[i] = n if col_width_max[i] < n + col_width_min[i] = n if col_width_min[i] > n + col_width_avg[i] += n + v.split.each {|w| + x = pdf.GetStringWidth(w) + col_padding + word_width_max[i] = x if word_width_max[i] < x + } + } + } + col_width_avg.map! {|x| x / k} + + # calculate columns width + ratio = table_width / col_width_avg.inject(0) {|s,w| s += w} + col_width = col_width_avg.map {|w| w * ratio} + + # correct max word width if too many columns + ratio = table_width / word_width_max.inject(0) {|s,w| s += w} + word_width_max.map! {|v| v * ratio} if ratio < 1 + + # correct and lock width of some columns + done = 1 + col_fix = [] + col_width.each_with_index do |w,i| + if w > col_width_max[i] + col_width[i] = col_width_max[i] + col_fix[i] = 1 + done = 0 + elsif w < word_width_max[i] + col_width[i] = word_width_max[i] + col_fix[i] = 1 + done = 0 + else + col_fix[i] = 0 + end + end + + # iterate while need to correct and lock coluns width + while done == 0 + # calculate free & locked columns width + done = 1 + fix_col_width = 0 + free_col_width = 0 + col_width.each_with_index do |w,i| + if col_fix[i] == 1 + fix_col_width += w + else + free_col_width += w + end + end + + # calculate column normalizing ratio + if free_col_width == 0 + ratio = table_width / col_width.inject(0) {|s,w| s += w} + else + ratio = (table_width - fix_col_width) / free_col_width + end + + # correct columns width + col_width.each_with_index do |w,i| + if col_fix[i] == 0 + col_width[i] = w * ratio + + # check if column width less then max word width + if col_width[i] < word_width_max[i] + col_width[i] = word_width_max[i] + col_fix[i] = 1 + done = 0 + elsif col_width[i] > col_width_max[i] + col_width[i] = col_width_max[i] + col_fix[i] = 1 + done = 0 + end + end + end + end + col_width + end + + def render_table_header(pdf, query, col_width, row_height, table_width) + # headers + pdf.SetFontStyle('B',8) + pdf.SetFillColor(230, 230, 230) + + # render it background to find the max height used + base_x = pdf.GetX + base_y = pdf.GetY + max_height = issues_to_pdf_write_cells(pdf, query.inline_columns, col_width, row_height, true) + pdf.Rect(base_x, base_y, table_width, max_height, 'FD'); + pdf.SetXY(base_x, base_y); + + # write the cells on page + issues_to_pdf_write_cells(pdf, query.inline_columns, col_width, row_height, true) + issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, 0, col_width) + pdf.SetY(base_y + max_height); + + # rows + pdf.SetFontStyle('',8) + pdf.SetFillColor(255, 255, 255) + end + + # Returns a PDF string of a list of issues + def issues_to_pdf(issues, project, query) + pdf = ITCPDF.new(current_language, "L") + title = query.new_record? ? l(:label_issue_plural) : query.name + title = "#{project} - #{title}" if project + pdf.SetTitle(title) + pdf.alias_nb_pages + pdf.footer_date = format_date(Date.today) + pdf.SetAutoPageBreak(false) + pdf.AddPage("L") + + # Landscape A4 = 210 x 297 mm + page_height = 210 + page_width = 297 + left_margin = 10 + right_margin = 10 + bottom_margin = 20 + row_height = 4 + + # column widths + table_width = page_width - right_margin - left_margin + col_width = [] + unless query.inline_columns.empty? + col_width = calc_col_width(issues, query, table_width, pdf) + table_width = col_width.inject(0) {|s,v| s += v} + end + + # use full width if the description is displayed + if table_width > 0 && query.has_column?(:description) + col_width = col_width.map {|w| w * (page_width - right_margin - left_margin) / table_width} + table_width = col_width.inject(0) {|s,v| s += v} + end + + # title + pdf.SetFontStyle('B',11) + pdf.RDMCell(190,10, title) + pdf.Ln + render_table_header(pdf, query, col_width, row_height, table_width) + previous_group = false + issue_list(issues) do |issue, level| + if query.grouped? && + (group = query.group_by_column.value(issue)) != previous_group + pdf.SetFontStyle('B',10) + group_label = group.blank? ? 'None' : group.to_s.dup + group_label << " (#{query.issue_count_by_group[group]})" + pdf.Bookmark group_label, 0, -1 + pdf.RDMCell(table_width, row_height * 2, group_label, 1, 1, 'L') + pdf.SetFontStyle('',8) + previous_group = group + end + + # fetch row values + col_values = fetch_row_values(issue, query, level) + + # render it off-page to find the max height used + base_x = pdf.GetX + base_y = pdf.GetY + pdf.SetY(2 * page_height) + max_height = issues_to_pdf_write_cells(pdf, col_values, col_width, row_height) + pdf.SetXY(base_x, base_y) + + # make new page if it doesn't fit on the current one + space_left = page_height - base_y - bottom_margin + if max_height > space_left + pdf.AddPage("L") + render_table_header(pdf, query, col_width, row_height, table_width) + base_x = pdf.GetX + base_y = pdf.GetY + end + + # write the cells on page + issues_to_pdf_write_cells(pdf, col_values, col_width, row_height) + issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, 0, col_width) + pdf.SetY(base_y + max_height); + + if query.has_column?(:description) && issue.description? + pdf.SetX(10) + pdf.SetAutoPageBreak(true, 20) + pdf.RDMwriteHTMLCell(0, 5, 10, 0, issue.description.to_s, issue.attachments, "LRBT") + pdf.SetAutoPageBreak(false) + end + end + + if issues.size == Setting.issues_export_limit.to_i + pdf.SetFontStyle('B',10) + pdf.RDMCell(0, row_height, '...') + end + pdf.Output + end + + # Renders MultiCells and returns the maximum height used + def issues_to_pdf_write_cells(pdf, col_values, col_widths, + row_height, head=false) + base_y = pdf.GetY + max_height = row_height + col_values.each_with_index do |column, i| + col_x = pdf.GetX + if head == true + pdf.RDMMultiCell(col_widths[i], row_height, column.caption, "T", 'L', 1) + else + pdf.RDMMultiCell(col_widths[i], row_height, column, "T", 'L', 1) + end + max_height = (pdf.GetY - base_y) if (pdf.GetY - base_y) > max_height + pdf.SetXY(col_x + col_widths[i], base_y); + end + return max_height + end + + # Draw lines to close the row (MultiCell border drawing in not uniform) + # + # parameter "col_id_width" is not used. it is kept for compatibility. + def issues_to_pdf_draw_borders(pdf, top_x, top_y, lower_y, + col_id_width, col_widths) + col_x = top_x + pdf.Line(col_x, top_y, col_x, lower_y) # id right border + col_widths.each do |width| + col_x += width + pdf.Line(col_x, top_y, col_x, lower_y) # columns right border + end + pdf.Line(top_x, top_y, top_x, lower_y) # left border + pdf.Line(top_x, lower_y, col_x, lower_y) # bottom border + end + + # Returns a PDF string of a single issue + def issue_to_pdf(issue, assoc={}) + pdf = ITCPDF.new(current_language) + pdf.SetTitle("#{issue.project} - #{issue.tracker} ##{issue.id}") + pdf.alias_nb_pages + pdf.footer_date = format_date(Date.today) + pdf.AddPage + pdf.SetFontStyle('B',11) + buf = "#{issue.project} - #{issue.tracker} ##{issue.id}" + pdf.RDMMultiCell(190, 5, buf) + pdf.SetFontStyle('',8) + base_x = pdf.GetX + i = 1 + issue.ancestors.visible.each do |ancestor| + pdf.SetX(base_x + i) + buf = "#{ancestor.tracker} # #{ancestor.id} (#{ancestor.status.to_s}): #{ancestor.subject}" + pdf.RDMMultiCell(190 - i, 5, buf) + i += 1 if i < 35 + end + pdf.SetFontStyle('B',11) + pdf.RDMMultiCell(190 - i, 5, issue.subject.to_s) + pdf.SetFontStyle('',8) + pdf.RDMMultiCell(190, 5, "#{format_time(issue.created_on)} - #{issue.author}") + pdf.Ln + + left = [] + left << [l(:field_status), issue.status] + left << [l(:field_priority), issue.priority] + left << [l(:field_assigned_to), issue.assigned_to] unless issue.disabled_core_fields.include?('assigned_to_id') + left << [l(:field_category), issue.category] unless issue.disabled_core_fields.include?('category_id') + left << [l(:field_fixed_version), issue.fixed_version] unless issue.disabled_core_fields.include?('fixed_version_id') + + right = [] + right << [l(:field_start_date), format_date(issue.start_date)] unless issue.disabled_core_fields.include?('start_date') + right << [l(:field_due_date), format_date(issue.due_date)] unless issue.disabled_core_fields.include?('due_date') + right << [l(:field_done_ratio), "#{issue.done_ratio}%"] unless issue.disabled_core_fields.include?('done_ratio') + right << [l(:field_estimated_hours), l_hours(issue.estimated_hours)] unless issue.disabled_core_fields.include?('estimated_hours') + right << [l(:label_spent_time), l_hours(issue.total_spent_hours)] if User.current.allowed_to?(:view_time_entries, issue.project) + + rows = left.size > right.size ? left.size : right.size + while left.size < rows + left << nil + end + while right.size < rows + right << nil + end + + half = (issue.custom_field_values.size / 2.0).ceil + issue.custom_field_values.each_with_index do |custom_value, i| + (i < half ? left : right) << [custom_value.custom_field.name, show_value(custom_value)] + end + + rows = left.size > right.size ? left.size : right.size + rows.times do |i| + item = left[i] + pdf.SetFontStyle('B',9) + pdf.RDMCell(35,5, item ? "#{item.first}:" : "", i == 0 ? "LT" : "L") + pdf.SetFontStyle('',9) + pdf.RDMCell(60,5, item ? item.last.to_s : "", i == 0 ? "RT" : "R") + + item = right[i] + pdf.SetFontStyle('B',9) + pdf.RDMCell(35,5, item ? "#{item.first}:" : "", i == 0 ? "LT" : "L") + pdf.SetFontStyle('',9) + pdf.RDMCell(60,5, item ? item.last.to_s : "", i == 0 ? "RT" : "R") + pdf.Ln + end + + pdf.SetFontStyle('B',9) + pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1) + pdf.SetFontStyle('',9) + + # Set resize image scale + pdf.SetImageScale(1.6) + pdf.RDMwriteHTMLCell(35+155, 5, 0, 0, + issue.description.to_s, issue.attachments, "LRB") + + unless issue.leaf? + # for CJK + truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 90 : 65 ) + + pdf.SetFontStyle('B',9) + pdf.RDMCell(35+155,5, l(:label_subtask_plural) + ":", "LTR") + pdf.Ln + issue_list(issue.descendants.visible.sort_by(&:lft)) do |child, level| + buf = truncate("#{child.tracker} # #{child.id}: #{child.subject}", + :length => truncate_length) + level = 10 if level >= 10 + pdf.SetFontStyle('',8) + pdf.RDMCell(35+135,5, (level >=1 ? " " * level : "") + buf, "L") + pdf.SetFontStyle('B',8) + pdf.RDMCell(20,5, child.status.to_s, "R") + pdf.Ln + end + end + + relations = issue.relations.select { |r| r.other_issue(issue).visible? } + unless relations.empty? + # for CJK + truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 80 : 60 ) + + pdf.SetFontStyle('B',9) + pdf.RDMCell(35+155,5, l(:label_related_issues) + ":", "LTR") + pdf.Ln + relations.each do |relation| + buf = "" + buf += "#{l(relation.label_for(issue))} " + if relation.delay && relation.delay != 0 + buf += "(#{l('datetime.distance_in_words.x_days', :count => relation.delay)}) " + end + if Setting.cross_project_issue_relations? + buf += "#{relation.other_issue(issue).project} - " + end + buf += "#{relation.other_issue(issue).tracker}" + + " # #{relation.other_issue(issue).id}: #{relation.other_issue(issue).subject}" + buf = truncate(buf, :length => truncate_length) + pdf.SetFontStyle('', 8) + pdf.RDMCell(35+155-60, 5, buf, "L") + pdf.SetFontStyle('B',8) + pdf.RDMCell(20,5, relation.other_issue(issue).status.to_s, "") + pdf.RDMCell(20,5, format_date(relation.other_issue(issue).start_date), "") + pdf.RDMCell(20,5, format_date(relation.other_issue(issue).due_date), "R") + pdf.Ln + end + end + pdf.RDMCell(190,5, "", "T") + pdf.Ln + + if issue.changesets.any? && + User.current.allowed_to?(:view_changesets, issue.project) + pdf.SetFontStyle('B',9) + pdf.RDMCell(190,5, l(:label_associated_revisions), "B") + pdf.Ln + for changeset in issue.changesets + pdf.SetFontStyle('B',8) + csstr = "#{l(:label_revision)} #{changeset.format_identifier} - " + csstr += format_time(changeset.committed_on) + " - " + changeset.author.to_s + pdf.RDMCell(190, 5, csstr) + pdf.Ln + unless changeset.comments.blank? + pdf.SetFontStyle('',8) + pdf.RDMwriteHTMLCell(190,5,0,0, + changeset.comments.to_s, issue.attachments, "") + end + pdf.Ln + end + end + + if assoc[:journals].present? + pdf.SetFontStyle('B',9) + pdf.RDMCell(190,5, l(:label_history), "B") + pdf.Ln + assoc[:journals].each do |journal| + pdf.SetFontStyle('B',8) + title = "##{journal.indice} - #{format_time(journal.created_on)} - #{journal.user}" + title << " (#{l(:field_private_notes)})" if journal.private_notes? + pdf.RDMCell(190,5, title) + pdf.Ln + pdf.SetFontStyle('I',8) + details_to_strings(journal.details, true).each do |string| + pdf.RDMMultiCell(190,5, "- " + string) + end + if journal.notes? + pdf.Ln unless journal.details.empty? + pdf.SetFontStyle('',8) + pdf.RDMwriteHTMLCell(190,5,0,0, + journal.notes.to_s, issue.attachments, "") + end + pdf.Ln + end + end + + if issue.attachments.any? + pdf.SetFontStyle('B',9) + pdf.RDMCell(190,5, l(:label_attachment_plural), "B") + pdf.Ln + for attachment in issue.attachments + pdf.SetFontStyle('',8) + pdf.RDMCell(80,5, attachment.filename) + pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R") + pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R") + pdf.RDMCell(65,5, attachment.author.name,0,0,"R") + pdf.Ln + end + end + pdf.Output + end + + # Returns a PDF string of a set of wiki pages + def wiki_pages_to_pdf(pages, project) + pdf = ITCPDF.new(current_language) + pdf.SetTitle(project.name) + pdf.alias_nb_pages + pdf.footer_date = format_date(Date.today) + pdf.AddPage + pdf.SetFontStyle('B',11) + pdf.RDMMultiCell(190,5, project.name) + pdf.Ln + # Set resize image scale + pdf.SetImageScale(1.6) + pdf.SetFontStyle('',9) + write_page_hierarchy(pdf, pages.group_by(&:parent_id)) + pdf.Output + end + + # Returns a PDF string of a single wiki page + def wiki_page_to_pdf(page, project) + pdf = ITCPDF.new(current_language) + pdf.SetTitle("#{project} - #{page.title}") + pdf.alias_nb_pages + pdf.footer_date = format_date(Date.today) + pdf.AddPage + pdf.SetFontStyle('B',11) + pdf.RDMMultiCell(190,5, + "#{project} - #{page.title} - # #{page.content.version}") + pdf.Ln + # Set resize image scale + pdf.SetImageScale(1.6) + pdf.SetFontStyle('',9) + write_wiki_page(pdf, page) + pdf.Output + end + + def write_page_hierarchy(pdf, pages, node=nil, level=0) + if pages[node] + pages[node].each do |page| + if @new_page + pdf.AddPage + else + @new_page = true + end + pdf.Bookmark page.title, level + write_wiki_page(pdf, page) + write_page_hierarchy(pdf, pages, page.id, level + 1) if pages[page.id] + end + end + end + + def write_wiki_page(pdf, page) + pdf.RDMwriteHTMLCell(190,5,0,0, + page.content.text.to_s, page.attachments, 0) + if page.attachments.any? + pdf.Ln + pdf.SetFontStyle('B',9) + pdf.RDMCell(190,5, l(:label_attachment_plural), "B") + pdf.Ln + for attachment in page.attachments + pdf.SetFontStyle('',8) + pdf.RDMCell(80,5, attachment.filename) + pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R") + pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R") + pdf.RDMCell(65,5, attachment.author.name,0,0,"R") + pdf.Ln + end + end + end + + class RDMPdfEncoding + def self.rdm_from_utf8(txt, encoding) + txt ||= '' + txt = Redmine::CodesetUtil.from_utf8(txt, encoding) + if txt.respond_to?(:force_encoding) + txt.force_encoding('ASCII-8BIT') + end + txt + end + + def self.attach(attachments, filename, encoding) + filename_utf8 = Redmine::CodesetUtil.to_utf8(filename, encoding) + atta = nil + if filename_utf8 =~ /^[^\/"]+\.(gif|jpg|jpe|jpeg|png)$/i + atta = Attachment.latest_attach(attachments, filename_utf8) + end + if atta && atta.readable? && atta.visible? + return atta + else + return nil + end + end + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/79/791795b2319be82f4ccbdcd799aa17c64410dd34.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/79/791795b2319be82f4ccbdcd799aa17c64410dd34.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,126 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +namespace :redmine do + namespace :attachments do + desc 'Removes uploaded files left unattached after one day.' + task :prune => :environment do + Attachment.prune + end + + desc 'Moves attachments stored at the root of the file directory (ie. created before Redmine 2.3) to their subdirectories' + task :move_to_subdirectories => :environment do + Attachment.move_from_root_to_target_directory + end + end + + namespace :tokens do + desc 'Removes expired tokens.' + task :prune => :environment do + Token.destroy_expired + end + end + + namespace :watchers do + desc 'Removes watchers from what they can no longer view.' + task :prune => :environment do + Watcher.prune + end + end + + desc 'Fetch changesets from the repositories' + task :fetch_changesets => :environment do + Repository.fetch_changesets + end + + desc 'Migrates and copies plugins assets.' + task :plugins do + Rake::Task["redmine:plugins:migrate"].invoke + Rake::Task["redmine:plugins:assets"].invoke + end + + namespace :plugins do + desc 'Migrates installed plugins.' + task :migrate => :environment do + name = ENV['NAME'] + version = nil + version_string = ENV['VERSION'] + if version_string + if version_string =~ /^\d+$/ + version = version_string.to_i + if name.nil? + abort "The VERSION argument requires a plugin NAME." + end + else + abort "Invalid VERSION #{version_string} given." + end + end + + begin + Redmine::Plugin.migrate(name, version) + rescue Redmine::PluginNotFound + abort "Plugin #{name} was not found." + end + + Rake::Task["db:schema:dump"].invoke + end + + desc 'Copies plugins assets into the public directory.' + task :assets => :environment do + name = ENV['NAME'] + + begin + Redmine::Plugin.mirror_assets(name) + rescue Redmine::PluginNotFound + abort "Plugin #{name} was not found." + end + end + + desc 'Runs the plugins tests.' + task :test do + Rake::Task["redmine:plugins:test:units"].invoke + Rake::Task["redmine:plugins:test:functionals"].invoke + Rake::Task["redmine:plugins:test:integration"].invoke + end + + namespace :test do + desc 'Runs the plugins unit tests.' + Rake::TestTask.new :units => "db:test:prepare" do |t| + t.libs << "test" + t.verbose = true + t.pattern = "plugins/#{ENV['NAME'] || '*'}/test/unit/**/*_test.rb" + end + + desc 'Runs the plugins functional tests.' + Rake::TestTask.new :functionals => "db:test:prepare" do |t| + t.libs << "test" + t.verbose = true + t.pattern = "plugins/#{ENV['NAME'] || '*'}/test/functional/**/*_test.rb" + end + + desc 'Runs the plugins integration tests.' + Rake::TestTask.new :integration => "db:test:prepare" do |t| + t.libs << "test" + t.verbose = true + t.pattern = "plugins/#{ENV['NAME'] || '*'}/test/integration/**/*_test.rb" + end + end + end +end + +# Load plugins' rake tasks +Dir[File.join(Rails.root, "plugins/*/lib/tasks/**/*.rake")].sort.each { |ext| load ext } diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/79/79764e68981f37bed805c67099bd3a957d4119fe.svn-base --- a/.svn/pristine/79/79764e68981f37bed805c67099bd3a957d4119fe.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,128 +0,0 @@ -// ** I18N - -// Calendar EU language -// Author: Ales Zabala Alava (Shagi), -// 2010-01-25 -// 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 -("Igandea", - "Astelehena", - "Asteartea", - "Asteazkena", - "Osteguna", - "Ostirala", - "Larunbata", - "Igandea"); - -// 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 -("Ig.", - "Al.", - "Ar.", - "Az.", - "Og.", - "Or.", - "La.", - "Ig."); - -// 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 -("Urtarrila", - "Otsaila", - "Martxoa", - "Apirila", - "Maiatza", - "Ekaina", - "Uztaila", - "Abuztua", - "Iraila", - "Urria", - "Azaroa", - "Abendua"); - -// short month names -Calendar._SMN = new Array -("Urt", - "Ots", - "Mar", - "Api", - "Mai", - "Eka", - "Uzt", - "Abu", - "Ira", - "Urr", - "Aza", - "Abe"); - -// tooltips -Calendar._TT = {}; -Calendar._TT["INFO"] = "Egutegiari buruz"; - -Calendar._TT["ABOUT"] = -"DHTML Data/Ordu Hautatzailea\n" + -"(c) dynarch.com 2002-2005 / Egilea: Mihai Bazon\n" + // don't translate this this ;-) -"Azken bertsiorako: http://www.dynarch.com/projects/calendar/\n" + -"GNU LGPL Lizentziapean banatuta. Ikusi http://gnu.org/licenses/lgpl.html zehaztasunentzako." + -"\n\n" + -"Data hautapena:\n" + -"- Erabili \xab, \xbb botoiak urtea hautatzeko\n" + -"- Erabili " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " botoiak hilabeteak hautatzeko\n" + -"- Mantendu saguaren botoia edo goiko edozein botoi hautapena bizkortzeko."; -Calendar._TT["ABOUT_TIME"] = "\n\n" + -"Ordu hautapena:\n" + -"- Klikatu orduaren edozein zati handitzeko\n" + -"- edo Shift-klikatu txikiagotzeko\n" + -"- edo klikatu eta arrastatu hautapena bizkortzeko."; - -Calendar._TT["PREV_YEAR"] = "Aurreko urtea (mantendu menuarentzako)"; -Calendar._TT["PREV_MONTH"] = "Aurreko hilabetea (mantendu menuarentzako)"; -Calendar._TT["GO_TODAY"] = "Joan Gaur-era"; -Calendar._TT["NEXT_MONTH"] = "Hurrengo hilabetea (mantendu menuarentzako)"; -Calendar._TT["NEXT_YEAR"] = "Hurrengo urtea (mantendu menuarentzako)"; -Calendar._TT["SEL_DATE"] = "Data hautatu"; -Calendar._TT["DRAG_TO_MOVE"] = "Arrastatu mugitzeko"; -Calendar._TT["PART_TODAY"] = " (gaur)"; - -// 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"] = "Erakutsi %s lehenbizi"; - -// 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"] = "Itxi"; -Calendar._TT["TODAY"] = "Gaur"; -Calendar._TT["TIME_PART"] = "(Shift-)Klikatu edo arrastatu balioa aldatzeko"; - -// 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"] = "Ordua:"; diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/79/798951d93ef15b8ef54ff1fdd4cd90be5520c718.svn-base --- a/.svn/pristine/79/798951d93ef15b8ef54ff1fdd4cd90be5520c718.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,15 +0,0 @@ -

    <%=l(:label_information_plural)%>

    - -

    <%= Redmine::Info.versioned_name %> (<%= @db_adapter_name %>)

    - - -<% @checklist.each do |label, result| %> - - - - -<% end %> -
    <%= l(label) %><%= image_tag((result ? 'true.png' : 'exclamation.png'), - :style => "vertical-align:bottom;") %>
    - -<% html_title(l(:label_information_plural)) -%> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/79/79c8a939d3701e3feac652838cc1b0649abfd617.svn-base --- a/.svn/pristine/79/79c8a939d3701e3feac652838cc1b0649abfd617.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,155 +0,0 @@ - - - - - - - -
    - -<% query.available_filters.sort{|a,b| a[1][:order]<=>b[1][:order]}.each do |filter| %> - <% field = filter[0] - options = filter[1] %> - id="tr_<%= field %>" class="filter"> - - - - -<% end %> -
    - <%= check_box_tag 'f[]', field, query.has_filter?(field), :onclick => "toggle_filter('#{field}');", :id => "cb_#{field}" %> - - - <%= label_tag "op_#{field}", l(:description_filter), :class => "hidden-for-sighted" %> - <%= select_tag "op[#{field}]", options_for_select(operators_for_select(options[:type]), - query.operator_for(field)), :id => "operators_#{field}", - :onchange => "toggle_operator('#{field}');" %> - - - -
    -
    -<%= label_tag('add_filter_select', 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();", - :name => nil %> -
    -<%= hidden_field_tag 'f[]', '' %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/79/79e517db7aa41460bc02061dfd2de5b4541e346b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/79/79e517db7aa41460bc02061dfd2de5b4541e346b.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,31 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class RoutingSearchTest < ActionController::IntegrationTest + def test_search + assert_routing( + { :method => 'get', :path => "/search" }, + { :controller => 'search', :action => 'index' } + ) + assert_routing( + { :method => 'get', :path => "/projects/foo/search" }, + { :controller => 'search', :action => 'index', :id => 'foo' } + ) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/79/79ebb49968818fca414e007a7d073ecb0152e756.svn-base --- a/.svn/pristine/79/79ebb49968818fca414e007a7d073ecb0152e756.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,71 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../../../../../test_helper', __FILE__) - -begin - require 'mocha' - - class SubversionAdapterTest < ActiveSupport::TestCase - - if repository_configured?('subversion') - def setup - @adapter = Redmine::Scm::Adapters::SubversionAdapter.new(self.class.subversion_repository_url) - end - - def test_client_version - v = Redmine::Scm::Adapters::SubversionAdapter.client_version - assert v.is_a?(Array) - end - - def test_scm_version - to_test = { "svn, version 1.6.13 (r1002816)\n" => [1,6,13], - "svn, versione 1.6.13 (r1002816)\n" => [1,6,13], - "1.6.1\n1.7\n1.8" => [1,6,1], - "1.6.2\r\n1.8.1\r\n1.9.1" => [1,6,2]} - to_test.each do |s, v| - test_scm_version_for(s, v) - end - end - - def test_info_not_nil - assert_not_nil @adapter.info - end - - def test_info_nil - adpt = Redmine::Scm::Adapters::SubversionAdapter.new( - "file:///invalid/invalid/" - ) - assert_nil adpt.info - end - - private - - def test_scm_version_for(scm_version, version) - @adapter.class.expects(:scm_version_from_command_line).returns(scm_version) - assert_equal version, @adapter.class.svn_binary_version - end - else - puts "Subversion test repository NOT FOUND. Skipping unit tests !!!" - def test_fake; assert true end - end - end -rescue LoadError - class SubversionMochaFake < ActiveSupport::TestCase - def test_fake; assert(false, "Requires mocha to run those tests") end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7a/7a017fdd06e31fbf6cfa542010617fca4f01cf26.svn-base --- a/.svn/pristine/7a/7a017fdd06e31fbf6cfa542010617fca4f01cf26.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,88 +0,0 @@ -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. Since it's a - # decorational picture, the alt text should be empty according to the - # XHTML specs. - :alt => '', - - # The title text to use for the img tag for the gravatar. - :title => '', - - # The class to assign to the img tag for the gravatar. - :class => 'gravatar', - - # Whether or not to display the gravatars using HTTPS instead of HTTP - :ssl => false, - } - - # 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, :title].each { |opt| options[opt] = h(options[opt]) } - "\"#{options[:alt]}\"" - end - - # Returns the base Gravatar URL for the given email hash. If ssl evaluates to true, - # a secure URL will be used instead. This is required when the gravatar is to be - # displayed on a HTTPS site. - def gravatar_api_url(hash, ssl=false) - if ssl - "https://secure.gravatar.com/avatar/#{hash}" - else - "http://www.gravatar.com/avatar/#{hash}" - end - 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? - gravatar_api_url(email_hash, options.delete(:ssl)).tap do |url| - opts = [] - [:rating, :size, :default].each do |opt| - unless options[opt].nil? - value = h(options[opt]) - opts << [opt, value].join('=') - end - end - url << "?#{opts.join('&')}" unless opts.empty? - end - end - - end - -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7a/7a074cee45b6ea26f101ec0ee24551820d4e929e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/7a/7a074cee45b6ea26f101ec0ee24551820d4e929e.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,176 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class CustomFieldsControllerTest < ActionController::TestCase + fixtures :custom_fields, :custom_values, :trackers, :users + + def setup + @request.session[:user_id] = 1 + end + + def test_index + get :index + assert_response :success + assert_template 'index' + end + + def test_new + custom_field_classes.each do |klass| + get :new, :type => klass.name + assert_response :success + assert_template 'new' + assert_kind_of klass, assigns(:custom_field) + assert_select 'form#custom_field_form' do + assert_select 'select#custom_field_field_format[name=?]', 'custom_field[field_format]' + assert_select 'input[type=hidden][name=type][value=?]', klass.name + end + end + end + + def test_new_issue_custom_field + get :new, :type => 'IssueCustomField' + assert_response :success + assert_template 'new' + assert_select 'form#custom_field_form' do + assert_select 'select#custom_field_field_format[name=?]', 'custom_field[field_format]' do + assert_select 'option[value=user]', :text => 'User' + assert_select 'option[value=version]', :text => 'Version' + end + assert_select 'input[type=hidden][name=type][value=IssueCustomField]' + end + end + + def test_default_value_should_be_an_input_for_string_custom_field + get :new, :type => 'IssueCustomField', :custom_field => {:field_format => 'string'} + assert_response :success + assert_select 'input[name=?]', 'custom_field[default_value]' + end + + def test_default_value_should_be_a_textarea_for_text_custom_field + get :new, :type => 'IssueCustomField', :custom_field => {:field_format => 'text'} + assert_response :success + assert_select 'textarea[name=?]', 'custom_field[default_value]' + end + + def test_default_value_should_be_a_checkbox_for_bool_custom_field + get :new, :type => 'IssueCustomField', :custom_field => {:field_format => 'bool'} + assert_response :success + assert_select 'input[name=?][type=checkbox]', 'custom_field[default_value]' + end + + def test_default_value_should_not_be_present_for_user_custom_field + get :new, :type => 'IssueCustomField', :custom_field => {:field_format => 'user'} + assert_response :success + assert_select '[name=?]', 'custom_field[default_value]', 0 + end + + def test_new_js + get :new, :type => 'IssueCustomField', :custom_field => {:field_format => 'list'}, :format => 'js' + assert_response :success + assert_template 'new' + assert_equal 'text/javascript', response.content_type + + field = assigns(:custom_field) + assert_equal 'list', field.field_format + end + + def test_new_with_invalid_custom_field_class_should_render_404 + get :new, :type => 'UnknownCustomField' + assert_response 404 + end + + def test_create_list_custom_field + assert_difference 'CustomField.count' do + post :create, :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?tab=IssueCustomField' + 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_create_with_failure + assert_no_difference 'CustomField.count' do + post :create, :type => "IssueCustomField", :custom_field => {:name => ''} + end + assert_response :success + assert_template 'new' + end + + def test_edit + get :edit, :id => 1 + assert_response :success + assert_template 'edit' + assert_tag 'input', :attributes => {:name => 'custom_field[name]', :value => 'Database'} + end + + def test_edit_invalid_custom_field_should_render_404 + get :edit, :id => 99 + assert_response 404 + end + + def test_update + put :update, :id => 1, :custom_field => {:name => 'New name'} + assert_redirected_to '/custom_fields?tab=IssueCustomField' + + field = CustomField.find(1) + assert_equal 'New name', field.name + end + + def test_update_with_failure + put :update, :id => 1, :custom_field => {:name => ''} + assert_response :success + assert_template 'edit' + end + + def test_destroy + custom_values_count = CustomValue.count(:conditions => {:custom_field_id => 1}) + assert custom_values_count > 0 + + assert_difference 'CustomField.count', -1 do + assert_difference 'CustomValue.count', - custom_values_count do + delete :destroy, :id => 1 + end + end + + assert_redirected_to '/custom_fields?tab=IssueCustomField' + assert_nil CustomField.find_by_id(1) + assert_nil CustomValue.find_by_custom_field_id(1) + end + + def custom_field_classes + files = Dir.glob(File.join(Rails.root, 'app/models/*_custom_field.rb')).map {|f| File.basename(f).sub(/\.rb$/, '') } + classes = files.map(&:classify).map(&:constantize) + assert classes.size > 0 + classes + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7a/7a09a663c8bfe9a76d00f982be754422b428c51b.svn-base --- a/.svn/pristine/7a/7a09a663c8bfe9a76d00f982be754422b428c51b.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1061 +0,0 @@ -# Korean translations for Ruby on Rails -# by Kihyun Yoon(ddumbugie@gmail.com),http://plenum.textcube.com/ -# by John Hwang (jhwang@tavon.org),http://github.com/tavon -# by Yonghwan SO(please insert your email), last update at 2009-09-11 -# by Ki Won Kim(xyz37@naver.com, http://xyz37.blog.me, https://x10.mine.nu/redmine), last update at 2012-02-01 -# last update at 2012-02-02 by Ki Won Kim -ko: - direction: ltr - date: - formats: - # Use the strftime parameters for formats. - # When no format has been given, it uses default. - # You can provide other formats here if you like! - default: "%Y/%m/%d" - short: "%m/%d" - long: "%Yë…„ %mì›” %dì¼ (%a)" - - day_names: [ì¼ìš”ì¼, 월요ì¼, 화요ì¼, 수요ì¼, 목요ì¼, 금요ì¼, 토요ì¼] - abbr_day_names: [ì¼, ì›”, í™”, 수, 목, 금, 토] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, 1ì›”, 2ì›”, 3ì›”, 4ì›”, 5ì›”, 6ì›”, 7ì›”, 8ì›”, 9ì›”, 10ì›”, 11ì›”, 12ì›”] - abbr_month_names: [~, 1ì›”, 2ì›”, 3ì›”, 4ì›”, 5ì›”, 6ì›”, 7ì›”, 8ì›”, 9ì›”, 10ì›”, 11ì›”, 12ì›”] - # Used in date_select and datime_select. - order: - - :year - - :month - - :day - - time: - formats: - default: "%Y/%m/%d %H:%M:%S" - time: "%H:%M" - short: "%y/%m/%d %H:%M" - long: "%Yë…„ %Bì›” %dì¼, %H시 %Më¶„ %Sì´ˆ %Z" - am: "오전" - pm: "오후" - - datetime: - distance_in_words: - half_a_minute: "30ì´ˆ" - less_than_x_seconds: - one: "ì¼ì´ˆ ì´í•˜" - other: "%{count}ì´ˆ ì´í•˜" - x_seconds: - one: "ì¼ì´ˆ" - other: "%{count}ì´ˆ" - less_than_x_minutes: - one: "ì¼ë¶„ ì´í•˜" - other: "%{count}ë¶„ ì´í•˜" - x_minutes: - one: "ì¼ë¶„" - other: "%{count}ë¶„" - about_x_hours: - one: "약 한시간" - other: "약 %{count}시간" - x_days: - one: "하루" - other: "%{count}ì¼" - about_x_months: - one: "약 한달" - other: "약 %{count}달" - x_months: - one: "한달" - other: "%{count}달" - about_x_years: - one: "약 ì¼ë…„" - other: "약 %{count}ë…„" - over_x_years: - one: "ì¼ë…„ ì´ìƒ" - other: "%{count}ë…„ ì´ìƒ" - almost_x_years: - one: "약 1ë…„" - other: "약 %{count}ë…„" - prompts: - year: "ë…„" - month: "ì›”" - day: "ì¼" - hour: "시" - minute: "ë¶„" - second: "ì´ˆ" - - number: - # Used in number_with_delimiter() - # These are also the defaults for 'currency', 'percentage', 'precision', and 'human' - format: - # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5) - separator: "." - # Delimets thousands (e.g. 1,000,000 is a million) (always in groups of three) - delimiter: "," - # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00) - precision: 3 - - # Used in number_to_currency() - currency: - format: - # Where is the currency sign? %u is the currency unit, %n the number (default: $5.00) - format: "%u%n" - unit: "â‚©" - # These three are to override number.format and are optional - separator: "." - delimiter: "," - precision: 0 - - # Used in number_to_percentage() - percentage: - format: - # These three are to override number.format and are optional - # separator: - delimiter: "" - # precision: - - # Used in number_to_precision() - precision: - format: - # These three are to override number.format and are optional - # separator: - delimiter: "" - # precision: - - # Used in number_to_human_size() - human: - format: - # These three are to override number.format and are optional - # separator: - delimiter: "" - precision: 1 - storage_units: - format: "%n %u" - units: - byte: - one: "Byte" - other: "Bytes" - kb: "KB" - mb: "MB" - gb: "GB" - tb: "TB" - -# Used in array.to_sentence. - support: - array: - words_connector: ", " - two_words_connector: "ê³¼ " - last_word_connector: ", " - sentence_connector: "그리고" - skip_last_comma: false - - activerecord: - errors: - template: - header: - one: "í•œê°œì˜ ì˜¤ë¥˜ê°€ ë°œìƒí•´ %{model}ì„(를) 저장하지 않았습니다." - other: "%{count}ê°œì˜ ì˜¤ë¥˜ê°€ ë°œìƒí•´ %{model}ì„(를) 저장하지 않았습니다." - # The variable :count is also available - body: "ë‹¤ìŒ í•­ëª©ì— ë¬¸ì œê°€ 발견했습니다:" - - messages: - inclusion: "ì€ ëª©ë¡ì— í¬í•¨ë˜ì–´ 있지 않습니다" - exclusion: "ì€ ì˜ˆì•½ë˜ì–´ 있습니다" - invalid: "ì€ ìœ íš¨í•˜ì§€ 않습니다." - confirmation: "ì€ í™•ì¸ì´ ë˜ì§€ 않았습니다" - accepted: "ì€ ì¸ì •ë˜ì–´ì•¼ 합니다" - empty: "ì€ ê¸¸ì´ê°€ 0ì´ì–´ì„œëŠ” 안ë©ë‹ˆë‹¤." - blank: "ì€ ë¹ˆ ê°’ì´ì–´ì„œëŠ” 안 ë©ë‹ˆë‹¤" - too_long: "ì€ ë„ˆë¬´ ê¹ë‹ˆë‹¤ (최대 %{count}ìž ê¹Œì§€)" - too_short: "ì€ ë„ˆë¬´ 짧습니다 (최소 %{count}ìž ê¹Œì§€)" - wrong_length: "ì€ ê¸¸ì´ê°€ 틀렸습니다 (%{count}ìžì´ì–´ì•¼ 합니다.)" - taken: "ì€ ì´ë¯¸ ì„ íƒëœ ê²ë‹ˆë‹¤" - not_a_number: "ì€ ìˆ«ìžê°€ 아닙니다" - greater_than: "ì€ %{count}보다 커야 합니다." - greater_than_or_equal_to: "ì€ %{count}보다 í¬ê±°ë‚˜ 같아야 합니다" - equal_to: "ì€ %{count}(와)ê³¼ 같아야 합니다" - less_than: "ì€ %{count}보다 작어야 합니다" - less_than_or_equal_to: "ì€ %{count}ê³¼ 같거나 ì´í•˜ì„ 요구합니다" - odd: "ì€ í™€ìˆ˜ì—¬ì•¼ 합니다" - even: "ì€ ì§ìˆ˜ì—¬ì•¼ 합니다" - greater_than_start_date: "는 시작날짜보다 커야 합니다" - not_same_project: "는 ê°™ì€ í”„ë¡œì íŠ¸ì— ì†í•´ 있지 않습니다" - circular_dependency: "ì´ ê´€ê³„ëŠ” 순환 ì˜ì¡´ê´€ê³„를 만들 수 있습니다" - cant_link_an_issue_with_a_descendant: "ì¼ê°ì€ ê·¸ê²ƒì˜ í•˜ìœ„ ì¼ê°ê³¼ ì—°ê²°í•  수 없습니다." - - actionview_instancetag_blank_option: ì„ íƒí•˜ì„¸ìš” - - general_text_No: '아니오' - general_text_Yes: '예' - general_text_no: '아니오' - general_text_yes: '예' - general_lang_name: '한국어(Korean)' - general_csv_separator: ',' - general_csv_decimal_separator: '.' - general_csv_encoding: CP949 - general_pdf_encoding: CP949 - general_first_day_of_week: '7' - - notice_account_updated: ê³„ì •ì´ ì„±ê³µì ìœ¼ë¡œ 변경ë˜ì—ˆìŠµë‹ˆë‹¤. - notice_account_invalid_creditentials: ìž˜ëª»ëœ ê³„ì • ë˜ëŠ” 비밀번호 - notice_account_password_updated: 비밀번호가 잘 변경ë˜ì—ˆìŠµë‹ˆë‹¤. - notice_account_wrong_password: ìž˜ëª»ëœ ë¹„ë°€ë²ˆí˜¸ - notice_account_register_done: ê³„ì •ì´ ìž˜ 만들어졌습니다. ê³„ì •ì„ í™œì„±í™”í•˜ì‹œë ¤ë©´ ë°›ì€ ë©”ì¼ì˜ ë§í¬ë¥¼ í´ë¦­í•´ì£¼ì„¸ìš”. - notice_account_unknown_email: 알려지지 ì•Šì€ ì‚¬ìš©ìž. - notice_can_t_change_password: ì´ ê³„ì •ì€ ì™¸ë¶€ ì¸ì¦ì„ ì´ìš©í•©ë‹ˆë‹¤. 비밀번호를 변경할 수 없습니다. - notice_account_lost_email_sent: 새로운 비밀번호를 위한 ë©”ì¼ì´ 발송ë˜ì—ˆìŠµë‹ˆë‹¤. - notice_account_activated: ê³„ì •ì´ í™œì„±í™”ë˜ì—ˆìŠµë‹ˆë‹¤. ì´ì œ ë¡œê·¸ì¸ í•˜ì‹¤ìˆ˜ 있습니다. - notice_successful_create: ìƒì„± 성공. - notice_successful_update: 변경 성공. - notice_successful_delete: ì‚­ì œ 성공. - notice_successful_connection: ì—°ê²° 성공. - notice_file_not_found: 요청하신 페ì´ì§€ëŠ” ì‚­ì œë˜ì—ˆê±°ë‚˜ 옮겨졌습니다. - notice_locking_conflict: 다른 사용ìžì— ì˜í•´ì„œ ë°ì´í„°ê°€ 변경ë˜ì—ˆìŠµë‹ˆë‹¤. - notice_not_authorized: ì´ íŽ˜ì´ì§€ì— 접근할 ê¶Œí•œì´ ì—†ìŠµë‹ˆë‹¤. - notice_email_sent: "%{value}님ì—게 ë©”ì¼ì´ 발송ë˜ì—ˆìŠµë‹ˆë‹¤." - notice_email_error: "ë©”ì¼ì„ 전송하는 ê³¼ì •ì— ì˜¤ë¥˜ê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤. (%{value})" - notice_feeds_access_key_reseted: RSSì— ì ‘ê·¼ê°€ëŠ¥í•œ 열쇠(key)ê°€ ìƒì„±ë˜ì—ˆìŠµë‹ˆë‹¤. - notice_failed_to_save_issues: "ì €ìž¥ì— ì‹¤íŒ¨í•˜ì˜€ìŠµë‹ˆë‹¤: 실패 %{count}(ì„ íƒ %{total}): %{ids}." - notice_no_issue_selected: "ì¼ê°ì´ ì„ íƒë˜ì§€ 않았습니다. 수정하기 ì›í•˜ëŠ” ì¼ê°ì„ ì„ íƒí•˜ì„¸ìš”" - notice_account_pending: "ê³„ì •ì´ ë§Œë“¤ì–´ì¡Œìœ¼ë©° ê´€ë¦¬ìž ìŠ¹ì¸ ëŒ€ê¸°ì¤‘ìž…ë‹ˆë‹¤." - notice_default_data_loaded: ê¸°ë³¸ê°’ì„ ì„±ê³µì ìœ¼ë¡œ ì½ì–´ë“¤ì˜€ìŠµë‹ˆë‹¤. - notice_unable_delete_version: 삭제할 수 없는 버전입니다. - - error_can_t_load_default_data: "ê¸°ë³¸ê°’ì„ ì½ì–´ë“¤ì¼ 수 없습니다.: %{value}" - error_scm_not_found: 항목ì´ë‚˜ ë¦¬ë¹„ì ¼ì´ ì €ìž¥ì†Œì— ì¡´ìž¬í•˜ì§€ 않습니다. - error_scm_command_failed: "ì €ìž¥ì†Œì— ì ‘ê·¼í•˜ëŠ” ë„ì¤‘ì— ì˜¤ë¥˜ê°€ ë°œìƒí•˜ì˜€ìŠµë‹ˆë‹¤.: %{value}" - error_scm_annotate: "í•­ëª©ì´ ì—†ê±°ë‚˜ 행별 ì´ë ¥ì„ ë³¼ 수 없습니다." - error_issue_not_found_in_project: 'ì¼ê°ì´ 없거나 ì´ í”„ë¡œì íŠ¸ì˜ ê²ƒì´ ì•„ë‹™ë‹ˆë‹¤.' - - warning_attachments_not_saved: "%{count}ê°œ 파ì¼ì„ 저장할 수 없습니다." - - mail_subject_lost_password: "%{value} 비밀번호" - mail_body_lost_password: '비밀번호를 변경하려면 ë‹¤ìŒ ë§í¬ë¥¼ í´ë¦­í•˜ì„¸ìš”.' - mail_subject_register: "%{value} 계정 활성화" - mail_body_register: 'ê³„ì •ì„ í™œì„±í™”í•˜ë ¤ë©´ ë§í¬ë¥¼ í´ë¦­í•˜ì„¸ìš”.:' - mail_body_account_information_external: "로그ì¸í•  때 %{value} ê³„ì •ì„ ì‚¬ìš©í•˜ì‹¤ 수 있습니다." - mail_body_account_information: 계정 ì •ë³´ - mail_subject_account_activation_request: "%{value} 계정 활성화 요청" - mail_body_account_activation_request: "새 사용ìž(%{value})ê°€ 등ë¡ë˜ì—ˆìŠµë‹ˆë‹¤. 관리ìžë‹˜ì˜ 승ì¸ì„ 기다리고 있습니다.:" - mail_body_reminder: "ë‹¹ì‹ ì´ ë§¡ê³  있는 ì¼ê° %{count}ê°œì˜ ì™„ë£Œ ê¸°í•œì´ %{days}ì¼ í›„ 입니다." - mail_subject_reminder: "ë‚´ì¼ì´ ë§Œê¸°ì¸ ì¼ê° %{count}ê°œ (%{days})" - mail_subject_wiki_content_added: "위키페ì´ì§€ '%{id}'ì´(ê°€) 추가ë˜ì—ˆìŠµë‹ˆë‹¤." - mail_subject_wiki_content_updated: "'위키페ì´ì§€ %{id}'ì´(ê°€) 수정ë˜ì—ˆìŠµë‹ˆë‹¤." - mail_body_wiki_content_added: "%{author}ì´(ê°€) 위키페ì´ì§€ '%{id}'ì„(를) 추가하였습니다." - mail_body_wiki_content_updated: "%{author}ì´(ê°€) 위키페ì´ì§€ '%{id}'ì„(를) 수정하였습니다." - - gui_validation_error: ì—러 - gui_validation_error_plural: "%{count}ê°œ ì—러" - - 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: ì •ê·œì‹ - field_min_length: 최소 ê¸¸ì´ - field_max_length: 최대 ê¸¸ì´ - field_value: ê°’ - field_category: 범주 - field_title: 제목 - field_project: 프로ì íЏ - field_issue: ì¼ê° - field_status: ìƒíƒœ - field_notes: ë§ê¸€ - field_is_closed: 완료 ìƒíƒœ - field_is_default: 기본값 - field_tracker: 유형 - field_subject: 제목 - field_due_date: 완료 기한 - field_assigned_to: ë‹´ë‹¹ìž - field_priority: 우선순위 - field_fixed_version: 목표버전 - field_user: ì‚¬ìš©ìž - field_role: ì—­í•  - field_homepage: 홈페ì´ì§€ - field_is_public: 공개 - field_parent: ìƒìœ„ 프로ì íЏ - field_is_in_roadmap: ë¡œë“œë§µì— í‘œì‹œ - field_login: ë¡œê·¸ì¸ - field_mail_notification: ë©”ì¼ ì•Œë¦¼ - field_admin: ê´€ë¦¬ìž - field_last_login_on: 마지막 ë¡œê·¸ì¸ - field_language: 언어 - field_effective_date: ë‚ ì§œ - field_password: 비밀번호 - field_new_password: 새 비밀번호 - field_password_confirmation: 비밀번호 í™•ì¸ - field_version: 버전 - field_type: ë°©ì‹ - field_host: 호스트 - field_port: í¬íЏ - field_account: 계정 - field_base_dn: 기본 DN - field_attr_login: ë¡œê·¸ì¸ ì†ì„± - field_attr_firstname: ì´ë¦„ ì†ì„± - field_attr_lastname: 성 ì†ì„± - field_attr_mail: ë©”ì¼ ì†ì„± - field_onthefly: ë™ì  ì‚¬ìš©ìž ìƒì„± - field_start_date: 시작시간 - field_done_ratio: ì§„ì²™ë„ - field_auth_source: ì¸ì¦ ê³µê¸‰ìž - field_hide_mail: ë©”ì¼ ì£¼ì†Œ 숨기기 - field_comments: 설명 - field_url: URL - field_start_page: 첫 페ì´ì§€ - field_subproject: 하위 프로ì íЏ - field_hours: 시간 - field_activity: 작업종류 - field_spent_on: 작업시간 - field_identifier: ì‹ë³„ìž - field_is_filter: 검색조건으로 ì‚¬ìš©ë¨ - field_issue_to_id: ì—°ê´€ëœ ì¼ê° - field_delay: 지연 - field_assignable: ì´ ì—­í• ì—게 ì¼ê°ì„ 맡길 수 ìžˆìŒ - field_redirect_existing_links: ê¸°ì¡´ì˜ ë§í¬ë¡œ ëŒë ¤ë³´ëƒ„(redirect) - field_estimated_hours: 추정시간 - field_column_names: 컬럼 - field_default_value: 기본값 - field_time_zone: 시간대 - field_searchable: 검색가능 - field_comments_sorting: 댓글 ì •ë ¬ - field_parent_title: ìƒìœ„ 제목 - field_editable: 편집가능 - field_watcher: ì¼ê°ì§€í‚´ì´ - field_identity_url: OpenID URL - field_content: ë‚´ìš© - field_group_by: 결과를 묶어 보여줄 기준 - - setting_app_title: ë ˆë“œë§ˆì¸ ì œëª© - setting_app_subtitle: ë ˆë“œë§ˆì¸ ë¶€ì œëª© - setting_welcome_text: í™˜ì˜ ë©”ì‹œì§€ - setting_default_language: 기본 언어 - setting_login_required: ì¸ì¦ì´ 필요함 - setting_self_registration: ì‚¬ìš©ìž ì§ì ‘ë“±ë¡ - setting_attachment_max_size: 최대 ì²¨ë¶€íŒŒì¼ í¬ê¸° - setting_issues_export_limit: ì¼ê° 내보내기 제한 - setting_mail_from: 발신 ë©”ì¼ ì£¼ì†Œ - setting_bcc_recipients: 참조ìžë“¤ì„ bcc로 숨기기 - setting_plain_text_mail: í…스트만 (HTML ì—†ì´) - setting_host_name: 호스트 ì´ë¦„ê³¼ 경로 - setting_text_formatting: 본문 í˜•ì‹ - setting_wiki_compression: 위키 ì´ë ¥ ì••ì¶• - setting_feeds_limit: í”¼ë“œì— í¬í•¨í•  í•­ëª©ì˜ ìˆ˜ - setting_default_projects_public: 새 프로ì íŠ¸ë¥¼ 공개로 설정 - setting_autofetch_changesets: 제출(commit)ëœ ë³€ê²½ë¬¶ìŒì„ ìžë™ìœ¼ë¡œ 가져오기 - setting_sys_api_enabled: 저장소 ê´€ë¦¬ì— WS를 사용 - setting_commit_ref_keywords: ì¼ê° ì°¸ì¡°ì— ì‚¬ìš©í•  키워드들 - setting_commit_fix_keywords: ì¼ê° í•´ê²°ì— ì‚¬ìš©í•  키워드들 - setting_autologin: ìžë™ ë¡œê·¸ì¸ - setting_date_format: ë‚ ì§œ í˜•ì‹ - setting_time_format: 시간 í˜•ì‹ - setting_cross_project_issue_relations: 다른 프로ì íŠ¸ì˜ ì¼ê°ê³¼ 연결하는 ê²ƒì„ í—ˆìš© - setting_issue_list_default_columns: ì¼ê° 목ë¡ì— 표시할 항목 - setting_emails_footer: ë©”ì¼ ê¼¬ë¦¬ - setting_protocol: 프로토콜 - setting_per_page_options: 목ë¡ì—서, 한 페ì´ì§€ì— 표시할 í–‰ - setting_user_format: ì‚¬ìš©ìž í‘œì‹œ í˜•ì‹ - setting_activity_days_default: 프로ì íЏ ìž‘ì—…ë‚´ì—­ì— í‘œì‹œí•  기간 - setting_display_subprojects_issues: 하위 프로ì íŠ¸ì˜ ì¼ê°ì„ 함께 표시 - setting_enabled_scm: "ì§€ì›í•  SCM(Source Control Management)" - setting_mail_handler_api_enabled: 수신 ë©”ì¼ì— WS를 허용 - setting_mail_handler_api_key: API 키 - setting_sequential_project_identifiers: 프로ì íЏ ì‹ë³„ìžë¥¼ 순차ì ìœ¼ë¡œ ìƒì„± - setting_gravatar_enabled: ê·¸ë¼ë°”타 ì‚¬ìš©ìž ì•„ì´ì½˜ 사용 - setting_diff_max_lines_displayed: ì°¨ì´ì (diff) ë³´ê¸°ì— í‘œì‹œí•  최대 줄수 - setting_repository_log_display_limit: 저장소 ë³´ê¸°ì— í‘œì‹œí•  ê°œì •íŒ ì´ë ¥ì˜ 최대 갯수 - setting_file_max_size_displayed: 바로 보여줄 í…스트파ì¼ì˜ 최대 í¬ê¸° - setting_openid: OpenID 로그ì¸ê³¼ ë“±ë¡ í—ˆìš© - setting_password_min_length: 최소 암호 ê¸¸ì´ - setting_new_project_user_role_id: 프로ì íŠ¸ë¥¼ 만든 사용ìžì—게 주어질 ì—­í•  - - permission_add_project: 프로ì íЏ ìƒì„± - 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: 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: 위키 관리 - 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: 프로ì íЏ 위키 페ì´ì§€ - 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: ì‹œê°„ì¶”ì  - project_module_news: 뉴스 - project_module_documents: 문서 - project_module_files: íŒŒì¼ - project_module_wiki: 위키 - project_module_repository: 저장소 - project_module_boards: ê²Œì‹œíŒ - - label_user: ì‚¬ìš©ìž - label_user_plural: ì‚¬ìš©ìž - label_user_new: 새 ì‚¬ìš©ìž - label_project: 프로ì íЏ - label_project_new: 새 프로ì íЏ - label_project_plural: 프로ì íЏ - label_x_projects: - zero: ì—†ìŒ - one: "한 프로ì íЏ" - other: "%{count}ê°œ 프로ì íЏ" - label_project_all: 모든 프로ì íЏ - label_project_latest: 최근 프로ì íЏ - label_issue: ì¼ê° - label_issue_new: 새 ì¼ê°ë§Œë“¤ê¸° - label_issue_plural: ì¼ê° - label_issue_view_all: 모든 ì¼ê° 보기 - label_issues_by: "%{value}별 ì¼ê°" - label_issue_added: ì¼ê° 추가 - label_issue_updated: ì¼ê° 수정 - label_document: 문서 - label_document_new: 새 문서 - label_document_plural: 문서 - label_document_added: 문서 추가 - label_role: ì—­í•  - label_role_plural: ì—­í•  - label_role_new: 새 ì—­í•  - label_role_and_permissions: ì—­í•  ë° ê¶Œí•œ - label_member: ë‹´ë‹¹ìž - label_member_new: 새 ë‹´ë‹¹ìž - label_member_plural: ë‹´ë‹¹ìž - label_tracker: ì¼ê° 유형 - label_tracker_plural: ì¼ê° 유형 - label_tracker_new: 새 ì¼ê° 유형 - label_workflow: 업무í름 - label_issue_status: ì¼ê° ìƒíƒœ - label_issue_status_plural: ì¼ê° ìƒíƒœ - label_issue_status_new: 새 ì¼ê° ìƒíƒœ - label_issue_category: ì¼ê° 범주 - label_issue_category_plural: ì¼ê° 범주 - label_issue_category_new: 새 ì¼ê° 범주 - label_custom_field: ì‚¬ìš©ìž ì •ì˜ í•­ëª© - label_custom_field_plural: ì‚¬ìš©ìž ì •ì˜ í•­ëª© - label_custom_field_new: 새 ì‚¬ìš©ìž ì •ì˜ í•­ëª© - label_enumerations: 코드값 - label_enumeration_new: 새 코드값 - label_information: ì •ë³´ - label_information_plural: ì •ë³´ - label_please_login: 로그ì¸í•˜ì„¸ìš”. - label_register: ë“±ë¡ - label_login_with_open_id_option: ë˜ëŠ” OpenID로 ë¡œê·¸ì¸ - 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_registered_on: 등ë¡ì‹œê° - label_activity: 작업내역 - label_overall_activity: ì „ì²´ 작업내역 - label_user_activity: "%{value}ì˜ ìž‘ì—…ë‚´ì—­" - label_new: 새로 만들기 - label_logged_as: '로그ì¸ê³„ì •:' - label_environment: 환경 - label_authentication: ì¸ì¦ - label_auth_source: ì¸ì¦ ê³µê¸‰ìž - label_auth_source_new: 새 ì¸ì¦ ê³µê¸‰ìž - label_auth_source_plural: ì¸ì¦ ê³µê¸‰ìž - label_subproject_plural: 하위 프로ì íЏ - label_and_its_subprojects: "%{value}와 하위 프로ì íŠ¸ë“¤" - label_min_max_length: 최소 - 최대 ê¸¸ì´ - label_list: ëª©ë¡ - label_date: ë‚ ì§œ - label_integer: 정수 - label_float: ë¶€ë™ì†Œìˆ˜ - label_boolean: 부울린 - label_string: 문ìžì—´ - label_text: í…스트 - label_attribute: ì†ì„± - label_attribute_plural: ì†ì„± - label_download: "%{count}회 다운로드" - label_download_plural: "%{count}회 다운로드" - 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_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_x_open_issues_abbr_on_total: - zero: "ì´ %{total} ê±´ ëª¨ë‘ ì™„ë£Œ" - one: "한 ê±´ ì§„í–‰ 중 / ì´ %{total} ê±´ 중 " - other: "%{count} ê±´ ì§„í–‰ 중 / ì´ %{total} ê±´" - label_x_open_issues_abbr: - zero: ëª¨ë‘ ì™„ë£Œ - one: 한 ê±´ ì§„í–‰ 중 - other: "%{count} ê±´ ì§„í–‰ 중" - label_x_closed_issues_abbr: - zero: ëª¨ë‘ ë¯¸ì™„ë£Œ - one: 한 ê±´ 완료 - other: "%{count} ê±´ 완료" - 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: "최근 %{count}ê°œì˜ ë³€ê²½ì‚¬í•­" - label_change_view_all: 모든 변경 ë‚´ì—­ 보기 - label_personalize_page: 입맛대로 구성하기 - label_comment: 댓글 - label_comment_plural: 댓글 - label_x_comments: - zero: 댓글 ì—†ìŒ - one: 한 ê°œì˜ ëŒ“ê¸€ - other: "%{count} ê°œì˜ ëŒ“ê¸€" - label_comment_add: 댓글 추가 - label_comment_added: ëŒ“ê¸€ì´ ì¶”ê°€ë˜ì—ˆìŠµë‹ˆë‹¤. - label_comment_delete: 댓글 ì‚­ì œ - label_query: ê²€ìƒ‰ì–‘ì‹ - label_query_plural: ê²€ìƒ‰ì–‘ì‹ - label_query_new: 새 ê²€ìƒ‰ì–‘ì‹ - label_filter_add: 검색조건 추가 - label_filter_plural: 검색조건 - label_equals: ì´ë‹¤ - label_not_equals: 아니다 - label_in_less_than: ì´ë‚´ - label_in_more_than: ì´í›„ - label_greater_or_equal: ">=" - label_less_or_equal: "<=" - label_in: ì´ë‚´ - label_today: 오늘 - label_all_time: 모든 시간 - label_yesterday: ì–´ì œ - label_this_week: ì´ë²ˆì£¼ - label_last_week: 지난 주 - label_last_n_days: "지난 %{count} ì¼" - label_this_month: ì´ë²ˆ 달 - label_last_month: 지난 달 - label_this_year: 올해 - label_date_range: ë‚ ì§œ 범위 - label_less_than_ago: ì´ì „ - label_more_than_ago: ì´í›„ - label_ago: ì¼ ì „ - label_contains: í¬í•¨ë˜ëŠ” 키워드 - label_not_contains: í¬í•¨í•˜ì§€ 않는 키워드 - label_day_plural: ì¼ - label_repository: 저장소 - label_repository_plural: 저장소 - label_browse: 저장소 둘러보기 - label_modification: "%{count} 변경" - label_modification_plural: "%{count} 변경" - label_revision: ê°œì •íŒ - label_revision_plural: ê°œì •íŒ - label_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_sort_highest: 맨 위로 - label_sort_higher: 위로 - label_sort_lower: 아래로 - label_sort_lowest: 맨 아래로 - label_roadmap: 로드맵 - label_roadmap_due_in: "기한 %{value}" - label_roadmap_overdue: "%{value} 지연" - 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: 제목별 ìƒ‰ì¸ - label_index_by_date: 날짜별 ìƒ‰ì¸ - label_current_version: 현재 버전 - label_preview: 미리보기 - label_feed_plural: 피드(Feeds) - label_changes_details: 모든 ìƒì„¸ 변경 ë‚´ì—­ - label_issue_tracking: ì¼ê° ì¶”ì  - label_spent_time: 소요 시간 - label_f_hour: "%{value} 시간" - label_f_hour_plural: "%{value} 시간" - label_time_tracking: ì‹œê°„ì¶”ì  - label_change_plural: 변경사항들 - label_statistics: 통계 - label_commits_per_month: 월별 제출 ë‚´ì—­ - label_commits_per_author: ì €ìžë³„ 제출 ë‚´ì—­ - label_view_diff: ì°¨ì´ì  보기 - label_diff_inline: 한줄로 - label_diff_side_by_side: ë‘줄로 - label_options: 옵션 - label_copy_workflow_from: 업무í름 복사하기 - label_permissions_report: 권한 보고서 - label_watched_issues: 지켜보고 있는 ì¼ê° - label_related_issues: ì—°ê²°ëœ ì¼ê° - label_applied_status: ì ìš©ëœ ìƒíƒœ - label_loading: ì½ëŠ” 중... - label_relation_new: 새 관계 - label_relation_delete: 관계 지우기 - label_relates_to: "ë‹¤ìŒ ì¼ê°ê³¼ 관련ë¨:" - label_duplicates: "ë‹¤ìŒ ì¼ê°ê³¼ 겹침:" - label_duplicated_by: "ë‹¤ìŒ ì¼ê°ê³¼ 겹침:" - label_blocks: "ë‹¤ìŒ ì¼ê°ì˜ í•´ê²°ì„ ë§‰ê³  있ìŒ:" - label_blocked_by: "ë‹¤ìŒ ì¼ê°ì—게 막혀 있ìŒ:" - label_precedes: "다ìŒì— 진행할 ì¼ê°:" - label_follows: "ë‹¤ìŒ ì¼ê°ì„ ìš°ì„  ì§„í–‰:" - label_end_to_start: "ëì—서 시작" - label_end_to_end: "ëì—서 ë" - label_start_to_start: "시작ì—서 시작" - label_start_to_end: "시작ì—서 ë" - label_stay_logged_in: ë¡œê·¸ì¸ ìœ ì§€ - label_disabled: 비활성화 - label_show_completed_versions: ì™„ë£Œëœ ë²„ì „ 보기 - label_me: 나 - label_board: ê²Œì‹œíŒ - label_board_new: 새 ê²Œì‹œíŒ - label_board_plural: ê²Œì‹œíŒ - label_topic_plural: 주제 - label_message_plural: 글 - label_message_last: 마지막 글 - label_message_new: 새글쓰기 - label_message_posted: 글 추가 - label_reply_plural: 답글 - label_send_information: 사용ìžì—게 계정정보를 보내기 - label_year: ë…„ - label_month: ì›” - label_week: 주 - label_date_from: '기간:' - label_date_to: ' ~ ' - label_language_based: ì–¸ì–´ì„¤ì •ì— ë”°ë¦„ - label_sort_by: "%{value}(으)로 ì •ë ¬" - label_send_test_email: 테스트 ë©”ì¼ ë³´ë‚´ê¸° - label_feeds_access_key_created_on: "피드 ì ‘ê·¼ 키가 %{value} ì´ì „ì— ìƒì„±ë˜ì—ˆìŠµë‹ˆë‹¤." - label_module_plural: 모듈 - label_added_time_by: "%{author}ì´(ê°€) %{age} ì „ì— ì¶”ê°€í•¨" - label_updated_time_by: "%{author}ì´(ê°€) %{age} ì „ì— ë³€ê²½" - label_updated_time: "%{value} ì „ì— ìˆ˜ì •ë¨" - label_jump_to_a_project: 프로ì íЏ 바로가기 - label_file_plural: íŒŒì¼ - label_changeset_plural: ë³€ê²½ë¬¶ìŒ - label_default_columns: 기본 컬럼 - label_no_change_option: (수정 안함) - label_bulk_edit_selected_issues: ì„ íƒëœ ì¼ê°ë“¤ì„ í•œêº¼ë²ˆì— ìˆ˜ì •í•˜ê¸° - label_theme: 테마 - label_default: 기본 - label_search_titles_only: 제목ì—서만 찾기 - label_user_mail_option_all: "ë‚´ê°€ ì†í•œ 프로ì íŠ¸ë¡œë“¤ë¶€í„° 모든 ë©”ì¼ ë°›ê¸°" - label_user_mail_option_selected: "ì„ íƒí•œ 프로ì íŠ¸ë“¤ë¡œë¶€í„° 모든 ë©”ì¼ ë°›ê¸°.." - label_user_mail_no_self_notified: "ë‚´ê°€ 만든 ë³€ê²½ì‚¬í•­ë“¤ì— ëŒ€í•´ì„œëŠ” 알림메ì¼ì„ 받지 않습니다." - label_registration_activation_by_email: ë©”ì¼ë¡œ ê³„ì •ì„ í™œì„±í™”í•˜ê¸° - label_registration_automatic_activation: ìžë™ 계정 활성화 - label_registration_manual_activation: ìˆ˜ë™ ê³„ì • 활성화 - label_display_per_page: "페ì´ì§€ë‹¹ 줄수: %{value}" - label_age: 마지막 ìˆ˜ì •ì¼ - label_change_properties: ì†ì„± 변경 - label_general: ì¼ë°˜ - label_more: 제목 ë° ì„¤ëª… 수정 - label_scm: 형ìƒê´€ë¦¬ì‹œìŠ¤í…œ - label_plugins: í”ŒëŸ¬ê·¸ì¸ - label_ldap_authentication: LDAP ì¸ì¦ - label_downloads_abbr: D/L - label_optional_description: 부가ì ì¸ 설명 - label_add_another_file: 다른 íŒŒì¼ ì¶”ê°€ - label_preferences: 설정 - label_chronological_order: 시간 순으로 ì •ë ¬ - label_reverse_chronological_order: 시간 역순으로 ì •ë ¬ - label_planning: 프로ì íŠ¸ê³„íš - label_incoming_emails: 수신 ë©”ì¼ - label_generate_key: 키 ìƒì„± - label_issue_watchers: ì¼ê°ì§€í‚´ì´ - label_example: 예 - label_display: í‘œì‹œë°©ì‹ - label_sort: ì •ë ¬ - label_ascending: 오름차순 - label_descending: 내림차순 - label_date_from_to: "%{start}부터 %{end}까지" - label_wiki_content_added: 위키페ì´ì§€ 추가 - label_wiki_content_updated: 위키페ì´ì§€ 수정 - - button_login: ë¡œê·¸ì¸ - button_submit: í™•ì¸ - button_save: 저장 - button_check_all: 모ë‘ì„ íƒ - button_uncheck_all: ì„ íƒí•´ì œ - button_delete: ì‚­ì œ - button_create: 만들기 - button_create_and_continue: 만들고 계ì†í•˜ê¸° - button_test: 테스트 - button_edit: 편집 - button_add: 추가 - button_change: 변경 - button_apply: ì ìš© - button_clear: 지우기 - button_lock: 잠금 - button_unlock: 잠금해제 - button_download: 다운로드 - button_list: ëª©ë¡ - button_view: 보기 - button_move: ì´ë™ - button_back: 뒤로 - button_cancel: 취소 - button_activate: 활성화 - button_sort: ì •ë ¬ - button_log_time: 작업시간 ê¸°ë¡ - button_rollback: ì´ ë²„ì „ìœ¼ë¡œ ë˜ëŒë¦¬ê¸° - button_watch: 지켜보기 - button_unwatch: 관심ë„기 - button_reply: 답글 - button_archive: 잠금보관 - button_unarchive: 잠금보관해제 - button_reset: 초기화 - button_rename: ì´ë¦„바꾸기 - button_change_password: 비밀번호 바꾸기 - button_copy: 복사 - button_annotate: ì´ë ¥í•´ì„¤ - button_update: 수정 - button_configure: 설정 - button_quote: 댓글달기 - - status_active: 사용중 - status_registered: 등ë¡ëŒ€ê¸° - status_locked: ìž ê¹€ - - text_select_mail_notifications: 알림메ì¼ì´ 필요한 ìž‘ì—…ì„ ì„ íƒí•˜ì„¸ìš”. - text_regexp_info: 예) ^[A-Z0-9]+$ - text_min_max_length_info: 0 는 ì œí•œì´ ì—†ìŒì„ ì˜ë¯¸í•¨ - text_project_destroy_confirmation: ì´ í”„ë¡œì íŠ¸ë¥¼ 삭제하고 모든 ë°ì´í„°ë¥¼ 지우시겠습니까? - text_subprojects_destroy_warning: "하위 프로ì íЏ(%{value})ì´(ê°€) ìžë™ìœ¼ë¡œ 지워질 것입니다." - text_workflow_edit: 업무í름 수정하려면 ì—­í• ê³¼ ì¼ê°ìœ í˜•ì„ ì„ íƒí•˜ì„¸ìš”. - text_are_you_sure: ê³„ì† ì§„í–‰ 하시겠습니까? - text_tip_issue_begin_day: 오늘 시작하는 업무(task) - text_tip_issue_end_day: 오늘 종료하는 업무(task) - text_tip_issue_begin_end_day: 오늘 시작하고 종료하는 업무(task) - text_project_identifier_info: 'ì˜ë¬¸ 소문ìž(a-z) ë° ìˆ«ìž, 대쉬(-) 가능.
    저장ëœí›„ì—는 ì‹ë³„ìž ë³€ê²½ 불가능.' - text_caracters_maximum: "최대 %{count} ê¸€ìž ê°€ëŠ¥" - text_caracters_minimum: "최소한 %{count} ê¸€ìž ì´ìƒì´ì–´ì•¼ 합니다." - text_length_between: "%{min} ì—서 %{max} 글ìž" - text_tracker_no_workflow: ì´ ì¼ê° 유형ì—는 업무íë¦„ì´ ì •ì˜ë˜ì§€ 않았습니다. - text_unallowed_characters: 허용ë˜ì§€ 않는 문ìžì—´ - text_comma_separated: "구분ìž','를 ì´ìš©í•´ì„œ 여러 ê°œì˜ ê°’ì„ ìž…ë ¥í•  수 있습니다." - text_issues_ref_in_commit_messages: 제출 메시지ì—서 ì¼ê°ì„ 참조하거나 해결하기 - text_issue_added: "%{author}ì´(ê°€) ì¼ê° %{id}ì„(를) 보고하였습니다." - text_issue_updated: "%{author}ì´(ê°€) ì¼ê° %{id}ì„(를) 수정하였습니다." - text_wiki_destroy_confirmation: ì´ ìœ„í‚¤ì™€ 모든 ë‚´ìš©ì„ ì§€ìš°ì‹œê² ìŠµë‹ˆê¹Œ? - text_issue_category_destroy_question: "ì¼ë¶€ ì¼ê°ë“¤(%{count}ê°œ)ì´ ì´ ë²”ì£¼ì— ì§€ì •ë˜ì–´ 있습니다. 어떻게 하시겠습니까?" - text_issue_category_destroy_assignments: 범주 지정 지우기 - text_issue_category_reassign_to: ì¼ê°ì„ ì´ ë²”ì£¼ì— ë‹¤ì‹œ 지정하기 - text_user_mail_option: "ì„ íƒí•˜ì§€ ì•Šì€ í”„ë¡œì íЏì—서ë„, 지켜보는 중ì´ê±°ë‚˜ ì†í•´ìžˆëŠ” 사항(ì¼ê°ì„ 발행했거나 í• ë‹¹ëœ ê²½ìš°)ì´ ìžˆìœ¼ë©´ 알림메ì¼ì„ 받게 ë©ë‹ˆë‹¤." - text_no_configuration_data: "ì—­í• , ì¼ê° 유형, ì¼ê° ìƒíƒœë“¤ê³¼ 업무íë¦„ì´ ì•„ì§ ì„¤ì •ë˜ì§€ 않았습니다.\n기본 ì„¤ì •ì„ ì½ì–´ë“¤ì´ëŠ” ê²ƒì„ ê¶Œìž¥í•©ë‹ˆë‹¤. ì½ì–´ë“¤ì¸ í›„ì— ìˆ˜ì •í•  수 있습니다." - text_load_default_configuration: 기본 ì„¤ì •ì„ ì½ì–´ë“¤ì´ê¸° - text_status_changed_by_changeset: "ë³€ê²½ë¬¶ìŒ %{value}ì— ì˜í•˜ì—¬ 변경ë¨" - text_issues_destroy_confirmation: 'ì„ íƒí•œ ì¼ê°ì„ ì •ë§ë¡œ 삭제하시겠습니까?' - text_select_project_modules: 'ì´ í”„ë¡œì íЏì—서 활성화시킬 ëª¨ë“ˆì„ ì„ íƒí•˜ì„¸ìš”:' - text_default_administrator_account_changed: 기본 ê´€ë¦¬ìž ê³„ì •ì´ ë³€ê²½ - text_file_repository_writable: íŒŒì¼ ì €ìž¥ì†Œ 쓰기 가능 - text_plugin_assets_writable: í”ŒëŸ¬ê·¸ì¸ ì „ìš© 디렉토리가 쓰기 가능 - text_rmagick_available: RMagick 사용 가능 (ì„ íƒì ) - text_destroy_time_entries_question: 삭제하려는 ì¼ê°ì— %{hours} ì‹œê°„ì´ ë³´ê³ ë˜ì–´ 있습니다. 어떻게 하시겠습니까? - text_destroy_time_entries: ë³´ê³ ëœ ì‹œê°„ì„ ì‚­ì œí•˜ê¸° - text_assign_time_entries_to_project: ë³´ê³ ëœ ì‹œê°„ì„ í”„ë¡œì íŠ¸ì— í• ë‹¹í•˜ê¸° - text_reassign_time_entries: 'ì´ ì•Œë¦¼ì— ë³´ê³ ëœ ì‹œê°„ì„ ìž¬í• ë‹¹í•˜ê¸°:' - text_user_wrote: "%{value}ì˜ ë§ê¸€:" - text_enumeration_category_reassign_to: '새로운 ê°’ì„ ì„¤ì •:' - text_enumeration_destroy_question: "%{count} ê°œì˜ ì¼ê°ì´ ì´ ê°’ì„ ì‚¬ìš©í•˜ê³  있습니다." - text_email_delivery_not_configured: "ì´ë©”ì¼ ì „ë‹¬ì´ ì„¤ì •ë˜ì§€ 않았습니다. 그래서 ì•Œë¦¼ì´ ë¹„í™œì„±í™”ë˜ì—ˆìŠµë‹ˆë‹¤.\n SMTP서버를 config/configuration.ymlì—서 설정하고 어플리케ì´ì…˜ì„ 다시 시작하십시오. 그러면 ë™ìž‘합니다." - text_repository_usernames_mapping: "저장소 로그ì—서 ë°œê²¬ëœ ê° ì‚¬ìš©ìžì— ë ˆë“œë§ˆì¸ ì‚¬ìš©ìžë¥¼ ì—…ë°ì´íŠ¸í• ë•Œ ì„ íƒí•©ë‹ˆë‹¤.\n레드마ì¸ê³¼ ì €ìž¥ì†Œì˜ ì´ë¦„ì´ë‚˜ ì´ë©”ì¼ì´ ê°™ì€ ì‚¬ìš©ìžê°€ ìžë™ìœ¼ë¡œ ì—°ê²°ë©ë‹ˆë‹¤." - text_diff_truncated: '... ì´ ì°¨ì´ì ì€ 표시할 수 있는 최대 줄수를 초과해서 ì´ ì°¨ì´ì ì€ 잘렸습니다.' - text_custom_field_possible_values_info: 'ê° ê°’ 당 한 줄' - text_wiki_page_destroy_question: ì´ íŽ˜ì´ì§€ëŠ” %{descendants} ê°œì˜ í•˜ìœ„ 페ì´ì§€ì™€ 관련 ë‚´ìš©ì´ ìžˆìŠµë‹ˆë‹¤. ì´ ë‚´ìš©ì„ ì–´ë–»ê²Œ 하시겠습니까? - text_wiki_page_nullify_children: 하위 페ì´ì§€ë¥¼ 최ìƒìœ„ 페ì´ì§€ 아래로 지정 - text_wiki_page_destroy_children: 모든 하위 페ì´ì§€ì™€ 관련 ë‚´ìš©ì„ ì‚­ì œ - text_wiki_page_reassign_children: 하위 페ì´ì§€ë¥¼ ì´ íŽ˜ì´ì§€ 아래로 지정 - - default_role_manager: ê´€ë¦¬ìž - default_role_developer: ê°œë°œìž - default_role_reporter: ë³´ê³ ìž - default_tracker_bug: 결함 - default_tracker_feature: 새기능 - default_tracker_support: ì§€ì› - default_issue_status_new: ì‹ ê·œ - default_issue_status_in_progress: ì§„í–‰ - default_issue_status_resolved: í•´ê²° - default_issue_status_feedback: ì˜ê²¬ - default_issue_status_closed: 완료 - default_issue_status_rejected: ê±°ì ˆ - default_doc_category_user: ì‚¬ìš©ìž ë¬¸ì„œ - default_doc_category_tech: 기술 문서 - default_priority_low: ë‚®ìŒ - default_priority_normal: 보통 - default_priority_high: ë†’ìŒ - default_priority_urgent: 긴급 - default_priority_immediate: 즉시 - default_activity_design: 설계 - default_activity_development: 개발 - - enumeration_issue_priorities: ì¼ê° 우선순위 - enumeration_doc_categories: 문서 범주 - enumeration_activities: 작업분류(시간추ì ) - - field_issue_to: 관련 ì¼ê° - label_view_all_revisions: 모든 ê°œì •íŒ í‘œì‹œ - label_tag: 표지(票識)저장소 - label_branch: 분기(分å²)저장소 - error_no_tracker_in_project: 사용할 수 있ë„ë¡ ì„¤ì •ëœ ì¼ê° ìœ í˜•ì´ ì—†ìŠµë‹ˆë‹¤. 프로ì íЏ ì„¤ì •ì„ í™•ì¸í•˜ì‹­ì‹œì˜¤. - error_no_default_issue_status: '기본 ìƒíƒœê°€ ì •í•´ì ¸ 있지 않습니다. ì„¤ì •ì„ í™•ì¸í•˜ì‹­ì‹œì˜¤. (주 ë©”ë‰´ì˜ "관리" -> "ì¼ê° ìƒíƒœ")' - text_journal_changed: "%{label}ì„(를) %{old}ì—서 %{new}(으)로 변경ë˜ì—ˆìŠµë‹ˆë‹¤." - text_journal_set_to: "%{label}ì„(를) %{value}(으)로 지정ë˜ì—ˆìŠµë‹ˆë‹¤." - text_journal_deleted: "%{label} ê°’ì´ ì§€ì›Œì¡ŒìŠµë‹ˆë‹¤. (%{old})" - label_group_plural: 그룹 - label_group: 그룹 - label_group_new: 새 그룹 - label_time_entry_plural: 작업시간 - text_journal_added: "%{label}ì— %{value}ì´(ê°€) 추가ë˜ì—ˆìŠµë‹ˆë‹¤." - field_active: 사용중 - enumeration_system_activity: 시스템 작업 - permission_delete_issue_watchers: ì¼ê°ì§€í‚´ì´ 지우기 - version_status_closed: 닫힘 - version_status_locked: ìž ê¹€ - version_status_open: ì§„í–‰ - error_can_not_reopen_issue_on_closed_version: 닫힌 ë²„ì „ì— í• ë‹¹ëœ ì¼ê°ì€ 다시 재발ìƒì‹œí‚¬ 수 없습니다. - label_user_anonymous: ì´ë¦„ì—†ìŒ - button_move_and_follow: ì´ë™í•˜ê³  ë”°ë¼ê°€ê¸° - setting_default_projects_modules: 새 프로ì íŠ¸ì— ê¸°ë³¸ì ìœ¼ë¡œ í™œì„±í™”ë  ëª¨ë“ˆ - setting_gravatar_default: 기본 ê·¸ë¼ë°”타 ì´ë¯¸ì§€ - field_sharing: 공유 - label_version_sharing_hierarchy: ìƒìœ„ ë° í•˜ìœ„ 프로ì íЏ - label_version_sharing_system: 모든 프로ì íЏ - label_version_sharing_descendants: 하위 프로ì íЏ - label_version_sharing_tree: 최ìƒìœ„ ë° ëª¨ë“  하위 프로ì íЏ - label_version_sharing_none: ê³µìœ ì—†ìŒ - error_can_not_archive_project: ì´ í”„ë¡œì íŠ¸ë¥¼ 잠금보관할 수 없습니다. - button_duplicate: 복제 - button_copy_and_follow: 복사하고 ë”°ë¼ê°€ê¸° - label_copy_source: ì›ë³¸ - setting_issue_done_ratio: ì¼ê°ì˜ ì§„ì²™ë„ ê³„ì‚°ë°©ë²• - setting_issue_done_ratio_issue_status: ì¼ê° ìƒíƒœë¥¼ 사용하기 - error_issue_done_ratios_not_updated: ì¼ê° ì§„ì²™ë„ê°€ 수정ë˜ì§€ 않았습니다. - error_workflow_copy_target: ëŒ€ìƒ ì¼ê°ìœ í˜•ê³¼ ì—­í• ì„ ì„ íƒí•˜ì„¸ìš”. - setting_issue_done_ratio_issue_field: ì¼ê° 수정ì—서 ì§„ì²™ë„ ìž…ë ¥í•˜ê¸° - label_copy_same_as_target: 대ìƒê³¼ ê°™ìŒ. - label_copy_target: ëŒ€ìƒ - notice_issue_done_ratios_updated: ì¼ê° ì§„ì²™ë„ê°€ 수정ë˜ì—ˆìŠµë‹ˆë‹¤. - error_workflow_copy_source: ì›ë³¸ ì¼ê°ìœ í˜•ì´ë‚˜ ì—­í• ì„ ì„ íƒí•˜ì„¸ìš”. - label_update_issue_done_ratios: 모든 ì¼ê° ì§„ì²™ë„ ê°±ì‹ í•˜ê¸° - setting_start_of_week: 달력 시작 ìš”ì¼ - permission_view_issues: ì¼ê° 보기 - label_display_used_statuses_only: ì´ ì¼ê°ìœ í˜•ì—서 사용ë˜ëŠ” ìƒíƒœë§Œ 보여주기 - label_revision_id: ê°œì •íŒ %{value} - label_api_access_key: API 접근키 - label_api_access_key_created_on: API 접근키가 %{value} ì „ì— ìƒì„±ë˜ì—ˆìŠµë‹ˆë‹¤. - label_feeds_access_key: RSS 접근키 - notice_api_access_key_reseted: API 접근키가 초기화ë˜ì—ˆìŠµë‹ˆë‹¤. - setting_rest_api_enabled: REST 웹서비스 활성화 - label_missing_api_access_key: API 접근키가 없습니다. - label_missing_feeds_access_key: RSS 접근키가 없습니다. - button_show: 보기 - text_line_separated: 여러 ê°’ì´ í—ˆìš©ë¨(ê°’ 마다 한 줄씩) - setting_mail_handler_body_delimiters: ë©”ì¼ ë³¸ë¬¸ êµ¬ë¶„ìž - permission_add_subprojects: 하위 프로ì íЏ 만들기 - label_subproject_new: 새 하위 프로ì íЏ - text_own_membership_delete_confirmation: |- - 권한들 ì¼ë¶€ ë˜ëŠ” 전부를 막 삭제하려고 하고 있습니다. 그렇게 ë˜ë©´ ì´ í”„ë¡œì íŠ¸ë¥¼ ë”ì´ìƒ 수정할 수 없게 ë©ë‹ˆë‹¤. - 계ì†í•˜ì‹œê² ìŠµë‹ˆê¹Œ? - label_close_versions: ì™„ë£Œëœ ë²„ì „ 닫기 - label_board_sticky: ë¶™ë°•ì´ - label_board_locked: 잠금 - permission_export_wiki_pages: 위키 페ì´ì§€ 내보내기 - setting_cache_formatted_text: 형ì‹ì„ 가진 í…스트 빠른 임시 기억 - permission_manage_project_activities: 프로ì íЏ 작업내역 관리 - error_unable_delete_issue_status: ì¼ê° ìƒíƒœë¥¼ 지울 수 없습니다. - label_profile: 사용ìžì •ë³´ - permission_manage_subtasks: 하위 ì¼ê° 관리 - field_parent_issue: ìƒìœ„ ì¼ê° - label_subtask_plural: 하위 ì¼ê° - label_project_copy_notifications: 프로ì íЏ 복사 ì¤‘ì— ì´ë©”ì¼ ì•Œë¦¼ 보내기 - error_can_not_delete_custom_field: ì‚¬ìš©ìž ì •ì˜ í•„ë“œë¥¼ 삭제할 수 없습니다. - error_unable_to_connect: ì—°ê²°í•  수 없습니다((%{value}) - error_can_not_remove_role: ì´ ì—­í• ì€ í˜„ìž¬ 사용 중ì´ì´ì„œ 삭제할 수 없습니다. - error_can_not_delete_tracker: ì´ ìœ í˜•ì˜ ì¼ê°ë“¤ì´ 있어서 삭제할 수 없습니다. - field_principal: ì‹ ì› - label_my_page_block: ë‚´ 페ì´ì§€ 출력화면 - notice_failed_to_save_members: "%{errors}:구성ì›ì„ 저장 중 실패하였습니다" - text_zoom_out: ë” ìž‘ê²Œ - text_zoom_in: ë” í¬ê²Œ - notice_unable_delete_time_entry: 시간 ê¸°ë¡ í•­ëª©ì„ ì‚­ì œí•  수 없습니다. - label_overall_spent_time: ì´ ì†Œìš”ì‹œê°„ - field_time_entries: 기ë¡ëœ 시간 - project_module_gantt: Gantt 챠트 - project_module_calendar: 달력 - button_edit_associated_wikipage: "ì—°ê´€ëœ ìœ„í‚¤ 페ì´ì§€ %{page_title} 수정" - text_are_you_sure_with_children: ì¼ê°ê³¼ 모든 하위 ì¼ê°ë“¤ì„ 삭제하시겠습니까? - field_text: í…스트 ì˜ì—­ - label_user_mail_option_only_owner: ë‚´ê°€ ì €ìžì¸ 사항만 - setting_default_notification_option: 기본 알림 옵션 - label_user_mail_option_only_my_events: ë‚´ê°€ 지켜보거나 ì†í•´ìžˆëŠ” 사항만 - label_user_mail_option_only_assigned: ë‚´ì—게 í• ë‹¹ëœ ì‚¬í•­ë§Œ - label_user_mail_option_none: 알림 ì—†ìŒ - field_member_of_group: í• ë‹¹ëœ ì‚¬ëžŒì˜ ê·¸ë£¹ - field_assigned_to_role: í• ë‹¹ëœ ì‚¬ëžŒì˜ ì—­í•  - notice_not_authorized_archived_project: 접근하려는 프로ì íŠ¸ëŠ” ì´ë¯¸ 잠금보관ë˜ì–´ 있습니다. - label_principal_search: "ì‚¬ìš©ìž ë° ê·¸ë£¹ 찾기:" - label_user_search: "ì‚¬ìš©ìž ì°¾ê¸°::" - field_visible: ë³´ì´ê¸° - setting_emails_header: ì´ë©”ì¼ í—¤ë” - setting_commit_logtime_activity_id: 기ë¡ëœ ì‹œê°„ì— ì ìš©í•  작업분류 - text_time_logged_by_changeset: "ë³€ê²½ë¬¶ìŒ %{value}ì—서 ì ìš©ë˜ì—ˆìŠµë‹ˆë‹¤." - setting_commit_logtime_enabled: 커밋 시ì ì— 작업 시간 ê¸°ë¡ í™œì„±í™” - notice_gantt_chart_truncated: "표시할 수 있는 최대 항목수(%{max})를 초과하여 차트가 잘렸습니다." - setting_gantt_items_limit: "Gantt ì°¨íŠ¸ì— í‘œì‹œë˜ëŠ” 최대 항목수" - field_warn_on_leaving_unsaved: "저장하지 ì•Šì€ íŽ˜ì´ì§€ë¥¼ 빠져나갈 때 나ì—게 알림" - text_warn_on_leaving_unsaved: "현재 페ì´ì§€ëŠ” 저장ë˜ì§€ ì•Šì€ ë¬¸ìžê°€ 있습니다. ì´ íŽ˜ì´ì§€ë¥¼ 빠져나가면 ë‚´ìš©ì„ ìžƒì„것입니다." - label_my_queries: "ë‚´ 검색 ì–‘ì‹" - text_journal_changed_no_detail: "%{label}ì´ ë³€ê²½ë˜ì—ˆìŠµë‹ˆë‹¤." - label_news_comment_added: "ë‰´ìŠ¤ì— ì„¤ëª…ì´ ì¶”ê°€ë˜ì—ˆìŠµë‹ˆë‹¤." - button_expand_all: "ëª¨ë‘ í™•ìž¥" - button_collapse_all: "ëª¨ë‘ ì¶•ì†Œ" - label_additional_workflow_transitions_for_assignee: "사용ìžê°€ 작업ìžì¼ 때 허용ë˜ëŠ” 추가 ìƒíƒœ" - label_additional_workflow_transitions_for_author: "사용ìžê°€ ì €ìžì¼ 때 허용ë˜ëŠ” 추가 ìƒíƒœ" - label_bulk_edit_selected_time_entries: "ì„ íƒëœ 소요 시간 대량 편집" - text_time_entries_destroy_confirmation: "ì„ íƒí•œ 소요 시간 í•­ëª©ì„ ì‚­ì œí•˜ì‹œê² ìŠµë‹ˆê¹Œ?" - label_role_anonymous: Anonymous - label_role_non_member: Non member - label_issue_note_added: "ë§ê¸€ì´ 추가ë˜ì—ˆìŠµë‹ˆë‹¤." - label_issue_status_updated: "ìƒíƒœê°€ 변경ë˜ì—ˆìŠµë‹ˆë‹¤." - label_issue_priority_updated: "ìš°ì„  순위가 변경ë˜ì—ˆìŠµë‹ˆë‹¤." - label_issues_visibility_own: "ì¼ê°ì„ ìƒì„±í•˜ê±°ë‚˜ í• ë‹¹ëœ ì‚¬ìš©ìž" - field_issues_visibility: "ì¼ê° ë³´ìž„" - label_issues_visibility_all: "모든 ì¼ê°" - permission_set_own_issues_private: "ìžì‹ ì˜ ì¼ê°ì„ 공개나 비공개로 설정" - field_is_private: "비공개" - permission_set_issues_private: "ì¼ê°ì„ 공개나 비공개로 설정" - label_issues_visibility_public: "모든 비공개 ì¼ê°" - text_issues_destroy_descendants_confirmation: "%{count} ê°œì˜ í•˜ìœ„ ì¼ê°ì„ 삭제할 것입니다." - field_commit_logs_encoding: "제출(commit) ê¸°ë¡ ì¸ì½”딩" - field_scm_path_encoding: "경로 ì¸ì½”딩" - text_scm_path_encoding_note: "기본: UTF-8" - field_path_to_repository: "저장소 경로" - field_root_directory: "루트 경로" - field_cvs_module: "모듈" - field_cvsroot: "CVS 루트" - text_mercurial_repository_note: "로컬 저장소 (예: /hgrepo, c:\hgrepo)" - text_scm_command: "명령" - text_scm_command_version: "버전" - label_git_report_last_commit: "파ì¼ì´ë‚˜ í´ë”ì˜ ë§ˆì§€ë§‰ 제출(commit)ì„ ë³´ê³ " - text_scm_config: "SCM ëª…ë ¹ì„ config/configuration.ymlì—서 수정할 수 있습니다. 수정후ì—는 재시작하십시오." - text_scm_command_not_available: "SCM ëª…ë ¹ì„ ì‚¬ìš©í•  수 없습니다. 관리 페ì´ì§€ì˜ ì„¤ì •ì„ ê²€ì‚¬í•˜ì‹­ì‹œì˜¤." - notice_issue_successful_create: "%{id} ì¼ê°ì´ ìƒì„±ë˜ì—ˆìŠµë‹ˆë‹¤." - label_between: "사ì´" - setting_issue_group_assignment: "ê·¸ë£¹ì— ì¼ê° 할당 허용" - label_diff: "비êµ(diff)" - text_git_repository_note: "저장소는 ë…¸ì¶œëœ ë¡œì»¬ìž…ë‹ˆë‹¤. (예: /gitrepo, c:\gitrepo)" - description_query_sort_criteria_direction: "ì •ë ¬ ë°©í–¥" - description_project_scope: "검색 범위" - description_filter: "검색 ì¡°ê±´" - description_user_mail_notification: "ë©”ì¼ ì•Œë¦¼ 설정" - description_date_from: "시작 ë‚ ì§œ ìž…ë ¥" - description_message_content: "메세지 ë‚´ìš©" - description_available_columns: "가능한 컬럼" - description_date_range_interval: 시작과 ë 날짜로 범위를 ì„ íƒí•˜ì‹­ì‹œì˜¤." - description_issue_category_reassign: "ì¼ê° 범주를 ì„ íƒí•˜ì‹­ì‹œì˜¤." - description_search: "검색항목" - description_notes: "ë§ê¸€" - description_date_range_list: "목ë¡ì—서 범위를 ì„ íƒ í•˜ì‹­ì‹œì˜¤." - description_choose_project: "프로ì íЏ" - description_date_to: "종료 ë‚ ì§œ ìž…ë ¥" - description_query_sort_criteria_attribute: "ì •ë ¬ ì†ì„±" - description_wiki_subpages_reassign: "새로운 ìƒìœ„ 페ì´ì§€ë¥¼ ì„ íƒí•˜ì‹­ì‹œì˜¤." - description_selected_columns: "ì„ íƒëœ 컬럼" - label_parent_revision: "ìƒìœ„" - label_child_revision: "하위" - error_scm_annotate_big_text_file: "최대 í…스트 íŒŒì¼ í¬ê¸°ë¥¼ 초과 하면 í•­ëª©ì€ ì´ë ¥í™” ë  ìˆ˜ 없습니다." - setting_default_issue_start_date_to_creation_date: "새로운 ì¼ê°ì˜ 시작 날짜로 오늘 ë‚ ì§œ 사용" - button_edit_section: "ì´ ë¶€ë¶„ 수정" - setting_repositories_encodings: "첨부파ì¼ì´ë‚˜ 저장소 ì¸ì½”딩" - description_all_columns: "모든 컬럼" - button_export: "내보내기" - label_export_options: "내보내기 옵션: %{export_format}" - error_attachment_too_big: "ì´ íŒŒì¼ì€ ì œí•œëœ í¬ê¸°(%{max_size})를 초과하였기 ë•Œë¬¸ì— ì—…ë¡œë“œ í•  수 없습니다." diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7a/7a2b37fe2ff5ae9d62a418e469dc500c74dfab37.svn-base --- a/.svn/pristine/7a/7a2b37fe2ff5ae9d62a418e469dc500c74dfab37.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,85 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../../test_helper', __FILE__) - -class SortHelperTest < ActionView::TestCase - include SortHelper - - def setup - @session = nil - @sort_param = nil - end - - def test_default_sort_clause_with_array - sort_init 'attr1', 'desc' - sort_update(['attr1', 'attr2']) - - assert_equal 'attr1 DESC', sort_clause - end - - def test_default_sort_clause_with_hash - sort_init 'attr1', 'desc' - sort_update({'attr1' => 'table1.attr1', 'attr2' => 'table2.attr2'}) - - assert_equal 'table1.attr1 DESC', sort_clause - end - - def test_default_sort_clause_with_multiple_columns - sort_init 'attr1', 'desc' - sort_update({'attr1' => ['table1.attr1', 'table1.attr2'], 'attr2' => 'table2.attr2'}) - - assert_equal 'table1.attr1 DESC, table1.attr2 DESC', sort_clause - end - - def test_params_sort - @sort_param = 'attr1,attr2:desc' - - sort_init 'attr1', 'desc' - sort_update({'attr1' => 'table1.attr1', 'attr2' => 'table2.attr2'}) - - assert_equal 'table1.attr1, table2.attr2 DESC', sort_clause - assert_equal 'attr1,attr2:desc', @session['foo_bar_sort'] - end - - def test_invalid_params_sort - @sort_param = 'invalid_key' - - sort_init 'attr1', 'desc' - sort_update({'attr1' => 'table1.attr1', 'attr2' => 'table2.attr2'}) - - assert_equal 'table1.attr1 DESC', sort_clause - assert_equal 'attr1:desc', @session['foo_bar_sort'] - end - - def test_invalid_order_params_sort - @sort_param = 'attr1:foo:bar,attr2' - - sort_init 'attr1', 'desc' - sort_update({'attr1' => 'table1.attr1', 'attr2' => 'table2.attr2'}) - - assert_equal 'table1.attr1, table2.attr2', sort_clause - assert_equal 'attr1,attr2', @session['foo_bar_sort'] - end - - private - - def controller_name; 'foo'; end - def action_name; 'bar'; end - def params; {:sort => @sort_param}; end - def session; @session ||= {}; end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7a/7a370e17f62630071e4722f1f8c066b9cfb99711.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/7a/7a370e17f62630071e4722f1f8c066b9cfb99711.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,32 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2013 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 WorkflowsHelper + def field_required?(field) + field.is_a?(CustomField) ? field.is_required? : %w(project_id tracker_id subject priority_id is_private).include?(field) + end + + def field_permission_tag(permissions, status, field) + name = field.is_a?(CustomField) ? field.id.to_s : field + options = [["", ""], [l(:label_readonly), "readonly"]] + options << [l(:label_required), "required"] unless field_required?(field) + + select_tag("permissions[#{name}][#{status.id}]", options_for_select(options, permissions[status.id][name])) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7a/7a59e0de6fb3a65fd73a1071a5762041233e7537.svn-base --- a/.svn/pristine/7a/7a59e0de6fb3a65fd73a1071a5762041233e7537.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,494 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) -require 'repositories_controller' - -# Re-raise errors caught by the controller. -class RepositoriesController; def rescue_action(e) raise e end; end - -class RepositoriesGitControllerTest < ActionController::TestCase - fixtures :projects, :users, :roles, :members, :member_roles, - :repositories, :enabled_modules - - REPOSITORY_PATH = Rails.root.join('tmp/test/git_repository').to_s - REPOSITORY_PATH.gsub!(/\//, "\\") if Redmine::Platform.mswin? - PRJ_ID = 3 - CHAR_1_HEX = "\xc3\x9c" - NUM_REV = 21 - - ## Git, Mercurial and CVS path encodings are binary. - ## Subversion supports URL encoding for path. - ## Redmine Mercurial adapter and extension use URL encoding. - ## Git accepts only binary path in command line parameter. - ## So, there is no way to use binary command line parameter in JRuby. - JRUBY_SKIP = (RUBY_PLATFORM == 'java') - JRUBY_SKIP_STR = "TODO: This test fails in JRuby" - - def setup - @ruby19_non_utf8_pass = - (RUBY_VERSION >= '1.9' && Encoding.default_external.to_s != 'UTF-8') - - @controller = RepositoriesController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - User.current = nil - @project = Project.find(PRJ_ID) - @repository = Repository::Git.create( - :project => @project, - :url => REPOSITORY_PATH, - :path_encoding => 'ISO-8859-1' - ) - assert @repository - @char_1 = CHAR_1_HEX.dup - if @char_1.respond_to?(:force_encoding) - @char_1.force_encoding('UTF-8') - end - - Setting.default_language = 'en' - end - - if File.directory?(REPOSITORY_PATH) - def test_browse_root - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - - get :show, :id => PRJ_ID - assert_response :success - assert_template 'show' - assert_not_nil assigns(:entries) - assert_equal 9, assigns(:entries).size - assert assigns(:entries).detect {|e| e.name == 'images' && e.kind == 'dir'} - assert assigns(:entries).detect {|e| e.name == 'this_is_a_really_long_and_verbose_directory_name' && e.kind == 'dir'} - assert assigns(:entries).detect {|e| e.name == 'sources' && e.kind == 'dir'} - assert assigns(:entries).detect {|e| e.name == 'README' && e.kind == 'file'} - assert assigns(:entries).detect {|e| e.name == 'copied_README' && e.kind == 'file'} - assert assigns(:entries).detect {|e| e.name == 'new_file.txt' && e.kind == 'file'} - assert assigns(:entries).detect {|e| e.name == 'renamed_test.txt' && e.kind == 'file'} - assert assigns(:entries).detect {|e| e.name == 'filemane with spaces.txt' && e.kind == 'file'} - assert assigns(:entries).detect {|e| e.name == ' filename with a leading space.txt ' && e.kind == 'file'} - assert_not_nil assigns(:changesets) - assert assigns(:changesets).size > 0 - end - - def test_browse_branch - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - get :show, :id => PRJ_ID, :rev => 'test_branch' - assert_response :success - assert_template 'show' - assert_not_nil assigns(:entries) - assert_equal 4, assigns(:entries).size - assert assigns(:entries).detect {|e| e.name == 'images' && e.kind == 'dir'} - assert assigns(:entries).detect {|e| e.name == 'sources' && e.kind == 'dir'} - assert assigns(:entries).detect {|e| e.name == 'README' && e.kind == 'file'} - assert assigns(:entries).detect {|e| e.name == 'test.txt' && e.kind == 'file'} - assert_not_nil assigns(:changesets) - assert assigns(:changesets).size > 0 - end - - def test_browse_tag - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - [ - "tag00.lightweight", - "tag01.annotated", - ].each do |t1| - get :show, :id => PRJ_ID, :rev => t1 - assert_response :success - assert_template 'show' - assert_not_nil assigns(:entries) - assert assigns(:entries).size > 0 - assert_not_nil assigns(:changesets) - assert assigns(:changesets).size > 0 - end - end - - def test_browse_directory - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - get :show, :id => PRJ_ID, :path => ['images'] - assert_response :success - assert_template 'show' - assert_not_nil assigns(:entries) - assert_equal ['edit.png'], assigns(:entries).collect(&:name) - entry = assigns(:entries).detect {|e| e.name == 'edit.png'} - assert_not_nil entry - assert_equal 'file', entry.kind - assert_equal 'images/edit.png', entry.path - assert_not_nil assigns(:changesets) - assert assigns(:changesets).size > 0 - end - - def test_browse_at_given_revision - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - get :show, :id => PRJ_ID, :path => ['images'], - :rev => '7234cb2750b63f47bff735edc50a1c0a433c2518' - assert_response :success - assert_template 'show' - assert_not_nil assigns(:entries) - assert_equal ['delete.png'], assigns(:entries).collect(&:name) - assert_not_nil assigns(:changesets) - assert assigns(:changesets).size > 0 - end - - def test_changes - get :changes, :id => PRJ_ID, :path => ['images', 'edit.png'] - assert_response :success - assert_template 'changes' - assert_tag :tag => 'h2', :content => 'edit.png' - end - - def test_entry_show - get :entry, :id => PRJ_ID, :path => ['sources', 'watchers_controller.rb'] - assert_response :success - assert_template 'entry' - # Line 19 - assert_tag :tag => 'th', - :content => '11', - :attributes => { :class => 'line-num' }, - :sibling => { :tag => 'td', :content => /WITHOUT ANY WARRANTY/ } - end - - def test_entry_show_latin_1 - if @ruby19_non_utf8_pass - puts_ruby19_non_utf8_pass() - elsif JRUBY_SKIP - puts JRUBY_SKIP_STR - else - with_settings :repositories_encodings => 'UTF-8,ISO-8859-1' do - ['57ca437c', '57ca437c0acbbcb749821fdf3726a1367056d364'].each do |r1| - get :entry, :id => PRJ_ID, - :path => ['latin-1-dir', "test-#{@char_1}.txt"], :rev => r1 - assert_response :success - assert_template 'entry' - assert_tag :tag => 'th', - :content => '1', - :attributes => { :class => 'line-num' }, - :sibling => { :tag => 'td', - :content => /test-#{@char_1}.txt/ } - end - end - end - end - - def test_entry_download - get :entry, :id => PRJ_ID, :path => ['sources', 'watchers_controller.rb'], - :format => 'raw' - assert_response :success - # File content - assert @response.body.include?('WITHOUT ANY WARRANTY') - end - - def test_directory_entry - get :entry, :id => PRJ_ID, :path => ['sources'] - assert_response :success - assert_template 'show' - assert_not_nil assigns(:entry) - assert_equal 'sources', assigns(:entry).name - end - - def test_diff - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - # Full diff of changeset 2f9c0091 - ['inline', 'sbs'].each do |dt| - get :diff, - :id => PRJ_ID, - :rev => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7', - :type => dt - assert_response :success - assert_template 'diff' - # Line 22 removed - assert_tag :tag => 'th', - :content => /22/, - :sibling => { :tag => 'td', - :attributes => { :class => /diff_out/ }, - :content => /def remove/ } - assert_tag :tag => 'h2', :content => /2f9c0091/ - end - end - - def test_diff_truncated - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - Setting.diff_max_lines_displayed = 5 - - # Truncated diff of changeset 2f9c0091 - with_cache do - get :diff, :id => PRJ_ID, :type => 'inline', - :rev => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7' - assert_response :success - assert @response.body.include?("... This diff was truncated") - - Setting.default_language = 'fr' - get :diff, :id => PRJ_ID, :type => 'inline', - :rev => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7' - assert_response :success - assert ! @response.body.include?("... This diff was truncated") - assert @response.body.include?("... Ce diff") - end - end - - def test_diff_two_revs - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - ['inline', 'sbs'].each do |dt| - get :diff, - :id => PRJ_ID, - :rev => '61b685fbe55ab05b5ac68402d5720c1a6ac973d1', - :rev_to => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7', - :type => dt - assert_response :success - assert_template 'diff' - diff = assigns(:diff) - assert_not_nil diff - assert_tag :tag => 'h2', :content => /2f9c0091:61b685fb/ - end - end - - def test_diff_latin_1 - if @ruby19_non_utf8_pass - puts_ruby19_non_utf8_pass() - else - with_settings :repositories_encodings => 'UTF-8,ISO-8859-1' do - ['57ca437c', '57ca437c0acbbcb749821fdf3726a1367056d364'].each do |r1| - ['inline', 'sbs'].each do |dt| - get :diff, :id => PRJ_ID, :rev => r1, :type => dt - assert_response :success - assert_template 'diff' - assert_tag :tag => 'thead', - :descendant => { - :tag => 'th', - :attributes => { :class => 'filename' } , - :content => /latin-1-dir\/test-#{@char_1}.txt/ , - }, - :sibling => { - :tag => 'tbody', - :descendant => { - :tag => 'td', - :attributes => { :class => /diff_in/ }, - :content => /test-#{@char_1}.txt/ - } - } - end - end - end - end - end - - def test_save_diff_type - @request.session[:user_id] = 1 # admin - user = User.find(1) - get :diff, - :id => PRJ_ID, - :rev => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7' - assert_response :success - assert_template 'diff' - user.reload - assert_equal "inline", user.pref[:diff_type] - get :diff, - :id => PRJ_ID, - :rev => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7', - :type => 'sbs' - assert_response :success - assert_template 'diff' - user.reload - assert_equal "sbs", user.pref[:diff_type] - end - - def test_annotate - get :annotate, :id => PRJ_ID, :path => ['sources', 'watchers_controller.rb'] - assert_response :success - assert_template 'annotate' - # Line 24, changeset 2f9c0091 - assert_tag :tag => 'th', :content => '24', - :sibling => { - :tag => 'td', - :child => { - :tag => 'a', - :content => /2f9c0091/ - } - } - assert_tag :tag => 'th', :content => '24', - :sibling => { :tag => 'td', :content => /jsmith/ } - assert_tag :tag => 'th', :content => '24', - :sibling => { - :tag => 'td', - :child => { - :tag => 'a', - :content => /2f9c0091/ - } - } - assert_tag :tag => 'th', :content => '24', - :sibling => { :tag => 'td', :content => /watcher =/ } - end - - def test_annotate_at_given_revision - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - get :annotate, :id => PRJ_ID, :rev => 'deff7', - :path => ['sources', 'watchers_controller.rb'] - assert_response :success - assert_template 'annotate' - assert_tag :tag => 'h2', :content => /@ deff712f/ - end - - def test_annotate_binary_file - get :annotate, :id => PRJ_ID, :path => ['images', 'edit.png'] - assert_response 500 - assert_tag :tag => 'p', :attributes => { :id => /errorExplanation/ }, - :content => /cannot be annotated/ - end - - def test_annotate_error_when_too_big - with_settings :file_max_size_displayed => 1 do - get :annotate, :id => PRJ_ID, :path => ['sources', 'watchers_controller.rb'], :rev => 'deff712f' - assert_response 500 - assert_tag :tag => 'p', :attributes => { :id => /errorExplanation/ }, - :content => /exceeds the maximum text file size/ - - get :annotate, :id => PRJ_ID, :path => ['README'], :rev => '7234cb2' - assert_response :success - assert_template 'annotate' - end - end - - def test_annotate_latin_1 - if @ruby19_non_utf8_pass - puts_ruby19_non_utf8_pass() - elsif JRUBY_SKIP - puts JRUBY_SKIP_STR - else - with_settings :repositories_encodings => 'UTF-8,ISO-8859-1' do - ['57ca437c', '57ca437c0acbbcb749821fdf3726a1367056d364'].each do |r1| - get :annotate, :id => PRJ_ID, - :path => ['latin-1-dir', "test-#{@char_1}.txt"], :rev => r1 - assert_tag :tag => 'th', - :content => '1', - :attributes => { :class => 'line-num' }, - :sibling => { :tag => 'td', - :content => /test-#{@char_1}.txt/ } - end - end - end - end - - def test_revision - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - ['61b685fbe55ab05b5ac68402d5720c1a6ac973d1', '61b685f'].each do |r| - get :revision, :id => PRJ_ID, :rev => r - assert_response :success - assert_template 'revision' - end - end - - def test_empty_revision - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - ['', ' ', nil].each do |r| - get :revision, :id => PRJ_ID, :rev => r - assert_response 404 - assert_error_tag :content => /was not found/ - end - end - - def test_destroy_valid_repository - @request.session[:user_id] = 1 # admin - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - - get :destroy, :id => PRJ_ID - assert_response 302 - @project.reload - assert_nil @project.repository - end - - def test_destroy_invalid_repository - @request.session[:user_id] = 1 # admin - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - - get :destroy, :id => PRJ_ID - assert_response 302 - @project.reload - assert_nil @project.repository - - @repository = Repository::Git.create( - :project => @project, - :url => "/invalid", - :path_encoding => 'ISO-8859-1' - ) - assert @repository - @repository.fetch_changesets - @repository.reload - assert_equal 0, @repository.changesets.count - - get :destroy, :id => PRJ_ID - assert_response 302 - @project.reload - assert_nil @project.repository - end - - private - - def puts_ruby19_non_utf8_pass - puts "TODO: This test fails in Ruby 1.9 " + - "and Encoding.default_external is not UTF-8. " + - "Current value is '#{Encoding.default_external.to_s}'" - end - else - puts "Git test repository NOT FOUND. Skipping functional tests !!!" - def test_fake; assert true end - end - - private - def with_cache(&block) - before = ActionController::Base.perform_caching - ActionController::Base.perform_caching = true - block.call - ActionController::Base.perform_caching = before - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7a/7a752fd775f30c709fd8d90851ab52b265da090b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/7a/7a752fd775f30c709fd8d90851ab52b265da090b.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,439 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class IssuesController < ApplicationController + menu_item :new_issue, :only => [:new, :create] + default_search_scope :issues + + before_filter :find_issue, :only => [:show, :edit, :update] + before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :destroy] + before_filter :find_project, :only => [:new, :create, :update_form] + before_filter :authorize, :except => [:index] + before_filter :find_optional_project, :only => [:index] + before_filter :check_for_default_issue_status, :only => [:new, :create] + before_filter :build_new_issue_from_params, :only => [:new, :create, :update_form] + accept_rss_auth :index, :show + accept_api_auth :index, :show, :create, :update, :destroy + + rescue_from Query::StatementInvalid, :with => :query_statement_invalid + + helper :journals + helper :projects + include ProjectsHelper + helper :custom_fields + include CustomFieldsHelper + helper :issue_relations + include IssueRelationsHelper + helper :watchers + include WatchersHelper + helper :attachments + include AttachmentsHelper + helper :queries + include QueriesHelper + helper :repositories + include RepositoriesHelper + helper :sort + include SortHelper + include IssuesHelper + helper :timelog + include Redmine::Export::PDF + + def index + retrieve_query + sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria) + sort_update(@query.sortable_columns) + @query.sort_criteria = sort_criteria.to_a + + if @query.valid? + case params[:format] + when 'csv', 'pdf' + @limit = Setting.issues_export_limit.to_i + when 'atom' + @limit = Setting.feeds_limit.to_i + when 'xml', 'json' + @offset, @limit = api_offset_and_limit + else + @limit = per_page_option + end + + @issue_count = @query.issue_count + @issue_pages = Paginator.new @issue_count, @limit, params['page'] + @offset ||= @issue_pages.offset + @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version], + :order => sort_clause, + :offset => @offset, + :limit => @limit) + @issue_count_by_group = @query.issue_count_by_group + + respond_to do |format| + format.html { render :template => 'issues/index', :layout => !request.xhr? } + format.api { + Issue.load_visible_relations(@issues) if include_in_api_response?('relations') + } + format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") } + format.csv { send_data(query_to_csv(@issues, @query, params), :type => 'text/csv; header=present', :filename => 'issues.csv') } + format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'issues.pdf') } + end + else + respond_to do |format| + format.html { render(:template => 'issues/index', :layout => !request.xhr?) } + format.any(:atom, :csv, :pdf) { render(:nothing => true) } + format.api { render_validation_errors(@query) } + end + end + rescue ActiveRecord::RecordNotFound + render_404 + end + + def show + @journals = @issue.journals.includes(:user, :details).reorder("#{Journal.table_name}.id ASC").all + @journals.each_with_index {|j,i| j.indice = i+1} + @journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project) + @journals.reverse! if User.current.wants_comments_in_reverse_order? + + @changesets = @issue.changesets.visible.all + @changesets.reverse! if User.current.wants_comments_in_reverse_order? + + @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? } + @allowed_statuses = @issue.new_statuses_allowed_to(User.current) + @edit_allowed = User.current.allowed_to?(:edit_issues, @project) + @priorities = IssuePriority.active + @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project) + respond_to do |format| + format.html { + retrieve_previous_and_next_issue_ids + render :template => 'issues/show' + } + format.api + format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' } + format.pdf { + pdf = issue_to_pdf(@issue, :journals => @journals) + send_data(pdf, :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") + } + end + end + + # Add a new issue + # The new issue will be created from an existing one if copy_from parameter is given + def new + respond_to do |format| + format.html { render :action => 'new', :layout => !request.xhr? } + end + end + + def create + call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue }) + @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads])) + if @issue.save + call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue}) + respond_to do |format| + format.html { + render_attachment_warning_if_needed(@issue) + flash[:notice] = l(:notice_issue_successful_create, :id => view_context.link_to("##{@issue.id}", issue_path(@issue), :title => @issue.subject)) + if params[:continue] + attrs = {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} + redirect_to new_project_issue_path(@issue.project, :issue => attrs) + else + redirect_to issue_path(@issue) + end + } + format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) } + end + return + else + respond_to do |format| + format.html { render :action => 'new' } + format.api { render_validation_errors(@issue) } + end + end + end + + def edit + return unless update_issue_from_params + + respond_to do |format| + format.html { } + format.xml { } + end + end + + def update + return unless update_issue_from_params + @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads])) + saved = false + begin + saved = @issue.save_issue_with_child_records(params, @time_entry) + rescue ActiveRecord::StaleObjectError + @conflict = true + if params[:last_journal_id] + @conflict_journals = @issue.journals_after(params[:last_journal_id]).all + @conflict_journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project) + end + end + + if saved + render_attachment_warning_if_needed(@issue) + flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record? + + respond_to do |format| + format.html { redirect_back_or_default issue_path(@issue) } + format.api { render_api_ok } + end + else + respond_to do |format| + format.html { render :action => 'edit' } + format.api { render_validation_errors(@issue) } + end + end + end + + # Updates the issue form when changing the project, status or tracker + # on issue creation/update + def update_form + end + + # Bulk edit/copy a set of issues + def bulk_edit + @issues.sort! + @copy = params[:copy].present? + @notes = params[:notes] + + if User.current.allowed_to?(:move_issues, @projects) + @allowed_projects = Issue.allowed_target_projects_on_move + if params[:issue] + @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:issue][:project_id].to_s} + if @target_project + target_projects = [@target_project] + end + end + end + target_projects ||= @projects + + if @copy + @available_statuses = [IssueStatus.default] + else + @available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&) + end + @custom_fields = target_projects.map{|p|p.all_issue_custom_fields}.reduce(:&) + @assignables = target_projects.map(&:assignable_users).reduce(:&) + @trackers = target_projects.map(&:trackers).reduce(:&) + @versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&) + @categories = target_projects.map {|p| p.issue_categories}.reduce(:&) + if @copy + @attachments_present = @issues.detect {|i| i.attachments.any?}.present? + @subtasks_present = @issues.detect {|i| !i.leaf?}.present? + end + + @safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&) + render :layout => false if request.xhr? + end + + def bulk_update + @issues.sort! + @copy = params[:copy].present? + attributes = parse_params_for_bulk_issue_attributes(params) + + unsaved_issue_ids = [] + moved_issues = [] + + if @copy && params[:copy_subtasks].present? + # Descendant issues will be copied with the parent task + # Don't copy them twice + @issues.reject! {|issue| @issues.detect {|other| issue.is_descendant_of?(other)}} + end + + @issues.each do |issue| + issue.reload + if @copy + issue = issue.copy({}, + :attachments => params[:copy_attachments].present?, + :subtasks => params[:copy_subtasks].present? + ) + end + journal = issue.init_journal(User.current, params[:notes]) + issue.safe_attributes = attributes + call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue }) + if issue.save + moved_issues << issue + else + # Keep unsaved issue ids to display them in flash error + unsaved_issue_ids << issue.id + end + end + set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids) + + if params[:follow] + if @issues.size == 1 && moved_issues.size == 1 + redirect_to issue_path(moved_issues.first) + elsif moved_issues.map(&:project).uniq.size == 1 + redirect_to project_issues_path(moved_issues.map(&:project).first) + end + else + redirect_back_or_default _project_issues_path(@project) + end + end + + def destroy + @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f + if @hours > 0 + case params[:todo] + when 'destroy' + # nothing to do + when 'nullify' + TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues]) + when 'reassign' + reassign_to = @project.issues.find_by_id(params[:reassign_to_id]) + if reassign_to.nil? + flash.now[:error] = l(:error_issue_not_found_in_project) + return + else + TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues]) + end + else + # display the destroy form if it's a user request + return unless api_request? + end + end + @issues.each do |issue| + begin + issue.reload.destroy + rescue ::ActiveRecord::RecordNotFound # raised by #reload if issue no longer exists + # nothing to do, issue was already deleted (eg. by a parent) + end + end + respond_to do |format| + format.html { redirect_back_or_default _project_issues_path(@project) } + format.api { render_api_ok } + end + end + + private + + def find_project + project_id = params[:project_id] || (params[:issue] && params[:issue][:project_id]) + @project = Project.find(project_id) + rescue ActiveRecord::RecordNotFound + render_404 + end + + def retrieve_previous_and_next_issue_ids + retrieve_query_from_session + if @query + sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria) + sort_update(@query.sortable_columns, 'issues_index_sort') + limit = 500 + issue_ids = @query.issue_ids(:order => sort_clause, :limit => (limit + 1), :include => [:assigned_to, :tracker, :priority, :category, :fixed_version]) + if (idx = issue_ids.index(@issue.id)) && idx < limit + if issue_ids.size < 500 + @issue_position = idx + 1 + @issue_count = issue_ids.size + end + @prev_issue_id = issue_ids[idx - 1] if idx > 0 + @next_issue_id = issue_ids[idx + 1] if idx < (issue_ids.size - 1) + end + end + end + + # Used by #edit and #update to set some common instance variables + # from the params + # TODO: Refactor, not everything in here is needed by #edit + def update_issue_from_params + @edit_allowed = User.current.allowed_to?(:edit_issues, @project) + @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project) + @time_entry.attributes = params[:time_entry] + + @issue.init_journal(User.current) + + issue_attributes = params[:issue] + if issue_attributes && params[:conflict_resolution] + case params[:conflict_resolution] + when 'overwrite' + issue_attributes = issue_attributes.dup + issue_attributes.delete(:lock_version) + when 'add_notes' + issue_attributes = issue_attributes.slice(:notes) + when 'cancel' + redirect_to issue_path(@issue) + return false + end + end + @issue.safe_attributes = issue_attributes + @priorities = IssuePriority.active + @allowed_statuses = @issue.new_statuses_allowed_to(User.current) + true + end + + # TODO: Refactor, lots of extra code in here + # TODO: Changing tracker on an existing issue should not trigger this + def build_new_issue_from_params + if params[:id].blank? + @issue = Issue.new + if params[:copy_from] + begin + @copy_from = Issue.visible.find(params[:copy_from]) + @copy_attachments = params[:copy_attachments].present? || request.get? + @copy_subtasks = params[:copy_subtasks].present? || request.get? + @issue.copy_from(@copy_from, :attachments => @copy_attachments, :subtasks => @copy_subtasks) + rescue ActiveRecord::RecordNotFound + render_404 + return + end + end + @issue.project = @project + else + @issue = @project.issues.visible.find(params[:id]) + end + + @issue.project = @project + @issue.author ||= User.current + # Tracker must be set before custom field values + @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first) + if @issue.tracker.nil? + render_error l(:error_no_tracker_in_project) + return false + end + @issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date? + @issue.safe_attributes = params[:issue] + + @priorities = IssuePriority.active + @allowed_statuses = @issue.new_statuses_allowed_to(User.current, true) + @available_watchers = (@issue.project.users.sort + @issue.watcher_users).uniq + end + + def check_for_default_issue_status + if IssueStatus.default.nil? + render_error l(:error_no_default_issue_status) + return false + end + end + + def parse_params_for_bulk_issue_attributes(params) + attributes = (params[:issue] || {}).reject {|k,v| v.blank?} + attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'} + if custom = attributes[:custom_field_values] + custom.reject! {|k,v| v.blank?} + custom.keys.each do |k| + if custom[k].is_a?(Array) + custom[k] << '' if custom[k].delete('__none__') + else + custom[k] = '' if custom[k] == '__none__' + end + end + end + attributes + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7a/7a8cd1b0e0860947e389cd0337442e3d3a963523.svn-base --- a/.svn/pristine/7a/7a8cd1b0e0860947e389cd0337442e3d3a963523.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1001 +0,0 @@ -ro: - direction: ltr - date: - formats: - default: "%d-%m-%Y" - short: "%d %b" - long: "%d %B %Y" - only_day: "%e" - - day_names: [Duminică, Luni, Marti, Miercuri, Joi, Vineri, Sâmbătă] - abbr_day_names: [Dum, Lun, Mar, Mie, Joi, Vin, Sâm] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, Ianuarie, Februarie, Martie, Aprilie, Mai, Iunie, Iulie, August, Septembrie, Octombrie, Noiembrie, Decembrie] - abbr_month_names: [~, Ian, Feb, Mar, Apr, Mai, Iun, Iul, Aug, Sep, Oct, Noi, Dec] - # Used in date_select and datime_select. - order: - - :day - - :month - - :year - - time: - formats: - default: "%m/%d/%Y %I:%M %p" - time: "%I:%M %p" - short: "%d %b %H:%M" - long: "%B %d, %Y %H:%M" - am: "am" - pm: "pm" - - datetime: - distance_in_words: - half_a_minute: "jumătate de minut" - less_than_x_seconds: - one: "mai puÈ›in de o secundă" - other: "mai puÈ›in de %{count} secunde" - x_seconds: - one: "o secundă" - other: "%{count} secunde" - less_than_x_minutes: - one: "mai puÈ›in de un minut" - other: "mai puÈ›in de %{count} minute" - x_minutes: - one: "un minut" - other: "%{count} minute" - about_x_hours: - one: "aproximativ o oră" - other: "aproximativ %{count} ore" - x_days: - one: "o zi" - other: "%{count} zile" - about_x_months: - one: "aproximativ o lună" - other: "aproximativ %{count} luni" - x_months: - one: "o luna" - other: "%{count} luni" - about_x_years: - one: "aproximativ un an" - other: "aproximativ %{count} ani" - over_x_years: - one: "peste un an" - other: "peste %{count} ani" - almost_x_years: - one: "almost 1 year" - other: "almost %{count} years" - - number: - human: - format: - precision: 1 - delimiter: "" - storage_units: - format: "%n %u" - units: - kb: KB - tb: TB - gb: GB - byte: - one: Byte - other: Bytes - mb: MB - -# Used in array.to_sentence. - support: - array: - sentence_connector: "È™i" - skip_last_comma: true - - activerecord: - errors: - template: - header: - one: "1 error prohibited this %{model} from being saved" - other: "%{count} errors prohibited this %{model} from being saved" - messages: - inclusion: "nu este inclus în listă" - exclusion: "este rezervat" - invalid: "nu este valid" - confirmation: "nu este identică" - accepted: "trebuie acceptat" - empty: "trebuie completat" - blank: "nu poate fi gol" - too_long: "este prea lung" - too_short: "este prea scurt" - wrong_length: "nu are lungimea corectă" - taken: "a fost luat deja" - not_a_number: "nu este un număr" - not_a_date: "nu este o dată validă" - greater_than: "trebuie să fie mai mare de %{count}" - greater_than_or_equal_to: "trebuie să fie mai mare sau egal cu %{count}" - equal_to: "trebuie să fie egal cu {count}}" - less_than: "trebuie să fie mai mic decat %{count}" - less_than_or_equal_to: "trebuie să fie mai mic sau egal cu %{count}" - odd: "trebuie să fie impar" - even: "trebuie să fie par" - greater_than_start_date: "trebuie să fie după data de început" - not_same_project: "trebuie să aparÈ›ină aceluiaÈ™i proiect" - circular_dependency: "Această relaÈ›ie ar crea o dependență circulară" - cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" - - actionview_instancetag_blank_option: SelectaÈ›i - - general_text_No: 'Nu' - general_text_Yes: 'Da' - general_text_no: 'nu' - general_text_yes: 'da' - general_lang_name: 'Română' - general_csv_separator: '.' - general_csv_decimal_separator: ',' - general_csv_encoding: UTF-8 - general_pdf_encoding: UTF-8 - general_first_day_of_week: '2' - - notice_account_updated: Cont actualizat. - notice_account_invalid_creditentials: Utilizator sau parola nevalidă - notice_account_password_updated: Parolă actualizată. - notice_account_wrong_password: Parolă greÈ™ită - notice_account_register_done: Contul a fost creat. Pentru activare, urmaÈ›i legătura trimisă prin email. - notice_account_unknown_email: Utilizator necunoscut. - notice_can_t_change_password: Acest cont foloseÈ™te o sursă externă de autentificare. Nu se poate schimba parola. - notice_account_lost_email_sent: S-a trimis un email cu instrucÈ›iuni de schimbare a parolei. - notice_account_activated: Contul a fost activat. Vă puteÈ›i autentifica acum. - notice_successful_create: Creat. - notice_successful_update: Actualizat. - notice_successful_delete: Șters. - notice_successful_connection: Conectat. - notice_file_not_found: Pagina pe care doriÈ›i să o accesaÈ›i nu există sau a fost È™tearsă. - notice_locking_conflict: Datele au fost actualizate de alt utilizator. - notice_not_authorized: Nu sunteÈ›i autorizat sa accesaÈ›i această pagină. - notice_email_sent: "S-a trimis un email către %{value}" - notice_email_error: "A intervenit o eroare la trimiterea de email (%{value})" - notice_feeds_access_key_reseted: Cheia de acces RSS a fost resetată. - notice_failed_to_save_issues: "Nu s-au putut salva %{count} tichete din cele %{total} selectate: %{ids}." - notice_no_issue_selected: "Niciun tichet selectat! Vă rugăm să selectaÈ›i tichetele pe care doriÈ›i să le editaÈ›i." - notice_account_pending: "Contul dumneavoastră a fost creat È™i aÈ™teaptă aprobarea administratorului." - notice_default_data_loaded: S-a încărcat configuraÈ›ia implicită. - notice_unable_delete_version: Nu se poate È™terge versiunea. - - error_can_t_load_default_data: "Nu s-a putut încărca configuraÈ›ia implicită: %{value}" - error_scm_not_found: "Nu s-a găsit articolul sau revizia în depozit." - error_scm_command_failed: "A intervenit o eroare la accesarea depozitului: %{value}" - error_scm_annotate: "Nu există sau nu poate fi adnotată." - error_issue_not_found_in_project: 'Tichetul nu a fost găsit sau nu aparÈ›ine acestui proiect' - - warning_attachments_not_saved: "Nu s-au putut salva %{count} fiÈ™iere." - - mail_subject_lost_password: "Parola dumneavoastră: %{value}" - mail_body_lost_password: 'Pentru a schimba parola, accesaÈ›i:' - mail_subject_register: "Activarea contului %{value}" - mail_body_register: 'Pentru activarea contului, accesaÈ›i:' - mail_body_account_information_external: "PuteÈ›i folosi contul „{value}}†pentru a vă autentifica." - mail_body_account_information: InformaÈ›ii despre contul dumneavoastră - mail_subject_account_activation_request: "Cerere de activare a contului %{value}" - mail_body_account_activation_request: "S-a înregistrat un utilizator nou (%{value}). Contul aÈ™teaptă aprobarea dumneavoastră:" - mail_subject_reminder: "%{count} tichete trebuie rezolvate în următoarele %{days} zile" - mail_body_reminder: "%{count} tichete atribuite dumneavoastră trebuie rezolvate în următoarele %{days} zile:" - - gui_validation_error: o eroare - gui_validation_error_plural: "%{count} erori" - - field_name: Nume - field_description: Descriere - field_summary: Rezumat - field_is_required: Obligatoriu - field_firstname: Prenume - field_lastname: Nume - field_mail: Email - field_filename: FiÈ™ier - field_filesize: Mărime - field_downloads: Descărcări - field_author: Autor - field_created_on: Creat la - field_updated_on: Actualizat la - field_field_format: Format - field_is_for_all: Pentru toate proiectele - field_possible_values: Valori posibile - field_regexp: Expresie regulară - field_min_length: lungime minimă - field_max_length: lungime maximă - field_value: Valoare - field_category: Categorie - field_title: Titlu - field_project: Proiect - field_issue: Tichet - field_status: Stare - field_notes: Note - field_is_closed: Rezolvat - field_is_default: Implicit - field_tracker: Tip de tichet - field_subject: Subiect - field_due_date: Data finalizării - field_assigned_to: Atribuit - field_priority: Prioritate - field_fixed_version: Versiune È›intă - field_user: Utilizator - field_role: Rol - field_homepage: Pagina principală - field_is_public: Public - field_parent: Sub-proiect al - field_is_in_roadmap: Tichete afiÈ™ate în plan - field_login: Autentificare - field_mail_notification: Notificări prin e-mail - field_admin: Administrator - field_last_login_on: Ultima autentificare în - field_language: Limba - field_effective_date: Data - field_password: Parola - field_new_password: Parola nouă - field_password_confirmation: Confirmare - field_version: Versiune - field_type: Tip - field_host: Gazdă - field_port: Port - field_account: Cont - field_base_dn: Base DN - field_attr_login: Atribut autentificare - field_attr_firstname: Atribut prenume - field_attr_lastname: Atribut nume - field_attr_mail: Atribut email - field_onthefly: Creare utilizator pe loc - field_start_date: Data începerii - field_done_ratio: Realizat (%) - field_auth_source: Mod autentificare - field_hide_mail: Nu se afiÈ™ează adresa de email - field_comments: Comentariu - field_url: URL - field_start_page: Pagina de start - field_subproject: Subproiect - field_hours: Ore - field_activity: Activitate - field_spent_on: Data - field_identifier: Identificator - field_is_filter: Filtru - field_issue_to: Tichet asociat - field_delay: ÃŽntârziere - field_assignable: Se pot atribui tichete acestui rol - field_redirect_existing_links: RedirecÈ›ionează legăturile existente - field_estimated_hours: Timp estimat - field_column_names: Coloane - field_time_zone: Fus orar - field_searchable: Căutare - field_default_value: Valoare implicita - field_comments_sorting: AfiÈ™ează comentarii - field_parent_title: Pagina superioara - field_editable: Modificabil - field_watcher: UrmăreÈ™te - field_identity_url: URL OpenID - field_content: ConÈ›inut - - setting_app_title: Titlu aplicaÈ›ie - setting_app_subtitle: Subtitlu aplicaÈ›ie - setting_welcome_text: Text de întâmpinare - setting_default_language: Limba implicita - setting_login_required: Necesita autentificare - setting_self_registration: ÃŽnregistrare automată - setting_attachment_max_size: Mărime maxima ataÈ™ament - setting_issues_export_limit: Limită de tichete exportate - setting_mail_from: Adresa de email a expeditorului - setting_bcc_recipients: AlÈ›i destinatari pentru email (BCC) - setting_plain_text_mail: Mesaje text (fără HTML) - setting_host_name: Numele gazdei È™i calea - setting_text_formatting: Formatare text - setting_wiki_compression: Comprimare istoric Wiki - setting_feeds_limit: Limita de actualizări din feed - setting_default_projects_public: Proiectele noi sunt implicit publice - setting_autofetch_changesets: Preluare automată a modificărilor din depozit - setting_sys_api_enabled: Activare WS pentru gestionat depozitul - setting_commit_ref_keywords: Cuvinte cheie pt. referire tichet - setting_commit_fix_keywords: Cuvinte cheie pt. rezolvare tichet - setting_autologin: Autentificare automată - setting_date_format: Format dată - setting_time_format: Format oră - setting_cross_project_issue_relations: Permite legături de tichete între proiecte - setting_issue_list_default_columns: Coloane implicite afiÈ™ate în lista de tichete - setting_emails_footer: Subsol email - setting_protocol: Protocol - setting_per_page_options: Număr de obiecte pe pagină - setting_user_format: Stil de afiÈ™are pentru utilizator - setting_activity_days_default: Se afiÈ™ează zile în jurnalul proiectului - setting_display_subprojects_issues: AfiÈ™ează implicit tichetele sub-proiectelor în proiectele principale - setting_enabled_scm: SCM activat - setting_mail_handler_api_enabled: Activare WS pentru email primit - setting_mail_handler_api_key: cheie API - setting_sequential_project_identifiers: Generează secvenÈ›ial identificatoarele de proiect - setting_gravatar_enabled: FoloseÈ™te poze Gravatar pentru utilizatori - setting_diff_max_lines_displayed: Număr maxim de linii de diferență afiÈ™ate - setting_file_max_size_displayed: Număr maxim de fiÈ™iere text afiÈ™ate în pagină (inline) - setting_repository_log_display_limit: Număr maxim de revizii afiÈ™ate în istoricul fiÈ™ierului - setting_openid: Permite înregistrare È™i autentificare cu OpenID - - permission_edit_project: Editează proiectul - permission_select_project_modules: Alege module pentru proiect - permission_manage_members: Editează membri - permission_manage_versions: Editează versiuni - permission_manage_categories: Editează categorii - permission_add_issues: Adaugă tichete - permission_edit_issues: Editează tichete - permission_manage_issue_relations: Editează relaÈ›ii tichete - permission_add_issue_notes: Adaugă note - permission_edit_issue_notes: Editează note - permission_edit_own_issue_notes: Editează notele proprii - permission_move_issues: Mută tichete - permission_delete_issues: Șterge tichete - permission_manage_public_queries: Editează căutările implicite - permission_save_queries: Salvează căutările - permission_view_gantt: AfiÈ™ează Gantt - permission_view_calendar: AfiÈ™ează calendarul - permission_view_issue_watchers: AfiÈ™ează lista de persoane interesate - permission_add_issue_watchers: Adaugă persoane interesate - permission_log_time: ÃŽnregistrează timpul de lucru - permission_view_time_entries: AfiÈ™ează timpul de lucru - permission_edit_time_entries: Editează jurnalele cu timp de lucru - permission_edit_own_time_entries: Editează jurnalele proprii cu timpul de lucru - permission_manage_news: Editează È™tiri - permission_comment_news: Comentează È™tirile - permission_manage_documents: Editează documente - permission_view_documents: AfiÈ™ează documente - permission_manage_files: Editează fiÈ™iere - permission_view_files: AfiÈ™ează fiÈ™iere - permission_manage_wiki: Editează wiki - permission_rename_wiki_pages: RedenumeÈ™te pagini wiki - permission_delete_wiki_pages: Șterge pagini wiki - permission_view_wiki_pages: AfiÈ™ează wiki - permission_view_wiki_edits: AfiÈ™ează istoricul wiki - permission_edit_wiki_pages: Editează pagini wiki - permission_delete_wiki_pages_attachments: Șterge ataÈ™amente - permission_protect_wiki_pages: Blochează pagini wiki - permission_manage_repository: Gestionează depozitul - permission_browse_repository: RăsfoieÈ™te depozitul - permission_view_changesets: AfiÈ™ează modificările din depozit - permission_commit_access: Acces commit - permission_manage_boards: Editează forum - permission_view_messages: AfiÈ™ează mesaje - permission_add_messages: Scrie mesaje - permission_edit_messages: Editează mesaje - permission_edit_own_messages: Editează mesajele proprii - permission_delete_messages: Șterge mesaje - permission_delete_own_messages: Șterge mesajele proprii - - project_module_issue_tracking: Tichete - project_module_time_tracking: Timp de lucru - project_module_news: Știri - project_module_documents: Documente - project_module_files: FiÈ™iere - project_module_wiki: Wiki - project_module_repository: Depozit - project_module_boards: Forum - - label_user: Utilizator - label_user_plural: Utilizatori - label_user_new: Utilizator nou - label_project: Proiect - label_project_new: Proiect nou - label_project_plural: Proiecte - label_x_projects: - zero: niciun proiect - one: un proiect - other: "%{count} proiecte" - label_project_all: Toate proiectele - label_project_latest: Proiecte noi - label_issue: Tichet - label_issue_new: Tichet nou - label_issue_plural: Tichete - label_issue_view_all: AfiÈ™ează toate tichetele - label_issues_by: "Sortează după %{value}" - label_issue_added: Adaugat - label_issue_updated: Actualizat - label_document: Document - label_document_new: Document nou - label_document_plural: Documente - label_document_added: Adăugat - label_role: Rol - label_role_plural: Roluri - label_role_new: Rol nou - label_role_and_permissions: Roluri È™i permisiuni - label_member: Membru - label_member_new: membru nou - label_member_plural: Membri - label_tracker: Tip de tichet - label_tracker_plural: Tipuri de tichete - label_tracker_new: Tip nou de tichet - label_workflow: Mod de lucru - label_issue_status: Stare tichet - label_issue_status_plural: Stare tichete - label_issue_status_new: Stare nouă - label_issue_category: Categorie de tichet - label_issue_category_plural: Categorii de tichete - label_issue_category_new: Categorie nouă - label_custom_field: Câmp personalizat - label_custom_field_plural: Câmpuri personalizate - label_custom_field_new: Câmp nou personalizat - label_enumerations: Enumerări - label_enumeration_new: Valoare nouă - label_information: InformaÈ›ie - label_information_plural: InformaÈ›ii - label_please_login: Vă rugăm să vă autentificaÈ›i - label_register: ÃŽnregistrare - label_login_with_open_id_option: sau autentificare cu OpenID - label_password_lost: Parolă uitată - label_home: Acasă - label_my_page: Pagina mea - label_my_account: Contul meu - label_my_projects: Proiectele mele - label_administration: Administrare - label_login: Autentificare - label_logout: IeÈ™ire din cont - label_help: Ajutor - label_reported_issues: Tichete - label_assigned_to_me_issues: Tichetele mele - label_last_login: Ultima conectare - label_registered_on: ÃŽnregistrat la - label_activity: Activitate - label_overall_activity: Activitate - vedere de ansamblu - label_user_activity: "Activitate %{value}" - label_new: Nou - label_logged_as: Autentificat ca - label_environment: Mediu - label_authentication: Autentificare - label_auth_source: Mod de autentificare - label_auth_source_new: Nou - label_auth_source_plural: Moduri de autentificare - label_subproject_plural: Sub-proiecte - label_and_its_subprojects: "%{value} È™i sub-proiecte" - label_min_max_length: lungime min - max - label_list: Listă - label_date: Dată - label_integer: ÃŽntreg - label_float: Zecimal - label_boolean: Valoare logică - label_string: Text - label_text: Text lung - label_attribute: Atribut - label_attribute_plural: Atribute - label_download: "%{count} descărcare" - label_download_plural: "%{count} descărcări" - label_no_data: Nu există date de afiÈ™at - label_change_status: Schimbă starea - label_history: Istoric - label_attachment: FiÈ™ier - label_attachment_new: FiÈ™ier nou - label_attachment_delete: Șterge fiÈ™ier - label_attachment_plural: FiÈ™iere - label_file_added: Adăugat - label_report: Raport - label_report_plural: Rapoarte - label_news: Știri - label_news_new: Adaugă È™tire - label_news_plural: Știri - label_news_latest: Ultimele È™tiri - label_news_view_all: AfiÈ™ează toate È™tirile - label_news_added: Adăugat - label_settings: Setări - label_overview: Pagină proiect - label_version: Versiune - label_version_new: Versiune nouă - label_version_plural: Versiuni - label_confirmation: Confirmare - label_export_to: 'Disponibil È™i în:' - label_read: CiteÈ™te... - label_public_projects: Proiecte publice - label_open_issues: deschis - label_open_issues_plural: deschise - label_closed_issues: închis - label_closed_issues_plural: închise - label_x_open_issues_abbr_on_total: - zero: 0 deschise / %{total} - one: 1 deschis / %{total} - other: "%{count} deschise / %{total}" - label_x_open_issues_abbr: - zero: 0 deschise - one: 1 deschis - other: "%{count} deschise" - label_x_closed_issues_abbr: - zero: 0 închise - one: 1 închis - other: "%{count} închise" - label_total: Total - label_permissions: Permisiuni - label_current_status: Stare curentă - label_new_statuses_allowed: Stări noi permise - label_all: toate - label_none: niciunul - label_nobody: nimeni - label_next: ÃŽnainte - label_previous: ÃŽnapoi - label_used_by: Folosit de - label_details: Detalii - label_add_note: Adaugă o notă - label_per_page: pe pagină - label_calendar: Calendar - label_months_from: luni de la - label_gantt: Gantt - label_internal: Intern - label_last_changes: "ultimele %{count} schimbări" - label_change_view_all: AfiÈ™ează toate schimbările - label_personalize_page: Personalizează aceasta pagina - label_comment: Comentariu - label_comment_plural: Comentarii - label_x_comments: - zero: fara comentarii - one: 1 comentariu - other: "%{count} comentarii" - label_comment_add: Adaugă un comentariu - label_comment_added: Adăugat - label_comment_delete: Șterge comentariul - label_query: Cautare personalizata - label_query_plural: Căutări personalizate - label_query_new: Căutare nouă - label_filter_add: Adaugă filtru - label_filter_plural: Filtre - label_equals: este - label_not_equals: nu este - label_in_less_than: în mai puÈ›in de - label_in_more_than: în mai mult de - label_in: în - label_today: astăzi - label_all_time: oricând - label_yesterday: ieri - label_this_week: săptămâna aceasta - label_last_week: săptămâna trecută - label_last_n_days: "ultimele %{count} zile" - label_this_month: luna aceasta - label_last_month: luna trecută - label_this_year: anul acesta - label_date_range: Perioada - label_less_than_ago: mai puÈ›in de ... zile - label_more_than_ago: mai mult de ... zile - label_ago: în urma - label_contains: conÈ›ine - label_not_contains: nu conÈ›ine - label_day_plural: zile - label_repository: Depozit - label_repository_plural: Depozite - label_browse: AfiÈ™ează - label_modification: "%{count} schimbare" - label_modification_plural: "%{count} schimbări" - label_revision: Revizie - label_revision_plural: Revizii - label_associated_revisions: Revizii asociate - label_added: adaugată - label_modified: modificată - label_copied: copiată - label_renamed: redenumită - label_deleted: È™tearsă - label_latest_revision: Ultima revizie - label_latest_revision_plural: Ultimele revizii - label_view_revisions: AfiÈ™ează revizii - label_max_size: Mărime maximă - label_sort_highest: Prima - label_sort_higher: ÃŽn sus - label_sort_lower: ÃŽn jos - label_sort_lowest: Ultima - label_roadmap: Planificare - label_roadmap_due_in: "De terminat în %{value}" - label_roadmap_overdue: "ÃŽntârziat cu %{value}" - label_roadmap_no_issues: Nu există tichete pentru această versiune - label_search: Caută - label_result_plural: Rezultate - label_all_words: toate cuvintele - label_wiki: Wiki - label_wiki_edit: Editare Wiki - label_wiki_edit_plural: Editări Wiki - label_wiki_page: Pagină Wiki - label_wiki_page_plural: Pagini Wiki - label_index_by_title: Sortează după titlu - label_index_by_date: Sortează după dată - label_current_version: Versiunea curentă - label_preview: Previzualizare - label_feed_plural: Feed-uri - label_changes_details: Detaliile tuturor schimbărilor - label_issue_tracking: Urmărire tichete - label_spent_time: Timp alocat - label_f_hour: "%{value} oră" - label_f_hour_plural: "%{value} ore" - label_time_tracking: Urmărire timp de lucru - label_change_plural: Schimbări - label_statistics: Statistici - label_commits_per_month: Commit pe luna - label_commits_per_author: Commit per autor - label_view_diff: AfiÈ™ează diferenÈ›ele - label_diff_inline: în linie - label_diff_side_by_side: una lângă alta - label_options: OpÈ›iuni - label_copy_workflow_from: Copiază modul de lucru de la - label_permissions_report: Permisiuni - label_watched_issues: Tichete urmărite - label_related_issues: Tichete asociate - label_applied_status: Stare aplicată - label_loading: ÃŽncarcă... - label_relation_new: Asociere nouă - label_relation_delete: Șterge asocierea - label_relates_to: asociat cu - label_duplicates: duplicate - label_duplicated_by: la fel ca - label_blocks: blocări - label_blocked_by: blocat de - label_precedes: precede - label_follows: urmează - label_end_to_start: de la sfârÈ™it la început - label_end_to_end: de la sfârÈ™it la sfârÈ™it - label_start_to_start: de la început la început - label_start_to_end: de la început la sfârÈ™it - label_stay_logged_in: Păstrează autentificarea - label_disabled: dezactivat - label_show_completed_versions: Arată versiunile terminate - label_me: eu - label_board: Forum - label_board_new: Forum nou - label_board_plural: Forumuri - label_topic_plural: Subiecte - label_message_plural: Mesaje - label_message_last: Ultimul mesaj - label_message_new: Mesaj nou - label_message_posted: Adăugat - label_reply_plural: Răspunsuri - label_send_information: Trimite utilizatorului informaÈ›iile despre cont - label_year: An - label_month: Lună - label_week: Săptămână - label_date_from: De la - label_date_to: La - label_language_based: Un funcÈ›ie de limba de afiÈ™are a utilizatorului - label_sort_by: "Sortează după %{value}" - label_send_test_email: Trimite email de test - label_feeds_access_key_created_on: "Cheie de acces creată acum %{value}" - label_module_plural: Module - label_added_time_by: "Adăugat de %{author} acum %{age}" - label_updated_time_by: "Actualizat de %{author} acum %{age}" - label_updated_time: "Actualizat acum %{value}" - label_jump_to_a_project: Alege proiectul... - label_file_plural: FiÈ™iere - label_changeset_plural: Schimbări - label_default_columns: Coloane implicite - label_no_change_option: (fără schimbări) - label_bulk_edit_selected_issues: Editează toate tichetele selectate - label_theme: Tema - label_default: Implicită - label_search_titles_only: Caută numai în titluri - label_user_mail_option_all: "Pentru orice eveniment, în toate proiectele mele" - label_user_mail_option_selected: " Pentru orice eveniment, în proiectele selectate..." - label_user_mail_no_self_notified: "Nu trimite notificări pentru modificările mele" - label_registration_activation_by_email: activare cont prin email - label_registration_manual_activation: activare manuală a contului - label_registration_automatic_activation: activare automată a contului - label_display_per_page: "pe pagină: %{value}" - label_age: vechime - label_change_properties: Schimbă proprietățile - label_general: General - label_more: Mai mult - label_scm: SCM - label_plugins: Plugin-uri - label_ldap_authentication: autentificare LDAP - label_downloads_abbr: D/L - label_optional_description: Descriere (opÈ›ională) - label_add_another_file: Adaugă alt fiÈ™ier - label_preferences: PreferinÈ›e - label_chronological_order: în ordine cronologică - label_reverse_chronological_order: ÃŽn ordine invers cronologică - label_planning: Planificare - label_incoming_emails: Mesaje primite - label_generate_key: Generează o cheie - label_issue_watchers: Cine urmăreÈ™te - label_example: Exemplu - label_display: AfiÈ™ează - - label_sort: Sortează - label_ascending: Crescător - label_descending: Descrescător - label_date_from_to: De la %{start} la %{end} - - - button_login: Autentificare - button_submit: Trimite - button_save: Salvează - button_check_all: Bifează tot - button_uncheck_all: Debifează tot - button_delete: Șterge - button_create: Creează - button_create_and_continue: Creează È™i continua - button_test: Testează - button_edit: Editează - button_add: Adaugă - button_change: Modifică - button_apply: Aplică - button_clear: Șterge - button_lock: Blochează - button_unlock: Deblochează - button_download: Descarcă - button_list: Listează - button_view: AfiÈ™ează - button_move: Mută - button_back: ÃŽnapoi - button_cancel: Anulează - button_activate: Activează - button_sort: Sortează - button_log_time: ÃŽnregistrează timpul de lucru - button_rollback: Revenire la această versiune - button_watch: Urmăresc - button_unwatch: Nu urmăresc - button_reply: Răspunde - button_archive: Arhivează - button_unarchive: Dezarhivează - button_reset: Resetează - button_rename: RedenumeÈ™te - button_change_password: Schimbare parolă - button_copy: Copiază - button_annotate: Adnotează - button_update: Actualizează - button_configure: Configurează - button_quote: Citează - - status_active: activ - status_registered: înregistrat - status_locked: blocat - - text_select_mail_notifications: SelectaÈ›i acÈ›iunile notificate prin email. - text_regexp_info: ex. ^[A-Z0-9]+$ - text_min_max_length_info: 0 înseamnă fără restricÈ›ii - text_project_destroy_confirmation: Sigur doriÈ›i să È™tergeÈ›i proiectul È™i toate datele asociate? - text_subprojects_destroy_warning: "Se vor È™terge È™i sub-proiectele: %{value}." - text_workflow_edit: SelectaÈ›i un rol È™i un tip de tichet pentru a edita modul de lucru - text_are_you_sure: SunteÈ›i sigur(ă)? - text_tip_issue_begin_day: sarcină care începe în această zi - text_tip_issue_end_day: sarcină care se termină în această zi - text_tip_issue_begin_end_day: sarcină care începe È™i se termină în această zi - text_project_identifier_info: 'Sunt permise doar litere mici (a-z), numere È™i cratime.
    Odată salvat, identificatorul nu mai poate fi modificat.' - text_caracters_maximum: "maxim %{count} caractere." - text_caracters_minimum: "Trebuie să fie minim %{count} caractere." - text_length_between: "Lungime între %{min} și %{max} caractere." - text_tracker_no_workflow: Nu sunt moduri de lucru pentru acest tip de tichet - text_unallowed_characters: Caractere nepermise - text_comma_separated: Sunt permise mai multe valori (separate cu virgulă). - text_issues_ref_in_commit_messages: Referire la tichete și rezolvare în textul mesajului - text_issue_added: "Tichetul %{id} a fost adăugat de %{author}." - text_issue_updated: "Tichetul %{id} a fost actualizat de %{author}." - text_wiki_destroy_confirmation: Sigur doriți ștergerea Wiki și a conținutului asociat? - text_issue_category_destroy_question: "Această categorie conține (%{count}) tichete. Ce doriți să faceți?" - text_issue_category_destroy_assignments: Șterge apartenența la categorie. - text_issue_category_reassign_to: Atribuie tichetele la această categorie - text_user_mail_option: "Pentru proiectele care nu sunt selectate, veți primi notificări doar pentru ceea ce urmăriți sau în ce sunteți implicat (ex: tichete create de dumneavoastră sau care vă sunt atribuite)." - text_no_configuration_data: "Nu s-au configurat încă rolurile, stările tichetelor și modurile de lucru.\nEste recomandat să încărcați configurația implicită. O veți putea modifica ulterior." - text_load_default_configuration: Încarcă configurația implicită - text_status_changed_by_changeset: "Aplicat în setul %{value}." - text_issues_destroy_confirmation: 'Sigur doriți să ștergeți tichetele selectate?' - text_select_project_modules: 'Selectați modulele active pentru acest proiect:' - text_default_administrator_account_changed: S-a schimbat contul administratorului implicit - text_file_repository_writable: Se poate scrie în directorul de atașamente - text_plugin_assets_writable: Se poate scrie în directorul de plugin-uri - text_rmagick_available: Este disponibil RMagick (opțional) - text_destroy_time_entries_question: "%{hours} ore sunt înregistrate la tichetele pe care doriți să le ștergeți. Ce doriți sa faceți?" - text_destroy_time_entries: Șterge orele înregistrate - text_assign_time_entries_to_project: Atribuie orele la proiect - text_reassign_time_entries: 'Atribuie orele înregistrate la tichetul:' - text_user_wrote: "%{value} a scris:" - text_enumeration_destroy_question: "Această valoare are %{count} obiecte." - text_enumeration_category_reassign_to: 'Atribuie la această valoare:' - text_email_delivery_not_configured: "Trimiterea de emailuri nu este configurată și ca urmare, notificările sunt dezactivate.\nConfigurați serverul SMTP în config/configuration.yml și reporniți aplicația pentru a le activa." - text_repository_usernames_mapping: "Selectați sau modificați contul Redmine echivalent contului din istoricul depozitului.\nUtilizatorii cu un cont (sau e-mail) identic în Redmine și depozit sunt echivalate automat." - text_diff_truncated: '... Comparația a fost trunchiată pentru ca depășește lungimea maximă de text care poate fi afișat.' - text_custom_field_possible_values_info: 'O linie pentru fiecare valoare' - - default_role_manager: Manager - default_role_developer: Dezvoltator - default_role_reporter: Creator de rapoarte - default_tracker_bug: Defect - default_tracker_feature: Funcție - default_tracker_support: Suport - default_issue_status_new: Nou - default_issue_status_in_progress: In Progress - default_issue_status_resolved: Rezolvat - default_issue_status_feedback: Așteaptă reacții - default_issue_status_closed: Închis - default_issue_status_rejected: Respins - default_doc_category_user: Documentație - default_doc_category_tech: Documentație tehnică - default_priority_low: mică - default_priority_normal: normală - default_priority_high: mare - default_priority_urgent: urgentă - default_priority_immediate: imediată - default_activity_design: Design - default_activity_development: Dezvoltare - - enumeration_issue_priorities: Priorități tichete - enumeration_doc_categories: Categorii documente - enumeration_activities: Activități (timp de lucru) - label_greater_or_equal: ">=" - label_less_or_equal: <= - text_wiki_page_destroy_question: Această pagină are %{descendants} pagini anterioare și descendenți. Ce doriți să faceți? - text_wiki_page_reassign_children: Atribuie paginile la această pagină - text_wiki_page_nullify_children: Menține paginile ca și pagini inițiale (root) - text_wiki_page_destroy_children: Șterge paginile și descendenții - setting_password_min_length: Lungime minimă parolă - field_group_by: Grupează după - mail_subject_wiki_content_updated: "Pagina wiki '%{id}' a fost actualizată" - label_wiki_content_added: Adăugat - mail_subject_wiki_content_added: "Pagina wiki '%{id}' a fost adăugată" - mail_body_wiki_content_added: Pagina wiki '%{id}' a fost adăugată de %{author}. - label_wiki_content_updated: Actualizat - mail_body_wiki_content_updated: Pagina wiki '%{id}' a fost actualizată de %{author}. - permission_add_project: Crează proiect - setting_new_project_user_role_id: Rol atribuit utilizatorului non-admin care crează un proiect. - label_view_all_revisions: Arată toate reviziile - label_tag: Tag - label_branch: Branch - error_no_tracker_in_project: Nu există un tracker asociat cu proiectul. Verificați vă rog setările proiectului. - error_no_default_issue_status: Nu există un status implicit al tichetelor. Verificați vă rog configurația (Mergeți la "Administrare -> Stări tichete"). - text_journal_changed: "%{label} schimbat din %{old} în %{new}" - text_journal_set_to: "%{label} setat ca %{value}" - text_journal_deleted: "%{label} șters (%{old})" - label_group_plural: Grupuri - label_group: Grup - label_group_new: Grup nou - label_time_entry_plural: Timp alocat - text_journal_added: "%{label} %{value} added" - field_active: Active - enumeration_system_activity: System Activity - permission_delete_issue_watchers: Delete watchers - version_status_closed: closed - version_status_locked: locked - version_status_open: open - error_can_not_reopen_issue_on_closed_version: An issue assigned to a closed version can not be reopened - label_user_anonymous: Anonymous - button_move_and_follow: Move and follow - setting_default_projects_modules: Default enabled modules for new projects - setting_gravatar_default: Default Gravatar image - field_sharing: Sharing - label_version_sharing_hierarchy: With project hierarchy - label_version_sharing_system: With all projects - label_version_sharing_descendants: With subprojects - label_version_sharing_tree: With project tree - label_version_sharing_none: Not shared - error_can_not_archive_project: This project can not be archived - button_duplicate: Duplicate - button_copy_and_follow: Copy and follow - label_copy_source: Source - setting_issue_done_ratio: Calculate the issue done ratio with - setting_issue_done_ratio_issue_status: Use the issue status - error_issue_done_ratios_not_updated: Issue done ratios not updated. - error_workflow_copy_target: Please select target tracker(s) and role(s) - setting_issue_done_ratio_issue_field: Use the issue field - label_copy_same_as_target: Same as target - label_copy_target: Target - notice_issue_done_ratios_updated: Issue done ratios updated. - error_workflow_copy_source: Please select a source tracker or role - label_update_issue_done_ratios: Update issue done ratios - setting_start_of_week: Start calendars on - permission_view_issues: View Issues - label_display_used_statuses_only: Only display statuses that are used by this tracker - label_revision_id: Revision %{value} - label_api_access_key: API access key - label_api_access_key_created_on: API access key created %{value} ago - label_feeds_access_key: RSS access key - notice_api_access_key_reseted: Your API access key was reset. - setting_rest_api_enabled: Enable REST web service - label_missing_api_access_key: Missing an API access key - label_missing_feeds_access_key: Missing a RSS access key - button_show: Show - text_line_separated: Multiple values allowed (one line for each value). - setting_mail_handler_body_delimiters: Truncate emails after one of these lines - permission_add_subprojects: Create subprojects - label_subproject_new: New subproject - text_own_membership_delete_confirmation: |- - You are about to remove some or all of your permissions and may no longer be able to edit this project after that. - Are you sure you want to continue? - label_close_versions: Close completed versions - label_board_sticky: Sticky - label_board_locked: Locked - permission_export_wiki_pages: Export wiki pages - setting_cache_formatted_text: Cache formatted text - permission_manage_project_activities: Manage project activities - error_unable_delete_issue_status: Unable to delete issue status - label_profile: Profile - permission_manage_subtasks: Manage subtasks - field_parent_issue: Parent task - label_subtask_plural: Subtasks - label_project_copy_notifications: Send email notifications during the project copy - error_can_not_delete_custom_field: Unable to delete custom field - error_unable_to_connect: Unable to connect (%{value}) - error_can_not_remove_role: This role is in use and can not be deleted. - error_can_not_delete_tracker: This tracker contains issues and can't be deleted. - field_principal: Principal - label_my_page_block: My page block - notice_failed_to_save_members: "Failed to save member(s): %{errors}." - text_zoom_out: Zoom out - text_zoom_in: Zoom in - notice_unable_delete_time_entry: Unable to delete time log entry. - label_overall_spent_time: Overall spent time - field_time_entries: Log time - project_module_gantt: Gantt - project_module_calendar: Calendar - button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" - text_are_you_sure_with_children: Delete issue and all child issues? - field_text: Text field - label_user_mail_option_only_owner: Only for things I am the owner of - setting_default_notification_option: Default notification option - label_user_mail_option_only_my_events: Only for things I watch or I'm involved in - label_user_mail_option_only_assigned: Only for things I am assigned to - label_user_mail_option_none: No events - field_member_of_group: Assignee's group - field_assigned_to_role: Assignee's role - notice_not_authorized_archived_project: The project you're trying to access has been archived. - label_principal_search: "Search for user or group:" - label_user_search: "Search for user:" - field_visible: Visible - setting_emails_header: Emails header - setting_commit_logtime_activity_id: Activity for logged time - text_time_logged_by_changeset: Applied in changeset %{value}. - setting_commit_logtime_enabled: Enable time logging - notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) - setting_gantt_items_limit: Maximum number of items displayed on the gantt chart - field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text - text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. - label_my_queries: My custom queries - text_journal_changed_no_detail: "%{label} updated" - label_news_comment_added: Comment added to a news - button_expand_all: Expand all - button_collapse_all: Collapse all - label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee - label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author - label_bulk_edit_selected_time_entries: Bulk edit selected time entries - text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? - label_role_anonymous: Anonymous - label_role_non_member: Non member - label_issue_note_added: Note added - label_issue_status_updated: Status updated - label_issue_priority_updated: Priority updated - label_issues_visibility_own: Issues created by or assigned to the user - field_issues_visibility: Issues visibility - label_issues_visibility_all: All issues - permission_set_own_issues_private: Set own issues public or private - field_is_private: Private - permission_set_issues_private: Set issues public or private - label_issues_visibility_public: All non private issues - text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). - field_commit_logs_encoding: Codare pentru mesaje - field_scm_path_encoding: Path encoding - text_scm_path_encoding_note: "Default: UTF-8" - field_path_to_repository: Path to repository - field_root_directory: Root directory - field_cvs_module: Module - field_cvsroot: CVSROOT - text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) - text_scm_command: Command - text_scm_command_version: Version - label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. - notice_issue_successful_create: Issue %{id} created. - label_between: between - setting_issue_group_assignment: Allow issue assignment to groups - label_diff: diff - text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) - description_query_sort_criteria_direction: Sort direction - description_project_scope: Search scope - description_filter: Filter - description_user_mail_notification: Mail notification settings - description_date_from: Enter start date - description_message_content: Message content - description_available_columns: Available Columns - description_date_range_interval: Choose range by selecting start and end date - description_issue_category_reassign: Choose issue category - description_search: Searchfield - description_notes: Notes - description_date_range_list: Choose range from list - description_choose_project: Projects - description_date_to: Enter end date - description_query_sort_criteria_attribute: Sort attribute - description_wiki_subpages_reassign: Choose new parent page - description_selected_columns: Selected Columns - label_parent_revision: Parent - label_child_revision: Child - error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. - setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues - button_edit_section: Edit this section - setting_repositories_encodings: Attachments and repositories encodings - description_all_columns: All Columns - button_export: Export - label_export_options: "%{export_format} export options" - error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7a/7acfd8a3d508663aeabae0309fddb7dc899a74cf.svn-base --- a/.svn/pristine/7a/7acfd8a3d508663aeabae0309fddb7dc899a74cf.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,39 +0,0 @@ -module CodeRay -module Encoders - - # Returns the number of tokens. - # - # Text and block tokens are counted. - class Count < Encoder - - register_for :count - - protected - - def setup options - super - - @count = 0 - end - - def finish options - output @count - end - - public - - def text_token text, kind - @count += 1 - end - - def begin_group kind - @count += 1 - end - alias end_group begin_group - alias begin_line begin_group - alias end_line begin_group - - end - -end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7a/7ae54c43f56efaece5e4ecd309a31e0849a96145.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/7a/7ae54c43f56efaece5e4ecd309a31e0849a96145.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,297 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2013 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 RepositoriesHelper + def format_revision(revision) + if revision.respond_to? :format_identifier + revision.format_identifier + else + revision.to_s + end + 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 = '' + properties.keys.sort.each do |property| + content << content_tag('li', "#{h property}: #{h properties[property]}".html_safe) + end + content_tag('ul', content.html_safe, :class => 'properties') + end + end + + def render_changeset_changes + changes = @changeset.filechanges.limit(1000).reorder('path').all.collect do |change| + case change.action + when 'A' + # Detects moved/copied files + if !change.from_path.blank? + change.action = + @changeset.filechanges.detect {|c| c.action == 'D' && c.path == change.from_path} ? 'R' : 'C' + end + change + when 'D' + @changeset.filechanges.detect {|c| c.from_path == change.path} ? nil : change + else + change + end + end.compact + + tree = { } + changes.each do |change| + p = tree + dirs = change.path.to_s.split('/').select {|d| !d.blank?} + path = '' + dirs.each do |dir| + path += '/' + dir + p[:s] ||= {} + p = p[:s] + p[path] ||= {} + p = p[path] + end + p[:c] = change + end + render_changes_tree(tree[:s]) + end + + def render_changes_tree(tree) + return '' if tree.nil? + output = '' + output << '
      ' + tree.keys.sort.each do |file| + style = 'change' + text = File.basename(h(file)) + if s = tree[file][:s] + style << ' folder' + path_param = to_path_param(@repository.relative_path(file)) + text = link_to(h(text), :controller => 'repositories', + :action => 'show', + :id => @project, + :repository_id => @repository.identifier_param, + :path => path_param, + :rev => @changeset.identifier) + output << "
    • #{text}" + output << render_changes_tree(s) + output << "
    • " + elsif c = tree[file][:c] + style << " change-#{c.action}" + path_param = to_path_param(@repository.relative_path(c.path)) + text = link_to(h(text), :controller => 'repositories', + :action => 'entry', + :id => @project, + :repository_id => @repository.identifier_param, + :path => path_param, + :rev => @changeset.identifier) unless c.action == 'D' + text << " - #{h(c.revision)}" unless c.revision.blank? + text << ' ('.html_safe + link_to(l(:label_diff), :controller => 'repositories', + :action => 'diff', + :id => @project, + :repository_id => @repository.identifier_param, + :path => path_param, + :rev => @changeset.identifier) + ') '.html_safe if c.action == 'M' + text << ' '.html_safe + content_tag('span', h(c.from_path), :class => 'copied-from') unless c.from_path.blank? + output << "
    • #{text}
    • " + end + end + output << '
    ' + output.html_safe + end + + def repository_field_tags(form, repository) + method = repository.class.name.demodulize.underscore + "_field_tags" + if repository.is_a?(Repository) && + respond_to?(method) && method != 'repository_field_tags' + send(method, form, repository) + end + end + + def scm_select_tag(repository) + scm_options = [["--- #{l(:actionview_instancetag_blank_option)} ---", '']] + Redmine::Scm::Base.all.each do |scm| + if Setting.enabled_scm.include?(scm) || + (repository && repository.class.name.demodulize == scm) + scm_options << ["Repository::#{scm}".constantize.scm_name, scm] + end + end + select_tag('repository_scm', + options_for_select(scm_options, repository.class.name.demodulize), + :disabled => (repository && !repository.new_record?), + :data => {:remote => true, :method => 'get'}) + end + + def with_leading_slash(path) + path.to_s.starts_with?('/') ? path : "/#{path}" + end + + def without_leading_slash(path) + path.gsub(%r{^/+}, '') + end + + def subversion_field_tags(form, repository) + content_tag('p', form.text_field(:url, :size => 60, :required => true, + :disabled => !repository.safe_attribute?('url')) + + '
    '.html_safe + + '(file:///, http://, https://, svn://, svn+[tunnelscheme]://)') + + content_tag('p', form.text_field(:login, :size => 30)) + + content_tag('p', form.password_field( + :password, :size => 30, :name => 'ignore', + :value => ((repository.new_record? || repository.password.blank?) ? '' : ('x'*15)), + :onfocus => "this.value=''; this.name='repository[password]';", + :onchange => "this.name='repository[password]';")) + end + + def darcs_field_tags(form, repository) + content_tag('p', form.text_field( + :url, :label => l(:field_path_to_repository), + :size => 60, :required => true, + :disabled => !repository.safe_attribute?('url'))) + + content_tag('p', form.select( + :log_encoding, [nil] + Setting::ENCODINGS, + :label => l(:field_commit_logs_encoding), :required => true)) + end + + def mercurial_field_tags(form, repository) + content_tag('p', form.text_field( + :url, :label => l(:field_path_to_repository), + :size => 60, :required => true, + :disabled => !repository.safe_attribute?('url') + ) + + '
    '.html_safe + l(:text_mercurial_repository_note)) + + content_tag('p', form.select( + :path_encoding, [nil] + Setting::ENCODINGS, + :label => l(:field_scm_path_encoding) + ) + + '
    '.html_safe + l(:text_scm_path_encoding_note)) + end + + def git_field_tags(form, repository) + content_tag('p', form.text_field( + :url, :label => l(:field_path_to_repository), + :size => 60, :required => true, + :disabled => !repository.safe_attribute?('url') + ) + + '
    '.html_safe + + l(:text_git_repository_note)) + + content_tag('p', form.select( + :path_encoding, [nil] + Setting::ENCODINGS, + :label => l(:field_scm_path_encoding) + ) + + '
    '.html_safe + l(:text_scm_path_encoding_note)) + + content_tag('p', form.check_box( + :extra_report_last_commit, + :label => l(:label_git_report_last_commit) + )) + end + + def cvs_field_tags(form, repository) + content_tag('p', form.text_field( + :root_url, + :label => l(:field_cvsroot), + :size => 60, :required => true, + :disabled => !repository.safe_attribute?('root_url'))) + + content_tag('p', form.text_field( + :url, + :label => l(:field_cvs_module), + :size => 30, :required => true, + :disabled => !repository.safe_attribute?('url'))) + + content_tag('p', form.select( + :log_encoding, [nil] + Setting::ENCODINGS, + :label => l(:field_commit_logs_encoding), :required => true)) + + content_tag('p', form.select( + :path_encoding, [nil] + Setting::ENCODINGS, + :label => l(:field_scm_path_encoding) + ) + + '
    '.html_safe + l(:text_scm_path_encoding_note)) + end + + def bazaar_field_tags(form, repository) + content_tag('p', form.text_field( + :url, :label => l(:field_path_to_repository), + :size => 60, :required => true, + :disabled => !repository.safe_attribute?('url'))) + + content_tag('p', form.select( + :log_encoding, [nil] + Setting::ENCODINGS, + :label => l(:field_commit_logs_encoding), :required => true)) + end + + def filesystem_field_tags(form, repository) + content_tag('p', form.text_field( + :url, :label => l(:field_root_directory), + :size => 60, :required => true, + :disabled => !repository.safe_attribute?('url'))) + + content_tag('p', form.select( + :path_encoding, [nil] + Setting::ENCODINGS, + :label => l(:field_scm_path_encoding) + ) + + '
    '.html_safe + l(:text_scm_path_encoding_note)) + end + + def index_commits(commits, heads) + return nil if commits.nil? or commits.first.parents.nil? + refs_map = {} + heads.each do |head| + refs_map[head.scmid] ||= [] + refs_map[head.scmid] << head + end + commits_by_scmid = {} + commits.reverse.each_with_index do |commit, commit_index| + commits_by_scmid[commit.scmid] = { + :parent_scmids => commit.parents.collect { |parent| parent.scmid }, + :rdmid => commit_index, + :refs => refs_map.include?(commit.scmid) ? refs_map[commit.scmid].join(" ") : nil, + :scmid => commit.scmid, + :href => block_given? ? yield(commit.scmid) : commit.scmid + } + end + heads.sort! { |head1, head2| head1.to_s <=> head2.to_s } + space = nil + heads.each do |head| + if commits_by_scmid.include? head.scmid + space = index_head((space || -1) + 1, head, commits_by_scmid) + end + end + # when no head matched anything use first commit + space ||= index_head(0, commits.first, commits_by_scmid) + return commits_by_scmid, space + end + + def index_head(space, commit, commits_by_scmid) + stack = [[space, commits_by_scmid[commit.scmid]]] + max_space = space + until stack.empty? + space, commit = stack.pop + commit[:space] = space if commit[:space].nil? + space -= 1 + commit[:parent_scmids].each_with_index do |parent_scmid, parent_index| + parent_commit = commits_by_scmid[parent_scmid] + if parent_commit and parent_commit[:space].nil? + stack.unshift [space += 1, parent_commit] + end + end + max_space = space if max_space < space + end + max_space + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7b/7b0091cb7275a16ba6daf28038e36a659196b7a1.svn-base --- a/.svn/pristine/7b/7b0091cb7275a16ba6daf28038e36a659196b7a1.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,44 +0,0 @@ -

    <%=l(:label_register)%> <%=link_to l(:label_login_with_open_id_option), signin_url if Setting.openid? %>

    - -<% form_tag({:action => 'register'}, :class => "tabular") do %> -<%= error_messages_for 'user' %> - -
    - -<% if @user.auth_source_id.nil? %> -

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

    - -

    -<%= password_field_tag 'password', nil, :size => 25 %>
    -<%= l(:text_caracters_minimum, :count => Setting.password_min_length) %>

    - -

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

    -<% end %> - -

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

    - -

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

    - -

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

    - -

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

    - -<% if Setting.openid? %> -

    -<%= text_field 'user', 'identity_url' %>

    -<% end %> - -<% @user.custom_field_values.select {|v| v.editable? || v.required?}.each do |value| %> -

    <%= custom_field_tag_with_label :user, value %>

    -<% end %> - -
    - -<%= submit_tag l(:button_submit) %> -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7b/7b4fab45dadc851ece3c8f70d2660b6cfb36eddc.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/7b/7b4fab45dadc851ece3c8f70d2660b6cfb36eddc.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,74 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class WikiRedirectTest < ActiveSupport::TestCase + fixtures :projects, :wikis, :wiki_pages + + def setup + @wiki = Wiki.find(1) + @original = WikiPage.create(:wiki => @wiki, :title => 'Original title') + end + + def test_create_redirect + @original.title = 'New title' + assert @original.save + @original.reload + + assert_equal 'New_title', @original.title + assert @wiki.redirects.find_by_title('Original_title') + assert @wiki.find_page('Original title') + assert @wiki.find_page('ORIGINAL title') + end + + def test_update_redirect + # create a redirect that point to this page + assert WikiRedirect.create(:wiki => @wiki, :title => 'An_old_page', :redirects_to => 'Original_title') + + @original.title = 'New title' + @original.save + # make sure the old page now points to the new page + assert_equal 'New_title', @wiki.find_page('An old page').title + end + + def test_reverse_rename + # create a redirect that point to this page + assert WikiRedirect.create(:wiki => @wiki, :title => 'An_old_page', :redirects_to => 'Original_title') + + @original.title = 'An old page' + @original.save + assert !@wiki.redirects.find_by_title_and_redirects_to('An_old_page', 'An_old_page') + assert @wiki.redirects.find_by_title_and_redirects_to('Original_title', 'An_old_page') + end + + def test_rename_to_already_redirected + assert WikiRedirect.create(:wiki => @wiki, :title => 'An_old_page', :redirects_to => 'Other_page') + + @original.title = 'An old page' + @original.save + # this redirect have to be removed since 'An old page' page now exists + assert !@wiki.redirects.find_by_title_and_redirects_to('An_old_page', 'Other_page') + end + + def test_redirects_removed_when_deleting_page + assert WikiRedirect.create(:wiki => @wiki, :title => 'An_old_page', :redirects_to => 'Original_title') + + @original.destroy + assert_nil @wiki.redirects.first + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7b/7b54bcb0df97869017d78b50300be1638742d7dc.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/7b/7b54bcb0df97869017d78b50300be1638742d7dc.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,208 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require 'redmine/scm/adapters/cvs_adapter' +require 'digest/sha1' + +class Repository::Cvs < Repository + validates_presence_of :url, :root_url, :log_encoding + + safe_attributes 'root_url', + :if => lambda {|repository, user| repository.new_record?} + + def self.human_attribute_name(attribute_key_name, *args) + attr_name = attribute_key_name.to_s + if attr_name == "root_url" + attr_name = "cvsroot" + elsif attr_name == "url" + attr_name = "cvs_module" + end + super(attr_name, *args) + end + + def self.scm_adapter_class + Redmine::Scm::Adapters::CvsAdapter + end + + def self.scm_name + 'CVS' + end + + def entry(path=nil, identifier=nil) + rev = identifier.nil? ? nil : changesets.find_by_revision(identifier) + scm.entry(path, rev.nil? ? nil : rev.committed_on) + end + + def entries(path=nil, identifier=nil) + rev = nil + if ! identifier.nil? + rev = changesets.find_by_revision(identifier) + return nil if rev.nil? + end + entries = scm.entries(path, rev.nil? ? nil : rev.committed_on) + if entries + entries.each() do |entry| + if ( ! entry.lastrev.nil? ) && ( ! entry.lastrev.revision.nil? ) + change = filechanges.find_by_revision_and_path( + entry.lastrev.revision, + scm.with_leading_slash(entry.path) ) + if change + entry.lastrev.identifier = change.changeset.revision + entry.lastrev.revision = change.changeset.revision + entry.lastrev.author = change.changeset.committer + # entry.lastrev.branch = change.branch + end + end + end + end + load_entries_changesets(entries) + entries + end + + def cat(path, identifier=nil) + rev = nil + if ! identifier.nil? + rev = changesets.find_by_revision(identifier) + return nil if rev.nil? + end + scm.cat(path, rev.nil? ? nil : rev.committed_on) + end + + def annotate(path, identifier=nil) + rev = nil + if ! identifier.nil? + rev = changesets.find_by_revision(identifier) + return nil if rev.nil? + end + scm.annotate(path, rev.nil? ? nil : rev.committed_on) + end + + def diff(path, rev, rev_to) + # convert rev to revision. CVS can't handle changesets here + diff=[] + changeset_from = changesets.find_by_revision(rev) + if rev_to.to_i > 0 + changeset_to = changesets.find_by_revision(rev_to) + end + changeset_from.filechanges.each() do |change_from| + revision_from = nil + revision_to = nil + if path.nil? || (change_from.path.starts_with? scm.with_leading_slash(path)) + revision_from = change_from.revision + end + if revision_from + if changeset_to + changeset_to.filechanges.each() do |change_to| + revision_to = change_to.revision if change_to.path == change_from.path + end + end + unless revision_to + revision_to = scm.get_previous_revision(revision_from) + end + file_diff = scm.diff(change_from.path, revision_from, revision_to) + diff = diff + file_diff unless file_diff.nil? + end + end + return diff + end + + def fetch_changesets + # some nifty bits to introduce a commit-id with cvs + # natively cvs doesn't provide any kind of changesets, + # there is only a revision per file. + # we now take a guess using the author, the commitlog and the commit-date. + + # last one is the next step to take. the commit-date is not equal for all + # commits in one changeset. cvs update the commit-date when the *,v file was touched. so + # we use a small delta here, to merge all changes belonging to _one_ changeset + time_delta = 10.seconds + fetch_since = latest_changeset ? latest_changeset.committed_on : nil + transaction do + tmp_rev_num = 1 + scm.revisions('', fetch_since, nil, :log_encoding => repo_log_encoding) do |revision| + # only add the change to the database, if it doen't exists. the cvs log + # is not exclusive at all. + tmp_time = revision.time.clone + unless filechanges.find_by_path_and_revision( + scm.with_leading_slash(revision.paths[0][:path]), + revision.paths[0][:revision] + ) + cmt = Changeset.normalize_comments(revision.message, repo_log_encoding) + author_utf8 = Changeset.to_utf8(revision.author, repo_log_encoding) + cs = changesets.find( + :first, + :conditions => { + :committed_on => tmp_time - time_delta .. tmp_time + time_delta, + :committer => author_utf8, + :comments => cmt + } + ) + # create a new changeset.... + unless cs + # we use a temporaray revision number here (just for inserting) + # later on, we calculate a continous positive number + tmp_time2 = tmp_time.clone.gmtime + branch = revision.paths[0][:branch] + scmid = branch + "-" + tmp_time2.strftime("%Y%m%d-%H%M%S") + cs = Changeset.create(:repository => self, + :revision => "tmp#{tmp_rev_num}", + :scmid => scmid, + :committer => revision.author, + :committed_on => tmp_time, + :comments => revision.message) + tmp_rev_num += 1 + end + # convert CVS-File-States to internal Action-abbrevations + # default action is (M)odified + action = "M" + if revision.paths[0][:action] == "Exp" && revision.paths[0][:revision] == "1.1" + action = "A" # add-action always at first revision (= 1.1) + elsif revision.paths[0][:action] == "dead" + action = "D" # dead-state is similar to Delete + end + Change.create( + :changeset => cs, + :action => action, + :path => scm.with_leading_slash(revision.paths[0][:path]), + :revision => revision.paths[0][:revision], + :branch => revision.paths[0][:branch] + ) + end + end + + # Renumber new changesets in chronological order + Changeset.all( + :order => 'committed_on ASC, id ASC', + :conditions => ["repository_id = ? AND revision LIKE 'tmp%'", id] + ).each do |changeset| + changeset.update_attribute :revision, next_revision_number + end + end # transaction + @current_revision_number = nil + end + + private + + # Returns the next revision number to assign to a CVS changeset + def next_revision_number + # Need to retrieve existing revision numbers to sort them as integers + sql = "SELECT revision FROM #{Changeset.table_name} " + sql << "WHERE repository_id = #{id} AND revision NOT LIKE 'tmp%'" + @current_revision_number ||= (connection.select_values(sql).collect(&:to_i).max || 0) + @current_revision_number += 1 + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7b/7b72cc518aa8eb0a7421ff01464081caccb29686.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/7b/7b72cc518aa8eb0a7421ff01464081caccb29686.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,544 @@ +package Apache::Authn::Redmine; + +=head1 Apache::Authn::Redmine + +Redmine - a mod_perl module to authenticate webdav subversion users +against redmine database + +=head1 SYNOPSIS + +This module allow anonymous users to browse public project and +registred users to browse and commit their project. Authentication is +done against the redmine database or the LDAP configured in redmine. + +This method is far simpler than the one with pam_* and works with all +database without an hassle but you need to have apache/mod_perl on the +svn server. + +=head1 INSTALLATION + +For this to automagically work, you need to have a recent reposman.rb +(after r860) and if you already use reposman, read the last section to +migrate. + +Sorry ruby users but you need some perl modules, at least mod_perl2, +DBI and DBD::mysql (or the DBD driver for you database as it should +work on allmost all databases). + +On debian/ubuntu you must do : + + aptitude install libapache-dbi-perl libapache2-mod-perl2 libdbd-mysql-perl + +If your Redmine users use LDAP authentication, you will also need +Authen::Simple::LDAP (and IO::Socket::SSL if LDAPS is used): + + aptitude install libauthen-simple-ldap-perl libio-socket-ssl-perl + +=head1 CONFIGURATION + + ## This module has to be in your perl path + ## eg: /usr/lib/perl5/Apache/Authn/Redmine.pm + PerlLoadModule Apache::Authn::Redmine + + DAV svn + SVNParentPath "/var/svn" + + AuthType Basic + AuthName redmine + Require valid-user + + PerlAccessHandler Apache::Authn::Redmine::access_handler + PerlAuthenHandler Apache::Authn::Redmine::authen_handler + + ## for mysql + RedmineDSN "DBI:mysql:database=databasename;host=my.db.server" + ## for postgres + # RedmineDSN "DBI:Pg:dbname=databasename;host=my.db.server" + + RedmineDbUser "redmine" + RedmineDbPass "password" + ## Optional where clause (fulltext search would be slow and + ## database dependant). + # RedmineDbWhereClause "and members.role_id IN (1,2)" + ## Optional credentials cache size + # RedmineCacheCredsMax 50 + + +To be able to browse repository inside redmine, you must add something +like that : + + + DAV svn + SVNParentPath "/var/svn" + Order deny,allow + Deny from all + # only allow reading orders + + Allow from redmine.server.ip + + + +and you will have to use this reposman.rb command line to create repository : + + reposman.rb --redmine my.redmine.server --svn-dir /var/svn --owner www-data -u http://svn.server/svn-private/ + +=head1 REPOSITORIES NAMING + +A projet repository must be named with the projet identifier. In case +of multiple repositories for the same project, use the project identifier +and the repository identifier separated with a dot: + + /var/svn/foo + /var/svn/foo.otherrepo + +=head1 MIGRATION FROM OLDER RELEASES + +If you use an older reposman.rb (r860 or before), you need to change +rights on repositories to allow the apache user to read and write +S + + sudo chown -R www-data /var/svn/* + sudo chmod -R u+w /var/svn/* + +And you need to upgrade at least reposman.rb (after r860). + +=head1 GIT SMART HTTP SUPPORT + +Git's smart HTTP protocol (available since Git 1.7.0) will not work with the +above settings. Redmine.pm normally does access control depending on the HTTP +method used: read-only methods are OK for everyone in public projects and +members with read rights in private projects. The rest require membership with +commit rights in the project. + +However, this scheme doesn't work for Git's smart HTTP protocol, as it will use +POST even for a simple clone. Instead, read-only requests must be detected using +the full URL (including the query string): anything that doesn't belong to the +git-receive-pack service is read-only. + +To activate this mode of operation, add this line inside your +block: + + RedmineGitSmartHttp yes + +Here's a sample Apache configuration which integrates git-http-backend with +a MySQL database and this new option: + + SetEnv GIT_PROJECT_ROOT /var/www/git/ + SetEnv GIT_HTTP_EXPORT_ALL + ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/ + + Order allow,deny + Allow from all + + AuthType Basic + AuthName Git + Require valid-user + + PerlAccessHandler Apache::Authn::Redmine::access_handler + PerlAuthenHandler Apache::Authn::Redmine::authen_handler + # for mysql + RedmineDSN "DBI:mysql:database=redmine;host=127.0.0.1" + RedmineDbUser "redmine" + RedmineDbPass "xxx" + RedmineGitSmartHttp yes + + +Make sure that all the names of the repositories under /var/www/git/ have a +matching identifier for some project: /var/www/git/myproject and +/var/www/git/myproject.git will work. You can put both bare and non-bare +repositories in /var/www/git, though bare repositories are strongly +recommended. You should create them with the rights of the user running Redmine, +like this: + + cd /var/www/git + sudo -u user-running-redmine mkdir myproject + cd myproject + sudo -u user-running-redmine git init --bare + +Once you have activated this option, you have three options when cloning a +repository: + +- Cloning using "http://user@host/git/repo(.git)" works, but will ask for the password + all the time. + +- Cloning with "http://user:pass@host/git/repo(.git)" does not have this problem, but + this could reveal accidentally your password to the console in some versions + of Git, and you would have to ensure that .git/config is not readable except + by the owner for each of your projects. + +- Use "http://host/git/repo(.git)", and store your credentials in the ~/.netrc + file. This is the recommended solution, as you only have one file to protect + and passwords will not be leaked accidentally to the console. + + IMPORTANT NOTE: It is *very important* that the file cannot be read by other + users, as it will contain your password in cleartext. To create the file, you + can use the following commands, replacing yourhost, youruser and yourpassword + with the right values: + + touch ~/.netrc + chmod 600 ~/.netrc + echo -e "machine yourhost\nlogin youruser\npassword yourpassword" > ~/.netrc + +=cut + +use strict; +use warnings FATAL => 'all', NONFATAL => 'redefine'; + +use DBI; +use Digest::SHA; +# optional module for LDAP authentication +my $CanUseLDAPAuth = eval("use Authen::Simple::LDAP; 1"); + +use Apache2::Module; +use Apache2::Access; +use Apache2::ServerRec qw(); +use Apache2::RequestRec qw(); +use Apache2::RequestUtil qw(); +use Apache2::Const qw(:common :override :cmd_how); +use APR::Pool (); +use APR::Table (); + +# use Apache2::Directive qw(); + +my @directives = ( + { + name => 'RedmineDSN', + req_override => OR_AUTHCFG, + args_how => TAKE1, + errmsg => 'Dsn in format used by Perl DBI. eg: "DBI:Pg:dbname=databasename;host=my.db.server"', + }, + { + name => 'RedmineDbUser', + req_override => OR_AUTHCFG, + args_how => TAKE1, + }, + { + name => 'RedmineDbPass', + req_override => OR_AUTHCFG, + args_how => TAKE1, + }, + { + name => 'RedmineDbWhereClause', + req_override => OR_AUTHCFG, + args_how => TAKE1, + }, + { + name => 'RedmineCacheCredsMax', + req_override => OR_AUTHCFG, + args_how => TAKE1, + errmsg => 'RedmineCacheCredsMax must be decimal number', + }, + { + name => 'RedmineGitSmartHttp', + req_override => OR_AUTHCFG, + args_how => TAKE1, + }, +); + +sub RedmineDSN { + my ($self, $parms, $arg) = @_; + $self->{RedmineDSN} = $arg; + my $query = "SELECT + users.hashed_password, users.salt, users.auth_source_id, roles.permissions, projects.status + FROM projects, users, roles + WHERE + users.login=? + AND projects.identifier=? + AND users.status=1 + AND ( + roles.id IN (SELECT member_roles.role_id FROM members, member_roles WHERE members.user_id = users.id AND members.project_id = projects.id AND members.id = member_roles.member_id) + OR + (roles.builtin=1 AND cast(projects.is_public as CHAR) IN ('t', '1')) + ) + AND roles.permissions IS NOT NULL"; + $self->{RedmineQuery} = trim($query); +} + +sub RedmineDbUser { set_val('RedmineDbUser', @_); } +sub RedmineDbPass { set_val('RedmineDbPass', @_); } +sub RedmineDbWhereClause { + my ($self, $parms, $arg) = @_; + $self->{RedmineQuery} = trim($self->{RedmineQuery}.($arg ? $arg : "")." "); +} + +sub RedmineCacheCredsMax { + my ($self, $parms, $arg) = @_; + if ($arg) { + $self->{RedmineCachePool} = APR::Pool->new; + $self->{RedmineCacheCreds} = APR::Table::make($self->{RedmineCachePool}, $arg); + $self->{RedmineCacheCredsCount} = 0; + $self->{RedmineCacheCredsMax} = $arg; + } +} + +sub RedmineGitSmartHttp { + my ($self, $parms, $arg) = @_; + $arg = lc $arg; + + if ($arg eq "yes" || $arg eq "true") { + $self->{RedmineGitSmartHttp} = 1; + } else { + $self->{RedmineGitSmartHttp} = 0; + } +} + +sub trim { + my $string = shift; + $string =~ s/\s{2,}/ /g; + return $string; +} + +sub set_val { + my ($key, $self, $parms, $arg) = @_; + $self->{$key} = $arg; +} + +Apache2::Module::add(__PACKAGE__, \@directives); + + +my %read_only_methods = map { $_ => 1 } qw/GET HEAD PROPFIND REPORT OPTIONS/; + +sub request_is_read_only { + my ($r) = @_; + my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config); + + # Do we use Git's smart HTTP protocol, or not? + if (defined $cfg->{RedmineGitSmartHttp} and $cfg->{RedmineGitSmartHttp}) { + my $uri = $r->unparsed_uri; + my $location = $r->location; + my $is_read_only = $uri !~ m{^$location/*[^/]+/+(info/refs\?service=)?git\-receive\-pack$}o; + return $is_read_only; + } else { + # Standard behaviour: check the HTTP method + my $method = $r->method; + return defined $read_only_methods{$method}; + } +} + +sub access_handler { + my $r = shift; + + unless ($r->some_auth_required) { + $r->log_reason("No authentication has been configured"); + return FORBIDDEN; + } + + return OK unless request_is_read_only($r); + + my $project_id = get_project_identifier($r); + + $r->set_handlers(PerlAuthenHandler => [\&OK]) + if is_public_project($project_id, $r) && anonymous_role_allows_browse_repository($r); + + return OK +} + +sub authen_handler { + my $r = shift; + + my ($res, $redmine_pass) = $r->get_basic_auth_pw(); + return $res unless $res == OK; + + if (is_member($r->user, $redmine_pass, $r)) { + return OK; + } else { + $r->note_auth_failure(); + return DECLINED; + } +} + +# check if authentication is forced +sub is_authentication_forced { + my $r = shift; + + my $dbh = connect_database($r); + my $sth = $dbh->prepare( + "SELECT value FROM settings where settings.name = 'login_required';" + ); + + $sth->execute(); + my $ret = 0; + if (my @row = $sth->fetchrow_array) { + if ($row[0] eq "1" || $row[0] eq "t") { + $ret = 1; + } + } + $sth->finish(); + undef $sth; + + $dbh->disconnect(); + undef $dbh; + + $ret; +} + +sub is_public_project { + my $project_id = shift; + my $r = shift; + + if (is_authentication_forced($r)) { + return 0; + } + + my $dbh = connect_database($r); + my $sth = $dbh->prepare( + "SELECT is_public FROM projects WHERE projects.identifier = ? AND projects.status <> 9;" + ); + + $sth->execute($project_id); + my $ret = 0; + if (my @row = $sth->fetchrow_array) { + if ($row[0] eq "1" || $row[0] eq "t") { + $ret = 1; + } + } + $sth->finish(); + undef $sth; + $dbh->disconnect(); + undef $dbh; + + $ret; +} + +sub anonymous_role_allows_browse_repository { + my $r = shift; + + my $dbh = connect_database($r); + my $sth = $dbh->prepare( + "SELECT permissions FROM roles WHERE builtin = 2;" + ); + + $sth->execute(); + my $ret = 0; + if (my @row = $sth->fetchrow_array) { + if ($row[0] =~ /:browse_repository/) { + $ret = 1; + } + } + $sth->finish(); + undef $sth; + $dbh->disconnect(); + undef $dbh; + + $ret; +} + +# perhaps we should use repository right (other read right) to check public access. +# it could be faster BUT it doesn't work for the moment. +# sub is_public_project_by_file { +# my $project_id = shift; +# my $r = shift; + +# my $tree = Apache2::Directive::conftree(); +# my $node = $tree->lookup('Location', $r->location); +# my $hash = $node->as_hash; + +# my $svnparentpath = $hash->{SVNParentPath}; +# my $repos_path = $svnparentpath . "/" . $project_id; +# return 1 if (stat($repos_path))[2] & 00007; +# } + +sub is_member { + my $redmine_user = shift; + my $redmine_pass = shift; + my $r = shift; + + my $dbh = connect_database($r); + my $project_id = get_project_identifier($r); + + my $pass_digest = Digest::SHA::sha1_hex($redmine_pass); + + my $access_mode = request_is_read_only($r) ? "R" : "W"; + + my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config); + my $usrprojpass; + if ($cfg->{RedmineCacheCredsMax}) { + $usrprojpass = $cfg->{RedmineCacheCreds}->get($redmine_user.":".$project_id.":".$access_mode); + return 1 if (defined $usrprojpass and ($usrprojpass eq $pass_digest)); + } + my $query = $cfg->{RedmineQuery}; + my $sth = $dbh->prepare($query); + $sth->execute($redmine_user, $project_id); + + my $ret; + while (my ($hashed_password, $salt, $auth_source_id, $permissions, $project_status) = $sth->fetchrow_array) { + if ($project_status eq "9" || ($project_status ne "1" && $access_mode eq "W")) { + last; + } + + unless ($auth_source_id) { + my $method = $r->method; + my $salted_password = Digest::SHA::sha1_hex($salt.$pass_digest); + if ($hashed_password eq $salted_password && (($access_mode eq "R" && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/) ) { + $ret = 1; + last; + } + } elsif ($CanUseLDAPAuth) { + my $sthldap = $dbh->prepare( + "SELECT host,port,tls,account,account_password,base_dn,attr_login from auth_sources WHERE id = ?;" + ); + $sthldap->execute($auth_source_id); + while (my @rowldap = $sthldap->fetchrow_array) { + my $bind_as = $rowldap[3] ? $rowldap[3] : ""; + my $bind_pw = $rowldap[4] ? $rowldap[4] : ""; + if ($bind_as =~ m/\$login/) { + # replace $login with $redmine_user and use $redmine_pass + $bind_as =~ s/\$login/$redmine_user/g; + $bind_pw = $redmine_pass + } + my $ldap = Authen::Simple::LDAP->new( + host => ($rowldap[2] eq "1" || $rowldap[2] eq "t") ? "ldaps://$rowldap[0]:$rowldap[1]" : $rowldap[0], + port => $rowldap[1], + basedn => $rowldap[5], + binddn => $bind_as, + bindpw => $bind_pw, + filter => "(".$rowldap[6]."=%s)" + ); + my $method = $r->method; + $ret = 1 if ($ldap->authenticate($redmine_user, $redmine_pass) && (($access_mode eq "R" && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/)); + + } + $sthldap->finish(); + undef $sthldap; + } + } + $sth->finish(); + undef $sth; + $dbh->disconnect(); + undef $dbh; + + if ($cfg->{RedmineCacheCredsMax} and $ret) { + if (defined $usrprojpass) { + $cfg->{RedmineCacheCreds}->set($redmine_user.":".$project_id.":".$access_mode, $pass_digest); + } else { + if ($cfg->{RedmineCacheCredsCount} < $cfg->{RedmineCacheCredsMax}) { + $cfg->{RedmineCacheCreds}->set($redmine_user.":".$project_id.":".$access_mode, $pass_digest); + $cfg->{RedmineCacheCredsCount}++; + } else { + $cfg->{RedmineCacheCreds}->clear(); + $cfg->{RedmineCacheCredsCount} = 0; + } + } + } + + $ret; +} + +sub get_project_identifier { + my $r = shift; + + my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config); + my $location = $r->location; + $location =~ s/\.git$// if (defined $cfg->{RedmineGitSmartHttp} and $cfg->{RedmineGitSmartHttp}); + my ($identifier) = $r->uri =~ m{$location/*([^/.]+)}; + $identifier; +} + +sub connect_database { + my $r = shift; + + my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config); + return DBI->connect($cfg->{RedmineDSN}, $cfg->{RedmineDbUser}, $cfg->{RedmineDbPass}); +} + +1; diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7b/7b79b629f3da71f401261cf1288a60cb2acf04af.svn-base --- a/.svn/pristine/7b/7b79b629f3da71f401261cf1288a60cb2acf04af.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,93 +0,0 @@ -# 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 ActivityProvider - def self.included(base) - base.extend ClassMethods - end - - module ClassMethods - def acts_as_activity_provider(options = {}) - unless self.included_modules.include?(Redmine::Acts::ActivityProvider::InstanceMethods) - cattr_accessor :activity_provider_options - send :include, Redmine::Acts::ActivityProvider::InstanceMethods - end - - options.assert_valid_keys(:type, :permission, :timestamp, :author_key, :find_options) - self.activity_provider_options ||= {} - - # One model can provide different event types - # We store these options in activity_provider_options hash - event_type = options.delete(:type) || self.name.underscore.pluralize - - 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 - - module InstanceMethods - def self.included(base) - base.extend ClassMethods - end - - module ClassMethods - # Returns events of type event_type visible by user that occured between from and to - def find_events(event_type, user, from, to, options) - provider_options = activity_provider_options[event_type] - raise "#{self.name} can not provide #{event_type} events." if provider_options.nil? - - 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 - - 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 - - scope = self - if provider_options.has_key?(:permission) - cond.add(Project.allowed_to_condition(user, provider_options[:permission] || :view_project, options)) - elsif respond_to?(:visible) - scope = scope.visible(user, options) - else - ActiveSupport::Deprecation.warn "acts_as_activity_provider with implicit :permission option is deprecated. Add a visible scope to the #{self.name} model or use explicit :permission option." - cond.add(Project.allowed_to_condition(user, "view_#{self.name.underscore.pluralize}".to_sym, options)) - end - scope_options[:conditions] = cond.conditions - - with_scope(:find => scope_options) do - scope.find(:all, provider_options[:find_options].dup) - end - end - end - end - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7b/7b9a1465ac8385995afb9de947ead6e1c8315950.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/7b/7b9a1465ac8385995afb9de947ead6e1c8315950.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,1085 @@ +sq: + # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl) + direction: ltr + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%m/%d/%Y" + short: "%b %d" + long: "%B %d, %Y" + + day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday] + abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December] + abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec] + # Used in date_select and datime_select. + order: + - :year + - :month + - :day + + time: + formats: + default: "%m/%d/%Y %I:%M %p" + time: "%I:%M %p" + short: "%d %b %H:%M" + long: "%B %d, %Y %H:%M" + am: "am" + pm: "pm" + + datetime: + distance_in_words: + half_a_minute: "half a minute" + less_than_x_seconds: + one: "less than 1 second" + other: "less than %{count} seconds" + x_seconds: + one: "1 second" + other: "%{count} seconds" + less_than_x_minutes: + one: "less than a minute" + other: "less than %{count} minutes" + x_minutes: + one: "1 minute" + other: "%{count} minutes" + about_x_hours: + one: "about 1 hour" + other: "about %{count} hours" + x_hours: + one: "1 ore" + other: "%{count} ore" + x_days: + one: "1 day" + other: "%{count} days" + about_x_months: + one: "about 1 month" + other: "about %{count} months" + x_months: + one: "1 month" + other: "%{count} months" + about_x_years: + one: "about 1 year" + other: "about %{count} years" + over_x_years: + one: "over 1 year" + other: "over %{count} years" + almost_x_years: + one: "almost 1 year" + other: "almost %{count} years" + + number: + format: + separator: "." + delimiter: "" + precision: 3 + + human: + format: + delimiter: "" + precision: 3 + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + +# Used in array.to_sentence. + support: + array: + sentence_connector: "dhe" + skip_last_comma: false + + activerecord: + errors: + template: + header: + one: "1 gabim nuk lejon kete %{model} te ruhet" + other: "%{count} gabime nuk lejon kete %{model} te ruhet" + messages: + inclusion: "nuk eshte perfshire ne liste" + exclusion: "eshte i/e rezervuar" + invalid: "eshte invalid" + confirmation: "nuk perkon me konfirmimin" + accepted: "duhet pranuar" + empty: "nuk mund te jete bosh" + blank: "nuk mund te jete blank" + too_long: "eshte shume i gjate (maksimumi eshte %{count} karaktere)" + too_short: "eshte shume i gjate (minimumi eshte %{count} karaktere)" + wrong_length: "eshte gjatesi e gabuar (duhet te jete %{count} karaktere)" + taken: "eshte zene" + not_a_number: "nuk eshte numer" + not_a_date: "nuk eshte date e vlefshme" + greater_than: "duhet te jete me i/e madh(e) se %{count}" + greater_than_or_equal_to: "duhet te jete me i/e madh(e) se ose i/e barabarte me %{count}" + equal_to: "duhet te jete i/e barabarte me %{count}" + less_than: "duhet te jete me i/e vogel se %{count}" + less_than_or_equal_to: "duhet te jete me i/e vogel se ose i/e barabarte me %{count}" + odd: "duhet te jete tek" + even: "duhet te jete cift" + greater_than_start_date: "duhet te jete me i/e madh(e) se data e fillimit" + not_same_project: "nuk i perket te njejtit projekt" + circular_dependency: "Ky relacion do te krijoje nje varesi ciklike (circular dependency)" + cant_link_an_issue_with_a_descendant: "Nje ceshtje nuk mund te lidhet me nenceshtje" + + actionview_instancetag_blank_option: Zgjidhni + + general_text_No: 'Jo' + general_text_Yes: 'Po' + general_text_no: 'jo' + general_text_yes: 'po' + general_lang_name: 'Albanian (Shqip)' + general_csv_separator: ',' + general_csv_decimal_separator: '.' + general_csv_encoding: ISO-8859-1 + general_pdf_encoding: UTF-8 + general_first_day_of_week: '7' + + notice_account_updated: Llogaria u perditesua me sukses. + notice_account_invalid_creditentials: Perdorues ose Fjalekalim i gabuar. + notice_account_password_updated: Fjalekalimi u ndryshua me sukses. + notice_account_wrong_password: Fjalekalim i gabuar + notice_account_register_done: Llogaria u krijua me sukses. Per te aktivizuar Llogarine tuaj, ndiqni link-un e derguar ne email-in tuaj. + notice_account_unknown_email: Perdorues i paidentifikuar. + notice_can_t_change_password: Kjo Llogari administrohet nga nje server tjeter. E pamundur te ndryshohet Fjalekalimi. + notice_account_lost_email_sent: Ju eshte derguar nje email me instruksionet per te zgjedhur nje Fjalekalim te ri. + notice_account_activated: Llogaria juaj u Aktivizua. Tani mund te beni Login. + notice_successful_create: Krijim me sukses. + notice_successful_update: Modifikim me sukses. + notice_successful_delete: Fshirje me sukses. + notice_successful_connection: Lidhje e suksesshme. + notice_file_not_found: Faqja qe po kerkoni te aksesoni nuk ekziston ose eshte shperngulur. + notice_locking_conflict: Te dhenat jane modifikuar nga nje Perdorues tjeter. + notice_not_authorized: Nuk jeni i autorizuar te aksesoni kete faqe. + notice_not_authorized_archived_project: Projekti, qe po tentoni te te aksesoni eshte arkivuar. + notice_email_sent: "Nje email eshte derguar ne %{value}" + notice_email_error: "Pati nje gabim gjate dergimit te email-it (%{value})" + notice_feeds_access_key_reseted: Your RSS access key was reset. + notice_api_access_key_reseted: Your API access key was reset. + notice_failed_to_save_issues: "Deshtoi ne ruajtjen e %{count} ceshtje(ve) ne %{total} te zgjedhura: %{ids}." + notice_failed_to_save_time_entries: "Deshtoi ne ruajtjen e %{count} time entrie(s) ne %{total} te zgjedhura: %{ids}." + notice_failed_to_save_members: "Deshtoi ne ruajtjen e member(s): %{errors}." + notice_no_issue_selected: "Nuk eshte zgjedhur asnje Ceshtje! Zgjidh Ceshtjen qe deshironi te modifikoni." + notice_account_pending: "Llogaria juaj u krijua dhe eshte ne pritje te aprovimit nga nje administrator." + notice_default_data_loaded: Konfigurimi i paracaktuar u ngarkua me sukses. + notice_unable_delete_version: E pamundur te fshije versionin. + notice_unable_delete_time_entry: E pamundur te fshije rekordin e log-ut. + notice_issue_done_ratios_updated: Issue done ratios updated. + notice_gantt_chart_truncated: "The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max})" + notice_issue_successful_create: "Ceshtja %{id} u krijua." + notice_issue_update_conflict: "Ceshtja eshte perditesuar nga Perdorues te tjere nderkohe qe ju po e modifikonit ate." + notice_account_deleted: "Llogaria juaj u fshi perfundimisht." + + error_can_t_load_default_data: "Konfigurimi i paracaktuar nuk mund te ngarkohet: %{value}" + 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: %{value}" + error_scm_annotate: "The entry does not exist or cannot be annotated." + error_scm_annotate_big_text_file: "The entry cannot be annotated, as it exceeds the maximum text file size." + error_issue_not_found_in_project: 'The issue was not found or does not belong to this project' + error_no_tracker_in_project: 'No tracker is associated to this project. Please check the Project settings.' + error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").' + error_can_not_delete_custom_field: Unable to delete custom field + error_can_not_delete_tracker: "This tracker contains issues and cannot be deleted." + error_can_not_remove_role: "This role is in use and cannot be deleted." + error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version cannot be reopened' + error_can_not_archive_project: This project cannot be archived + error_issue_done_ratios_not_updated: "Issue done ratios not updated." + error_workflow_copy_source: 'Please select a source tracker or role' + error_workflow_copy_target: 'Please select target tracker(s) and role(s)' + error_unable_delete_issue_status: 'Unable to delete issue status' + error_unable_to_connect: "Unable to connect (%{value})" + error_attachment_too_big: "This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size})" + warning_attachments_not_saved: "%{count} file(s) could not be saved." + + mail_subject_lost_password: "Fjalekalimi %{value} i juaj" + mail_body_lost_password: 'Per te ndryshuar Fjalekalimin, ndiq link-un ne vijim:' + mail_subject_register: "Aktivizimi %{value} i Llogarise tuaj" + mail_body_register: 'Per te aktivizuar Llogarine tuaj, ndiqni link-un ne vijim:' + mail_body_account_information_external: "Mund te perdorni Llogarine tuaj %{value} per Login." + mail_body_account_information: Informacioni i Llogarise suaj + mail_subject_account_activation_request: "%{value} kerkesa aktivizimi Llogarije" + mail_body_account_activation_request: "Nje Perdorues i ri (%{value}) eshte regjistruar. Llogaria pret aprovimin tuaj:" + mail_subject_reminder: "%{count} Ceshtje te pritshme ne %{days} ditet pasardhese" + mail_body_reminder: "%{count} Ceshtje qe ju jane caktuar jane te pritshme ne %{days} ditet pasardhese:" + mail_subject_wiki_content_added: "'%{id}' wiki page eshte shtuar" + mail_body_wiki_content_added: "The '%{id}' wiki page eshte shtuar nga %{author}." + mail_subject_wiki_content_updated: "'%{id}' wiki page eshte modifikuar" + mail_body_wiki_content_updated: "The '%{id}' wiki page eshte modifikuar nga %{author}." + + + field_name: Emri + field_description: Pershkrimi + field_summary: Permbledhje + field_is_required: E Detyrueshme + field_firstname: Emri + field_lastname: Mbiemri + field_mail: Email + field_filename: File + field_filesize: Madhesia + field_downloads: Shkarkime + field_author: Autori + field_created_on: Krijuar me + field_updated_on: Perditesuar me + field_field_format: Formati + field_is_for_all: Per te gjthe Projektet + field_possible_values: Vlera e Mundshme + field_regexp: Shprehja e Duhur + field_min_length: Gjatesia Minimale + field_max_length: Gjatesia Maksimale + field_value: Vlera + field_category: Kategoria + field_title: Titulli + field_project: Projekti + field_issue: Problemi + field_status: Statusi + field_notes: Shenime + field_is_closed: Problemet e Mbyllura + field_is_default: Vlera e Paracaktuar + field_tracker: Gjurmuesi + field_subject: Subjekti + field_due_date: Deri me + field_assigned_to: I Ngarkuari + field_priority: Prioriteti + field_fixed_version: Menyra e Etiketimit + field_user: Perdoruesi + field_principal: Kapitali + field_role: Roli + field_homepage: Faqja Kryesore + field_is_public: Publike + field_parent: Nenprojekti i + field_is_in_roadmap: Ceshtje e shfaqur ne roadmap + field_login: Login + field_mail_notification: Njoftim me Email + field_admin: Administratori + field_last_login_on: Lidhja e Fundit + field_language: Gjuha + field_effective_date: Data + field_password: Fjalekalimi + field_new_password: Fjalekalimi i Ri + field_password_confirmation: Konfirmim Fjalekalimi + field_version: Versioni + field_type: Type + field_host: Host + field_port: Port + field_account: Llogaria + 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: Start date + field_done_ratio: "% Done" + field_auth_source: Authentication mode + field_hide_mail: Hide my email address + field_comments: Comment + field_url: URL + field_start_page: Start page + field_subproject: Subproject + field_hours: Hours + field_activity: Activity + field_spent_on: Date + field_identifier: Identifier + field_is_filter: Used as a filter + field_issue_to: 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_column_names: Columns + field_time_entries: Log time + field_time_zone: Time zone + field_searchable: Searchable + field_default_value: Default value + field_comments_sorting: Display comments + field_parent_title: Parent page + field_editable: Editable + field_watcher: Watcher + field_identity_url: OpenID URL + field_content: Content + field_group_by: Group results by + field_sharing: Sharing + field_parent_issue: Parent task + field_member_of_group: "Assignee's group" + field_assigned_to_role: "Assignee's role" + field_text: Text field + field_visible: Visible + field_warn_on_leaving_unsaved: "Warn me when leaving a page with unsaved text" + field_issues_visibility: Issues visibility + field_is_private: Private + field_commit_logs_encoding: Commit messages encoding + field_scm_path_encoding: Path encoding + field_path_to_repository: Path to repository + field_root_directory: Root directory + field_cvsroot: CVSROOT + field_cvs_module: Module + field_repository_is_default: Main repository + field_multiple: Multiple values + field_auth_source_ldap_filter: LDAP filter + + setting_app_title: Application title + setting_app_subtitle: Application subtitle + setting_welcome_text: Welcome text + setting_default_language: Default language + setting_login_required: Authentication required + setting_self_registration: Self-registration + setting_attachment_max_size: Maximum attachment size + setting_issues_export_limit: Issues export limit + setting_mail_from: Emission email address + setting_bcc_recipients: Blind carbon copy recipients (bcc) + 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: Maximum number of items in Atom feeds + setting_default_projects_public: New projects are public by default + setting_autofetch_changesets: Fetch commits automatically + setting_sys_api_enabled: Enable WS for repository management + setting_commit_ref_keywords: Referencing keywords + setting_commit_fix_keywords: Fixing keywords + setting_autologin: Autologin + setting_date_format: Date format + setting_time_format: Time format + setting_cross_project_issue_relations: Allow cross-project issue relations + setting_issue_list_default_columns: Default columns displayed on the issue list + setting_repositories_encodings: Attachments and repositories encodings + setting_protocol: Protocol + setting_per_page_options: Objects per page options + setting_user_format: Users display format + setting_activity_days_default: Days displayed on project activity + setting_display_subprojects_issues: Display subprojects issues on main projects by default + setting_enabled_scm: Enabled SCM + setting_mail_handler_body_delimiters: "Truncate emails after one of these lines" + 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_gravatar_enabled: Use Gravatar user icons + setting_gravatar_default: Default Gravatar image + setting_diff_max_lines_displayed: Maximum number of diff lines displayed + setting_file_max_size_displayed: Maximum size of text files displayed inline + setting_repository_log_display_limit: Maximum number of revisions displayed on file log + setting_openid: Allow OpenID login and registration + setting_password_min_length: Minimum password length + setting_new_project_user_role_id: Role given to a non-admin user who creates a project + setting_default_projects_modules: Default enabled modules for new projects + setting_issue_done_ratio: Calculate the issue done ratio with + setting_issue_done_ratio_issue_field: Use the issue field + setting_issue_done_ratio_issue_status: Use the issue status + setting_start_of_week: Start calendars on + setting_rest_api_enabled: Enable REST web service + setting_cache_formatted_text: Cache formatted text + setting_default_notification_option: Default notification option + setting_commit_logtime_enabled: Enable time logging + setting_commit_logtime_activity_id: Activity for logged time + setting_gantt_items_limit: Maximum number of items displayed on the gantt chart + setting_issue_group_assignment: Allow issue assignment to groups + setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues + setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed + setting_unsubscribe: Allow users to delete their own account + + permission_add_project: Create project + permission_add_subprojects: Create subprojects + permission_edit_project: Edit project + permission_select_project_modules: Select project modules + permission_manage_members: Manage members + permission_manage_project_activities: Manage project activities + permission_manage_versions: Manage versions + permission_manage_categories: Manage issue categories + permission_view_issues: View Issues + permission_add_issues: Add issues + permission_edit_issues: Edit issues + permission_manage_issue_relations: Manage issue relations + permission_set_issues_private: Set issues public or private + permission_set_own_issues_private: Set own issues public or private + 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 calendar + permission_view_issue_watchers: View watchers list + permission_add_issue_watchers: Add watchers + permission_delete_issue_watchers: Delete 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_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 forums + 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 + permission_export_wiki_pages: Export wiki pages + permission_manage_subtasks: Manage subtasks + permission_manage_related_issues: Manage related issues + + project_module_issue_tracking: Issue tracking + project_module_time_tracking: Time tracking + project_module_news: News + project_module_documents: Documents + project_module_files: Files + project_module_wiki: Wiki + project_module_repository: Repository + project_module_boards: Forums + project_module_calendar: Calendar + project_module_gantt: Gantt + + label_user: Perdoruesi + label_user_plural: Perdoruesit + label_user_new: Perdorues i ri + label_user_anonymous: Anonim + label_project: Projekt + label_project_new: Projekt i ri + label_project_plural: Projekte + label_x_projects: + zero: asnje projekt + one: 1 projekt + other: "%{count} projekte" + label_project_all: Te gjithe Projektet + label_project_latest: Projektet me te fundit + label_issue: Ceshtje + label_issue_new: Ceshtje e re + label_issue_plural: Ceshtjet + label_issue_view_all: Shih te gjitha Ceshtjet + label_issues_by: "Ceshtje per %{value}" + label_issue_added: Ceshtje te shtuara + label_issue_updated: Ceshtje te modifikuara + label_issue_note_added: Shenime te shtuara + label_issue_status_updated: Statusi u modifikua + label_issue_priority_updated: Prioriteti u modifikua + label_document: Dokument + label_document_new: Dokument i ri + label_document_plural: Dokumente + label_document_added: Dokumente te shtuara + label_role: Roli + label_role_plural: Role + label_role_new: Rol i ri + label_role_and_permissions: Role dhe te Drejta + label_role_anonymous: Anonim + label_role_non_member: Jo Anetar + label_member: Anetar + label_member_new: Anetar i ri + label_member_plural: Anetare + label_tracker: Gjurmues + label_tracker_plural: Gjurmuesa + label_tracker_new: Gjurmues i ri + label_workflow: Workflow + label_issue_status: Statusi i Ceshtjes + label_issue_status_plural: Statuset e Ceshtjeve + label_issue_status_new: Statusi i ri + label_issue_category: Kategoria e Ceshtjes + label_issue_category_plural: Kategorite e Ceshtjeve + label_issue_category_new: Kategori e re + label_custom_field: Fushe e personalizuar + label_custom_field_plural: Fusha te personalizuara + label_custom_field_new: Fushe e personalizuar e re + label_enumerations: Enumerations + label_enumeration_new: Vlere e re + label_information: Informacion + label_information_plural: Informacione + label_please_login: Lutemi login + label_register: Regjistrohu + label_login_with_open_id_option: ose lidhu me OpenID + label_password_lost: Fjalekalim i humbur + label_home: Home + label_my_page: Faqja ime + label_my_account: Llogaria ime + label_my_projects: Projektet e mia + label_my_page_block: My page block + label_administration: Administrim + label_login: Login + label_logout: Dalje + label_help: Ndihme + label_reported_issues: Ceshtje te raportuara + label_assigned_to_me_issues: Ceshtje te caktuara mua + label_last_login: Hyrja e fundit + label_registered_on: Regjistruar me + label_activity: Aktiviteti + label_overall_activity: Aktiviteti i pergjithshem + label_user_activity: "Aktiviteti i %{value}" + label_new: Shto + label_logged_as: Lidhur si + label_environment: Ambienti + label_authentication: Authentikimi + label_auth_source: Menyra e Authentikimit + label_auth_source_new: Menyre e re Authentikimi + label_auth_source_plural: Menyrat e Authentikimit + label_subproject_plural: Nenprojekte + label_subproject_new: Nenprojekt i ri + label_and_its_subprojects: "%{value} dhe Nenprojektet e vet" + label_min_max_length: Gjatesia Min - Max + label_list: List + label_date: Date + label_integer: Integer + label_float: Float + label_boolean: Boolean + label_string: Text + label_text: Long text + label_attribute: Attribute + label_attribute_plural: Attributes + label_no_data: No data to display + label_change_status: Change status + label_history: Histori + label_attachment: File + label_attachment_new: File i ri + label_attachment_delete: Fshi file + label_attachment_plural: Files + label_file_added: File te shtuar + label_report: Raport + label_report_plural: Raporte + label_news: Lajm + label_news_new: Shto Lajm + label_news_plural: Lajme + label_news_latest: Lajmet e fundit + label_news_view_all: Veshtro gjithe Lajmet + label_news_added: Lajme te shtuara + label_news_comment_added: Komenti iu shtua Lajmeve + label_settings: Settings + label_overview: Overview + label_version: Version + label_version_new: Version i ri + label_version_plural: Versione + label_close_versions: Mbyll Versionet e perfunduara + label_confirmation: Konfirmim + label_export_to: 'Mund te gjendet gjithashtu ne:' + label_read: Lexim... + label_public_projects: Projekte publike + label_open_issues: e hapur + label_open_issues_plural: te hapura + label_closed_issues: e mbyllur + label_closed_issues_plural: te mbyllura + label_x_open_issues_abbr_on_total: + zero: 0 te hapura / %{total} + one: 1 e hapur / %{total} + other: "%{count} te hapura / %{total}" + label_x_open_issues_abbr: + zero: 0 te hapura + one: 1 e hapur + other: "%{count} te hapura" + label_x_closed_issues_abbr: + zero: 0 te mbyllura + one: 1 e mbyllur + other: "%{count} te mbyllura" + label_x_issues: + zero: 0 ceshtje + one: 1 ceshtje + other: "%{count} ceshtje" + label_total: Total + label_permissions: Te drejta + label_current_status: Statusi aktual + label_new_statuses_allowed: Statuse te reja te lejuara + label_all: te gjitha + label_none: asnje + label_nobody: askush + label_next: Pasardhes + label_previous: Paraardhes + label_used_by: Perdorur nga + label_details: Detaje + label_add_note: Shto nje Shenim + label_per_page: Per Faqe + label_calendar: Kalendar + label_months_from: muaj nga + label_gantt: Gantt + label_internal: I brendshem + label_last_changes: "%{count} ndryshimet e fundit" + label_change_view_all: Shih gjithe ndryshimet + label_personalize_page: Personalizo kete Faqe + label_comment: Koment + label_comment_plural: Komente + label_x_comments: + zero: asnje koment + one: 1 koment + other: "%{count} komente" + label_comment_add: Shto nje koment + label_comment_added: Komenti u shtua + label_comment_delete: Fshi komente + label_query: Custom query + label_query_plural: Custom queries + label_query_new: New query + label_my_queries: My custom queries + label_filter_add: Shto filter + label_filter_plural: Filtra + label_equals: eshte + label_not_equals: nuk eshte + label_in_less_than: ne me pak se + label_in_more_than: ne me shume se + label_greater_or_equal: '>=' + label_less_or_equal: '<=' + label_between: ndermjet + label_in: ne + label_today: sot + label_all_time: cdo kohe + label_yesterday: dje + label_this_week: kete jave + label_last_week: javen e kaluar + label_last_n_days: "%{count} ditet e fundit" + label_this_month: kete muaj + label_last_month: muajin e kaluar + label_this_year: kete vit + label_date_range: Date range + label_less_than_ago: me pak se dite para + label_more_than_ago: me shume se dite para + label_ago: dite para + label_contains: permban + label_not_contains: nuk permban + label_day_plural: dite + label_repository: Repository + label_repository_new: New repository + label_repository_plural: Repositories + label_browse: Browse + label_branch: Dege + label_tag: Tag + label_revision: Revizion + label_revision_plural: Revizione + label_revision_id: "Revizion %{value}" + label_associated_revisions: Associated revisions + label_added: te shtuara + label_modified: te modifikuara + label_copied: te kopjuara + label_renamed: te riemeruara + label_deleted: te fshira + label_latest_revision: Revizioni i fundit + label_latest_revision_plural: Revizionet e fundit + label_view_revisions: Shih Revizionet + label_view_all_revisions: Shih te gjitha Revizionet + label_max_size: Maximum size + label_sort_highest: Coje ne krye + label_sort_higher: Coje lart + label_sort_lower: Coje poshte + label_sort_lowest: Coje ne fund + label_roadmap: Roadmap + label_roadmap_due_in: "E pritshme ne %{value}" + label_roadmap_overdue: "%{value} me vonese" + label_roadmap_no_issues: Asnje Ceshtje per kete version + label_search: Kerko + label_result_plural: Rezultatet + label_all_words: Te gjitha fjalet + 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: Current version + label_preview: Preview + label_feed_plural: Feeds + label_changes_details: Details of all changes + label_issue_tracking: Issue tracking + label_spent_time: Spent time + label_overall_spent_time: Overall spent time + label_f_hour: "%{value} ore" + label_f_hour_plural: "%{value} ore" + label_time_tracking: Time tracking + label_change_plural: Ndryshimet + label_statistics: Statistika + label_commits_per_month: Commits per month + label_commits_per_author: Commits per author + label_diff: diff + label_view_diff: View differences + label_diff_inline: inline + label_diff_side_by_side: side by side + label_options: Options + label_copy_workflow_from: Copy workflow from + label_permissions_report: Permissions report + 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_duplicated_by: duplicated by + label_blocks: blocks + label_blocked_by: blocked by + label_precedes: precedes + label_follows: follows + label_end_to_start: end to start + label_end_to_end: end to end + label_start_to_start: start to start + label_start_to_end: start to end + label_stay_logged_in: Stay logged in + label_disabled: disabled + label_show_completed_versions: Show completed versions + label_me: me + label_board: Forum + label_board_new: New forum + label_board_plural: Forums + label_board_locked: Locked + label_board_sticky: Sticky + label_topic_plural: Topics + label_message_plural: Messages + label_message_last: Last message + label_message_new: New message + label_message_posted: Message added + 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: Based on user's language + label_sort_by: "Sort by %{value}" + label_send_test_email: Send a test email + label_feeds_access_key: RSS access key + label_missing_feeds_access_key: Missing a RSS access key + label_feeds_access_key_created_on: "RSS access key created %{value} ago" + label_module_plural: Modules + label_added_time_by: "Added by %{author} %{age} ago" + label_updated_time_by: "Updated by %{author} %{age} ago" + label_updated_time: "Updated %{value} ago" + label_jump_to_a_project: Jump to a project... + label_file_plural: Files + label_changeset_plural: Changesets + label_default_columns: Default columns + label_no_change_option: (No change) + label_bulk_edit_selected_issues: Bulk edit selected issues + label_bulk_edit_selected_time_entries: Bulk edit selected time entries + label_theme: Theme + label_default: Default + label_search_titles_only: Search titles only + label_user_mail_option_all: "For any event on all my projects" + label_user_mail_option_selected: "For any event on the selected projects only..." + label_user_mail_option_none: "No events" + label_user_mail_option_only_my_events: "Only for things I watch or I'm involved in" + label_user_mail_option_only_assigned: "Only for things I am assigned to" + label_user_mail_option_only_owner: "Only for things I am the owner of" + label_user_mail_no_self_notified: "I don't want to be notified of changes that I make myself" + 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: "Per page: %{value}" + label_age: Age + label_change_properties: Change properties + label_general: General + label_more: More + label_scm: SCM + label_plugins: Plugins + label_ldap_authentication: LDAP authentication + label_downloads_abbr: D/L + label_optional_description: Optional description + label_add_another_file: Add another file + label_preferences: Preferences + label_chronological_order: In chronological order + label_reverse_chronological_order: In reverse chronological order + label_planning: Planning + label_incoming_emails: Incoming emails + label_generate_key: Generate a key + label_issue_watchers: Watchers + label_example: Example + label_display: Display + label_sort: Sort + label_ascending: Ascending + label_descending: Descending + label_date_from_to: From %{start} to %{end} + label_wiki_content_added: Wiki page added + label_wiki_content_updated: Wiki page updated + label_group: Group + label_group_plural: Groups + label_group_new: New group + label_time_entry_plural: Spent time + label_version_sharing_none: Not shared + label_version_sharing_descendants: With subprojects + label_version_sharing_hierarchy: With project hierarchy + label_version_sharing_tree: With project tree + label_version_sharing_system: With all projects + label_update_issue_done_ratios: Update issue done ratios + label_copy_source: Source + label_copy_target: Target + label_copy_same_as_target: Same as target + label_display_used_statuses_only: Only display statuses that are used by this tracker + label_api_access_key: API access key + label_missing_api_access_key: Missing an API access key + label_api_access_key_created_on: "API access key created %{value} ago" + label_profile: Profile + label_subtask_plural: Subtasks + label_project_copy_notifications: Send email notifications during the project copy + label_principal_search: "Search for user or group:" + label_user_search: "Search for user:" + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + label_issues_visibility_all: All issues + label_issues_visibility_public: All non private issues + label_issues_visibility_own: Issues created by or assigned to the user + label_git_report_last_commit: Report last commit for files and directories + label_parent_revision: Parent + label_child_revision: Child + label_export_options: "%{export_format} export options" + label_copy_attachments: Copy attachments + label_item_position: "%{position} of %{count}" + label_completed_versions: Completed versions + label_search_for_watchers: Search for watchers to add + + button_login: Login + button_submit: Submit + button_save: Save + button_check_all: Check all + button_uncheck_all: Uncheck all + button_collapse_all: Collapse all + button_expand_all: Expand all + button_delete: Delete + button_create: Create + button_create_and_continue: Create and continue + button_test: Test + button_edit: Edit + button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" + button_add: Add + button_change: Change + button_apply: Apply + button_clear: Clear + button_lock: Lock + button_unlock: Unlock + button_download: Download + button_list: List + button_view: View + button_move: Move + button_move_and_follow: Move and follow + button_back: Back + button_cancel: Cancel + button_activate: Activate + button_sort: Sort + button_log_time: Log time + button_rollback: Rollback to this version + button_watch: Watch + button_unwatch: Unwatch + button_reply: Reply + button_archive: Archive + button_unarchive: Unarchive + button_reset: Reset + button_rename: Rename + button_change_password: Change password + button_copy: Copy + button_copy_and_follow: Copy and follow + button_annotate: Annotate + button_update: Update + button_configure: Configure + button_quote: Quote + button_duplicate: Duplicate + button_show: Show + button_edit_section: Edit this section + button_export: Export + button_delete_my_account: Delete my account + + status_active: active + status_registered: registered + status_locked: locked + + version_status_open: open + version_status_locked: locked + version_status_closed: closed + + field_active: Active + + 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 means no restriction + text_project_destroy_confirmation: Are you sure you want to delete this project and related data? + text_subprojects_destroy_warning: "Its subproject(s): %{value} will be also deleted." + text_workflow_edit: Select a role and a tracker to edit the workflow + text_are_you_sure: Are you sure? + text_journal_changed: "%{label} changed from %{old} to %{new}" + text_journal_changed_no_detail: "%{label} updated" + text_journal_set_to: "%{label} set to %{value}" + text_journal_deleted: "%{label} deleted (%{old})" + text_journal_added: "%{label} %{value} added" + text_tip_issue_begin_day: issue beginning this day + text_tip_issue_end_day: issue ending this day + text_tip_issue_begin_end_day: issue beginning and ending this day + text_project_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed.' + text_caracters_maximum: "%{count} characters maximum." + text_caracters_minimum: "Must be at least %{count} characters long." + text_length_between: "Length between %{min} and %{max} characters." + text_tracker_no_workflow: No workflow defined for this tracker + text_unallowed_characters: Unallowed characters + text_comma_separated: Multiple values allowed (comma separated). + text_line_separated: Multiple values allowed (one line for each value). + text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages + text_issue_added: "Issue %{id} has been reported by %{author}." + text_issue_updated: "Issue %{id} has been updated by %{author}." + text_wiki_destroy_confirmation: Are you sure you want to delete this wiki and all its content? + text_issue_category_destroy_question: "Some issues (%{count}) 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: "For unselected projects, you will only receive notifications about things you watch or you're involved in (eg. issues you're the author or assignee)." + text_no_configuration_data: "Roles, trackers, issue statuses and workflow have not been configured yet.\nIt is highly recommended to load the default configuration. You will be able to modify it once loaded." + text_load_default_configuration: Load the default configuration + text_status_changed_by_changeset: "Applied in changeset %{value}." + text_time_logged_by_changeset: "Applied in changeset %{value}." + text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s)?' + text_issues_destroy_descendants_confirmation: "This will also delete %{count} subtask(s)." + text_time_entries_destroy_confirmation: 'Are you sure you want to delete the selected time entr(y/ies)?' + text_select_project_modules: 'Select modules to enable for this project:' + text_default_administrator_account_changed: Default administrator account changed + 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: "%{hours} 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: "%{value} wrote:" + text_enumeration_destroy_question: "%{count} 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/configuration.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' + text_wiki_page_destroy_question: "This page has %{descendants} child page(s) and descendant(s). What do you want to do?" + text_wiki_page_nullify_children: "Keep child pages as root pages" + text_wiki_page_destroy_children: "Delete child pages and all their descendants" + text_wiki_page_reassign_children: "Reassign child pages to this parent page" + text_own_membership_delete_confirmation: "You are about to remove some or all of your permissions and may no longer be able to edit this project after that.\nAre you sure you want to continue?" + text_zoom_in: Zoom in + text_zoom_out: Zoom out + text_warn_on_leaving_unsaved: "The current page contains unsaved text that will be lost if you leave this page." + text_scm_path_encoding_note: "Default: UTF-8" + text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) + text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) + text_scm_command: Command + text_scm_command_version: Version + text_issue_conflict_resolution_overwrite: "Apply my changes anyway (previous notes will be kept but some changes may be overwritten)" + text_issue_conflict_resolution_add_notes: "Add my notes and discard my other changes" + text_issue_conflict_resolution_cancel: "Discard all my changes and redisplay %{link}" + text_account_destroy_confirmation: "Are you sure you want to proceed?\nYour account will be permanently deleted, with no way to reactivate it." + + default_role_manager: Manager + default_role_developer: Developer + default_role_reporter: Reporter + default_tracker_bug: Bug + default_tracker_feature: Feature + default_tracker_support: Support + default_issue_status_new: New + default_issue_status_in_progress: In Progress + default_issue_status_resolved: Resolved + default_issue_status_feedback: Feedback + default_issue_status_closed: Closed + default_issue_status_rejected: Rejected + default_doc_category_user: User documentation + default_doc_category_tech: Technical documentation + default_priority_low: Low + default_priority_normal: Normal + default_priority_high: High + default_priority_urgent: Urgent + default_priority_immediate: Immediate + default_activity_design: Design + default_activity_development: Development + + enumeration_issue_priorities: Issue priorities + enumeration_doc_categories: Document categories + enumeration_activities: Activities (time tracking) + enumeration_system_activity: System Activity + description_filter: Filter + description_search: Searchfield + description_choose_project: Projects + description_project_scope: Search scope + description_notes: Notes + description_message_content: Message content + description_query_sort_criteria_attribute: Sort attribute + description_query_sort_criteria_direction: Sort direction + description_user_mail_notification: Mail notification settings + description_available_columns: Available Columns + description_selected_columns: Selected Columns + description_all_columns: All Columns + description_issue_category_reassign: Choose issue category + description_wiki_subpages_reassign: Choose new parent page + description_date_range_list: Choose range from list + description_date_range_interval: Choose range by selecting start and end date + description_date_from: Enter start date + description_date_to: Enter end date + error_session_expired: Your session has expired. Please login again. + text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." + setting_session_lifetime: Session maximum lifetime + setting_session_timeout: Session inactivity timeout + label_session_expiration: Session expiration + permission_close_project: Close / reopen the project + label_show_closed_projects: View closed projects + button_close: Close + button_reopen: Reopen + project_status_active: active + project_status_closed: closed + project_status_archived: archived + text_project_closed: This project is closed and read-only. + notice_user_successful_create: User %{id} created. + field_core_fields: Standard fields + field_timeout: Timeout (in seconds) + setting_thumbnails_enabled: Display attachment thumbnails + setting_thumbnails_size: Thumbnails size (in pixels) + label_status_transitions: Status transitions + label_fields_permissions: Fields permissions + label_readonly: Read-only + label_required: Required + text_repository_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed.' + field_board_parent: Parent forum + label_attribute_of_project: Project's %{name} + label_attribute_of_author: Author's %{name} + label_attribute_of_assigned_to: Assignee's %{name} + label_attribute_of_fixed_version: Target version's %{name} + label_copy_subtasks: Copy subtasks + label_copied_to: copied to + label_copied_from: copied from + label_any_issues_in_project: any issues in project + label_any_issues_not_in_project: any issues not in project + field_private_notes: Private notes + permission_view_private_notes: View private notes + permission_set_notes_private: Set notes as private + label_no_issues_in_project: no issues in project + label_any: te gjitha + label_last_n_weeks: last %{count} weeks + setting_cross_project_subtasks: Allow cross-project subtasks + label_cross_project_descendants: With subprojects + label_cross_project_tree: With project tree + label_cross_project_hierarchy: With project hierarchy + label_cross_project_system: With all projects + button_hide: Hide + setting_non_working_week_days: Non-working days + label_in_the_next_days: in the next + label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Total + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_footer: Email footer + setting_emails_header: Email header diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7c/7c0793046e6842f067096f93f3d1b6e963a83b49.svn-base --- a/.svn/pristine/7c/7c0793046e6842f067096f93f3d1b6e963a83b49.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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 -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7c/7c1301162ea6bb82a007534a13501f191a1f47c9.svn-base --- a/.svn/pristine/7c/7c1301162ea6bb82a007534a13501f191a1f47c9.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,90 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) - -class JournalTest < ActiveSupport::TestCase - fixtures :projects, :issues, :issue_statuses, :journals, :journal_details, :users, :members, :member_roles - - def setup - @journal = Journal.find 1 - end - - def test_journalized_is_an_issue - issue = @journal.issue - assert_kind_of Issue, issue - assert_equal 1, issue.id - end - - def test_new_status - status = @journal.new_status - assert_not_nil status - assert_kind_of IssueStatus, status - assert_equal 2, status.id - end - - def test_create_should_send_email_notification - ActionMailer::Base.deliveries.clear - issue = Issue.find(:first) - user = User.find(:first) - journal = issue.init_journal(user, issue) - - assert journal.save - assert_equal 1, ActionMailer::Base.deliveries.size - end - - def test_visible_scope_for_anonymous - # Anonymous user should see issues of public projects only - journals = Journal.visible(User.anonymous).all - assert journals.any? - assert_nil journals.detect {|journal| !journal.issue.project.is_public?} - # Anonymous user should not see issues without permission - Role.anonymous.remove_permission!(:view_issues) - journals = Journal.visible(User.anonymous).all - assert journals.empty? - end - - def test_visible_scope_for_user - user = User.find(9) - assert user.projects.empty? - # Non member user should see issues of public projects only - journals = Journal.visible(user).all - assert journals.any? - assert_nil journals.detect {|journal| !journal.issue.project.is_public?} - # Non member user should not see issues without permission - Role.non_member.remove_permission!(:view_issues) - user.reload - journals = Journal.visible(user).all - assert journals.empty? - # User should see issues of projects for which he has view_issues permissions only - Member.create!(:principal => user, :project_id => 1, :role_ids => [1]) - user.reload - journals = Journal.visible(user).all - assert journals.any? - assert_nil journals.detect {|journal| journal.issue.project_id != 1} - end - - def test_visible_scope_for_admin - user = User.find(1) - user.members.each(&:destroy) - assert user.projects.empty? - journals = Journal.visible(user).all - assert journals.any? - # Admin should see issues on private projects that he does not belong to - assert journals.detect {|journal| !journal.issue.project.is_public?} - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7c/7c196d5273bcb940eb10ad58017c16601e771158.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/7c/7c196d5273bcb940eb10ad58017c16601e771158.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,18 @@ +

    <%=l(:label_assigned_to_me_issues)%> (<%= Issue.visible.open.count(:conditions => {:assigned_to_id => ([User.current.id] + User.current.group_ids)})%>)

    + +<% assigned_issues = issuesassignedtome_items %> +<%= render :partial => 'issues/list_simple', :locals => { :issues => assigned_issues } %> +<% if assigned_issues.length > 0 %> +

    <%= link_to l(:label_issue_view_all), :controller => 'issues', + :action => 'index', + :set_filter => 1, + :assigned_to_id => 'me', + :sort => 'priority:desc,updated_on:desc' %>

    +<% end %> + +<% content_for :header_tags do %> +<%= auto_discovery_link_tag(:atom, + {:controller => 'issues', :action => 'index', :set_filter => 1, + :assigned_to_id => 'me', :format => 'atom', :key => User.current.rss_key}, + {:title => l(:label_assigned_to_me_issues)}) %> +<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7c/7c48a2f2c6660008fb7234d1e70560afa7f9be22.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/7c/7c48a2f2c6660008fb7234d1e70560afa7f9be22.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,1108 @@ +# Turkish translations for Ruby on Rails +# by Ozgun Ataman (ozataman@gmail.com) +# by Burak Yigit Kaya (ben@byk.im) + +tr: + locale: + native_name: Türkçe + address_separator: " " + direction: ltr + date: + formats: + default: "%d.%m.%Y" + numeric: "%d.%m.%Y" + short: "%e %b" + long: "%e %B %Y, %A" + only_day: "%e" + + day_names: [Pazar, Pazartesi, Salı, Çarşamba, Perşembe, Cuma, Cumartesi] + abbr_day_names: [Pzr, Pzt, Sal, Çrş, Prş, Cum, Cts] + month_names: [~, Ocak, Şubat, Mart, Nisan, Mayıs, Haziran, Temmuz, Ağustos, Eylül, Ekim, Kasım, Aralık] + abbr_month_names: [~, Oca, Şub, Mar, Nis, May, Haz, Tem, Ağu, Eyl, Eki, Kas, Ara] + order: + - :day + - :month + - :year + + time: + formats: + default: "%a %d.%b.%y %H:%M" + numeric: "%d.%b.%y %H:%M" + short: "%e %B, %H:%M" + long: "%e %B %Y, %A, %H:%M" + time: "%H:%M" + + am: "öğleden önce" + pm: "öğleden sonra" + + datetime: + distance_in_words: + half_a_minute: 'yarım dakika' + less_than_x_seconds: + zero: '1 saniyeden az' + one: '1 saniyeden az' + other: '%{count} saniyeden az' + x_seconds: + one: '1 saniye' + other: '%{count} saniye' + less_than_x_minutes: + zero: '1 dakikadan az' + one: '1 dakikadan az' + other: '%{count} dakikadan az' + x_minutes: + one: '1 dakika' + other: '%{count} dakika' + about_x_hours: + one: 'yaklaşık 1 saat' + other: 'yaklaşık %{count} saat' + x_hours: + one: "1 saat" + other: "%{count} saat" + x_days: + one: '1 gün' + other: '%{count} gün' + about_x_months: + one: 'yaklaşık 1 ay' + other: 'yaklaşık %{count} ay' + x_months: + one: '1 ay' + other: '%{count} ay' + about_x_years: + one: 'yaklaşık 1 yıl' + other: 'yaklaşık %{count} yıl' + over_x_years: + one: '1 yıldan fazla' + other: '%{count} yıldan fazla' + almost_x_years: + one: "neredeyse 1 Yıl" + other: "neredeyse %{count} yıl" + + number: + format: + precision: 2 + separator: ',' + delimiter: '.' + currency: + format: + unit: 'TRY' + format: '%n%u' + separator: ',' + delimiter: '.' + precision: 2 + percentage: + format: + delimiter: '.' + separator: ',' + precision: 2 + precision: + format: + delimiter: '.' + separator: ',' + human: + format: + delimiter: '.' + separator: ',' + precision: 3 + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Byte" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + + support: + array: + sentence_connector: "ve" + skip_last_comma: true + + activerecord: + errors: + template: + header: + one: "%{model} girişi kaydedilemedi: 1 hata." + other: "%{model} girişi kadedilemedi: %{count} hata." + body: "Lütfen aşağıdaki hataları düzeltiniz:" + + messages: + inclusion: "kabul edilen bir kelime değil" + exclusion: "kullanılamaz" + invalid: "geçersiz" + confirmation: "teyidi uyuşmamakta" + accepted: "kabul edilmeli" + empty: "doldurulmalı" + blank: "doldurulmalı" + too_long: "çok uzun (en fazla %{count} karakter)" + too_short: "çok kısa (en az %{count} karakter)" + wrong_length: "yanlış uzunlukta (tam olarak %{count} karakter olmalı)" + taken: "hali hazırda kullanılmakta" + not_a_number: "geçerli bir sayı değil" + greater_than: "%{count} sayısından büyük olmalı" + greater_than_or_equal_to: "%{count} sayısına eşit veya büyük olmalı" + equal_to: "tam olarak %{count} olmalı" + less_than: "%{count} sayısından küçük olmalı" + less_than_or_equal_to: "%{count} sayısına eşit veya küçük olmalı" + odd: "tek olmalı" + even: "çift olmalı" + greater_than_start_date: "başlangıç tarihinden büyük olmalı" + not_same_project: "aynı projeye ait değil" + circular_dependency: "Bu ilişki döngüsel bağımlılık meydana getirecektir" + cant_link_an_issue_with_a_descendant: "Bir iş, alt işlerinden birine bağlanamaz" + models: + + actionview_instancetag_blank_option: Lütfen Seçin + + general_text_No: 'Hayır' + general_text_Yes: 'Evet' + general_text_no: 'hayır' + general_text_yes: 'evet' + general_lang_name: 'Türkçe' + general_csv_separator: ',' + general_csv_encoding: ISO-8859-9 + general_pdf_encoding: UTF-8 + general_first_day_of_week: '7' + + notice_account_updated: Hesap başarıyla güncelleştirildi. + notice_account_invalid_creditentials: Geçersiz kullanıcı ya da parola + notice_account_password_updated: Parola başarıyla güncellendi. + notice_account_wrong_password: Yanlış parola + notice_account_register_done: Hesap başarıyla oluşturuldu. Hesabınızı etkinleştirmek için, size gönderilen e-postadaki bağlantıya tıklayın. + notice_account_unknown_email: Tanınmayan kullanıcı. + notice_can_t_change_password: Bu hesap harici bir denetim kaynağı kullanıyor. Parolayı değiştirmek mümkün değil. + notice_account_lost_email_sent: Yeni parola seçme talimatlarını içeren e-postanız gönderildi. + notice_account_activated: Hesabınız etkinleştirildi. Şimdi giriş yapabilirsiniz. + notice_successful_create: Başarıyla oluşturuldu. + notice_successful_update: Başarıyla güncellendi. + notice_successful_delete: Başarıyla silindi. + notice_successful_connection: Bağlantı başarılı. + notice_file_not_found: Erişmek istediğiniz sayfa mevcut değil ya da kaldırılmış. + notice_locking_conflict: Veri başka bir kullanıcı tarafından güncellendi. + notice_not_authorized: Bu sayfaya erişme yetkiniz yok. + notice_email_sent: "E-posta gönderildi %{value}" + notice_email_error: "E-posta gönderilirken bir hata oluştu (%{value})" + notice_feeds_access_key_reseted: RSS erişim anahtarınız sıfırlandı. + notice_failed_to_save_issues: "Failed to save %{count} issue(s) on %{total} selected: %{ids}." + notice_no_issue_selected: "Seçili iş yok! Lütfen, düzenlemek istediğiniz işleri işaretleyin." + notice_account_pending: "Hesabınız oluşturuldu ve yönetici onayı bekliyor." + notice_default_data_loaded: Varasayılan konfigürasyon başarılıyla yüklendi. + + error_can_t_load_default_data: "Varsayılan konfigürasyon yüklenemedi: %{value}" + error_scm_not_found: "Depoda, giriş ya da değişiklik yok." + error_scm_command_failed: "Depoya erişmeye çalışırken bir hata meydana geldi: %{value}" + error_scm_annotate: "Giriş mevcut değil veya izah edilemedi." + error_issue_not_found_in_project: 'İş bilgisi bulunamadı veya bu projeye ait değil' + + mail_subject_lost_password: "Parolanız %{value}" + mail_body_lost_password: 'Parolanızı değiştirmek için, aşağıdaki bağlantıya tıklayın:' + mail_subject_register: "%{value} hesap aktivasyonu" + mail_body_register: 'Hesabınızı etkinleştirmek için, aşağıdaki bağlantıya tıklayın:' + mail_body_account_information_external: "Hesabınızı %{value} giriş yapmak için kullanabilirsiniz." + mail_body_account_information: Hesap bilgileriniz + mail_subject_account_activation_request: "%{value} hesabı etkinleştirme isteği" + mail_body_account_activation_request: "Yeni bir kullanıcı (%{value}) kaydedildi. Hesap onaylanmayı bekliyor:" + + + field_name: İsim + field_description: Yorum + field_summary: Özet + field_is_required: Gerekli + field_firstname: Ad + field_lastname: Soyad + field_mail: E-Posta + field_filename: Dosya + field_filesize: Boyut + field_downloads: İndirilenler + field_author: Yazar + field_created_on: Oluşturulma + field_updated_on: Güncellenme + field_field_format: Biçim + field_is_for_all: Tüm projeler için + field_possible_values: Kullanılabilir değerler + field_regexp: Düzenli ifadeler + field_min_length: En az uzunluk + field_max_length: En çok uzunluk + field_value: Değer + field_category: Kategori + field_title: Başlık + field_project: Proje + field_issue: İş + field_status: Durum + field_notes: Notlar + field_is_closed: İş kapatıldı + field_is_default: Varsayılan Değer + field_tracker: İş tipi + field_subject: Konu + field_due_date: Bitiş Tarihi + field_assigned_to: Atanan + field_priority: Öncelik + field_fixed_version: Hedef Sürüm + field_user: Kullanıcı + field_role: Rol + field_homepage: Anasayfa + field_is_public: Genel + field_parent: 'Üst proje: ' + field_is_in_roadmap: Yol haritasında gösterilen işler + field_login: Giriş + field_mail_notification: E-posta uyarıları + field_admin: Yönetici + field_last_login_on: Son Bağlantı + field_language: Dil + field_effective_date: Tarih + field_password: Parola + field_new_password: Yeni Parola + field_password_confirmation: Onay + field_version: Sürüm + field_type: Tip + field_host: Host + field_port: Port + field_account: Hesap + field_base_dn: Base DN + field_attr_login: Giriş Niteliği + field_attr_firstname: Ad Niteliği + field_attr_lastname: Soyad Niteliği + field_attr_mail: E-Posta Niteliği + field_onthefly: Anında kullanıcı oluşturma + field_start_date: Başlangıç Tarihi + field_done_ratio: Tamamlanma yüzdesi + field_auth_source: Kimlik Denetim Modu + field_hide_mail: E-posta adresimi gizle + field_comments: Yorumlar + field_url: URL + field_start_page: Başlangıç Sayfası + field_subproject: Alt Proje + field_hours: Saat + field_activity: Etkinlik + field_spent_on: Tarih + field_identifier: Tanımlayıcı + field_is_filter: filtre olarak kullanılmış + field_issue_to: İlişkili iş + field_delay: Gecikme + field_assignable: Bu role atanabilecek işler + field_redirect_existing_links: Mevcut bağlantıları yönlendir + field_estimated_hours: Kalan zaman + field_column_names: Sütunlar + field_time_zone: Saat dilimi + field_searchable: Aranabilir + field_default_value: Varsayılan değer + field_comments_sorting: Yorumları göster + + setting_app_title: Uygulama Bağlığı + setting_app_subtitle: Uygulama alt başlığı + setting_welcome_text: Hoşgeldin Mesajı + setting_default_language: Varsayılan Dil + setting_login_required: Kimlik denetimi gerekli mi + setting_self_registration: Otomatik kayıt + setting_attachment_max_size: Maksimum ek boyutu + setting_issues_export_limit: İşlerin dışa aktarılma sınırı + setting_mail_from: Gönderici e-posta adresi + setting_bcc_recipients: Alıcıları birbirinden gizle (bcc) + setting_host_name: Host adı + setting_text_formatting: Metin biçimi + setting_wiki_compression: Wiki geçmişini sıkıştır + setting_feeds_limit: Haber yayını içerik limiti + setting_default_projects_public: Yeni projeler varsayılan olarak herkese açık + setting_autofetch_changesets: Otomatik gönderi al + setting_sys_api_enabled: Depo yönetimi için WS'yi etkinleştir + setting_commit_ref_keywords: Başvuru Kelimeleri + setting_commit_fix_keywords: Sabitleme kelimeleri + setting_autologin: Otomatik Giriş + setting_date_format: Tarih Formati + setting_time_format: Zaman Formatı + setting_cross_project_issue_relations: Çapraz-Proje iş ilişkilendirmesine izin ver + setting_issue_list_default_columns: İş listesinde gösterilen varsayılan sütunlar + setting_emails_footer: E-posta dip not + setting_protocol: Protokol + setting_per_page_options: Sayfada başına öğe sayısı + setting_user_format: Kullanıcı gösterim biçimi + setting_activity_days_default: Proje etkinliklerinde gösterilen gün sayısı + setting_display_subprojects_issues: Varsayılan olarak ana projenin iş listesinde alt proje işlerini göster + + project_module_issue_tracking: İş Takibi + project_module_time_tracking: Zaman Takibi + project_module_news: Haberler + project_module_documents: Belgeler + project_module_files: Dosyalar + project_module_wiki: Wiki + project_module_repository: Depo + project_module_boards: Tartışma Alanı + + label_user: Kullanıcı + label_user_plural: Kullanıcılar + label_user_new: Yeni Kullanıcı + label_project: Proje + label_project_new: Yeni proje + label_project_plural: Projeler + label_x_projects: + zero: hiç proje yok + one: 1 proje + other: "%{count} proje" + label_project_all: Tüm Projeler + label_project_latest: En son projeler + label_issue: İş + label_issue_new: Yeni İş + label_issue_plural: İşler + label_issue_view_all: Tüm işleri izle + label_issues_by: "%{value} tarafından gönderilmiş işler" + label_issue_added: İş eklendi + label_issue_updated: İş güncellendi + label_document: Belge + label_document_new: Yeni belge + label_document_plural: Belgeler + label_document_added: Belge eklendi + label_role: Rol + label_role_plural: Roller + label_role_new: Yeni rol + label_role_and_permissions: Roller ve izinler + label_member: Üye + label_member_new: Yeni üye + label_member_plural: Üyeler + label_tracker: İş tipi + label_tracker_plural: İş tipleri + label_tracker_new: Yeni iş tipi + label_workflow: İş akışı + label_issue_status: İş durumu + label_issue_status_plural: İş durumuları + label_issue_status_new: Yeni durum + label_issue_category: İş kategorisi + label_issue_category_plural: İş kategorileri + label_issue_category_new: Yeni kategori + label_custom_field: Özel alan + label_custom_field_plural: Özel alanlar + label_custom_field_new: Yeni özel alan + label_enumerations: Numaralandırmalar + label_enumeration_new: Yeni değer + label_information: Bilgi + label_information_plural: Bilgi + label_please_login: Lütfen giriş yapın + label_register: Kayıt + label_password_lost: Parolamı unuttum + label_home: Anasayfa + label_my_page: Kişisel Sayfam + label_my_account: Hesabım + label_my_projects: Projelerim + label_administration: Yönetim + label_login: Giriş + label_logout: Çıkış + label_help: Yardım + label_reported_issues: Rapor edilmiş işler + label_assigned_to_me_issues: Bana atanmış işler + label_last_login: Son bağlantı + label_registered_on: Kayıt tarihi + label_activity: Etkinlik + label_overall_activity: Tüm etkinlikler + label_new: Yeni + label_logged_as: "Kullanıcı :" + label_environment: Çevre + label_authentication: Kimlik Denetimi + label_auth_source: Kimlik Denetim Modu + label_auth_source_new: Yeni Denetim Modu + label_auth_source_plural: Denetim Modları + label_subproject_plural: Alt Projeler + label_min_max_length: Min - Maks uzunluk + label_list: Liste + label_date: Tarih + label_integer: Tam sayı + label_float: Ondalıklı sayı + label_boolean: "Evet/Hayır" + label_string: Metin + label_text: Uzun Metin + label_attribute: Nitelik + label_attribute_plural: Nitelikler + label_no_data: Gösterilecek veri yok + label_change_status: Değişim Durumu + label_history: Geçmiş + label_attachment: Dosya + label_attachment_new: Yeni Dosya + label_attachment_delete: Dosyayı Sil + label_attachment_plural: Dosyalar + label_file_added: Eklenen Dosyalar + label_report: Rapor + label_report_plural: Raporlar + label_news: Haber + label_news_new: Haber ekle + label_news_plural: Haber + label_news_latest: Son Haberler + label_news_view_all: Tüm haberleri oku + label_news_added: Haber eklendi + label_settings: Ayarlar + label_overview: Genel + label_version: Sürüm + label_version_new: Yeni sürüm + label_version_plural: Sürümler + label_confirmation: Doğrulamama + label_export_to: "Diğer uygun kaynaklar:" + label_read: "Oku..." + label_public_projects: Genel Projeler + label_open_issues: açık + label_open_issues_plural: açık + label_closed_issues: kapalı + label_closed_issues_plural: kapalı + label_x_open_issues_abbr_on_total: + zero: tamamı kapalı, toplam %{total} + one: 1'i' açık, toplam %{total} + other: "%{count} açık, toplam %{total}" + label_x_open_issues_abbr: + zero: hiç açık yok + one: 1 açık + other: "%{count} açık" + label_x_closed_issues_abbr: + zero: hiç kapalı yok + one: 1 kapalı + other: "%{count} kapalı" + label_total: Toplam + label_permissions: İzinler + label_current_status: Mevcut Durum + label_new_statuses_allowed: Yeni durumlara izin verildi + label_all: Hepsi + label_none: Hiçbiri + label_nobody: Hiçkimse + label_next: Sonraki + label_previous: Önceki + label_used_by: 'Kullanan: ' + label_details: Ayrıntılar + label_add_note: Not ekle + label_per_page: Sayfa başına + label_calendar: Takvim + label_months_from: ay öncesinden itibaren + label_gantt: İş-Zaman Çizelgesi + label_internal: Dahili + label_last_changes: "Son %{count} değişiklik" + label_change_view_all: Tüm Değişiklikleri gör + label_personalize_page: Bu sayfayı kişiselleştir + label_comment: Yorum + label_comment_plural: Yorumlar + label_x_comments: + zero: hiç yorum yok + one: 1 yorum + other: "%{count} yorum" + label_comment_add: Yorum Ekle + label_comment_added: Yorum Eklendi + label_comment_delete: Yorumları sil + label_query: Özel Sorgu + label_query_plural: Özel Sorgular + label_query_new: Yeni Sorgu + label_filter_add: Filtre ekle + label_filter_plural: Filtreler + label_equals: Eşit + label_not_equals: Eşit değil + label_in_less_than: küçüktür + label_in_more_than: büyüktür + label_in: içinde + label_today: bugün + label_all_time: Tüm Zamanlar + label_yesterday: Dün + label_this_week: Bu hafta + label_last_week: Geçen hafta + label_last_n_days: "Son %{count} gün" + label_this_month: Bu ay + label_last_month: Geçen ay + label_this_year: Bu yıl + label_date_range: Tarih aralığı + label_less_than_ago: günler öncesinden az + label_more_than_ago: günler öncesinden fazla + label_ago: gün önce + label_contains: içeriyor + label_not_contains: içermiyor + label_day_plural: Günler + label_repository: Depo + label_repository_plural: Depolar + label_browse: Gözat + label_revision: Değişiklik + label_revision_plural: Değişiklikler + label_associated_revisions: Birleştirilmiş değişiklikler + label_added: eklendi + label_modified: güncellendi + label_deleted: silindi + label_latest_revision: En son değişiklik + label_latest_revision_plural: En son değişiklikler + label_view_revisions: Değişiklikleri izle + label_max_size: En büyük boyut + label_sort_highest: Üste taşı + label_sort_higher: Yukarı taşı + label_sort_lower: Aşağı taşı + label_sort_lowest: Dibe taşı + label_roadmap: Yol Haritası + label_roadmap_due_in: "%{value} içinde bitmeli" + label_roadmap_overdue: "%{value} geç" + label_roadmap_no_issues: Bu sürüm için iş yok + label_search: Ara + label_result_plural: Sonuçlar + label_all_words: Tüm Kelimeler + label_wiki: Wiki + label_wiki_edit: Wiki düzenleme + label_wiki_edit_plural: Wiki düzenlemeleri + label_wiki_page: Wiki sayfası + label_wiki_page_plural: Wiki sayfaları + label_index_by_title: Başlığa göre diz + label_index_by_date: Tarihe göre diz + label_current_version: Güncel sürüm + label_preview: Önizleme + label_feed_plural: Beslemeler + label_changes_details: Bütün değişikliklerin detayları + label_issue_tracking: İş Takibi + label_spent_time: Harcanan zaman + label_f_hour: "%{value} saat" + label_f_hour_plural: "%{value} saat" + label_time_tracking: Zaman Takibi + label_change_plural: Değişiklikler + label_statistics: İstatistikler + label_commits_per_month: Aylık teslim + label_commits_per_author: Yazar başına teslim + label_view_diff: Farkları izle + label_diff_inline: satır içi + label_diff_side_by_side: Yan yana + label_options: Tercihler + label_copy_workflow_from: İşakışı kopyala + label_permissions_report: İzin raporu + label_watched_issues: İzlenmiş işler + label_related_issues: İlişkili işler + label_applied_status: uygulanmış işler + label_loading: Yükleniyor... + label_relation_new: Yeni ilişki + label_relation_delete: İlişkiyi sil + label_relates_to: ilişkili + label_duplicates: yinelenmiş + label_blocks: Engeller + label_blocked_by: Engelleyen + label_precedes: önce gelir + label_follows: sonra gelir + label_end_to_start: sondan başa + label_end_to_end: sondan sona + label_start_to_start: baştan başa + label_start_to_end: baştan sona + label_stay_logged_in: Sürekli bağlı kal + label_disabled: Devredışı + label_show_completed_versions: Tamamlanmış sürümleri göster + label_me: Ben + label_board: Tartışma Alanı + label_board_new: Yeni alan + label_board_plural: Tartışma alanları + label_topic_plural: Konular + label_message_plural: Mesajlar + label_message_last: Son mesaj + label_message_new: Yeni mesaj + label_message_posted: Mesaj eklendi + label_reply_plural: Cevaplar + label_send_information: Hesap bilgisini kullanıcıya gönder + label_year: Yıl + label_month: Ay + label_week: Hafta + label_date_from: Başlangıç + label_date_to: Bitiş + label_language_based: Kullanıcı dili bazlı + label_sort_by: "%{value} göre sırala" + label_send_test_email: Test e-postası gönder + label_feeds_access_key_created_on: "RSS erişim anahtarı %{value} önce oluşturuldu" + label_module_plural: Modüller + label_added_time_by: "%{author} tarafından %{age} önce eklendi" + label_updated_time: "%{value} önce güncellendi" + label_jump_to_a_project: Projeye git... + label_file_plural: Dosyalar + label_changeset_plural: Değişiklik Listeleri + label_default_columns: Varsayılan Sütunlar + label_no_change_option: (Değişiklik yok) + label_bulk_edit_selected_issues: Seçili işleri toplu olarak düzenle + label_theme: Tema + label_default: Varsayılan + label_search_titles_only: Sadece başlıkları ara + label_user_mail_option_all: "Tüm projelerimdeki herhangi bir olay için" + label_user_mail_option_selected: "Sadece seçili projelerdeki herhangi bir olay için" + label_user_mail_no_self_notified: "Kendi yaptığım değişikliklerden haberdar olmak istemiyorum" + label_registration_activation_by_email: e-posta ile hesap etkinleştirme + label_registration_manual_activation: Elle hesap etkinleştirme + label_registration_automatic_activation: Otomatik hesap etkinleştirme + label_display_per_page: "Sayfa başına: %{value}" + label_age: Yaş + label_change_properties: Özellikleri değiştir + label_general: Genel + label_more: Daha fazla + label_scm: KY + label_plugins: Eklentiler + label_ldap_authentication: LDAP Denetimi + label_downloads_abbr: D/L + label_optional_description: İsteğe bağlı açıklama + label_add_another_file: Bir dosya daha ekle + label_preferences: Tercihler + label_chronological_order: Tarih sırasına göre + label_reverse_chronological_order: Ters tarih sırasına göre + label_planning: Planlanıyor + + button_login: Giriş + button_submit: Gönder + button_save: Kaydet + button_check_all: Hepsini işaretle + button_uncheck_all: Tüm işaretleri kaldır + button_delete: Sil + button_create: Oluştur + button_test: Sına + button_edit: Düzenle + button_add: Ekle + button_change: Değiştir + button_apply: Uygula + button_clear: Temizle + button_lock: Kilitle + button_unlock: Kilidi aç + button_download: İndir + button_list: Listele + button_view: Bak + button_move: Taşı + button_back: Geri + button_cancel: İptal + button_activate: Etkinleştir + button_sort: Sırala + button_log_time: Zaman kaydı + button_rollback: Bu sürüme geri al + button_watch: İzle + button_unwatch: İzlemeyi iptal et + button_reply: Cevapla + button_archive: Arşivle + button_unarchive: Arşivlemeyi kaldır + button_reset: Sıfırla + button_rename: Yeniden adlandır + button_change_password: Parolayı değiştir + button_copy: Kopyala + button_annotate: Değişiklik geçmişine göre göster + button_update: Güncelle + button_configure: Yapılandır + + status_active: faal + status_registered: kayıtlı + status_locked: kilitli + + text_select_mail_notifications: Gönderilecek e-posta uyarısına göre hareketi seçin. + text_regexp_info: örn. ^[A-Z0-9]+$ + text_min_max_length_info: 0 sınırlama yok demektir + text_project_destroy_confirmation: Bu projeyi ve bağlantılı verileri silmek istediğinizden emin misiniz? + text_subprojects_destroy_warning: "Ayrıca %{value} alt proje silinecek." + text_workflow_edit: İşakışını düzenlemek için bir rol ve iş tipi seçin + text_are_you_sure: Emin misiniz ? + text_tip_issue_begin_day: Bugün başlayan görevler + text_tip_issue_end_day: Bugün sona eren görevler + text_tip_issue_begin_end_day: Bugün başlayan ve sona eren görevler + text_caracters_maximum: "En çok %{count} karakter." + text_caracters_minimum: "En az %{count} karakter uzunluğunda olmalı." + text_length_between: "%{min} ve %{max} karakterleri arasındaki uzunluk." + text_tracker_no_workflow: Bu iş tipi için işakışı tanımlanmamış + text_unallowed_characters: Yasaklı karakterler + text_comma_separated: Çoklu değer girilebilir(Virgül ile ayrılmış). + text_issues_ref_in_commit_messages: Teslim mesajlarındaki işleri çözme ve başvuruda bulunma + text_issue_added: "İş %{id}, %{author} tarafından rapor edildi." + text_issue_updated: "İş %{id}, %{author} tarafından güncellendi." + text_wiki_destroy_confirmation: bu wikiyi ve tüm içeriğini silmek istediğinizden emin misiniz? + text_issue_category_destroy_question: "Bazı işler (%{count}) bu kategoriye atandı. Ne yapmak istersiniz?" + text_issue_category_destroy_assignments: Kategori atamalarını kaldır + text_issue_category_reassign_to: İşleri bu kategoriye tekrar ata + text_user_mail_option: "Seçili olmayan projeler için, sadece dahil olduğunuz ya da izlediğiniz öğeler hakkında uyarılar alacaksınız (örneğin,yazarı veya atandığınız işler)." + text_no_configuration_data: "Roller, iş tipleri, iş durumları ve işakışı henüz yapılandırılmadı.\nVarsayılan yapılandırılmanın yüklenmesi şiddetle tavsiye edilir. Bir kez yüklendiğinde yapılandırmayı değiştirebileceksiniz." + text_load_default_configuration: Varsayılan yapılandırmayı yükle + text_status_changed_by_changeset: "Değişiklik listesi %{value} içinde uygulandı." + text_issues_destroy_confirmation: 'Seçili işleri silmek istediğinizden emin misiniz ?' + text_select_project_modules: 'Bu proje için etkinleştirmek istediğiniz modülleri seçin:' + text_default_administrator_account_changed: Varsayılan yönetici hesabı değişti + text_file_repository_writable: Dosya deposu yazılabilir + text_rmagick_available: RMagick Kullanılabilir (isteğe bağlı) + text_destroy_time_entries_question: Silmek üzere olduğunuz işler üzerine %{hours} saat raporlandı.Ne yapmak istersiniz ? + text_destroy_time_entries: Raporlanmış süreleri sil + text_assign_time_entries_to_project: Raporlanmış süreleri projeye ata + text_reassign_time_entries: 'Raporlanmış süreleri bu işe tekrar ata:' + + default_role_manager: Yönetici + default_role_developer: Geliştirici + default_role_reporter: Raporlayıcı + default_tracker_bug: Hata + default_tracker_feature: Özellik + default_tracker_support: Destek + default_issue_status_new: Yeni + default_issue_status_in_progress: Yapılıyor + default_issue_status_resolved: Çözüldü + default_issue_status_feedback: Geribildirim + default_issue_status_closed: "Kapatıldı" + default_issue_status_rejected: Reddedildi + default_doc_category_user: Kullanıcı Dökümantasyonu + default_doc_category_tech: Teknik Dökümantasyon + default_priority_low: Düşük + default_priority_normal: Normal + default_priority_high: Yüksek + default_priority_urgent: Acil + default_priority_immediate: Derhal + default_activity_design: Tasarım + default_activity_development: Geliştirme + + enumeration_issue_priorities: İş önceliği + enumeration_doc_categories: Belge Kategorileri + enumeration_activities: Faaliyetler (zaman takibi) + button_quote: Alıntı + setting_enabled_scm: KKY Açık + label_incoming_emails: "Gelen e-postalar" + label_generate_key: "Anahtar oluştur" + setting_sequential_project_identifiers: "Sıralı proje tanımlayıcıları oluştur" + field_parent_title: Üst sayfa + text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/configuration.yml and restart the application to enable them." + text_enumeration_category_reassign_to: 'Hepsini şuna çevir:' + label_issue_watchers: Takipçiler + mail_body_reminder: "Size atanmış olan %{count} iş %{days} gün içerisinde bitirilmeli:" + label_duplicated_by: yineleyen + text_enumeration_destroy_question: "Bu nesneye %{count} değer bağlanmış." + text_user_wrote: "%{value} demiş ki:" + setting_mail_handler_api_enabled: Gelen e-postalar için WS'yi aç + label_and_its_subprojects: "%{value} ve alt projeleri" + mail_subject_reminder: "%{count} iş bir kaç güne bitecek" + setting_mail_handler_api_key: API anahtarı + setting_commit_logs_encoding: Gönderim mesajlarının kodlaması (UTF-8 vs.) + general_csv_decimal_separator: '.' + notice_unable_delete_version: Sürüm silinemiyor + label_renamed: yeniden adlandırılmış + label_copied: kopyalanmış + setting_plain_text_mail: sadece düz metin (HTML yok) + permission_view_files: Dosyaları görme + permission_edit_issues: İşleri düzenleme + permission_edit_own_time_entries: Kendi zaman girişlerini düzenleme + permission_manage_public_queries: Herkese açık sorguları yönetme + permission_add_issues: İş ekleme + permission_log_time: Harcanan zamanı kaydetme + permission_view_changesets: Değişimleri görme(SVN, vs.) + permission_view_time_entries: Harcanan zamanı görme + permission_manage_versions: Sürümleri yönetme + permission_manage_wiki: Wiki'yi yönetme + permission_manage_categories: İş kategorilerini yönetme + permission_protect_wiki_pages: Wiki sayfalarını korumaya alma + permission_comment_news: Haberlere yorum yapma + permission_delete_messages: Mesaj silme + permission_select_project_modules: Proje modüllerini seçme + permission_edit_wiki_pages: Wiki sayfalarını düzenleme + permission_add_issue_watchers: Takipçi ekleme + permission_view_gantt: İş-Zaman çizelgesi görme + permission_move_issues: İşlerin yerini değiştirme + permission_manage_issue_relations: İşlerin biribiriyle bağlantılarını yönetme + permission_delete_wiki_pages: Wiki sayfalarını silme + permission_manage_boards: Panoları yönetme + permission_delete_wiki_pages_attachments: Ekleri silme + permission_view_wiki_edits: Wiki geçmişini görme + permission_add_messages: Mesaj gönderme + permission_view_messages: Mesajları görme + permission_manage_files: Dosyaları yönetme + permission_edit_issue_notes: Notları düzenleme + permission_manage_news: Haberleri yönetme + permission_view_calendar: Takvimleri görme + permission_manage_members: Üyeleri yönetme + permission_edit_messages: Mesajları düzenleme + permission_delete_issues: İşleri silme + permission_view_issue_watchers: Takipçi listesini görme + permission_manage_repository: Depo yönetimi + permission_commit_access: Gönderme erişimi + permission_browse_repository: Depoya gözatma + permission_view_documents: Belgeleri görme + permission_edit_project: Projeyi düzenleme + permission_add_issue_notes: Not ekleme + permission_save_queries: Sorgu kaydetme + permission_view_wiki_pages: Wiki görme + permission_rename_wiki_pages: Wiki sayfasının adını değiştirme + permission_edit_time_entries: Zaman kayıtlarını düzenleme + permission_edit_own_issue_notes: Kendi notlarını düzenleme + setting_gravatar_enabled: Kullanıcı resimleri için Gravatar kullan + label_example: Örnek + 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: Kendi mesajlarını düzenleme + permission_delete_own_messages: Kendi mesajlarını silme + label_user_activity: "%{value} kullanıcısının etkinlikleri" + label_updated_time_by: "%{author} tarafından %{age} önce güncellendi" + text_diff_truncated: '... Bu fark tam olarak gösterilemiyor çünkü gösterim için ayarlanmış üst sınırı aşıyor.' + setting_diff_max_lines_displayed: Gösterilebilecek maksimumu fark satırı + text_plugin_assets_writable: Eklenti yardımcı dosya dizini yazılabilir + warning_attachments_not_saved: "%{count} adet dosya kaydedilemedi." + button_create_and_continue: Oluştur ve devam et + text_custom_field_possible_values_info: 'Her değer için bir satır' + label_display: Göster + field_editable: Düzenlenebilir + setting_repository_log_display_limit: Dosya kaydında gösterilecek maksimum değişim sayısı + setting_file_max_size_displayed: Dahili olarak gösterilecek metin dosyaları için maksimum satır sayısı + field_watcher: Takipçi + setting_openid: Kayıt ve giriş için OpenID'ye izin ver + field_identity_url: OpenID URL + label_login_with_open_id_option: veya OpenID kullanın + field_content: İçerik + label_descending: Azalan + label_sort: Sırala + label_ascending: Artan + label_date_from_to: "%{start} - %{end} arası" + label_greater_or_equal: ">=" + label_less_or_equal: <= + text_wiki_page_destroy_question: Bu sayfanın %{descendants} adet alt sayfası var. Ne yapmak istersiniz? + text_wiki_page_reassign_children: Alt sayfaları bu sayfanın altına bağla + text_wiki_page_nullify_children: Alt sayfaları ana sayfa olarak sakla + text_wiki_page_destroy_children: Alt sayfaları ve onların alt sayfalarını tamamen sil + setting_password_min_length: Minimum parola uzunluğu + field_group_by: Sonuçları grupla + mail_subject_wiki_content_updated: "'%{id}' wiki sayfası güncellendi" + label_wiki_content_added: Wiki sayfası eklendi + mail_subject_wiki_content_added: "'%{id}' wiki sayfası eklendi" + mail_body_wiki_content_added: "'%{id}' wiki sayfası, %{author} tarafından eklendi." + label_wiki_content_updated: Wiki sayfası güncellendi + mail_body_wiki_content_updated: "'%{id}' wiki sayfası, %{author} tarafından güncellendi." + permission_add_project: Proje oluştur + setting_new_project_user_role_id: Yönetici olmayan ancak proje yaratabilen kullanıcıya verilen rol + label_view_all_revisions: Tüm değişiklikleri gör + label_tag: Etiket + label_branch: Kol + error_no_tracker_in_project: Bu projeye bağlanmış bir iş tipi yok. Lütfen proje ayarlarını kontrol edin. + error_no_default_issue_status: Varsayılan iş durumu tanımlanmamış. Lütfen ayarlarınızı kontrol edin ("Yönetim -> İş durumları" sayfasına gidin). + label_group_plural: Gruplar + label_group: Grup + label_group_new: Yeni grup + label_time_entry_plural: Harcanan zaman + text_journal_changed: "%{label}: %{old} -> %{new}" + text_journal_set_to: "%{label} %{value} yapıldı" + text_journal_deleted: "%{label} silindi (%{old})" + text_journal_added: "%{label} %{value} eklendi" + field_active: Etkin + enumeration_system_activity: Sistem Etkinlikleri + permission_delete_issue_watchers: İzleyicileri sil + version_status_closed: kapalı + version_status_locked: kilitli + version_status_open: açık + error_can_not_reopen_issue_on_closed_version: Kapatılmış bir sürüme ait işler tekrar açılamaz + label_user_anonymous: Anonim + button_move_and_follow: Yerini değiştir ve takip et + setting_default_projects_modules: Yeni projeler için varsayılan modüller + setting_gravatar_default: Varsayılan Gravatar resmi + field_sharing: Paylaşım + label_version_sharing_hierarchy: Proje hiyerarşisi ile + label_version_sharing_system: Tüm projeler ile + label_version_sharing_descendants: Alt projeler ile + label_version_sharing_tree: Proje ağacı ile + label_version_sharing_none: Paylaşılmamış + error_can_not_archive_project: Bu proje arşivlenemez + button_duplicate: Yinele + button_copy_and_follow: Kopyala ve takip et + label_copy_source: Kaynak + setting_issue_done_ratio: İş tamamlanma oranını şununla hesapla + setting_issue_done_ratio_issue_status: İş durumunu kullan + error_issue_done_ratios_not_updated: İş tamamlanma oranları güncellenmedi. + error_workflow_copy_target: Lütfen hedef iş tipi ve rolleri seçin + setting_issue_done_ratio_issue_field: İşteki alanı kullan + label_copy_same_as_target: Hedef ile aynı + label_copy_target: Hedef + notice_issue_done_ratios_updated: İş tamamlanma oranları güncellendi. + error_workflow_copy_source: Lütfen kaynak iş tipi ve rolleri seçin + label_update_issue_done_ratios: İş tamamlanma oranlarını güncelle + setting_start_of_week: Takvimleri şundan başlat + permission_view_issues: İşleri Gör + label_display_used_statuses_only: Sadece bu iş tipi tarafından kullanılan durumları göster + label_revision_id: Değişiklik %{value} + label_api_access_key: API erişim anahtarı + label_api_access_key_created_on: API erişim anahtarı %{value} önce oluşturuldu + label_feeds_access_key: RSS erişim anahtarı + notice_api_access_key_reseted: API erişim anahtarınız sıfırlandı. + setting_rest_api_enabled: REST web servisini etkinleştir + label_missing_api_access_key: Bir API erişim anahtarı eksik + label_missing_feeds_access_key: Bir RSS erişim anahtarı eksik + button_show: Göster + text_line_separated: Çoklu değer girilebilir (her satıra bir değer). + setting_mail_handler_body_delimiters: Şu satırların birinden sonra e-postayı sonlandır + permission_add_subprojects: Alt proje yaratma + label_subproject_new: Yeni alt proje + text_own_membership_delete_confirmation: "Projeyi daha sonra düzenleyememenize sebep olacak bazı yetkilerinizi kaldırmak üzeresiniz.\nDevam etmek istediğinize emin misiniz?" + label_close_versions: Tamamlanmış sürümleri kapat + label_board_sticky: Yapışkan + label_board_locked: Kilitli + permission_export_wiki_pages: Wiki sayfalarını dışarı aktar + setting_cache_formatted_text: Biçimlendirilmiş metni önbelleğe al + permission_manage_project_activities: Proje etkinliklerini yönetme + error_unable_delete_issue_status: İş durumu silinemiyor + label_profile: Profil + permission_manage_subtasks: Alt işleri yönetme + field_parent_issue: Üst iş + label_subtask_plural: Alt işler + label_project_copy_notifications: Proje kopyalaması esnasında bilgilendirme e-postaları gönder + error_can_not_delete_custom_field: Özel alan silinemiyor + error_unable_to_connect: Bağlanılamıyor (%{value}) + error_can_not_remove_role: Bu rol kullanımda olduğundan silinemez. + error_can_not_delete_tracker: Bu iş tipi içerisinde iş barındırdığından silinemiyor. + field_principal: Temel + label_my_page_block: Kişisel sayfa bloğum + notice_failed_to_save_members: "Üyeler kaydedilemiyor: %{errors}." + text_zoom_out: Uzaklaş + text_zoom_in: Yakınlaş + notice_unable_delete_time_entry: Zaman kayıt girdisi silinemiyor. + label_overall_spent_time: Toplam harcanan zaman + field_time_entries: Zaman Kayıtları + project_module_gantt: İş-Zaman Çizelgesi + project_module_calendar: Takvim + button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" + field_text: Metin alanı + label_user_mail_option_only_owner: Sadece sahibi olduğum şeyler için + setting_default_notification_option: Varsayılan bildirim seçeneği + label_user_mail_option_only_my_events: Sadece takip ettiğim ya da içinde olduğum şeyler için + label_user_mail_option_only_assigned: Sadece bana atanan şeyler için + label_user_mail_option_none: Hiç bir şey için + field_member_of_group: Atananın grubu + field_assigned_to_role: Atananın rolü + notice_not_authorized_archived_project: Erişmeye çalıştığınız proje arşive kaldırılmış. + label_principal_search: "Kullanıcı ya da grup ara:" + label_user_search: "Kullanıcı ara:" + field_visible: Görünür + setting_emails_header: "E-Posta başlığı" + setting_commit_logtime_activity_id: Kaydedilen zaman için etkinlik + text_time_logged_by_changeset: Applied in changeset %{value}. + setting_commit_logtime_enabled: Zaman kaydını etkinleştir + notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) + setting_gantt_items_limit: İş-Zaman çizelgesinde gösterilecek en fazla öğe sayısı + field_warn_on_leaving_unsaved: Kaydedilmemiş metin bulunan bir sayfadan çıkarken beni uyar + text_warn_on_leaving_unsaved: Bu sayfada terkettiğiniz takdirde kaybolacak kaydedilmemiş metinler var. + label_my_queries: Özel sorgularım + text_journal_changed_no_detail: "%{label} güncellendi" + label_news_comment_added: Bir habere yorum eklendi + button_expand_all: Tümünü genişlet + button_collapse_all: Tümünü daralt + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + label_bulk_edit_selected_time_entries: Seçilen zaman kayıtlarını toplu olarak düzenle + text_time_entries_destroy_confirmation: Seçilen zaman kaydını/kayıtlarını silmek istediğinize emin misiniz? + label_role_anonymous: Anonim + label_role_non_member: Üye Değil + label_issue_note_added: Not eklendi + label_issue_status_updated: Durum güncellendi + label_issue_priority_updated: Öncelik güncellendi + label_issues_visibility_own: Issues created by or assigned to the user + field_issues_visibility: İşlerin görünürlüğü + label_issues_visibility_all: Tüm işler + permission_set_own_issues_private: Set own issues public or private + field_is_private: Özel + permission_set_issues_private: İşleri özel ya da genel olarak işaretleme + label_issues_visibility_public: Özel olmayan tüm işler + text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). + field_commit_logs_encoding: Commit messages encoding + field_scm_path_encoding: Yol kodlaması(encoding) + text_scm_path_encoding_note: "Varsayılan: UTF-8" + field_path_to_repository: Path to repository + field_root_directory: Ana dizin + field_cvs_module: Modül + field_cvsroot: CVSROOT + text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) + text_scm_command: Komut + text_scm_command_version: Sürüm + label_git_report_last_commit: Report last commit for files and directories + notice_issue_successful_create: Issue %{id} created. + label_between: between + setting_issue_group_assignment: Allow issue assignment to groups + label_diff: diff + text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) + description_query_sort_criteria_direction: Sort direction + description_project_scope: Search scope + description_filter: Filter + description_user_mail_notification: Mail notification settings + description_date_from: Enter start date + description_message_content: Message content + description_available_columns: Available Columns + description_date_range_interval: Choose range by selecting start and end date + description_issue_category_reassign: Choose issue category + description_search: Searchfield + description_notes: Notes + description_date_range_list: Choose range from list + description_choose_project: Projects + description_date_to: Enter end date + description_query_sort_criteria_attribute: Sort attribute + description_wiki_subpages_reassign: Choose new parent page + description_selected_columns: Selected Columns + label_parent_revision: Parent + label_child_revision: Child + error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. + setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues + button_edit_section: Edit this section + setting_repositories_encodings: Attachments and repositories encodings + description_all_columns: All Columns + button_export: Export + label_export_options: "%{export_format} export options" + error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) + notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." + label_x_issues: + zero: 0 İş + one: 1 İş + other: "%{count} İşler" + label_repository_new: New repository + field_repository_is_default: Main repository + label_copy_attachments: Copy attachments + label_item_position: "%{position}/%{count}" + label_completed_versions: Completed versions + text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. + field_multiple: Multiple values + setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed + text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes + text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) + notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. + text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} + permission_manage_related_issues: Manage related issues + field_auth_source_ldap_filter: LDAP filter + label_search_for_watchers: Search for watchers to add + notice_account_deleted: Your account has been permanently deleted. + setting_unsubscribe: Allow users to delete their own account + button_delete_my_account: Delete my account + text_account_destroy_confirmation: |- + Are you sure you want to proceed? + Your account will be permanently deleted, with no way to reactivate it. + error_session_expired: Your session has expired. Please login again. + text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." + setting_session_lifetime: Session maximum lifetime + setting_session_timeout: Session inactivity timeout + label_session_expiration: Session expiration + permission_close_project: Close / reopen the project + label_show_closed_projects: View closed projects + button_close: Close + button_reopen: Reopen + project_status_active: active + project_status_closed: closed + project_status_archived: archived + text_project_closed: This project is closed and read-only. + notice_user_successful_create: User %{id} created. + field_core_fields: Standard fields + field_timeout: Timeout (in seconds) + setting_thumbnails_enabled: Display attachment thumbnails + setting_thumbnails_size: Thumbnails size (in pixels) + label_status_transitions: Status transitions + label_fields_permissions: Fields permissions + label_readonly: Read-only + label_required: Required + text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
    Once saved, the identifier cannot be changed. + field_board_parent: Parent forum + label_attribute_of_project: Project's %{name} + label_attribute_of_author: Author's %{name} + label_attribute_of_assigned_to: Assignee's %{name} + label_attribute_of_fixed_version: Target version's %{name} + label_copy_subtasks: Copy subtasks + label_copied_to: copied to + label_copied_from: copied from + label_any_issues_in_project: any issues in project + label_any_issues_not_in_project: any issues not in project + field_private_notes: Private notes + permission_view_private_notes: View private notes + permission_set_notes_private: Set notes as private + label_no_issues_in_project: no issues in project + label_any: Hepsi + label_last_n_weeks: last %{count} weeks + setting_cross_project_subtasks: Allow cross-project subtasks + label_cross_project_descendants: Alt projeler ile + label_cross_project_tree: Proje ağacı ile + label_cross_project_hierarchy: Proje hiyerarşisi ile + label_cross_project_system: Tüm projeler ile + button_hide: Hide + setting_non_working_week_days: Non-working days + label_in_the_next_days: in the next + label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Toplam + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7c/7c8477332553f0c9a3e3cf4a6ba241f56066f91e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/7c/7c8477332553f0c9a3e3cf4a6ba241f56066f91e.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,323 @@ +# Redmine - project management software +# Copyright (C) 2006 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class Setup < ActiveRecord::Migration + + class User < ActiveRecord::Base; end + # model removed + class Permission < ActiveRecord::Base; end + + def self.up + create_table "attachments", :force => true do |t| + t.column "container_id", :integer, :default => 0, :null => false + t.column "container_type", :string, :limit => 30, :default => "", :null => false + t.column "filename", :string, :default => "", :null => false + t.column "disk_filename", :string, :default => "", :null => false + t.column "filesize", :integer, :default => 0, :null => false + t.column "content_type", :string, :limit => 60, :default => "" + t.column "digest", :string, :limit => 40, :default => "", :null => false + t.column "downloads", :integer, :default => 0, :null => false + t.column "author_id", :integer, :default => 0, :null => false + t.column "created_on", :timestamp + end + + create_table "auth_sources", :force => true do |t| + t.column "type", :string, :limit => 30, :default => "", :null => false + t.column "name", :string, :limit => 60, :default => "", :null => false + t.column "host", :string, :limit => 60 + t.column "port", :integer + t.column "account", :string, :limit => 60 + t.column "account_password", :string, :limit => 60 + t.column "base_dn", :string, :limit => 255 + t.column "attr_login", :string, :limit => 30 + t.column "attr_firstname", :string, :limit => 30 + t.column "attr_lastname", :string, :limit => 30 + t.column "attr_mail", :string, :limit => 30 + t.column "onthefly_register", :boolean, :default => false, :null => false + end + + create_table "custom_fields", :force => true do |t| + t.column "type", :string, :limit => 30, :default => "", :null => false + t.column "name", :string, :limit => 30, :default => "", :null => false + t.column "field_format", :string, :limit => 30, :default => "", :null => false + t.column "possible_values", :text + t.column "regexp", :string, :default => "" + t.column "min_length", :integer, :default => 0, :null => false + t.column "max_length", :integer, :default => 0, :null => false + t.column "is_required", :boolean, :default => false, :null => false + t.column "is_for_all", :boolean, :default => false, :null => false + end + + create_table "custom_fields_projects", :id => false, :force => true do |t| + t.column "custom_field_id", :integer, :default => 0, :null => false + t.column "project_id", :integer, :default => 0, :null => false + end + + create_table "custom_fields_trackers", :id => false, :force => true do |t| + t.column "custom_field_id", :integer, :default => 0, :null => false + t.column "tracker_id", :integer, :default => 0, :null => false + end + + create_table "custom_values", :force => true do |t| + t.column "customized_type", :string, :limit => 30, :default => "", :null => false + t.column "customized_id", :integer, :default => 0, :null => false + t.column "custom_field_id", :integer, :default => 0, :null => false + t.column "value", :text + end + + create_table "documents", :force => true do |t| + t.column "project_id", :integer, :default => 0, :null => false + t.column "category_id", :integer, :default => 0, :null => false + t.column "title", :string, :limit => 60, :default => "", :null => false + t.column "description", :text + t.column "created_on", :timestamp + end + + add_index "documents", ["project_id"], :name => "documents_project_id" + + create_table "enumerations", :force => true do |t| + t.column "opt", :string, :limit => 4, :default => "", :null => false + t.column "name", :string, :limit => 30, :default => "", :null => false + end + + create_table "issue_categories", :force => true do |t| + t.column "project_id", :integer, :default => 0, :null => false + t.column "name", :string, :limit => 30, :default => "", :null => false + end + + add_index "issue_categories", ["project_id"], :name => "issue_categories_project_id" + + create_table "issue_histories", :force => true do |t| + t.column "issue_id", :integer, :default => 0, :null => false + t.column "status_id", :integer, :default => 0, :null => false + t.column "author_id", :integer, :default => 0, :null => false + t.column "notes", :text + t.column "created_on", :timestamp + end + + add_index "issue_histories", ["issue_id"], :name => "issue_histories_issue_id" + + create_table "issue_statuses", :force => true do |t| + t.column "name", :string, :limit => 30, :default => "", :null => false + t.column "is_closed", :boolean, :default => false, :null => false + t.column "is_default", :boolean, :default => false, :null => false + t.column "html_color", :string, :limit => 6, :default => "FFFFFF", :null => false + end + + create_table "issues", :force => true do |t| + t.column "tracker_id", :integer, :default => 0, :null => false + t.column "project_id", :integer, :default => 0, :null => false + t.column "subject", :string, :default => "", :null => false + t.column "description", :text + t.column "due_date", :date + t.column "category_id", :integer + t.column "status_id", :integer, :default => 0, :null => false + t.column "assigned_to_id", :integer + t.column "priority_id", :integer, :default => 0, :null => false + t.column "fixed_version_id", :integer + t.column "author_id", :integer, :default => 0, :null => false + t.column "lock_version", :integer, :default => 0, :null => false + t.column "created_on", :timestamp + t.column "updated_on", :timestamp + end + + add_index "issues", ["project_id"], :name => "issues_project_id" + + create_table "members", :force => true do |t| + t.column "user_id", :integer, :default => 0, :null => false + t.column "project_id", :integer, :default => 0, :null => false + t.column "role_id", :integer, :default => 0, :null => false + t.column "created_on", :timestamp + end + + create_table "news", :force => true do |t| + t.column "project_id", :integer + t.column "title", :string, :limit => 60, :default => "", :null => false + t.column "summary", :string, :limit => 255, :default => "" + t.column "description", :text + t.column "author_id", :integer, :default => 0, :null => false + t.column "created_on", :timestamp + end + + add_index "news", ["project_id"], :name => "news_project_id" + + create_table "permissions", :force => true do |t| + t.column "controller", :string, :limit => 30, :default => "", :null => false + t.column "action", :string, :limit => 30, :default => "", :null => false + t.column "description", :string, :limit => 60, :default => "", :null => false + t.column "is_public", :boolean, :default => false, :null => false + t.column "sort", :integer, :default => 0, :null => false + t.column "mail_option", :boolean, :default => false, :null => false + t.column "mail_enabled", :boolean, :default => false, :null => false + end + + create_table "permissions_roles", :id => false, :force => true do |t| + t.column "permission_id", :integer, :default => 0, :null => false + t.column "role_id", :integer, :default => 0, :null => false + end + + add_index "permissions_roles", ["role_id"], :name => "permissions_roles_role_id" + + create_table "projects", :force => true do |t| + t.column "name", :string, :limit => 30, :default => "", :null => false + t.column "description", :string, :default => "", :null => false + t.column "homepage", :string, :limit => 60, :default => "" + t.column "is_public", :boolean, :default => true, :null => false + t.column "parent_id", :integer + t.column "projects_count", :integer, :default => 0 + t.column "created_on", :timestamp + t.column "updated_on", :timestamp + end + + create_table "roles", :force => true do |t| + t.column "name", :string, :limit => 30, :default => "", :null => false + end + + create_table "tokens", :force => true do |t| + t.column "user_id", :integer, :default => 0, :null => false + t.column "action", :string, :limit => 30, :default => "", :null => false + t.column "value", :string, :limit => 40, :default => "", :null => false + t.column "created_on", :datetime, :null => false + end + + create_table "trackers", :force => true do |t| + t.column "name", :string, :limit => 30, :default => "", :null => false + t.column "is_in_chlog", :boolean, :default => false, :null => false + end + + create_table "users", :force => true do |t| + t.column "login", :string, :limit => 30, :default => "", :null => false + t.column "hashed_password", :string, :limit => 40, :default => "", :null => false + t.column "firstname", :string, :limit => 30, :default => "", :null => false + t.column "lastname", :string, :limit => 30, :default => "", :null => false + t.column "mail", :string, :limit => 60, :default => "", :null => false + t.column "mail_notification", :boolean, :default => true, :null => false + t.column "admin", :boolean, :default => false, :null => false + t.column "status", :integer, :default => 1, :null => false + t.column "last_login_on", :datetime + t.column "language", :string, :limit => 2, :default => "" + t.column "auth_source_id", :integer + t.column "created_on", :timestamp + t.column "updated_on", :timestamp + end + + create_table "versions", :force => true do |t| + t.column "project_id", :integer, :default => 0, :null => false + t.column "name", :string, :limit => 30, :default => "", :null => false + t.column "description", :string, :default => "" + t.column "effective_date", :date + t.column "created_on", :timestamp + t.column "updated_on", :timestamp + end + + add_index "versions", ["project_id"], :name => "versions_project_id" + + create_table "workflows", :force => true do |t| + t.column "tracker_id", :integer, :default => 0, :null => false + t.column "old_status_id", :integer, :default => 0, :null => false + t.column "new_status_id", :integer, :default => 0, :null => false + t.column "role_id", :integer, :default => 0, :null => false + end + + # project + Permission.create :controller => "projects", :action => "show", :description => "label_overview", :sort => 100, :is_public => true + Permission.create :controller => "projects", :action => "changelog", :description => "label_change_log", :sort => 105, :is_public => true + Permission.create :controller => "reports", :action => "issue_report", :description => "label_report_plural", :sort => 110, :is_public => true + Permission.create :controller => "projects", :action => "settings", :description => "label_settings", :sort => 150 + Permission.create :controller => "projects", :action => "edit", :description => "button_edit", :sort => 151 + # members + Permission.create :controller => "projects", :action => "list_members", :description => "button_list", :sort => 200, :is_public => true + Permission.create :controller => "projects", :action => "add_member", :description => "button_add", :sort => 220 + Permission.create :controller => "members", :action => "edit", :description => "button_edit", :sort => 221 + Permission.create :controller => "members", :action => "destroy", :description => "button_delete", :sort => 222 + # versions + Permission.create :controller => "projects", :action => "add_version", :description => "button_add", :sort => 320 + Permission.create :controller => "versions", :action => "edit", :description => "button_edit", :sort => 321 + Permission.create :controller => "versions", :action => "destroy", :description => "button_delete", :sort => 322 + # issue categories + Permission.create :controller => "projects", :action => "add_issue_category", :description => "button_add", :sort => 420 + Permission.create :controller => "issue_categories", :action => "edit", :description => "button_edit", :sort => 421 + Permission.create :controller => "issue_categories", :action => "destroy", :description => "button_delete", :sort => 422 + # issues + Permission.create :controller => "projects", :action => "list_issues", :description => "button_list", :sort => 1000, :is_public => true + Permission.create :controller => "projects", :action => "export_issues_csv", :description => "label_export_csv", :sort => 1001, :is_public => true + Permission.create :controller => "issues", :action => "show", :description => "button_view", :sort => 1005, :is_public => true + Permission.create :controller => "issues", :action => "download", :description => "button_download", :sort => 1010, :is_public => true + Permission.create :controller => "projects", :action => "add_issue", :description => "button_add", :sort => 1050, :mail_option => 1, :mail_enabled => 1 + Permission.create :controller => "issues", :action => "edit", :description => "button_edit", :sort => 1055 + Permission.create :controller => "issues", :action => "change_status", :description => "label_change_status", :sort => 1060, :mail_option => 1, :mail_enabled => 1 + Permission.create :controller => "issues", :action => "destroy", :description => "button_delete", :sort => 1065 + Permission.create :controller => "issues", :action => "add_attachment", :description => "label_attachment_new", :sort => 1070 + Permission.create :controller => "issues", :action => "destroy_attachment", :description => "label_attachment_delete", :sort => 1075 + # news + Permission.create :controller => "projects", :action => "list_news", :description => "button_list", :sort => 1100, :is_public => true + Permission.create :controller => "news", :action => "show", :description => "button_view", :sort => 1101, :is_public => true + Permission.create :controller => "projects", :action => "add_news", :description => "button_add", :sort => 1120 + Permission.create :controller => "news", :action => "edit", :description => "button_edit", :sort => 1121 + Permission.create :controller => "news", :action => "destroy", :description => "button_delete", :sort => 1122 + # documents + Permission.create :controller => "projects", :action => "list_documents", :description => "button_list", :sort => 1200, :is_public => true + Permission.create :controller => "documents", :action => "show", :description => "button_view", :sort => 1201, :is_public => true + Permission.create :controller => "documents", :action => "download", :description => "button_download", :sort => 1202, :is_public => true + Permission.create :controller => "projects", :action => "add_document", :description => "button_add", :sort => 1220 + Permission.create :controller => "documents", :action => "edit", :description => "button_edit", :sort => 1221 + Permission.create :controller => "documents", :action => "destroy", :description => "button_delete", :sort => 1222 + Permission.create :controller => "documents", :action => "add_attachment", :description => "label_attachment_new", :sort => 1223 + Permission.create :controller => "documents", :action => "destroy_attachment", :description => "label_attachment_delete", :sort => 1224 + # files + Permission.create :controller => "projects", :action => "list_files", :description => "button_list", :sort => 1300, :is_public => true + Permission.create :controller => "versions", :action => "download", :description => "button_download", :sort => 1301, :is_public => true + Permission.create :controller => "projects", :action => "add_file", :description => "button_add", :sort => 1320 + Permission.create :controller => "versions", :action => "destroy_file", :description => "button_delete", :sort => 1322 + + # create default administrator account + user = User.create :login => "admin", + :hashed_password => "d033e22ae348aeb5660fc2140aec35850c4da997", + :admin => true, + :firstname => "Redmine", + :lastname => "Admin", + :mail => "admin@example.net", + :mail_notification => true, + :status => 1 + end + + def self.down + drop_table :attachments + drop_table :auth_sources + drop_table :custom_fields + drop_table :custom_fields_projects + drop_table :custom_fields_trackers + drop_table :custom_values + drop_table :documents + drop_table :enumerations + drop_table :issue_categories + drop_table :issue_histories + drop_table :issue_statuses + drop_table :issues + drop_table :members + drop_table :news + drop_table :permissions + drop_table :permissions_roles + drop_table :projects + drop_table :roles + drop_table :trackers + drop_table :tokens + drop_table :users + drop_table :versions + drop_table :workflows + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7c/7c8eae02c98b193159d580b9779b53a3e1041ed2.svn-base --- a/.svn/pristine/7c/7c8eae02c98b193159d580b9779b53a3e1041ed2.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -
    -
      - <%= render_menu :admin_menu %> -
    -
    diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7c/7ca8a329852a4cbc7fbc376618c93059ca39014d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/7c/7ca8a329852a4cbc7fbc376618c93059ca39014d.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,3 @@ +# English strings go here for Rails i18n +en: + # my_label: "My label" diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7d/7d062943a639ef0006481fe9e87f6780c25c226e.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/7d/7d062943a639ef0006481fe9e87f6780c25c226e.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,66 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class RoutingMessagesTest < ActionController::IntegrationTest + def test_messages + assert_routing( + { :method => 'get', :path => "/boards/22/topics/2" }, + { :controller => 'messages', :action => 'show', :id => '2', + :board_id => '22' } + ) + assert_routing( + { :method => 'get', :path => "/boards/lala/topics/new" }, + { :controller => 'messages', :action => 'new', :board_id => 'lala' } + ) + assert_routing( + { :method => 'get', :path => "/boards/lala/topics/22/edit" }, + { :controller => 'messages', :action => 'edit', :id => '22', + :board_id => 'lala' } + ) + assert_routing( + { :method => 'post', :path => "/boards/lala/topics/quote/22" }, + { :controller => 'messages', :action => 'quote', :id => '22', + :board_id => 'lala' } + ) + assert_routing( + { :method => 'post', :path => "/boards/lala/topics/new" }, + { :controller => 'messages', :action => 'new', :board_id => 'lala' } + ) + assert_routing( + { :method => 'post', :path => "/boards/lala/topics/preview" }, + { :controller => 'messages', :action => 'preview', + :board_id => 'lala' } + ) + assert_routing( + { :method => 'post', :path => "/boards/lala/topics/22/edit" }, + { :controller => 'messages', :action => 'edit', :id => '22', + :board_id => 'lala' } + ) + assert_routing( + { :method => 'post', :path => "/boards/22/topics/555/replies" }, + { :controller => 'messages', :action => 'reply', :id => '555', + :board_id => '22' } + ) + assert_routing( + { :method => 'post', :path => "/boards/22/topics/555/destroy" }, + { :controller => 'messages', :action => 'destroy', :id => '555', + :board_id => '22' } + ) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7d/7d0c82a8b17bf23c8cab9114300db25398956dda.svn-base --- a/.svn/pristine/7d/7d0c82a8b17bf23c8cab9114300db25398956dda.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,70 +0,0 @@ -@import url(../../../stylesheets/application.css); - -body, #wrapper { background-color:#EEEEEE; } -#header, #top-menu { margin: 0px 10px 0px 11px; } -#main { background: #EEEEEE; margin: 8px 10px 0px 10px; } -#content, #main.nosidebar #content { background: #fff; border-right: 1px solid #bbb; border-bottom: 1px solid #bbb; border-left: 1px solid #d7d7d7; border-top: 1px solid #d7d7d7; } -#footer { background-color:#EEEEEE; border: 0px; } - -/* Headers */ -h2, h3, h4, .wiki h1, .wiki h2, .wiki h3 {border-bottom: 0px;} - -/* Menu */ -#main-menu li a { background-color: #507AAA; font-weight: bold;} -#main-menu li a:hover { background: #507AAA; text-decoration: underline; } -#main-menu li a.selected, #main-menu li a.selected:hover { background-color:#EEEEEE; } - -/* Tables */ -table.list tbody td, table.list tbody tr:hover td { border: solid 1px #d7d7d7; } -table.list thead th { - border-width: 1px; - border-style: solid; - border-top-color: #d7d7d7; - border-right-color: #d7d7d7; - border-left-color: #d7d7d7; - border-bottom-color: #999999; -} - -/* Issues grid styles by priorities (provided by Wynn Netherland) */ -table.list tr.issue a { color: #666; } - -tr.odd.priority-5, table.list tbody tr.odd.priority-5:hover { color: #900; font-weight: bold; } -tr.odd.priority-5 { background: #ffc4c4; } -tr.even.priority-5, table.list tbody tr.even.priority-5:hover { color: #900; font-weight: bold; } -tr.even.priority-5 { background: #ffd4d4; } -tr.priority-5 a, tr.priority-5:hover a { color: #900; } -tr.odd.priority-5 td, tr.even.priority-5 td { border-color: #ffb4b4; } - -tr.odd.priority-4, table.list tbody tr.odd.priority-4:hover { color: #900; } -tr.odd.priority-4 { background: #ffc4c4; } -tr.even.priority-4, table.list tbody tr.even.priority-4:hover { color: #900; } -tr.even.priority-4 { background: #ffd4d4; } -tr.priority-4 a { color: #900; } -tr.odd.priority-4 td, tr.even.priority-4 td { border-color: #ffb4b4; } - -tr.odd.priority-3, table.list tbody tr.odd.priority-3:hover { color: #900; } -tr.odd.priority-3 { background: #fee; } -tr.even.priority-3, table.list tbody tr.even.priority-3:hover { color: #900; } -tr.even.priority-3 { background: #fff2f2; } -tr.priority-3 a { color: #900; } -tr.odd.priority-3 td, tr.even.priority-3 td { border-color: #fcc; } - -tr.odd.priority-1, table.list tbody tr.odd.priority-1:hover { color: #559; } -tr.odd.priority-1 { background: #eaf7ff; } -tr.even.priority-1, table.list tbody tr.even.priority-1:hover { color: #559; } -tr.even.priority-1 { background: #f2faff; } -tr.priority-1 a { color: #559; } -tr.odd.priority-1 td, tr.even.priority-1 td { border-color: #add7f3; } - -/* Buttons */ -input[type="button"], input[type="submit"], input[type="reset"] { background-color: #f2f2f2; color: #222222; border: 1px outset #cccccc; } -input[type="button"]:hover, input[type="submit"]:hover, input[type="reset"]:hover { background-color: #ccccbb; } - -/* Fields */ -input[type="text"], input[type="password"], textarea, select { padding: 2px; border: 1px solid #d7d7d7; } -input[type="text"], input[type="password"] { padding: 3px; } -input[type="text"]:focus, input[type="password"]:focus, textarea:focus, select:focus { border: 1px solid #888866; } -option { border-bottom: 1px dotted #d7d7d7; } - -/* Misc */ -.box { background-color: #fcfcfc; } diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7d/7d347f5736383447d929cfa493b44727c73c536a.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/7d/7d347f5736383447d929cfa493b44727c73c536a.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,67 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../../../../test_helper', __FILE__) + +class Redmine::Views::Builders::XmlTest < ActiveSupport::TestCase + + def test_hash + assert_xml_output('Ryan32') do |b| + b.person do + b.name 'Ryan' + b.age 32 + end + end + end + + def test_array + assert_xml_output('') do |b| + b.array :books do |b| + b.book :title => 'Book 1' + b.book :title => 'Book 2' + end + end + end + + def test_array_with_content_tags + assert_xml_output('Book 1Book 2') do |b| + b.array :books do |b| + b.book 'Book 1', :author => 'B. Smith' + b.book 'Book 2', :author => 'G. Cooper' + end + end + end + + def test_nested_arrays + assert_xml_output('B. SmithG. Cooper') do |b| + b.array :books do |books| + books.book do |book| + book.array :authors do |authors| + authors.author 'B. Smith' + authors.author 'G. Cooper' + end + end + end + end + end + + def assert_xml_output(expected, &block) + builder = Redmine::Views::Builders::Xml.new(ActionDispatch::TestRequest.new, ActionDispatch::TestResponse.new) + block.call(builder) + assert_equal('' + expected, builder.output) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7d/7d3ac76c84dee77bab1ef37f05ae263ad699e2b8.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/7d/7d3ac76c84dee77bab1ef37f05ae263ad699e2b8.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,264 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class RepositoriesControllerTest < ActionController::TestCase + fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules, + :repositories, :issues, :issue_statuses, :changesets, :changes, + :issue_categories, :enumerations, :custom_fields, :custom_values, :trackers + + def setup + User.current = nil + end + + def test_new + @request.session[:user_id] = 1 + get :new, :project_id => 'subproject1' + assert_response :success + assert_template 'new' + assert_kind_of Repository::Subversion, assigns(:repository) + assert assigns(:repository).new_record? + assert_tag 'input', :attributes => {:name => 'repository[url]', :disabled => nil} + end + + def test_new_should_propose_enabled_scm_only + @request.session[:user_id] = 1 + with_settings :enabled_scm => ['Mercurial', 'Git'] do + get :new, :project_id => 'subproject1' + end + assert_response :success + assert_template 'new' + assert_kind_of Repository::Mercurial, assigns(:repository) + assert_tag 'select', :attributes => {:name => 'repository_scm'}, + :children => {:count => 3} + assert_tag 'select', :attributes => {:name => 'repository_scm'}, + :child => {:tag => 'option', :attributes => {:value => 'Mercurial', :selected => 'selected'}} + assert_tag 'select', :attributes => {:name => 'repository_scm'}, + :child => {:tag => 'option', :attributes => {:value => 'Git', :selected => nil}} + end + + def test_create + @request.session[:user_id] = 1 + assert_difference 'Repository.count' do + post :create, :project_id => 'subproject1', + :repository_scm => 'Subversion', + :repository => {:url => 'file:///test', :is_default => '1', :identifier => ''} + end + assert_response 302 + repository = Repository.first(:order => 'id DESC') + assert_kind_of Repository::Subversion, repository + assert_equal 'file:///test', repository.url + end + + def test_create_with_failure + @request.session[:user_id] = 1 + assert_no_difference 'Repository.count' do + post :create, :project_id => 'subproject1', + :repository_scm => 'Subversion', + :repository => {:url => 'invalid'} + end + assert_response :success + assert_template 'new' + assert_kind_of Repository::Subversion, assigns(:repository) + assert assigns(:repository).new_record? + end + + def test_edit + @request.session[:user_id] = 1 + get :edit, :id => 11 + assert_response :success + assert_template 'edit' + assert_equal Repository.find(11), assigns(:repository) + assert_tag 'input', :attributes => {:name => 'repository[url]', :value => 'svn://localhost/test', :disabled => 'disabled'} + end + + def test_update + @request.session[:user_id] = 1 + put :update, :id => 11, :repository => {:password => 'test_update'} + assert_response 302 + assert_equal 'test_update', Repository.find(11).password + end + + def test_update_with_failure + @request.session[:user_id] = 1 + put :update, :id => 11, :repository => {:password => 'x'*260} + assert_response :success + assert_template 'edit' + assert_equal Repository.find(11), assigns(:repository) + end + + def test_destroy + @request.session[:user_id] = 1 + assert_difference 'Repository.count', -1 do + delete :destroy, :id => 11 + end + assert_response 302 + assert_nil Repository.find_by_id(11) + end + + def test_revisions + get :revisions, :id => 1 + assert_response :success + assert_template 'revisions' + assert_equal Repository.find(10), assigns(:repository) + assert_not_nil assigns(:changesets) + end + + def test_revisions_for_other_repository + repository = Repository::Subversion.create!(:project_id => 1, :identifier => 'foo', :url => 'file:///foo') + + get :revisions, :id => 1, :repository_id => 'foo' + assert_response :success + assert_template 'revisions' + assert_equal repository, assigns(:repository) + assert_not_nil assigns(:changesets) + end + + def test_revisions_for_invalid_repository + get :revisions, :id => 1, :repository_id => 'foo' + assert_response 404 + end + + def test_revision + get :revision, :id => 1, :rev => 1 + assert_response :success + assert_not_nil assigns(:changeset) + assert_equal "1", assigns(:changeset).revision + end + + def test_revision_should_not_change_the_project_menu_link + get :revision, :id => 1, :rev => 1 + assert_response :success + + assert_tag 'a', :attributes => {:href => '/projects/ecookbook/repository', :class => /repository/}, + :ancestor => {:attributes => {:id => 'main-menu'}} + 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 => '/projects/ecookbook/repository/revisions/0'} + } + assert_tag :tag => "div", :attributes => { :class => "contextual" }, + :child => { :tag => "a", :attributes => { :href => '/projects/ecookbook/repository/revisions/2'} + } + end + + def test_add_related_issue + @request.session[:user_id] = 2 + assert_difference 'Changeset.find(103).issues.size' do + xhr :post, :add_related_issue, :id => 1, :rev => 4, :issue_id => 2, :format => 'js' + assert_response :success + assert_template 'add_related_issue' + assert_equal 'text/javascript', response.content_type + end + assert_equal [2], Changeset.find(103).issue_ids + assert_include 'related-issues', response.body + assert_include 'Feature request #2', response.body + end + + def test_add_related_issue_with_invalid_issue_id + @request.session[:user_id] = 2 + assert_no_difference 'Changeset.find(103).issues.size' do + xhr :post, :add_related_issue, :id => 1, :rev => 4, :issue_id => 9999, :format => 'js' + assert_response :success + assert_template 'add_related_issue' + assert_equal 'text/javascript', response.content_type + end + assert_include 'alert("Issue is invalid")', response.body + end + + def test_remove_related_issue + Changeset.find(103).issues << Issue.find(1) + Changeset.find(103).issues << Issue.find(2) + + @request.session[:user_id] = 2 + assert_difference 'Changeset.find(103).issues.size', -1 do + xhr :delete, :remove_related_issue, :id => 1, :rev => 4, :issue_id => 2, :format => 'js' + assert_response :success + assert_template 'remove_related_issue' + assert_equal 'text/javascript', response.content_type + end + assert_equal [1], Changeset.find(103).issue_ids + assert_include 'related-issue-2', response.body + end + + def test_graph_commits_per_month + # Make sure there's some data to display + latest = Project.find(1).repository.changesets.maximum(:commit_date) + assert_not_nil latest + Date.stubs(:today).returns(latest.to_date + 10) + + get :graph, :id => 1, :graph => 'commits_per_month' + assert_response :success + assert_equal 'image/svg+xml', @response.content_type + end + + def test_graph_commits_per_author + get :graph, :id => 1, :graph => 'commits_per_author' + assert_response :success + assert_equal 'image/svg+xml', @response.content_type + end + + def test_get_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 => 10 + 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_post_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 => 10, :committers => { '0' => ['foo', '2'], '1' => ['dlopper', '3']} + assert_response 302 + assert_equal User.find(2), c.reload.user + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7d/7d3cbe14fc84c9fc42efa7908095cf2d1fad9643.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/7d/7d3cbe14fc84c9fc42efa7908095cf2d1fad9643.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,94 @@ +var revisionGraph = null; + +function drawRevisionGraph(holder, commits_hash, graph_space) { + var XSTEP = 20, + CIRCLE_INROW_OFFSET = 10; + var commits_by_scmid = commits_hash, + commits = $.map(commits_by_scmid, function(val,i){return val;}); + var max_rdmid = commits.length - 1; + var commit_table_rows = $('table.changesets tr.changeset'); + + // create graph + if(revisionGraph != null) + revisionGraph.clear(); + else + revisionGraph = Raphael(holder); + + var top = revisionGraph.set(); + // init dimensions + var graph_x_offset = commit_table_rows.first().find('td').first().position().left - $(holder).position().left, + graph_y_offset = $(holder).position().top, + graph_right_side = graph_x_offset + (graph_space + 1) * XSTEP, + graph_bottom = commit_table_rows.last().position().top + commit_table_rows.last().height() - graph_y_offset; + + revisionGraph.setSize(graph_right_side, graph_bottom); + + // init colors + var colors = []; + Raphael.getColor.reset(); + for (var k = 0; k <= graph_space; k++) { + colors.push(Raphael.getColor()); + } + + var parent_commit; + var x, y, parent_x, parent_y; + var path, title; + var revision_dot_overlay; + $.each(commits, function(index, commit) { + if (!commit.hasOwnProperty("space")) + commit.space = 0; + + y = commit_table_rows.eq(max_rdmid - commit.rdmid).position().top - graph_y_offset + CIRCLE_INROW_OFFSET; + x = graph_x_offset + XSTEP / 2 + XSTEP * commit.space; + revisionGraph.circle(x, y, 3) + .attr({ + fill: colors[commit.space], + stroke: 'none' + }).toFront(); + // paths to parents + $.each(commit.parent_scmids, function(index, parent_scmid) { + parent_commit = commits_by_scmid[parent_scmid]; + if (parent_commit) { + if (!parent_commit.hasOwnProperty("space")) + parent_commit.space = 0; + + parent_y = commit_table_rows.eq(max_rdmid - parent_commit.rdmid).position().top - graph_y_offset + CIRCLE_INROW_OFFSET; + parent_x = graph_x_offset + XSTEP / 2 + XSTEP * parent_commit.space; + if (parent_commit.space == commit.space) { + // vertical path + path = revisionGraph.path([ + 'M', x, y, + 'V', parent_y]); + } else { + // path to a commit in a different branch (Bezier curve) + path = revisionGraph.path([ + 'M', x, y, + 'C', x, y, x, y + (parent_y - y) / 2, x + (parent_x - x) / 2, y + (parent_y - y) / 2, + 'C', x + (parent_x - x) / 2, y + (parent_y - y) / 2, parent_x, parent_y-(parent_y-y)/2, parent_x, parent_y]); + } + } else { + // vertical path ending at the bottom of the revisionGraph + path = revisionGraph.path([ + 'M', x, y, + 'V', graph_bottom]); + } + path.attr({stroke: colors[commit.space], "stroke-width": 1.5}).toBack(); + }); + revision_dot_overlay = revisionGraph.circle(x, y, 10); + revision_dot_overlay + .attr({ + fill: '#000', + opacity: 0, + cursor: 'pointer', + href: commit.href + }); + + if(commit.refs != null && commit.refs.length > 0) { + title = document.createElementNS(revisionGraph.canvas.namespaceURI, 'title'); + title.appendChild(document.createTextNode(commit.refs)); + revision_dot_overlay.node.appendChild(title); + } + top.push(revision_dot_overlay); + }); + top.toFront(); +}; diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7d/7d7a63dac05990ebb68fcd3fce0da9965a2d6f7a.svn-base --- a/.svn/pristine/7d/7d7a63dac05990ebb68fcd3fce0da9965a2d6f7a.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,282 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) -require 'repositories_controller' - -# Re-raise errors caught by the controller. -class RepositoriesController; def rescue_action(e) raise e end; end - -class RepositoriesCvsControllerTest < ActionController::TestCase - fixtures :projects, :users, :roles, :members, :member_roles, - :repositories, :enabled_modules - - REPOSITORY_PATH = Rails.root.join('tmp/test/cvs_repository').to_s - REPOSITORY_PATH.gsub!(/\//, "\\") if Redmine::Platform.mswin? - # CVS module - MODULE_NAME = 'test' - PRJ_ID = 3 - NUM_REV = 7 - - def setup - @controller = RepositoriesController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - Setting.default_language = 'en' - User.current = nil - - @project = Project.find(PRJ_ID) - @repository = Repository::Cvs.create(:project => Project.find(PRJ_ID), - :root_url => REPOSITORY_PATH, - :url => MODULE_NAME, - :log_encoding => 'UTF-8') - assert @repository - end - - if File.directory?(REPOSITORY_PATH) - def test_browse_root - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - get :show, :id => PRJ_ID - assert_response :success - assert_template 'show' - assert_not_nil assigns(:entries) - assert_equal 3, assigns(:entries).size - - entry = assigns(:entries).detect {|e| e.name == 'images'} - assert_equal 'dir', entry.kind - - entry = assigns(:entries).detect {|e| e.name == 'README'} - assert_equal 'file', entry.kind - - assert_not_nil assigns(:changesets) - assert assigns(:changesets).size > 0 - end - - def test_browse_directory - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - get :show, :id => PRJ_ID, :path => ['images'] - assert_response :success - assert_template 'show' - assert_not_nil assigns(:entries) - assert_equal ['add.png', 'delete.png', 'edit.png'], assigns(:entries).collect(&:name) - entry = assigns(:entries).detect {|e| e.name == 'edit.png'} - assert_not_nil entry - assert_equal 'file', entry.kind - assert_equal 'images/edit.png', entry.path - end - - def test_browse_at_given_revision - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - get :show, :id => PRJ_ID, :path => ['images'], :rev => 1 - assert_response :success - assert_template 'show' - assert_not_nil assigns(:entries) - assert_equal ['delete.png', 'edit.png'], assigns(:entries).collect(&:name) - end - - def test_entry - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - get :entry, :id => PRJ_ID, :path => ['sources', 'watchers_controller.rb'] - assert_response :success - assert_template 'entry' - assert_no_tag :tag => 'td', - :attributes => { :class => /line-code/}, - :content => /before_filter/ - end - - def test_entry_at_given_revision - # changesets must be loaded - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - get :entry, :id => PRJ_ID, :path => ['sources', 'watchers_controller.rb'], :rev => 2 - assert_response :success - assert_template 'entry' - # this line was removed in r3 - assert_tag :tag => 'td', - :attributes => { :class => /line-code/}, - :content => /before_filter/ - end - - def test_entry_not_found - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - get :entry, :id => PRJ_ID, :path => ['sources', 'zzz.c'] - assert_tag :tag => 'p', - :attributes => { :id => /errorExplanation/ }, - :content => /The entry or revision was not found in the repository/ - end - - def test_entry_download - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - get :entry, :id => PRJ_ID, :path => ['sources', 'watchers_controller.rb'], - :format => 'raw' - assert_response :success - end - - def test_directory_entry - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - get :entry, :id => PRJ_ID, :path => ['sources'] - assert_response :success - assert_template 'show' - assert_not_nil assigns(:entry) - assert_equal 'sources', assigns(:entry).name - end - - def test_diff - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - ['inline', 'sbs'].each do |dt| - get :diff, :id => PRJ_ID, :rev => 3, :type => dt - assert_response :success - assert_template 'diff' - assert_tag :tag => 'td', :attributes => { :class => 'line-code diff_out' }, - :content => /before_filter :require_login/ - assert_tag :tag => 'td', :attributes => { :class => 'line-code diff_in' }, - :content => /with one change/ - end - end - - def test_diff_new_files - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - ['inline', 'sbs'].each do |dt| - get :diff, :id => PRJ_ID, :rev => 1, :type => dt - assert_response :success - assert_template 'diff' - assert_tag :tag => 'td', :attributes => { :class => 'line-code diff_in' }, - :content => /watched.remove_watcher/ - assert_tag :tag => 'th', :attributes => { :class => 'filename' }, - :content => /test\/README/ - assert_tag :tag => 'th', :attributes => { :class => 'filename' }, - :content => /test\/images\/delete.png / - assert_tag :tag => 'th', :attributes => { :class => 'filename' }, - :content => /test\/images\/edit.png/ - assert_tag :tag => 'th', :attributes => { :class => 'filename' }, - :content => /test\/sources\/watchers_controller.rb/ - end - end - - def test_annotate - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - get :annotate, :id => PRJ_ID, :path => ['sources', 'watchers_controller.rb'] - assert_response :success - assert_template 'annotate' - # 1.1 line - assert_tag :tag => 'th', - :attributes => { :class => 'line-num' }, - :content => '18', - :sibling => { - :tag => 'td', - :attributes => { :class => 'revision' }, - :content => /1.1/, - :sibling => { - :tag => 'td', - :attributes => { :class => 'author' }, - :content => /LANG/ - } - } - # 1.2 line - assert_tag :tag => 'th', - :attributes => { :class => 'line-num' }, - :content => '32', - :sibling => { - :tag => 'td', - :attributes => { :class => 'revision' }, - :content => /1.2/, - :sibling => { - :tag => 'td', - :attributes => { :class => 'author' }, - :content => /LANG/ - } - } - end - - def test_destroy_valid_repository - @request.session[:user_id] = 1 # admin - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - - get :destroy, :id => PRJ_ID - assert_response 302 - @project.reload - assert_nil @project.repository - end - - def test_destroy_invalid_repository - @request.session[:user_id] = 1 # admin - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - - get :destroy, :id => PRJ_ID - assert_response 302 - @project.reload - assert_nil @project.repository - - @repository = Repository::Cvs.create( - :project => Project.find(PRJ_ID), - :root_url => "/invalid", - :url => MODULE_NAME, - :log_encoding => 'UTF-8' - ) - assert @repository - @repository.fetch_changesets - @project.reload - assert_equal 0, @repository.changesets.count - - get :destroy, :id => PRJ_ID - assert_response 302 - @project.reload - assert_nil @project.repository - end - else - puts "CVS test repository NOT FOUND. Skipping functional tests !!!" - def test_fake; assert true end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7d/7dd0539603ad9e25c32256869656657e4135a1c8.svn-base --- a/.svn/pristine/7d/7dd0539603ad9e25c32256869656657e4135a1c8.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -

    <%= link_to l(:label_group_plural), groups_path %> » <%= l(:label_group_new) %>

    - -<% labelled_form_for @group do |f| %> -<%= render :partial => 'form', :locals => { :f => f } %> -

    - <%= f.submit l(:button_create) %> - <%= f.submit l(:button_create_and_continue), :name => 'continue' %> -

    -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7d/7dd1b8397e24cced4bd7016e085055ff385a2960.svn-base --- a/.svn/pristine/7d/7dd1b8397e24cced4bd7016e085055ff385a2960.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -I18n.default_locale = 'en' -# Adds fallback to default locale for untranslated strings -I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks) - -require 'redmine' diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7e/7e362e9571e296e9ab312aca34fbe7dfde644cd2.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/7e/7e362e9571e296e9ab312aca34fbe7dfde644cd2.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,81 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +module RedminePmTest + class TestCase < ActiveSupport::TestCase + attr_reader :command, :response, :status, :username, :password + + # Cannot use transactional fixtures here: database + # will be accessed from Redmine.pm with its own connection + self.use_transactional_fixtures = false + + def test_dummy + end + + protected + + def assert_response(expected, msg=nil) + case expected + when :success + assert_equal 0, status, + (msg || "The command failed (exit: #{status}):\n #{command}\nOutput was:\n#{formatted_response}") + when :failure + assert_not_equal 0, status, + (msg || "The command succeed (exit: #{status}):\n #{command}\nOutput was:\n#{formatted_response}") + else + assert_equal expected, status, msg + end + end + + def assert_success(*args) + execute *args + assert_response :success + end + + def assert_failure(*args) + execute *args + assert_response :failure + end + + def with_credentials(username, password) + old_username, old_password = @username, @password + @username, @password = username, password + yield if block_given? + ensure + @username, @password = old_username, old_password + end + + def execute(*args) + @command = args.join(' ') + @status = nil + IO.popen("#{command} 2>&1") do |io| + @response = io.read + end + @status = $?.exitstatus + end + + def formatted_response + "#{'='*40}\n#{response}#{'='*40}" + end + + def random_filename + Redmine::Utils.random_hex(16) + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7e/7e3d39301783d138f7a6d49cba0462adeb117d57.svn-base --- a/.svn/pristine/7e/7e3d39301783d138f7a6d49cba0462adeb117d57.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,223 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) -require 'account_controller' - -# Re-raise errors caught by the controller. -class AccountController; def rescue_action(e) raise e end; end - -class AccountControllerTest < ActionController::TestCase - fixtures :users, :roles - - def setup - @controller = AccountController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - User.current = nil - end - - def test_login_should_redirect_to_back_url_param - # request.uri is "test.host" in test environment - post :login, :username => 'jsmith', :password => 'jsmith', :back_url => 'http%3A%2F%2Ftest.host%2Fissues%2Fshow%2F1' - assert_redirected_to '/issues/show/1' - end - - def test_login_should_not_redirect_to_another_host - post :login, :username => 'jsmith', :password => 'jsmith', :back_url => 'http%3A%2F%2Ftest.foo%2Ffake' - assert_redirected_to '/my/page' - end - - def test_login_with_wrong_password - post :login, :username => 'admin', :password => 'bad' - assert_response :success - assert_template 'login' - assert_tag 'div', - :attributes => { :class => "flash error" }, - :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_invalid_openid_provider - Setting.self_registration = '0' - Setting.openid = '1' - post :login, :openid_url => 'http;//openid.example.com/good_user' - assert_redirected_to home_url - end - - def test_login_with_openid_for_existing_non_active_user - Setting.self_registration = '2' - Setting.openid = '1' - existing_user = User.new(:firstname => 'Cool', - :lastname => 'User', - :mail => 'user@somedomain.com', - :identity_url => 'http://openid.example.com/good_user', - :status => User::STATUS_REGISTERED) - existing_user.login = 'cool_user' - assert existing_user.save! - - post :login, :openid_url => existing_user.identity_url - assert_redirected_to '/login' - 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 - - def test_setting_openid_should_return_true_when_set_to_true - Setting.openid = '1' - assert_equal true, Setting.openid? - end - - else - puts "Skipping openid tests." - end - - def test_logout - @request.session[:user_id] = 2 - get :logout - assert_redirected_to '/' - assert_nil @request.session[:user_id] - end - - context "GET #register" do - context "with self registration on" do - setup do - Setting.self_registration = '3' - get :register - end - - should_respond_with :success - should_render_template :register - should_assign_to :user - end - - context "with self registration off" do - setup do - Setting.self_registration = '0' - get :register - end - - should_redirect_to('/') { home_url } - end - end - - # See integration/account_test.rb for the full test - context "POST #register" do - context "with self registration on automatic" do - setup do - Setting.self_registration = '3' - post :register, :user => { - :login => 'register', - :password => 'test', - :password_confirmation => 'test', - :firstname => 'John', - :lastname => 'Doe', - :mail => 'register@example.com' - } - end - - should_respond_with :redirect - should_assign_to :user - should_redirect_to('my page') { {:controller => 'my', :action => 'account'} } - - should_create_a_new_user { User.last(:conditions => {:login => 'register'}) } - - should 'set the user status to active' do - user = User.last(:conditions => {:login => 'register'}) - assert user - assert_equal User::STATUS_ACTIVE, user.status - end - end - - context "with self registration off" do - setup do - Setting.self_registration = '0' - post :register - end - - should_redirect_to('/') { home_url } - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7e/7e3f9cdf448ee38697e8614780073ec72927018c.svn-base --- a/.svn/pristine/7e/7e3f9cdf448ee38697e8614780073ec72927018c.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,62 +0,0 @@ -<% roles = Role.find_all_givable %> -<% projects = Project.active.find(:all, :order => 'lft') %> - -
    -<% if @user.memberships.any? %> - - - - - - <%= call_hook(:view_users_memberships_table_header, :user => @user )%> - - - <% @user.memberships.each do |membership| %> - <% next if membership.new_record? %> - - - - - <%= call_hook(:view_users_memberships_table_row, :user => @user, :membership => membership, :roles => roles, :projects => projects )%> - - <% end; reset_cycle %> - -
    <%= l(:label_project) %><%= l(:label_role_plural) %>
    - <%= link_to_project membership.project %> - - <%=h membership.roles.sort.collect(&:to_s).join(', ') %> - <% remote_form_for(:membership, :url => { :action => 'edit_membership', :id => @user, :membership_id => membership }, - :html => { :id => "member-#{membership.id}-roles-form", :style => 'display:none;'}) do %> -

    <% roles.each do |role| %> -
    - <% end %>

    - <%= hidden_field_tag 'membership[role_ids][]', '' %> -

    <%= submit_tag l(:button_change) %> - <%= link_to_function l(:button_cancel), "$('member-#{membership.id}-roles').show(); $('member-#{membership.id}-roles-form').hide(); return false;" %>

    - <% end %> -
    - <%= link_to_function l(:button_edit), "$('member-#{membership.id}-roles').hide(); $('member-#{membership.id}-roles-form').show(); return false;", :class => 'icon icon-edit' %> - <%= link_to_remote(l(:button_delete), { :url => { :controller => 'users', :action => 'destroy_membership', :id => @user, :membership_id => membership }, - :method => :post }, - :class => 'icon icon-del') if membership.deletable? %> -
    -<% else %> -

    <%= l(:label_no_data) %>

    -<% end %> -
    - -
    -<% if projects.any? %> -
    <%=l(:label_project_new)%> -<% remote_form_for(:membership, :url => { :action => 'edit_membership', :id => @user }) do %> -<%= select_tag 'membership[project_id]', options_for_membership_project_select(@user, projects) %> -

    <%= l(:label_role_plural) %>: -<% roles.each do |role| %> - -<% end %>

    -

    <%= submit_tag l(:button_add) %>

    -<% end %> -
    -<% end %> -
    diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7e/7e8e9292e4aaad251a7746c0dc0d152386a542fc.svn-base --- a/.svn/pristine/7e/7e8e9292e4aaad251a7746c0dc0d152386a542fc.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,96 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) - -class RepositoryDarcsTest < ActiveSupport::TestCase - fixtures :projects - - REPOSITORY_PATH = Rails.root.join('tmp/test/darcs_repository').to_s - NUM_REV = 6 - - def setup - @project = Project.find(3) - @repository = Repository::Darcs.create( - :project => @project, - :url => REPOSITORY_PATH, - :log_encoding => 'UTF-8' - ) - assert @repository - end - - if File.directory?(REPOSITORY_PATH) - def test_fetch_changesets_from_scratch - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - - assert_equal NUM_REV, @repository.changesets.count - assert_equal 13, @repository.changes.count - assert_equal "Initial commit.", @repository.changesets.find_by_revision('1').comments - end - - def test_fetch_changesets_incremental - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - - # Remove changesets with revision > 3 - @repository.changesets.find(:all).each {|c| c.destroy if c.revision.to_i > 3} - @project.reload - assert_equal 3, @repository.changesets.count - - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - end - - def test_entries_invalid_revision - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - assert_nil @repository.entries('', '123') - end - - def test_deleted_files_should_not_be_listed - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - 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 - if @repository.scm.supports_cat? - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - cat = @repository.cat("sources/welcome_controller.rb", 2) - assert_not_nil cat - assert cat.include?('class WelcomeController < ApplicationController') - end - end - else - puts "Darcs test repository NOT FOUND. Skipping unit tests !!!" - def test_fake; assert true end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7e/7ea8be01cd62ea12ccab2b959df562421b7f5a7b.svn-base --- a/.svn/pristine/7e/7ea8be01cd62ea12ccab2b959df562421b7f5a7b.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,29 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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 JournalObserver < ActiveRecord::Observer - def after_create(journal) - if journal.notify? && - (Setting.notified_events.include?('issue_updated') || - (Setting.notified_events.include?('issue_note_added') && journal.notes.present?) || - (Setting.notified_events.include?('issue_status_updated') && journal.new_status.present?) || - (Setting.notified_events.include?('issue_priority_updated') && journal.new_value_for('priority_id').present?) - ) - Mailer.deliver_issue_edit(journal) - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7e/7ee8c8273c1a633a982d9a4836b18c68168b73d6.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/7e/7ee8c8273c1a633a982d9a4836b18c68168b73d6.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,108 @@ +
    +<% if !@query.new_record? && @query.editable_by?(User.current) %> + <%= link_to l(:button_edit), edit_query_path(@query), :class => 'icon icon-edit' %> + <%= delete_link query_path(@query) %> +<% end %> +
    + +

    <%= @query.new_record? ? l(:label_issue_plural) : h(@query.name) %>

    +<% html_title(@query.new_record? ? l(:label_issue_plural) : @query.name) %> + +<%= form_tag({ :controller => 'issues', :action => 'index', :project_id => @project }, + :method => :get, :id => 'query_form') do %> + <%= hidden_field_tag 'set_filter', '1' %> +
    +
    "> + <%= l(:label_filter_plural) %> +
    "> + <%= render :partial => 'queries/filters', :locals => {:query => @query} %> +
    +
    + +
    +

    + + <%= link_to_function l(:button_apply), 'submit_query_form("query_form")', :class => 'icon icon-checked' %> + <%= link_to l(:button_clear), { :set_filter => 1, :project_id => @project }, :class => 'icon icon-reload' %> + <% if @query.new_record? && User.current.allowed_to?(:save_queries, @project, :global => true) %> + <%= link_to_function l(:button_save), + "$('#query_form').attr('action', '#{ @project ? new_project_query_path(@project) : new_query_path }'); submit_query_form('query_form')", + :class => 'icon icon-save' %> + <% end %> +

    +<% end %> + +<%= error_messages_for 'query' %> +<% if @query.valid? %> +<% if @issues.empty? %> +

    <%= l(:label_no_data) %>

    +<% else %> +<%= render :partial => 'issues/list', :locals => {:issues => @issues, :query => @query} %> +

    <%= pagination_links_full @issue_pages, @issue_count %>

    +<% end %> + +<% other_formats_links do |f| %> + <%= f.link_to 'Atom', :url => params.merge(:key => User.current.rss_key) %> + <%= f.link_to 'CSV', :url => params, :onclick => "showModal('csv-export-options', '330px'); return false;" %> + <%= f.link_to 'PDF', :url => params %> +<% end %> + + + +<% end %> +<%= call_hook(:view_issues_index_bottom, { :issues => @issues, :project => @project, :query => @query }) %> + +<% content_for :sidebar do %> + <%= render :partial => 'issues/sidebar' %> +<% end %> + +<% content_for :header_tags do %> + <%= auto_discovery_link_tag(:atom, + {:query_id => @query, :format => 'atom', + :page => nil, :key => User.current.rss_key}, + :title => l(:label_issue_plural)) %> + <%= auto_discovery_link_tag(:atom, + {:controller => 'journals', :action => 'index', + :query_id => @query, :format => 'atom', + :page => nil, :key => User.current.rss_key}, + :title => l(:label_changes_details)) %> +<% end %> + +<%= context_menu issues_context_menu_path %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7f/7f29e28dea9f944faa0f0c10063d8ae096007167.svn-base --- a/.svn/pristine/7f/7f29e28dea9f944faa0f0c10063d8ae096007167.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1022 +0,0 @@ -#Ernad Husremovic hernad@bring.out.ba - -bs: - direction: ltr - date: - formats: - default: "%d.%m.%Y" - short: "%e. %b" - long: "%e. %B %Y" - only_day: "%e" - - - day_names: [Nedjelja, Ponedjeljak, Utorak, Srijeda, ÄŒetvrtak, Petak, Subota] - abbr_day_names: [Ned, Pon, Uto, Sri, ÄŒet, Pet, Sub] - - month_names: [~, Januar, Februar, Mart, April, Maj, Jun, Jul, Avgust, Septembar, Oktobar, Novembar, Decembar] - abbr_month_names: [~, Jan, Feb, Mar, Apr, Maj, Jun, Jul, Avg, Sep, Okt, Nov, Dec] - order: - - :day - - :month - - :year - - time: - formats: - default: "%A, %e. %B %Y, %H:%M" - short: "%e. %B, %H:%M Uhr" - long: "%A, %e. %B %Y, %H:%M" - time: "%H:%M" - - am: "prijepodne" - pm: "poslijepodne" - - datetime: - distance_in_words: - half_a_minute: "pola minute" - less_than_x_seconds: - one: "manje od 1 sekunde" - other: "manje od %{count} sekudni" - x_seconds: - one: "1 sekunda" - other: "%{count} sekundi" - less_than_x_minutes: - one: "manje od 1 minute" - other: "manje od %{count} minuta" - x_minutes: - one: "1 minuta" - other: "%{count} minuta" - about_x_hours: - one: "oko 1 sahat" - other: "oko %{count} sahata" - x_days: - one: "1 dan" - other: "%{count} dana" - about_x_months: - one: "oko 1 mjesec" - other: "oko %{count} mjeseci" - x_months: - one: "1 mjesec" - other: "%{count} mjeseci" - about_x_years: - one: "oko 1 godine" - other: "oko %{count} godina" - over_x_years: - one: "preko 1 godine" - other: "preko %{count} godina" - almost_x_years: - one: "almost 1 year" - other: "almost %{count} years" - - - number: - format: - precision: 2 - separator: ',' - delimiter: '.' - currency: - format: - unit: 'KM' - format: '%u %n' - separator: - delimiter: - precision: - percentage: - format: - delimiter: "" - precision: - format: - delimiter: "" - human: - format: - delimiter: "" - precision: 1 - storage_units: - format: "%n %u" - units: - byte: - one: "Byte" - other: "Bytes" - kb: "KB" - mb: "MB" - gb: "GB" - tb: "TB" - -# Used in array.to_sentence. - support: - array: - sentence_connector: "i" - skip_last_comma: false - - activerecord: - errors: - template: - header: - one: "1 error prohibited this %{model} from being saved" - other: "%{count} errors prohibited this %{model} from being saved" - messages: - inclusion: "nije ukljuÄeno u listu" - exclusion: "je rezervisano" - invalid: "nije ispravno" - confirmation: "ne odgovara potvrdi" - accepted: "mora se prihvatiti" - empty: "ne može biti prazno" - blank: "ne može biti znak razmaka" - too_long: "je predugaÄko" - too_short: "je prekratko" - wrong_length: "je pogreÅ¡ne dužine" - taken: "već je zauzeto" - not_a_number: "nije broj" - not_a_date: "nije ispravan datum" - greater_than: "mora bit veći od %{count}" - greater_than_or_equal_to: "mora bit veći ili jednak %{count}" - equal_to: "mora biti jednak %{count}" - less_than: "mora biti manji od %{count}" - less_than_or_equal_to: "mora bit manji ili jednak %{count}" - odd: "mora biti neparan" - even: "mora biti paran" - greater_than_start_date: "mora biti veći nego poÄetni datum" - not_same_project: "ne pripada istom projektu" - circular_dependency: "Ova relacija stvar cirkularnu zavisnost" - cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" - - actionview_instancetag_blank_option: Molimo odaberite - - general_text_No: 'Da' - general_text_Yes: 'Ne' - general_text_no: 'ne' - general_text_yes: 'da' - general_lang_name: 'Bosanski' - general_csv_separator: ',' - general_csv_decimal_separator: '.' - general_csv_encoding: UTF-8 - general_pdf_encoding: UTF-8 - general_first_day_of_week: '7' - - notice_account_activated: VaÅ¡ nalog je aktiviran. Možete se prijaviti. - notice_account_invalid_creditentials: PogreÅ¡an korisnik ili lozinka - notice_account_lost_email_sent: Email sa uputstvima o izboru nove Å¡ifre je poslat na vaÅ¡u adresu. - notice_account_password_updated: Lozinka je uspjeÅ¡no promjenjena. - notice_account_pending: "VaÅ¡ nalog je kreiran i Äeka odobrenje administratora." - notice_account_register_done: Nalog je uspjeÅ¡no kreiran. Da bi ste aktivirali vaÅ¡ nalog kliknite na link koji vam je poslat. - notice_account_unknown_email: Nepoznati korisnik. - notice_account_updated: Nalog je uspjeÅ¡no promjenen. - notice_account_wrong_password: PogreÅ¡na lozinka - notice_can_t_change_password: Ovaj nalog koristi eksterni izvor prijavljivanja. Ne mogu da promjenim Å¡ifru. - notice_default_data_loaded: Podrazumjevana konfiguracija uspjeÄno uÄitana. - notice_email_error: DoÅ¡lo je do greÅ¡ke pri slanju emaila (%{value}) - notice_email_sent: "Email je poslan %{value}" - notice_failed_to_save_issues: "NeuspjeÅ¡no snimanje %{count} aktivnosti na %{total} izabrano: %{ids}." - notice_feeds_access_key_reseted: VaÅ¡ RSS pristup je resetovan. - notice_file_not_found: Stranica kojoj pokuÅ¡avate da pristupite ne postoji ili je uklonjena. - notice_locking_conflict: "Konflikt: podaci su izmjenjeni od strane drugog korisnika." - notice_no_issue_selected: "Nijedna aktivnost nije izabrana! Molim, izaberite aktivnosti koje želite za ispravljate." - notice_not_authorized: Niste ovlašćeni da pristupite ovoj stranici. - notice_successful_connection: UspjeÅ¡na konekcija. - notice_successful_create: UspjeÅ¡no kreiranje. - notice_successful_delete: Brisanje izvrÅ¡eno. - notice_successful_update: Promjene uspjeÅ¡no izvrÅ¡ene. - - error_can_t_load_default_data: "Podrazumjevane postavke se ne mogu uÄitati %{value}" - error_scm_command_failed: "Desila se greÅ¡ka pri pristupu repozitoriju: %{value}" - error_scm_not_found: "Unos i/ili revizija ne postoji u repozitoriju." - - error_scm_annotate: "Ova stavka ne postoji ili nije oznaÄena." - error_issue_not_found_in_project: 'Aktivnost nije naÄ‘ena ili ne pripada ovom projektu' - - warning_attachments_not_saved: "%{count} fajl(ovi) ne mogu biti snimljen(i)." - - mail_subject_lost_password: "VaÅ¡a %{value} lozinka" - mail_body_lost_password: 'Za promjenu lozinke, kliknite na sljedeći link:' - mail_subject_register: "Aktivirajte %{value} vaÅ¡ korisniÄki raÄun" - mail_body_register: 'Za aktivaciju vaÅ¡eg korisniÄkog raÄuna, kliknite na sljedeći link:' - mail_body_account_information_external: "Možete koristiti vaÅ¡ %{value} korisniÄki raÄun za prijavu na sistem." - mail_body_account_information: Informacija o vaÅ¡em korisniÄkom raÄunu - mail_subject_account_activation_request: "%{value} zahtjev za aktivaciju korisniÄkog raÄuna" - mail_body_account_activation_request: "Novi korisnik (%{value}) se registrovao. KorisniÄki raÄun Äeka vaÅ¡e odobrenje za aktivaciju:" - mail_subject_reminder: "%{count} aktivnost(i) u kaÅ¡njenju u narednim %{days} danima" - mail_body_reminder: "%{count} aktivnost(i) koje su dodjeljenje vama u narednim %{days} danima:" - - gui_validation_error: 1 greÅ¡ka - gui_validation_error_plural: "%{count} greÅ¡aka" - - field_name: Ime - field_description: Opis - field_summary: PojaÅ¡njenje - field_is_required: Neophodno popuniti - field_firstname: Ime - field_lastname: Prezime - field_mail: Email - field_filename: Fajl - field_filesize: VeliÄina - field_downloads: Downloadi - field_author: Autor - field_created_on: Kreirano - field_updated_on: Izmjenjeno - field_field_format: Format - field_is_for_all: Za sve projekte - field_possible_values: Moguće vrijednosti - field_regexp: '"Regularni izraz"' - field_min_length: Minimalna veliÄina - field_max_length: Maksimalna veliÄina - field_value: Vrijednost - field_category: Kategorija - field_title: Naslov - field_project: Projekat - field_issue: Aktivnost - field_status: Status - field_notes: BiljeÅ¡ke - field_is_closed: Aktivnost zatvorena - field_is_default: Podrazumjevana vrijednost - field_tracker: PodruÄje aktivnosti - field_subject: Subjekat - field_due_date: ZavrÅ¡iti do - field_assigned_to: Dodijeljeno - field_priority: Prioritet - field_fixed_version: Ciljna verzija - field_user: Korisnik - field_role: Uloga - field_homepage: Naslovna strana - field_is_public: Javni - field_parent: Podprojekt od - field_is_in_roadmap: Aktivnosti prikazane u planu realizacije - field_login: Prijava - field_mail_notification: Email notifikacije - field_admin: Administrator - field_last_login_on: Posljednja konekcija - field_language: Jezik - field_effective_date: Datum - field_password: Lozinka - field_new_password: Nova lozinka - field_password_confirmation: Potvrda - field_version: Verzija - field_type: Tip - field_host: Host - field_port: Port - field_account: KorisniÄki raÄun - field_base_dn: Base DN - field_attr_login: Attribut za prijavu - field_attr_firstname: Attribut za ime - field_attr_lastname: Atribut za prezime - field_attr_mail: Atribut za email - field_onthefly: 'Kreiranje korisnika "On-the-fly"' - field_start_date: PoÄetak - field_done_ratio: "% Realizovano" - field_auth_source: Mod za authentifikaciju - field_hide_mail: Sakrij moju email adresu - field_comments: Komentar - field_url: URL - field_start_page: PoÄetna stranica - field_subproject: Podprojekat - field_hours: Sahata - field_activity: Operacija - field_spent_on: Datum - field_identifier: Identifikator - field_is_filter: KoriÅ¡teno kao filter - field_issue_to: Povezana aktivnost - field_delay: OdgaÄ‘anje - field_assignable: Aktivnosti dodijeljene ovoj ulozi - field_redirect_existing_links: IzvrÅ¡i redirekciju postojećih linkova - field_estimated_hours: Procjena vremena - field_column_names: Kolone - field_time_zone: Vremenska zona - field_searchable: Pretraživo - field_default_value: Podrazumjevana vrijednost - field_comments_sorting: Prikaži komentare - field_parent_title: 'Stranica "roditelj"' - field_editable: Može se mijenjati - field_watcher: PosmatraÄ - field_identity_url: OpenID URL - field_content: Sadržaj - - setting_app_title: Naslov aplikacije - setting_app_subtitle: Podnaslov aplikacije - setting_welcome_text: Tekst dobrodoÅ¡lice - setting_default_language: Podrazumjevani jezik - setting_login_required: Authentifikacija neophodna - setting_self_registration: Samo-registracija - setting_attachment_max_size: Maksimalna veliÄina prikaÄenog fajla - setting_issues_export_limit: Limit za eksport aktivnosti - setting_mail_from: Mail adresa - poÅ¡aljilac - setting_bcc_recipients: '"BCC" (blind carbon copy) primaoci ' - setting_plain_text_mail: Email sa obiÄnim tekstom (bez HTML-a) - setting_host_name: Ime hosta i putanja - setting_text_formatting: Formatiranje teksta - setting_wiki_compression: Kompresija Wiki istorije - - setting_feeds_limit: 'Limit za "RSS" feed-ove' - setting_default_projects_public: Podrazumjeva se da je novi projekat javni - setting_autofetch_changesets: 'Automatski kupi "commit"-e' - setting_sys_api_enabled: 'Omogući "WS" za upravljanje repozitorijom' - setting_commit_ref_keywords: KljuÄne rijeÄi za reference - setting_commit_fix_keywords: 'KljuÄne rijeÄi za status "zatvoreno"' - setting_autologin: Automatski login - setting_date_format: Format datuma - setting_time_format: Format vremena - setting_cross_project_issue_relations: Omogući relacije izmeÄ‘u aktivnosti na razliÄitim projektima - setting_issue_list_default_columns: Podrazumjevane koleone za prikaz na listi aktivnosti - setting_emails_footer: Potpis na email-ovima - setting_protocol: Protokol - setting_per_page_options: Broj objekata po stranici - setting_user_format: Format korisniÄkog prikaza - setting_activity_days_default: Prikaz promjena na projektu - opseg dana - setting_display_subprojects_issues: Prikaz podprojekata na glavnom projektima (podrazumjeva se) - setting_enabled_scm: Omogući SCM (source code management) - setting_mail_handler_api_enabled: Omogući automatsku obradu ulaznih emailova - setting_mail_handler_api_key: API kljuÄ (obrada ulaznih mailova) - setting_sequential_project_identifiers: GeneriÅ¡i identifikatore projekta sekvencijalno - setting_gravatar_enabled: 'Koristi "gravatar" korisniÄke ikone' - setting_diff_max_lines_displayed: Maksimalan broj linija za prikaz razlika izmeÄ‘u dva fajla - setting_file_max_size_displayed: Maksimalna veliÄina fajla kod prikaza razlika unutar fajla (inline) - setting_repository_log_display_limit: Maksimalna veliÄina revizija prikazanih na log fajlu - setting_openid: Omogući OpenID prijavu i registraciju - - permission_edit_project: Ispravke projekta - permission_select_project_modules: Odaberi module projekta - permission_manage_members: Upravljanje Älanovima - permission_manage_versions: Upravljanje verzijama - permission_manage_categories: Upravljanje kategorijama aktivnosti - permission_add_issues: Dodaj aktivnosti - permission_edit_issues: Ispravka aktivnosti - permission_manage_issue_relations: Upravljaj relacijama meÄ‘u aktivnostima - permission_add_issue_notes: Dodaj biljeÅ¡ke - permission_edit_issue_notes: Ispravi biljeÅ¡ke - permission_edit_own_issue_notes: Ispravi sopstvene biljeÅ¡ke - permission_move_issues: Pomjeri aktivnosti - permission_delete_issues: IzbriÅ¡i aktivnosti - permission_manage_public_queries: Upravljaj javnim upitima - permission_save_queries: Snimi upite - permission_view_gantt: Pregled gantograma - permission_view_calendar: Pregled kalendara - permission_view_issue_watchers: Pregled liste korisnika koji prate aktivnost - permission_add_issue_watchers: Dodaj onoga koji prati aktivnost - permission_log_time: Evidentiraj utroÅ¡ak vremena - permission_view_time_entries: Pregled utroÅ¡ka vremena - permission_edit_time_entries: Ispravka utroÅ¡ka vremena - permission_edit_own_time_entries: Ispravka svog utroÅ¡ka vremena - permission_manage_news: Upravljaj novostima - permission_comment_news: Komentiraj novosti - permission_manage_documents: Upravljaj dokumentima - permission_view_documents: Pregled dokumenata - permission_manage_files: Upravljaj fajlovima - permission_view_files: Pregled fajlova - permission_manage_wiki: Upravljaj wiki stranicama - permission_rename_wiki_pages: Ispravi wiki stranicu - permission_delete_wiki_pages: IzbriÅ¡i wiki stranicu - permission_view_wiki_pages: Pregled wiki sadržaja - permission_view_wiki_edits: Pregled wiki istorije - permission_edit_wiki_pages: Ispravka wiki stranica - permission_delete_wiki_pages_attachments: Brisanje fajlova prikaÄenih wiki-ju - permission_protect_wiki_pages: ZaÅ¡titi wiki stranicu - permission_manage_repository: Upravljaj repozitorijem - permission_browse_repository: Pregled repozitorija - permission_view_changesets: Pregled setova promjena - permission_commit_access: 'Pristup "commit"-u' - permission_manage_boards: Upravljaj forumima - permission_view_messages: Pregled poruka - permission_add_messages: Å alji poruke - permission_edit_messages: Ispravi poruke - permission_edit_own_messages: Ispravka sopstvenih poruka - permission_delete_messages: Prisanje poruka - permission_delete_own_messages: Brisanje sopstvenih poruka - - project_module_issue_tracking: Praćenje aktivnosti - project_module_time_tracking: Praćenje vremena - project_module_news: Novosti - project_module_documents: Dokumenti - project_module_files: Fajlovi - project_module_wiki: Wiki stranice - project_module_repository: Repozitorij - project_module_boards: Forumi - - 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_x_projects: - zero: 0 projekata - one: 1 projekat - other: "%{count} projekata" - label_project_all: Svi projekti - label_project_latest: Posljednji projekti - label_issue: Aktivnost - label_issue_new: Nova aktivnost - label_issue_plural: Aktivnosti - label_issue_view_all: Vidi sve aktivnosti - label_issues_by: "Aktivnosti po %{value}" - label_issue_added: Aktivnost je dodana - label_issue_updated: Aktivnost je izmjenjena - label_document: Dokument - label_document_new: Novi dokument - label_document_plural: Dokumenti - label_document_added: Dokument je dodan - label_role: Uloga - label_role_plural: Uloge - label_role_new: Nove uloge - label_role_and_permissions: Uloge i dozvole - label_member: IzvrÅ¡ilac - label_member_new: Novi izvrÅ¡ilac - label_member_plural: IzvrÅ¡ioci - label_tracker: PodruÄje aktivnosti - label_tracker_plural: PodruÄja aktivnosti - label_tracker_new: Novo podruÄje aktivnosti - label_workflow: Tok promjena na aktivnosti - label_issue_status: Status aktivnosti - label_issue_status_plural: Statusi aktivnosti - label_issue_status_new: Novi status - label_issue_category: Kategorija aktivnosti - label_issue_category_plural: Kategorije aktivnosti - label_issue_category_new: Nova kategorija - label_custom_field: Proizvoljno polje - label_custom_field_plural: Proizvoljna polja - label_custom_field_new: Novo proizvoljno polje - label_enumerations: Enumeracije - label_enumeration_new: Nova vrijednost - label_information: Informacija - label_information_plural: Informacije - label_please_login: Molimo prijavite se - label_register: Registracija - label_login_with_open_id_option: ili prijava sa OpenID-om - label_password_lost: Izgubljena lozinka - label_home: PoÄetna stranica - label_my_page: Moja stranica - label_my_account: Moj korisniÄki raÄun - label_my_projects: Moji projekti - label_administration: Administracija - label_login: Prijavi se - label_logout: Odjavi se - label_help: Pomoć - label_reported_issues: Prijavljene aktivnosti - label_assigned_to_me_issues: Aktivnosti dodjeljene meni - label_last_login: Posljednja konekcija - label_registered_on: Registrovan na - label_activity_plural: Promjene - label_activity: Operacija - label_overall_activity: Pregled svih promjena - label_user_activity: "Promjene izvrÅ¡ene od: %{value}" - label_new: Novi - label_logged_as: Prijavljen kao - label_environment: Sistemsko okruženje - label_authentication: Authentifikacija - label_auth_source: Mod authentifikacije - label_auth_source_new: Novi mod authentifikacije - label_auth_source_plural: Modovi authentifikacije - label_subproject_plural: Podprojekti - label_and_its_subprojects: "%{value} i njegovi podprojekti" - label_min_max_length: Min - Maks dužina - label_list: Lista - label_date: Datum - label_integer: Cijeli broj - label_float: Float - label_boolean: LogiÄka varijabla - label_string: Tekst - label_text: Dugi tekst - label_attribute: Atribut - label_attribute_plural: Atributi - label_download: "%{count} download" - label_download_plural: "%{count} download-i" - label_no_data: Nema podataka za prikaz - label_change_status: Promjeni status - label_history: Istorija - label_attachment: Fajl - label_attachment_new: Novi fajl - label_attachment_delete: IzbriÅ¡i fajl - label_attachment_plural: Fajlovi - label_file_added: Fajl je dodan - label_report: IzvjeÅ¡taj - label_report_plural: IzvjeÅ¡taji - label_news: Novosti - label_news_new: Dodaj novosti - label_news_plural: Novosti - label_news_latest: Posljednje novosti - label_news_view_all: Pogledaj sve novosti - label_news_added: Novosti su dodane - label_settings: Postavke - label_overview: Pregled - label_version: Verzija - label_version_new: Nova verzija - label_version_plural: Verzije - label_confirmation: Potvrda - label_export_to: 'TakoÄ‘e dostupno u:' - label_read: ÄŒitaj... - label_public_projects: Javni projekti - label_open_issues: otvoren - label_open_issues_plural: otvoreni - label_closed_issues: zatvoren - label_closed_issues_plural: zatvoreni - label_x_open_issues_abbr_on_total: - zero: 0 otvoreno / %{total} - one: 1 otvorena / %{total} - other: "%{count} otvorene / %{total}" - label_x_open_issues_abbr: - zero: 0 otvoreno - one: 1 otvorena - other: "%{count} otvorene" - label_x_closed_issues_abbr: - zero: 0 zatvoreno - one: 1 zatvorena - other: "%{count} zatvorene" - label_total: Ukupno - label_permissions: Dozvole - label_current_status: Tekući status - label_new_statuses_allowed: Novi statusi dozvoljeni - label_all: sve - label_none: niÅ¡ta - label_nobody: niko - label_next: Sljedeće - label_previous: Predhodno - label_used_by: KoriÅ¡teno od - label_details: Detalji - label_add_note: Dodaj biljeÅ¡ku - label_per_page: Po stranici - label_calendar: Kalendar - label_months_from: mjeseci od - label_gantt: Gantt - label_internal: Interno - label_last_changes: "posljednjih %{count} promjena" - label_change_view_all: Vidi sve promjene - label_personalize_page: Personaliziraj ovu stranicu - label_comment: Komentar - label_comment_plural: Komentari - label_x_comments: - zero: bez komentara - one: 1 komentar - other: "%{count} komentari" - label_comment_add: Dodaj komentar - label_comment_added: Komentar je dodan - label_comment_delete: IzbriÅ¡i komentar - label_query: Proizvoljan upit - label_query_plural: Proizvoljni upiti - label_query_new: Novi upit - label_filter_add: Dodaj filter - label_filter_plural: Filteri - label_equals: je - label_not_equals: nije - label_in_less_than: je manji nego - label_in_more_than: je viÅ¡e nego - label_in: u - label_today: danas - label_all_time: sve vrijeme - label_yesterday: juÄe - label_this_week: ova hefta - label_last_week: zadnja hefta - label_last_n_days: "posljednjih %{count} dana" - label_this_month: ovaj mjesec - label_last_month: posljednji mjesec - label_this_year: ova godina - label_date_range: Datumski opseg - label_less_than_ago: ranije nego (dana) - label_more_than_ago: starije nego (dana) - label_ago: prije (dana) - label_contains: sadrži - label_not_contains: ne sadrži - label_day_plural: dani - label_repository: Repozitorij - label_repository_plural: Repozitoriji - label_browse: Listaj - label_modification: "%{count} promjena" - label_modification_plural: "%{count} promjene" - label_revision: Revizija - label_revision_plural: Revizije - label_associated_revisions: Doddjeljene revizije - label_added: dodano - label_modified: izmjenjeno - label_copied: kopirano - label_renamed: preimenovano - label_deleted: izbrisano - label_latest_revision: Posljednja revizija - label_latest_revision_plural: Posljednje revizije - label_view_revisions: Vidi revizije - label_max_size: Maksimalna veliÄina - label_sort_highest: Pomjeri na vrh - label_sort_higher: Pomjeri gore - label_sort_lower: Pomjeri dole - label_sort_lowest: Pomjeri na dno - label_roadmap: Plan realizacije - label_roadmap_due_in: "Obavezan do %{value}" - label_roadmap_overdue: "%{value} kasni" - label_roadmap_no_issues: Nema aktivnosti za ovu verziju - label_search: Traži - label_result_plural: Rezultati - label_all_words: Sve rijeÄi - label_wiki: Wiki stranice - label_wiki_edit: ispravka wiki-ja - label_wiki_edit_plural: ispravke wiki-ja - label_wiki_page: Wiki stranica - label_wiki_page_plural: Wiki stranice - label_index_by_title: Indeks prema naslovima - label_index_by_date: Indeks po datumima - label_current_version: Tekuća verzija - label_preview: Pregled - label_feed_plural: Feeds - label_changes_details: Detalji svih promjena - label_issue_tracking: Evidencija aktivnosti - label_spent_time: UtroÅ¡ak vremena - label_f_hour: "%{value} sahat" - label_f_hour_plural: "%{value} sahata" - label_time_tracking: Evidencija vremena - label_change_plural: Promjene - label_statistics: Statistika - label_commits_per_month: '"Commit"-a po mjesecu' - label_commits_per_author: '"Commit"-a po autoru' - label_view_diff: Pregled razlika - label_diff_inline: zajedno - label_diff_side_by_side: jedna pored druge - label_options: Opcije - label_copy_workflow_from: Kopiraj tok promjena statusa iz - label_permissions_report: IzvjeÅ¡taj - label_watched_issues: Aktivnosti koje pratim - label_related_issues: Korelirane aktivnosti - label_applied_status: Status je primjenjen - label_loading: UÄitavam... - label_relation_new: Nova relacija - label_relation_delete: IzbriÅ¡i relaciju - label_relates_to: korelira sa - label_duplicates: duplikat - label_duplicated_by: duplicirano od - label_blocks: blokira - label_blocked_by: blokirano on - label_precedes: predhodi - label_follows: slijedi - label_end_to_start: 'kraj -> poÄetak' - label_end_to_end: 'kraja -> kraj' - label_start_to_start: 'poÄetak -> poÄetak' - label_start_to_end: 'poÄetak -> kraj' - label_stay_logged_in: Ostani prijavljen - label_disabled: onemogućen - label_show_completed_versions: Prikaži zavrÅ¡ene verzije - label_me: ja - label_board: Forum - label_board_new: Novi forum - label_board_plural: Forumi - label_topic_plural: Teme - label_message_plural: Poruke - label_message_last: Posljednja poruka - label_message_new: Nova poruka - label_message_posted: Poruka je dodana - label_reply_plural: Odgovori - label_send_information: PoÅ¡alji informaciju o korisniÄkom raÄunu - label_year: Godina - label_month: Mjesec - label_week: Hefta - label_date_from: Od - label_date_to: Do - label_language_based: Bazirano na korisnikovom jeziku - label_sort_by: "Sortiraj po %{value}" - label_send_test_email: PoÅ¡alji testni email - label_feeds_access_key_created_on: "RSS pristupni kljuÄ kreiran prije %{value} dana" - label_module_plural: Moduli - label_added_time_by: "Dodano od %{author} prije %{age}" - label_updated_time_by: "Izmjenjeno od %{author} prije %{age}" - label_updated_time: "Izmjenjeno prije %{value}" - label_jump_to_a_project: SkoÄi na projekat... - label_file_plural: Fajlovi - label_changeset_plural: Setovi promjena - label_default_columns: Podrazumjevane kolone - label_no_change_option: (Bez promjene) - label_bulk_edit_selected_issues: Ispravi odjednom odabrane aktivnosti - label_theme: Tema - label_default: Podrazumjevano - label_search_titles_only: Pretraži samo naslove - label_user_mail_option_all: "Za bilo koji dogaÄ‘aj na svim mojim projektima" - label_user_mail_option_selected: "Za bilo koji dogaÄ‘aj na odabranim projektima..." - label_user_mail_no_self_notified: "Ne želim notifikaciju za promjene koje sam ja napravio" - label_registration_activation_by_email: aktivacija korisniÄkog raÄuna email-om - label_registration_manual_activation: ruÄna aktivacija korisniÄkog raÄuna - label_registration_automatic_activation: automatska kreacija korisniÄkog raÄuna - label_display_per_page: "Po stranici: %{value}" - label_age: Starost - label_change_properties: Promjena osobina - label_general: Generalno - label_more: ViÅ¡e - label_scm: SCM - label_plugins: Plugin-ovi - label_ldap_authentication: LDAP authentifikacija - label_downloads_abbr: D/L - label_optional_description: Opis (opciono) - label_add_another_file: Dodaj joÅ¡ jedan fajl - label_preferences: Postavke - label_chronological_order: HronoloÅ¡ki poredak - label_reverse_chronological_order: Reverzni hronoloÅ¡ki poredak - label_planning: Planiranje - label_incoming_emails: Dolazni email-ovi - label_generate_key: GeneriÅ¡i kljuÄ - label_issue_watchers: Praćeno od - label_example: Primjer - label_display: Prikaz - - button_apply: Primjeni - button_add: Dodaj - button_archive: Arhiviranje - button_back: Nazad - button_cancel: Odustani - button_change: Izmjeni - button_change_password: Izmjena lozinke - button_check_all: OznaÄi sve - button_clear: BriÅ¡i - button_copy: Kopiraj - button_create: Novi - button_delete: BriÅ¡i - button_download: Download - button_edit: Ispravka - button_list: Lista - button_lock: ZakljuÄaj - button_log_time: UtroÅ¡ak vremena - button_login: Prijava - button_move: Pomjeri - button_rename: Promjena imena - button_reply: Odgovor - button_reset: Resetuj - button_rollback: Vrati predhodno stanje - button_save: Snimi - button_sort: Sortiranje - button_submit: PoÅ¡alji - button_test: Testiraj - button_unarchive: Otpakuj arhivu - button_uncheck_all: IskljuÄi sve - button_unlock: OtkljuÄaj - button_unwatch: Prekini notifikaciju - button_update: Promjena na aktivnosti - button_view: Pregled - button_watch: Notifikacija - button_configure: Konfiguracija - button_quote: Citat - - status_active: aktivan - status_registered: registrovan - status_locked: zakljuÄan - - text_select_mail_notifications: Odaberi dogaÄ‘aje za koje će se slati email notifikacija. - text_regexp_info: npr. ^[A-Z0-9]+$ - text_min_max_length_info: 0 znaÄi bez restrikcije - text_project_destroy_confirmation: Sigurno želite izbrisati ovaj projekat i njegove podatke ? - text_subprojects_destroy_warning: "Podprojekt(i): %{value} će takoÄ‘e biti izbrisani." - text_workflow_edit: Odaberite ulogu i podruÄje aktivnosti za ispravku toka promjena na aktivnosti - text_are_you_sure: Da li ste sigurni ? - text_tip_issue_begin_day: zadatak poÄinje danas - text_tip_issue_end_day: zadatak zavrÅ¡ava danas - text_tip_issue_begin_end_day: zadatak zapoÄinje i zavrÅ¡ava danas - text_project_identifier_info: 'Samo mala slova (a-z), brojevi i crtice su dozvoljeni.
    Nakon snimanja, identifikator se ne može mijenjati.' - text_caracters_maximum: "maksimum %{count} karaktera." - text_caracters_minimum: "Dužina mora biti najmanje %{count} znakova." - text_length_between: "Broj znakova izmeÄ‘u %{min} i %{max}." - text_tracker_no_workflow: Tok statusa nije definisan za ovo podruÄje aktivnosti - text_unallowed_characters: Nedozvoljeni znakovi - text_comma_separated: ViÅ¡estruke vrijednosti dozvoljene (odvojiti zarezom). - text_issues_ref_in_commit_messages: 'Referenciranje i zatvaranje aktivnosti putem "commit" poruka' - text_issue_added: "Aktivnost %{id} je prijavljena od %{author}." - text_issue_updated: "Aktivnost %{id} je izmjenjena od %{author}." - text_wiki_destroy_confirmation: Sigurno želite izbrisati ovaj wiki i Äitav njegov sadržaj ? - text_issue_category_destroy_question: "Neke aktivnosti (%{count}) pripadaju ovoj kategoriji. Sigurno to želite uraditi ?" - text_issue_category_destroy_assignments: Ukloni kategoriju - text_issue_category_reassign_to: Ponovo dodijeli ovu kategoriju - text_user_mail_option: "Za projekte koje niste odabrali, primićete samo notifikacije o stavkama koje pratite ili ste u njih ukljuÄeni (npr. vi ste autor ili su vama dodjeljenje)." - text_no_configuration_data: "Uloge, podruÄja aktivnosti, statusi aktivnosti i tok promjena statusa nisu konfigurisane.\nKrajnje je preporuÄeno da uÄitate tekuÄ‘e postavke. Kasnije ćete ih moći mjenjati po svojim potrebama." - text_load_default_configuration: UÄitaj tekuću konfiguraciju - text_status_changed_by_changeset: "Primjenjeno u setu promjena %{value}." - text_issues_destroy_confirmation: 'Sigurno želite izbrisati odabranu/e aktivnost/i ?' - text_select_project_modules: 'Odaberi module koje želite u ovom projektu:' - text_default_administrator_account_changed: Tekući administratorski raÄun je promjenjen - text_file_repository_writable: U direktorij sa fajlovima koji su prilozi se može pisati - text_plugin_assets_writable: U direktorij plugin-ova se može pisati - text_rmagick_available: RMagick je dostupan (opciono) - text_destroy_time_entries_question: "%{hours} sahata je prijavljeno na aktivnostima koje želite brisati. Želite li to uÄiniti ?" - text_destroy_time_entries: IzbriÅ¡i prijavljeno vrijeme - text_assign_time_entries_to_project: Dodaj prijavljenoo vrijeme projektu - text_reassign_time_entries: 'Preraspodjeli prijavljeno vrijeme na ovu aktivnost:' - text_user_wrote: "%{value} je napisao/la:" - text_enumeration_destroy_question: "Za %{count} objekata je dodjeljenja ova vrijednost." - text_enumeration_category_reassign_to: 'Ponovo im dodjeli ovu vrijednost:' - text_email_delivery_not_configured: "Email dostava nije konfiguraisana, notifikacija je onemogućena.\nKonfiguriÅ¡i SMTP server u config/configuration.yml i restartuj aplikaciju nakon toga." - text_repository_usernames_mapping: "Odaberi ili ispravi redmine korisnika mapiranog za svako korisniÄko ima naÄ‘eno u logu repozitorija.\nKorisnici sa istim imenom u redmineu i u repozitoruju se automatski mapiraju." - text_diff_truncated: '... Ovaj prikaz razlike je odsjeÄen poÅ¡to premaÅ¡uje maksimalnu veliÄinu za prikaz' - text_custom_field_possible_values_info: 'Jedna linija za svaku vrijednost' - - default_role_manager: Menadžer - default_role_developer: Programer - default_role_reporter: Reporter - default_tracker_bug: GreÅ¡ka - default_tracker_feature: Nova funkcija - default_tracker_support: PodrÅ¡ka - default_issue_status_new: Novi - default_issue_status_in_progress: In Progress - default_issue_status_resolved: RijeÅ¡en - default_issue_status_feedback: ÄŒeka se povratna informacija - default_issue_status_closed: Zatvoren - default_issue_status_rejected: Odbijen - default_doc_category_user: KorisniÄka dokumentacija - default_doc_category_tech: TehniÄka dokumentacija - default_priority_low: Nizak - default_priority_normal: Normalan - default_priority_high: Visok - default_priority_urgent: Urgentno - default_priority_immediate: Odmah - default_activity_design: Dizajn - default_activity_development: Programiranje - - enumeration_issue_priorities: Prioritet aktivnosti - enumeration_doc_categories: Kategorije dokumenata - enumeration_activities: Operacije (utroÅ¡ak vremena) - notice_unable_delete_version: Ne mogu izbrisati verziju. - button_create_and_continue: Kreiraj i nastavi - button_annotate: Zabilježi - button_activate: Aktiviraj - label_sort: Sortiranje - label_date_from_to: Od %{start} do %{end} - label_ascending: Rastuće - label_descending: Opadajuće - label_greater_or_equal: ">=" - label_less_or_equal: <= - text_wiki_page_destroy_question: This page has %{descendants} child page(s) and descendant(s). What do you want to do? - text_wiki_page_reassign_children: Reassign child pages to this parent page - text_wiki_page_nullify_children: Keep child pages as root pages - text_wiki_page_destroy_children: Delete child pages and all their descendants - setting_password_min_length: Minimum password length - field_group_by: Group results by - mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated" - label_wiki_content_added: Wiki page added - mail_subject_wiki_content_added: "'%{id}' wiki page has been added" - mail_body_wiki_content_added: The '%{id}' wiki page has been added by %{author}. - label_wiki_content_updated: Wiki page updated - mail_body_wiki_content_updated: The '%{id}' wiki page has been updated by %{author}. - permission_add_project: Create project - setting_new_project_user_role_id: Role given to a non-admin user who creates a project - label_view_all_revisions: View all revisions - label_tag: Tag - label_branch: Branch - error_no_tracker_in_project: No tracker is associated to this project. Please check the Project settings. - error_no_default_issue_status: No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses"). - text_journal_changed: "%{label} changed from %{old} to %{new}" - text_journal_set_to: "%{label} set to %{value}" - text_journal_deleted: "%{label} deleted (%{old})" - label_group_plural: Groups - label_group: Group - label_group_new: New group - label_time_entry_plural: Spent time - text_journal_added: "%{label} %{value} added" - field_active: Active - enumeration_system_activity: System Activity - permission_delete_issue_watchers: Delete watchers - version_status_closed: closed - version_status_locked: locked - version_status_open: open - error_can_not_reopen_issue_on_closed_version: An issue assigned to a closed version can not be reopened - label_user_anonymous: Anonymous - button_move_and_follow: Move and follow - setting_default_projects_modules: Default enabled modules for new projects - setting_gravatar_default: Default Gravatar image - field_sharing: Sharing - label_version_sharing_hierarchy: With project hierarchy - label_version_sharing_system: With all projects - label_version_sharing_descendants: With subprojects - label_version_sharing_tree: With project tree - label_version_sharing_none: Not shared - error_can_not_archive_project: This project can not be archived - button_duplicate: Duplicate - button_copy_and_follow: Copy and follow - label_copy_source: Source - setting_issue_done_ratio: Calculate the issue done ratio with - setting_issue_done_ratio_issue_status: Use the issue status - error_issue_done_ratios_not_updated: Issue done ratios not updated. - error_workflow_copy_target: Please select target tracker(s) and role(s) - setting_issue_done_ratio_issue_field: Use the issue field - label_copy_same_as_target: Same as target - label_copy_target: Target - notice_issue_done_ratios_updated: Issue done ratios updated. - error_workflow_copy_source: Please select a source tracker or role - label_update_issue_done_ratios: Update issue done ratios - setting_start_of_week: Start calendars on - permission_view_issues: View Issues - label_display_used_statuses_only: Only display statuses that are used by this tracker - label_revision_id: Revision %{value} - label_api_access_key: API access key - label_api_access_key_created_on: API access key created %{value} ago - label_feeds_access_key: RSS access key - notice_api_access_key_reseted: Your API access key was reset. - setting_rest_api_enabled: Enable REST web service - label_missing_api_access_key: Missing an API access key - label_missing_feeds_access_key: Missing a RSS access key - button_show: Show - text_line_separated: Multiple values allowed (one line for each value). - setting_mail_handler_body_delimiters: Truncate emails after one of these lines - permission_add_subprojects: Create subprojects - label_subproject_new: New subproject - text_own_membership_delete_confirmation: |- - You are about to remove some or all of your permissions and may no longer be able to edit this project after that. - Are you sure you want to continue? - label_close_versions: Close completed versions - label_board_sticky: Sticky - label_board_locked: Locked - permission_export_wiki_pages: Export wiki pages - setting_cache_formatted_text: Cache formatted text - permission_manage_project_activities: Manage project activities - error_unable_delete_issue_status: Unable to delete issue status - label_profile: Profile - permission_manage_subtasks: Manage subtasks - field_parent_issue: Parent task - label_subtask_plural: Subtasks - label_project_copy_notifications: Send email notifications during the project copy - error_can_not_delete_custom_field: Unable to delete custom field - error_unable_to_connect: Unable to connect (%{value}) - error_can_not_remove_role: This role is in use and can not be deleted. - error_can_not_delete_tracker: This tracker contains issues and can't be deleted. - field_principal: Principal - label_my_page_block: My page block - notice_failed_to_save_members: "Failed to save member(s): %{errors}." - text_zoom_out: Zoom out - text_zoom_in: Zoom in - notice_unable_delete_time_entry: Unable to delete time log entry. - label_overall_spent_time: Overall spent time - field_time_entries: Log time - project_module_gantt: Gantt - project_module_calendar: Calendar - button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" - text_are_you_sure_with_children: Delete issue and all child issues? - field_text: Text field - label_user_mail_option_only_owner: Only for things I am the owner of - setting_default_notification_option: Default notification option - label_user_mail_option_only_my_events: Only for things I watch or I'm involved in - label_user_mail_option_only_assigned: Only for things I am assigned to - label_user_mail_option_none: No events - field_member_of_group: Assignee's group - field_assigned_to_role: Assignee's role - notice_not_authorized_archived_project: The project you're trying to access has been archived. - label_principal_search: "Search for user or group:" - label_user_search: "Search for user:" - field_visible: Visible - setting_emails_header: Emails header - setting_commit_logtime_activity_id: Activity for logged time - text_time_logged_by_changeset: Applied in changeset %{value}. - setting_commit_logtime_enabled: Enable time logging - notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) - setting_gantt_items_limit: Maximum number of items displayed on the gantt chart - field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text - text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. - label_my_queries: My custom queries - text_journal_changed_no_detail: "%{label} updated" - label_news_comment_added: Comment added to a news - button_expand_all: Expand all - button_collapse_all: Collapse all - label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee - label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author - label_bulk_edit_selected_time_entries: Bulk edit selected time entries - text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? - label_role_anonymous: Anonymous - label_role_non_member: Non member - label_issue_note_added: Note added - label_issue_status_updated: Status updated - label_issue_priority_updated: Priority updated - label_issues_visibility_own: Issues created by or assigned to the user - field_issues_visibility: Issues visibility - label_issues_visibility_all: All issues - permission_set_own_issues_private: Set own issues public or private - field_is_private: Private - permission_set_issues_private: Set issues public or private - label_issues_visibility_public: All non private issues - text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). - field_commit_logs_encoding: 'Enkodiranje "commit" poruka' - field_scm_path_encoding: Path encoding - text_scm_path_encoding_note: "Default: UTF-8" - field_path_to_repository: Path to repository - field_root_directory: Root directory - field_cvs_module: Module - field_cvsroot: CVSROOT - text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) - text_scm_command: Command - text_scm_command_version: Version - label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. - notice_issue_successful_create: Issue %{id} created. - label_between: between - setting_issue_group_assignment: Allow issue assignment to groups - label_diff: diff - text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) - description_query_sort_criteria_direction: Sort direction - description_project_scope: Search scope - description_filter: Filter - description_user_mail_notification: Mail notification settings - description_date_from: Enter start date - description_message_content: Message content - description_available_columns: Available Columns - description_date_range_interval: Choose range by selecting start and end date - description_issue_category_reassign: Choose issue category - description_search: Searchfield - description_notes: Notes - description_date_range_list: Choose range from list - description_choose_project: Projects - description_date_to: Enter end date - description_query_sort_criteria_attribute: Sort attribute - description_wiki_subpages_reassign: Choose new parent page - description_selected_columns: Selected Columns - label_parent_revision: Parent - label_child_revision: Child - error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. - setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues - button_edit_section: Edit this section - setting_repositories_encodings: Attachments and repositories encodings - description_all_columns: All Columns - button_export: Export - label_export_options: "%{export_format} export options" - error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7f/7f4c045aa51ecaded0b2c44282890f940876ab00.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/7f/7f4c045aa51ecaded0b2c44282890f940876ab00.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,35 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class RoutingFilesTest < ActionController::IntegrationTest + def test_files + assert_routing( + { :method => 'get', :path => "/projects/33/files" }, + { :controller => 'files', :action => 'index', :project_id => '33' } + ) + assert_routing( + { :method => 'get', :path => "/projects/33/files/new" }, + { :controller => 'files', :action => 'new', :project_id => '33' } + ) + assert_routing( + { :method => 'post', :path => "/projects/33/files" }, + { :controller => 'files', :action => 'create', :project_id => '33' } + ) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7f/7f793d123c11e16acd2d74a86ee6e86fa59cf1d2.svn-base --- a/.svn/pristine/7f/7f793d123c11e16acd2d74a86ee6e86fa59cf1d2.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,97 +0,0 @@ -== Redmine installation - -Redmine - project management software -Copyright (C) 2006-2011 Jean-Philippe Lang -http://www.redmine.org/ - - -== Requirements - -* Ruby 1.8.6 or 1.8.7 - -* RubyGems 1.3.7 - -* Ruby on Rails 2.3.14 (official downloadable Redmine releases are packaged with - the appropriate Rails version) - -* Rack 1.1.2 gem - -* Rake 0.9.2 gem - -* I18n 0.4.2 gem - -* A database: - * MySQL (tested with MySQL 5.1) - * PostgreSQL (tested with PostgreSQL 8.4) - * SQLite3 (tested with SQLite 3.6) - -Optional: -* SCM binaries (e.g. svn), for repository browsing (must be available in PATH) -* RMagick (to enable Gantt export to png images) -* Ruby OpenID Library >= version 2 (to enable OpenID support) - -== Installation - -1. Uncompress the program archive - -2. Create an empty database: "redmine" for example - -3. Configure the database parameters in config/database.yml - for the "production" environment (default database is MySQL) - -4. Generate a session store secret - - Redmine stores session data in cookies by default, which requires - a secret to be generated. Under the application main directory run: - rake generate_session_store - -5. Create the database structure - - Under the application main directory run: - rake db:migrate RAILS_ENV="production" - - It will create all the tables and an administrator account. - -6. Setting up permissions (Windows users have to skip this section) - - The user who runs Redmine must have write permission on the following - subdirectories: files, log, tmp & public/plugin_assets (create the last - two if they are not yet present). - - Assuming you run Redmine with a user named "redmine": - mkdir tmp public/plugin_assets - sudo chown -R redmine:redmine files log tmp public/plugin_assets - sudo chmod -R 755 files log tmp public/plugin_assets - -7. Test the installation by running the WEBrick web server - - Under the main application directory run: - ruby script/server -e production - - Once WEBrick has started, point your browser to http://localhost:3000/ - You should now see the application welcome page. - -8. Use the default administrator account to log in: - login: admin - password: admin - - Go to "Administration" to load the default configuration data (roles, - trackers, statuses, workflow) and to adjust the application settings - -== SMTP server Configuration - -Copy config/configuration.yml.example to config/configuration.yml and -edit this file to adjust your SMTP settings. -Do not forget to restart the application after any change to this file. - -Please do not enter your SMTP settings in environment.rb. - -== References - -* http://www.redmine.org/wiki/redmine/RedmineInstall -* http://www.redmine.org/wiki/redmine/EmailConfiguration -* http://www.redmine.org/wiki/redmine/RedmineSettings -* http://www.redmine.org/wiki/redmine/RedmineRepositories -* http://www.redmine.org/wiki/redmine/RedmineReceivingEmails -* http://www.redmine.org/wiki/redmine/RedmineReminderEmails -* http://www.redmine.org/wiki/redmine/RedmineLDAP diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/7f/7fd2e45fcd400139b222df563299c0b121c16798.svn-base --- a/.svn/pristine/7f/7fd2e45fcd400139b222df563299c0b121c16798.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,6 +0,0 @@ -

    <%= l(:label_query_new) %>

    - -<% form_tag(@project ? project_queries_path : queries_path, :onsubmit => 'selectAllOptions("selected_columns");') do %> - <%= render :partial => 'form', :locals => {:query => @query} %> - <%= submit_tag l(:button_save) %> -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/80/801b8da4e3d5456a0e7124a4e9736317dd235f5b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/80/801b8da4e3d5456a0e7124a4e9736317dd235f5b.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,377 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class RepositoryMercurialTest < ActiveSupport::TestCase + fixtures :projects + + include Redmine::I18n + + REPOSITORY_PATH = Rails.root.join('tmp/test/mercurial_repository').to_s + NUM_REV = 32 + CHAR_1_HEX = "\xc3\x9c" + + def setup + @project = Project.find(3) + @repository = Repository::Mercurial.create( + :project => @project, + :url => REPOSITORY_PATH, + :path_encoding => 'ISO-8859-1' + ) + assert @repository + @char_1 = CHAR_1_HEX.dup + @tag_char_1 = "tag-#{CHAR_1_HEX}-00" + @branch_char_0 = "branch-#{CHAR_1_HEX}-00" + @branch_char_1 = "branch-#{CHAR_1_HEX}-01" + if @char_1.respond_to?(:force_encoding) + @char_1.force_encoding('UTF-8') + @tag_char_1.force_encoding('UTF-8') + @branch_char_0.force_encoding('UTF-8') + @branch_char_1.force_encoding('UTF-8') + end + end + + + def test_blank_path_to_repository_error_message + set_language_if_valid 'en' + repo = Repository::Mercurial.new( + :project => @project, + :identifier => 'test' + ) + assert !repo.save + assert_include "Path to repository can't be blank", + repo.errors.full_messages + end + + def test_blank_path_to_repository_error_message_fr + set_language_if_valid 'fr' + str = "Chemin du d\xc3\xa9p\xc3\xb4t doit \xc3\xaatre renseign\xc3\xa9(e)" + str.force_encoding('UTF-8') if str.respond_to?(:force_encoding) + repo = Repository::Mercurial.new( + :project => @project, + :url => "", + :identifier => 'test', + :path_encoding => '' + ) + assert !repo.save + assert_include str, repo.errors.full_messages + end + + if File.directory?(REPOSITORY_PATH) + def test_scm_available + klass = Repository::Mercurial + assert_equal "Mercurial", klass.scm_name + assert klass.scm_adapter_class + assert_not_equal "", klass.scm_command + assert_equal true, klass.scm_available + end + + def test_entries + entries = @repository.entries + assert_kind_of Redmine::Scm::Adapters::Entries, entries + end + + def test_fetch_changesets_from_scratch + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + assert_equal 46, @repository.filechanges.count + assert_equal "Initial import.\nThe repository contains 3 files.", + @repository.changesets.find_by_revision('0').comments + end + + def test_fetch_changesets_incremental + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + # Remove changesets with revision > 2 + @repository.changesets.all.each {|c| c.destroy if c.revision.to_i > 2} + @project.reload + assert_equal 3, @repository.changesets.count + + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + end + + def test_isodatesec + # Template keyword 'isodatesec' supported in Mercurial 1.0 and higher + if @repository.scm.class.client_version_above?([1, 0]) + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + rev0_committed_on = Time.gm(2007, 12, 14, 9, 22, 52) + assert_equal @repository.changesets.find_by_revision('0').committed_on, rev0_committed_on + end + end + + def test_changeset_order_by_revision + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + + c0 = @repository.latest_changeset + c1 = @repository.changesets.find_by_revision('0') + # sorted by revision (id), not by date + assert c0.revision.to_i > c1.revision.to_i + assert c0.committed_on < c1.committed_on + end + + def test_latest_changesets + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + + # with_limit + changesets = @repository.latest_changesets('', nil, 2) + assert_equal %w|31 30|, changesets.collect(&:revision) + + # with_filepath + changesets = @repository.latest_changesets( + '/sql_escape/percent%dir/percent%file1.txt', nil) + assert_equal %w|30 11 10 9|, changesets.collect(&:revision) + + changesets = @repository.latest_changesets( + '/sql_escape/underscore_dir/understrike_file.txt', nil) + assert_equal %w|30 12 9|, changesets.collect(&:revision) + + changesets = @repository.latest_changesets('README', nil) + assert_equal %w|31 30 28 17 8 6 1 0|, changesets.collect(&:revision) + + changesets = @repository.latest_changesets('README','8') + assert_equal %w|8 6 1 0|, changesets.collect(&:revision) + + changesets = @repository.latest_changesets('README','8', 2) + assert_equal %w|8 6|, changesets.collect(&:revision) + + # with_dirpath + changesets = @repository.latest_changesets('images', nil) + assert_equal %w|1 0|, changesets.collect(&:revision) + + path = 'sql_escape/percent%dir' + changesets = @repository.latest_changesets(path, nil) + assert_equal %w|30 13 11 10 9|, changesets.collect(&:revision) + + changesets = @repository.latest_changesets(path, '11') + assert_equal %w|11 10 9|, changesets.collect(&:revision) + + changesets = @repository.latest_changesets(path, '11', 2) + assert_equal %w|11 10|, changesets.collect(&:revision) + + path = 'sql_escape/underscore_dir' + changesets = @repository.latest_changesets(path, nil) + assert_equal %w|30 13 12 9|, changesets.collect(&:revision) + + changesets = @repository.latest_changesets(path, '12') + assert_equal %w|12 9|, changesets.collect(&:revision) + + changesets = @repository.latest_changesets(path, '12', 1) + assert_equal %w|12|, changesets.collect(&:revision) + + # tag + changesets = @repository.latest_changesets('', 'tag_test.00') + assert_equal %w|5 4 3 2 1 0|, changesets.collect(&:revision) + + changesets = @repository.latest_changesets('', 'tag_test.00', 2) + assert_equal %w|5 4|, changesets.collect(&:revision) + + changesets = @repository.latest_changesets('sources', 'tag_test.00') + assert_equal %w|4 3 2 1 0|, changesets.collect(&:revision) + + changesets = @repository.latest_changesets('sources', 'tag_test.00', 2) + assert_equal %w|4 3|, changesets.collect(&:revision) + + # named branch + if @repository.scm.class.client_version_above?([1, 6]) + changesets = @repository.latest_changesets('', @branch_char_1) + assert_equal %w|27 26|, changesets.collect(&:revision) + end + + changesets = @repository.latest_changesets("latin-1-dir/test-#{@char_1}-subdir", @branch_char_1) + assert_equal %w|27|, changesets.collect(&:revision) + end + + def test_copied_files + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + + cs1 = @repository.changesets.find_by_revision('13') + assert_not_nil cs1 + c1 = cs1.filechanges.sort_by(&:path) + assert_equal 2, c1.size + + assert_equal 'A', c1[0].action + assert_equal '/sql_escape/percent%dir/percentfile1.txt', c1[0].path + assert_equal '/sql_escape/percent%dir/percent%file1.txt', c1[0].from_path + assert_equal '3a330eb32958', c1[0].from_revision + + assert_equal 'A', c1[1].action + assert_equal '/sql_escape/underscore_dir/understrike-file.txt', c1[1].path + assert_equal '/sql_escape/underscore_dir/understrike_file.txt', c1[1].from_path + + cs2 = @repository.changesets.find_by_revision('15') + c2 = cs2.filechanges + assert_equal 1, c2.size + + assert_equal 'A', c2[0].action + assert_equal '/README (1)[2]&,%.-3_4', c2[0].path + assert_equal '/README', c2[0].from_path + assert_equal '933ca60293d7', c2[0].from_revision + + cs3 = @repository.changesets.find_by_revision('19') + c3 = cs3.filechanges + assert_equal 1, c3.size + assert_equal 'A', c3[0].action + assert_equal "/latin-1-dir/test-#{@char_1}-1.txt", c3[0].path + assert_equal "/latin-1-dir/test-#{@char_1}.txt", c3[0].from_path + assert_equal '5d9891a1b425', c3[0].from_revision + end + + def test_find_changeset_by_name + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + %w|2 400bb8672109 400|.each do |r| + assert_equal '2', @repository.find_changeset_by_name(r).revision + end + end + + def test_find_changeset_by_invalid_name + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + assert_nil @repository.find_changeset_by_name('100000') + end + + def test_identifier + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + c = @repository.changesets.find_by_revision('2') + assert_equal c.scmid, c.identifier + end + + def test_format_identifier + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + c = @repository.changesets.find_by_revision('2') + assert_equal '2:400bb8672109', c.format_identifier + end + + def test_find_changeset_by_empty_name + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + ['', ' ', nil].each do |r| + assert_nil @repository.find_changeset_by_name(r) + end + end + + def test_parents + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + r1 = @repository.changesets.find_by_revision('0') + assert_equal [], r1.parents + r2 = @repository.changesets.find_by_revision('1') + assert_equal 1, r2.parents.length + assert_equal "0885933ad4f6", + r2.parents[0].identifier + r3 = @repository.changesets.find_by_revision('30') + assert_equal 2, r3.parents.length + r4 = [r3.parents[0].identifier, r3.parents[1].identifier].sort + assert_equal "3a330eb32958", r4[0] + assert_equal "a94b0528f24f", r4[1] + end + + def test_activities + c = Changeset.new(:repository => @repository, + :committed_on => Time.now, + :revision => '123', + :scmid => 'abc400bb8672', + :comments => 'test') + assert c.event_title.include?('123:abc400bb8672:') + assert_equal 'abc400bb8672', c.event_url[:rev] + end + + def test_previous + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + %w|28 3ae45e2d177d 3ae45|.each do |r1| + changeset = @repository.find_changeset_by_name(r1) + %w|27 7bbf4c738e71 7bbf|.each do |r2| + assert_equal @repository.find_changeset_by_name(r2), changeset.previous + end + end + end + + def test_previous_nil + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + %w|0 0885933ad4f6 0885|.each do |r1| + changeset = @repository.find_changeset_by_name(r1) + assert_nil changeset.previous + end + end + + def test_next + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + %w|27 7bbf4c738e71 7bbf|.each do |r2| + changeset = @repository.find_changeset_by_name(r2) + %w|28 3ae45e2d177d 3ae45|.each do |r1| + assert_equal @repository.find_changeset_by_name(r1), changeset.next + end + end + end + + def test_next_nil + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + %w|31 31eeee7395c8 31eee|.each do |r1| + changeset = @repository.find_changeset_by_name(r1) + assert_nil changeset.next + end + end + else + puts "Mercurial test repository NOT FOUND. Skipping unit tests !!!" + def test_fake; assert true end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/80/8038f5899dbf50a50f4a5b31cb29861746dd574c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/80/8038f5899dbf50a50f4a5b31cb29861746dd574c.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,51 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class Redmine::ApiTest::TrackersTest < Redmine::ApiTest::Base + fixtures :trackers + + def setup + Setting.rest_api_enabled = '1' + end + + context "/trackers" do + context "GET" do + + should "return trackers" do + get '/trackers.xml' + + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_tag :tag => 'trackers', + :attributes => {:type => 'array'}, + :child => { + :tag => 'tracker', + :child => { + :tag => 'id', + :content => '2', + :sibling => { + :tag => 'name', + :content => 'Feature request' + } + } + } + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/80/803dbec2bea6411200938908113051447e1d81f1.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/80/803dbec2bea6411200938908113051447e1d81f1.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,173 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require 'digest/md5' + +module Redmine + module WikiFormatting + class StaleSectionError < Exception; end + + @@formatters = {} + + class << self + def map + yield self + end + + def register(name, formatter, helper) + raise ArgumentError, "format name '#{name}' is already taken" if @@formatters[name.to_s] + @@formatters[name.to_s] = {:formatter => formatter, :helper => helper} + end + + def formatter + formatter_for(Setting.text_formatting) + end + + def formatter_for(name) + entry = @@formatters[name.to_s] + (entry && entry[:formatter]) || Redmine::WikiFormatting::NullFormatter::Formatter + end + + def helper_for(name) + entry = @@formatters[name.to_s] + (entry && entry[:helper]) || Redmine::WikiFormatting::NullFormatter::Helper + end + + def format_names + @@formatters.keys.map + end + + def to_html(format, text, options = {}) + text = if Setting.cache_formatted_text? && text.size > 2.kilobyte && cache_store && cache_key = cache_key_for(format, text, options[:object], options[:attribute]) + # Text retrieved from the cache store may be frozen + # We need to dup it so we can do in-place substitutions with gsub! + cache_store.fetch cache_key do + formatter_for(format).new(text).to_html + end.dup + else + formatter_for(format).new(text).to_html + end + text + end + + # Returns true if the text formatter supports single section edit + def supports_section_edit? + (formatter.instance_methods & ['update_section', :update_section]).any? + end + + # Returns a cache key for the given text +format+, +text+, +object+ and +attribute+ or nil if no caching should be done + def cache_key_for(format, text, object, attribute) + if object && attribute && !object.new_record? && format.present? + "formatted_text/#{format}/#{object.class.model_name.cache_key}/#{object.id}-#{attribute}-#{Digest::MD5.hexdigest text}" + end + end + + # Returns the cache store used to cache HTML output + def cache_store + ActionController::Base.cache_store + end + end + + module LinksHelper + AUTO_LINK_RE = %r{ + ( # leading text + <\w+.*?>| # leading HTML tag, or + [\s\(\[,;]| # leading punctuation, or + ^ # beginning of line + ) + ( + (?:https?://)| # protocol spec, or + (?:s?ftps?://)| + (?:www\.) # www.* + ) + ( + ([^<]\S*?) # url + (\/)? # slash + ) + ((?:>)?|[^[:alnum:]_\=\/;\(\)]*?) # post + (?=<|\s|$) + }x unless const_defined?(:AUTO_LINK_RE) + + # Destructively remplaces urls into clickable links + def auto_link!(text) + text.gsub!(AUTO_LINK_RE) do + all, leading, proto, url, post = $&, $1, $2, $3, $6 + if 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 + content = proto + url + href = "#{proto=="www."?"http://www.":proto}#{url}" + %(#{leading}#{ERB::Util.html_escape content}#{post}).html_safe + end + end + end + + # Destructively remplaces email addresses into clickable links + def auto_mailto!(text) + text.gsub!(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do + mail = $1 + if text.match(/]*>(.*)(#{Regexp.escape(mail)})(.*)<\/a>/) + mail + else + %().html_safe + end + end + end + end + + # Default formatter module + module NullFormatter + class Formatter + include ActionView::Helpers::TagHelper + include ActionView::Helpers::TextHelper + include ActionView::Helpers::UrlHelper + include Redmine::WikiFormatting::LinksHelper + + def initialize(text) + @text = text + end + + def to_html(*args) + t = CGI::escapeHTML(@text) + auto_link!(t) + auto_mailto!(t) + simple_format(t, {}, :sanitize => false) + end + end + + module Helper + def wikitoolbar_for(field_id) + end + + def heads_for_wiki_formatter + end + + def initial_page_content(page) + page.pretty_title.to_s + end + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/80/805a8b03f317de442787ef8a23fd231ec228a620.svn-base --- a/.svn/pristine/80/805a8b03f317de442787ef8a23fd231ec228a620.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,77 +0,0 @@ -# ActsAsWatchable -module Redmine - module Acts - module Watchable - def self.included(base) - base.extend ClassMethods - end - - module ClassMethods - def acts_as_watchable(options = {}) - return if self.included_modules.include?(Redmine::Acts::Watchable::InstanceMethods) - send :include, Redmine::Acts::Watchable::InstanceMethods - - class_eval do - has_many :watchers, :as => :watchable, :dependent => :delete_all - has_many :watcher_users, :through => :watchers, :source => :user, :validate => false - - named_scope :watched_by, lambda { |user_id| - { :include => :watchers, - :conditions => ["#{Watcher.table_name}.user_id = ?", user_id] } - } - attr_protected :watcher_ids, :watcher_user_ids - end - end - end - - module InstanceMethods - def self.included(base) - base.extend ClassMethods - end - - # Returns an array of users that are proposed as watchers - def addable_watcher_users - users = self.project.users.sort - self.watcher_users - if respond_to?(:visible?) - users.reject! {|user| !visible?(user)} - end - users - end - - # Adds user as a watcher - def add_watcher(user) - self.watchers << Watcher.new(:user => user) - end - - # Removes user from the watchers list - def remove_watcher(user) - return nil unless user && user.is_a?(User) - Watcher.delete_all "watchable_type = '#{self.class}' AND watchable_id = #{self.id} AND user_id = #{user.id}" - end - - # Adds/removes watcher - def set_watcher(user, watching=true) - watching ? add_watcher(user) : remove_watcher(user) - end - - # Returns true if object is watched by +user+ - def watched_by?(user) - !!(user && self.watcher_user_ids.detect {|uid| uid == user.id }) - end - - # Returns an array of watchers' email addresses - def watcher_recipients - notified = watcher_users.active - notified.reject! {|user| user.mail_notification == 'none'} - - if respond_to?(:visible?) - notified.reject! {|user| !visible?(user)} - end - notified.collect(&:mail).compact - end - - module ClassMethods; end - end - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/80/808c2b8d656d91389ca1e0955ebdc3c622ab9ac0.svn-base --- a/.svn/pristine/80/808c2b8d656d91389ca1e0955ebdc3c622ab9ac0.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +0,0 @@ -

    <%= @query.new_record? ? l(:label_calendar) : h(@query.name) %>

    - -<% form_tag({:controller => 'calendars', :action => 'show', :project_id => @project}, :method => :get, :id => 'query_form') do %> -<%= hidden_field_tag 'set_filter', '1' %> -
    "> - <%= l(:label_filter_plural) %> -
    "> - <%= render :partial => 'queries/filters', :locals => {:query => @query} %> -
    -
    - -

    - <%= link_to_previous_month(@year, @month) %> | <%= link_to_next_month(@year, @month) %> -

    - -

    -<%= label_tag('month', l(:label_month)) %> -<%= select_month(@month, :prefix => "month", :discard_type => true) %> -<%= label_tag('year', l(:label_year)) %> -<%= select_year(@year, :prefix => "year", :discard_type => true) %> - -<%= link_to_function l(:button_apply), '$("query_form").submit()', :class => 'icon icon-checked' %> -<%= link_to l(:button_clear), { :project_id => @project, :set_filter => 1 }, :class => 'icon icon-reload' %> -

    -<% end %> - -<%= error_messages_for 'query' %> -<% if @query.valid? %> -<%= render :partial => 'common/calendar', :locals => {:calendar => @calendar} %> - -

    - <%= l(:text_tip_issue_begin_day) %> - <%= l(:text_tip_issue_end_day) %> - <%= l(:text_tip_issue_begin_end_day) %> -

    -<% end %> - -<% content_for :sidebar do %> - <%= render :partial => 'issues/sidebar' %> -<% end %> - -<% html_title(l(:label_calendar)) -%> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/80/80cc012f0ec91e8ef1adc8047efcacc9dac1c803.svn-base --- a/.svn/pristine/80/80cc012f0ec91e8ef1adc8047efcacc9dac1c803.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,52 +0,0 @@ -

    <%= link_to l(:label_role_plural), :controller => 'roles', :action => 'index' %> » <%=l(:label_permissions_report)%>

    - -<% form_tag({:action => 'report'}, :id => 'permissions_form') do %> -<%= hidden_field_tag 'permissions[0]', '', :id => nil %> -
    - - - - - <% @roles.each do |role| %> - - <% end %> - - - -<% perms_by_module = @permissions.group_by {|p| p.project_module.to_s} %> -<% perms_by_module.keys.sort.each do |mod| %> - <% unless mod.blank? %> - - - - <% end %> - <% perms_by_module[mod].each do |permission| %> - - - <% @roles.each do |role| %> - - <% end %> - - <% end %> -<% end %> - -
    <%=l(:label_permissions)%> - <%= content_tag(role.builtin? ? 'em' : 'span', h(role.name)) %> - <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('input.role-#{role.id}')", - :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %> -
    -   - <%= l_or_humanize(mod, :prefix => 'project_module_') %> -
    - <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('.permission-#{permission.name} input')", - :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %> - <%= l_or_humanize(permission.name, :prefix => 'permission_') %> - - <% if role.setable_permissions.include? permission %> - <%= check_box_tag "permissions[#{role.id}][]", permission.name, (role.permissions.include? permission.name), :id => nil, :class => "role-#{role.id}" %> - <% end %> -
    -
    -

    <%= check_all_links 'permissions_form' %>

    -

    <%= submit_tag l(:button_save) %>

    -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/81/81172b2094c31db1f0abe25641889634ec935550.svn-base --- a/.svn/pristine/81/81172b2094c31db1f0abe25641889634ec935550.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,13 +0,0 @@ -<%= javascript_include_tag "raphael.js" %> - -<%= javascript_include_tag "revision_graph.js" %> - - - -
    diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/81/811f1ba6d3aa9ff895b79ad6fc02919805520105.svn-base --- a/.svn/pristine/81/811f1ba6d3aa9ff895b79ad6fc02919805520105.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -# just here so that Rails recognizes this as a plugin \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/81/815073e9f3bcaa80c2a4ee60b82257dc8dc26440.svn-base --- a/.svn/pristine/81/815073e9f3bcaa80c2a4ee60b82257dc8dc26440.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1195 +0,0 @@ -# vim:ts=4:sw=4: -# = RedCloth - Textile and Markdown Hybrid for Ruby -# -# Homepage:: http://whytheluckystiff.net/ruby/redcloth/ -# Author:: why the lucky stiff (http://whytheluckystiff.net/) -# Copyright:: (cc) 2004 why the lucky stiff (and his puppet organizations.) -# License:: BSD -# -# (see http://hobix.com/textile/ for a Textile Reference.) -# -# Based on (and also inspired by) both: -# -# PyTextile: http://diveintomark.org/projects/textile/textile.py.txt -# Textism for PHP: http://www.textism.com/tools/textile/ -# -# - -# = RedCloth -# -# RedCloth is a Ruby library for converting Textile and/or Markdown -# into HTML. You can use either format, intermingled or separately. -# You can also extend RedCloth to honor your own custom text stylings. -# -# RedCloth users are encouraged to use Textile if they are generating -# HTML and to use Markdown if others will be viewing the plain text. -# -# == What is Textile? -# -# Textile is a simple formatting style for text -# documents, loosely based on some HTML conventions. -# -# == Sample Textile Text -# -# h2. This is a title -# -# h3. This is a subhead -# -# This is a bit of paragraph. -# -# bq. This is a blockquote. -# -# = Writing Textile -# -# A Textile document consists of paragraphs. Paragraphs -# can be specially formatted by adding a small instruction -# to the beginning of the paragraph. -# -# h[n]. Header of size [n]. -# bq. Blockquote. -# # Numeric list. -# * Bulleted list. -# -# == Quick Phrase Modifiers -# -# Quick phrase modifiers are also included, to allow formatting -# of small portions of text within a paragraph. -# -# \_emphasis\_ -# \_\_italicized\_\_ -# \*strong\* -# \*\*bold\*\* -# ??citation?? -# -deleted text- -# +inserted text+ -# ^superscript^ -# ~subscript~ -# @code@ -# %(classname)span% -# -# ==notextile== (leave text alone) -# -# == Links -# -# To make a hypertext link, put the link text in "quotation -# marks" followed immediately by a colon and the URL of the link. -# -# Optional: text in (parentheses) following the link text, -# but before the closing quotation mark, will become a Title -# attribute for the link, visible as a tool tip when a cursor is above it. -# -# Example: -# -# "This is a link (This is a title) ":http://www.textism.com -# -# Will become: -# -# This is a link -# -# == Images -# -# To insert an image, put the URL for the image inside exclamation marks. -# -# Optional: text that immediately follows the URL in (parentheses) will -# be used as the Alt text for the image. Images on the web should always -# have descriptive Alt text for the benefit of readers using non-graphical -# browsers. -# -# Optional: place a colon followed by a URL immediately after the -# closing ! to make the image into a link. -# -# Example: -# -# !http://www.textism.com/common/textist.gif(Textist)! -# -# Will become: -# -# Textist -# -# With a link: -# -# !/common/textist.gif(Textist)!:http://textism.com -# -# Will become: -# -# Textist -# -# == Defining Acronyms -# -# HTML allows authors to define acronyms via the tag. The definition appears as a -# tool tip when a cursor hovers over the acronym. A crucial aid to clear writing, -# this should be used at least once for each acronym in documents where they appear. -# -# To quickly define an acronym in Textile, place the full text in (parentheses) -# immediately following the acronym. -# -# Example: -# -# ACLU(American Civil Liberties Union) -# -# Will become: -# -# ACLU -# -# == Adding Tables -# -# In Textile, simple tables can be added by seperating each column by -# a pipe. -# -# |a|simple|table|row| -# |And|Another|table|row| -# -# Attributes are defined by style definitions in parentheses. -# -# table(border:1px solid black). -# (background:#ddd;color:red). |{}| | | | -# -# == Using RedCloth -# -# RedCloth is simply an extension of the String class, which can handle -# Textile formatting. Use it like a String and output HTML with its -# RedCloth#to_html method. -# -# doc = RedCloth.new " -# -# h2. Test document -# -# Just a simple test." -# -# puts doc.to_html -# -# By default, RedCloth uses both Textile and Markdown formatting, with -# Textile formatting taking precedence. If you want to turn off Markdown -# formatting, to boost speed and limit the processor: -# -# class RedCloth::Textile.new( str ) - -class RedCloth3 < String - - VERSION = '3.0.4' - DEFAULT_RULES = [:textile, :markdown] - - # - # Two accessor for setting security restrictions. - # - # This is a nice thing if you're using RedCloth for - # formatting in public places (e.g. Wikis) where you - # don't want users to abuse HTML for bad things. - # - # If +:filter_html+ is set, HTML which wasn't - # created by the Textile processor will be escaped. - # - # If +:filter_styles+ is set, it will also disable - # the style markup specifier. ('{color: red}') - # - attr_accessor :filter_html, :filter_styles - - # - # Accessor for toggling hard breaks. - # - # If +:hard_breaks+ is set, single newlines will - # be converted to HTML break tags. This is the - # default behavior for traditional RedCloth. - # - attr_accessor :hard_breaks - - # Accessor for toggling lite mode. - # - # In lite mode, block-level rules are ignored. This means - # that tables, paragraphs, lists, and such aren't available. - # Only the inline markup for bold, italics, entities and so on. - # - # r = RedCloth.new( "And then? She *fell*!", [:lite_mode] ) - # r.to_html - # #=> "And then? She fell!" - # - attr_accessor :lite_mode - - # - # Accessor for toggling span caps. - # - # Textile places `span' tags around capitalized - # words by default, but this wreaks havoc on Wikis. - # If +:no_span_caps+ is set, this will be - # suppressed. - # - attr_accessor :no_span_caps - - # - # Establishes the markup predence. Available rules include: - # - # == Textile Rules - # - # The following textile rules can be set individually. Or add the complete - # set of rules with the single :textile rule, which supplies the rule set in - # the following precedence: - # - # refs_textile:: Textile references (i.e. [hobix]http://hobix.com/) - # block_textile_table:: Textile table block structures - # block_textile_lists:: Textile list structures - # block_textile_prefix:: Textile blocks with prefixes (i.e. bq., h2., etc.) - # inline_textile_image:: Textile inline images - # inline_textile_link:: Textile inline links - # inline_textile_span:: Textile inline spans - # glyphs_textile:: Textile entities (such as em-dashes and smart quotes) - # - # == Markdown - # - # refs_markdown:: Markdown references (for example: [hobix]: http://hobix.com/) - # block_markdown_setext:: Markdown setext headers - # block_markdown_atx:: Markdown atx headers - # block_markdown_rule:: Markdown horizontal rules - # block_markdown_bq:: Markdown blockquotes - # block_markdown_lists:: Markdown lists - # inline_markdown_link:: Markdown links - attr_accessor :rules - - # Returns a new RedCloth object, based on _string_ and - # enforcing all the included _restrictions_. - # - # r = RedCloth.new( "h1. A bold man", [:filter_html] ) - # r.to_html - # #=>"

    A <b>bold</b> man

    " - # - def initialize( string, restrictions = [] ) - restrictions.each { |r| method( "#{ r }=" ).call( true ) } - super( string ) - end - - # - # Generates HTML from the Textile contents. - # - # r = RedCloth.new( "And then? She *fell*!" ) - # r.to_html( true ) - # #=>"And then? She fell!" - # - def to_html( *rules ) - rules = DEFAULT_RULES if rules.empty? - # make our working copy - text = self.dup - - @urlrefs = {} - @shelf = [] - textile_rules = [:block_textile_table, :block_textile_lists, - :block_textile_prefix, :inline_textile_image, :inline_textile_link, - :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] - @rules = rules.collect do |rule| - case rule - when :markdown - markdown_rules - when :textile - textile_rules - else - rule - end - end.flatten - - # standard clean up - incoming_entities text - clean_white_space text - - # start processor - @pre_list = [] - rip_offtags text - no_textile text - escape_html_tags text - # need to do this before #hard_break and #blocks - block_textile_quotes text unless @lite_mode - hard_break text - unless @lite_mode - refs text - blocks text - end - inline text - smooth_offtags text - - retrieve text - - text.gsub!( /<\/?notextile>/, '' ) - text.gsub!( /x%x%/, '&' ) - clean_html text if filter_html - text.strip! - text - - end - - ####### - private - ####### - # - # Mapping of 8-bit ASCII codes to HTML numerical entity equivalents. - # (from PyTextile) - # - TEXTILE_TAGS = - - [[128, 8364], [129, 0], [130, 8218], [131, 402], [132, 8222], [133, 8230], - [134, 8224], [135, 8225], [136, 710], [137, 8240], [138, 352], [139, 8249], - [140, 338], [141, 0], [142, 0], [143, 0], [144, 0], [145, 8216], [146, 8217], - [147, 8220], [148, 8221], [149, 8226], [150, 8211], [151, 8212], [152, 732], - [153, 8482], [154, 353], [155, 8250], [156, 339], [157, 0], [158, 0], [159, 376]]. - - collect! do |a, b| - [a.chr, ( b.zero? and "" or "&#{ b };" )] - end - - # - # Regular expressions to convert to HTML. - # - A_HLGN = /(?:(?:<>|<|>|\=|[()]+)+)/ - A_VLGN = /[\-^~]/ - C_CLAS = '(?:\([^")]+\))' - C_LNGE = '(?:\[[^"\[\]]+\])' - C_STYL = '(?:\{[^"}]+\})' - S_CSPN = '(?:\\\\\d+)' - S_RSPN = '(?:/\d+)' - A = "(?:#{A_HLGN}?#{A_VLGN}?|#{A_VLGN}?#{A_HLGN}?)" - S = "(?:#{S_CSPN}?#{S_RSPN}|#{S_RSPN}?#{S_CSPN}?)" - C = "(?:#{C_CLAS}?#{C_STYL}?#{C_LNGE}?|#{C_STYL}?#{C_LNGE}?#{C_CLAS}?|#{C_LNGE}?#{C_STYL}?#{C_CLAS}?)" - # PUNCT = Regexp::quote( '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' ) - PUNCT = Regexp::quote( '!"#$%&\'*+,-./:;=?@\\^_`|~' ) - PUNCT_NOQ = Regexp::quote( '!"#$&\',./:;=?@\\`|' ) - PUNCT_Q = Regexp::quote( '*-_+^~%' ) - HYPERLINK = '(\S+?)([^\w\s/;=\?]*?)(?=\s|<|$)' - - # Text markup tags, don't conflict with block tags - SIMPLE_HTML_TAGS = [ - 'tt', 'b', 'i', 'big', 'small', 'em', 'strong', 'dfn', 'code', - 'samp', 'kbd', 'var', 'cite', 'abbr', 'acronym', 'a', 'img', 'br', - 'br', 'map', 'q', 'sub', 'sup', 'span', 'bdo' - ] - - QTAGS = [ - ['**', 'b', :limit], - ['*', 'strong', :limit], - ['??', 'cite', :limit], - ['-', 'del', :limit], - ['__', 'i', :limit], - ['_', 'em', :limit], - ['%', 'span', :limit], - ['+', 'ins', :limit], - ['^', 'sup', :limit], - ['~', 'sub', :limit] - ] - QTAGS_JOIN = QTAGS.map {|rc, ht, rtype| Regexp::quote rc}.join('|') - - QTAGS.collect! do |rc, ht, rtype| - rcq = Regexp::quote rc - re = - case rtype - when :limit - /(^|[>\s\(]) # sta - (?!\-\-) - (#{QTAGS_JOIN}|) # oqs - (#{rcq}) # qtag - (\w|[^\s].*?[^\s]) # content - (?!\-\-) - #{rcq} - (#{QTAGS_JOIN}|) # oqa - (?=[[:punct:]]|<|\s|\)|$)/x - else - /(#{rcq}) - (#{C}) - (?::(\S+))? - (\w|[^\s\-].*?[^\s\-]) - #{rcq}/xm - end - [rc, ht, re, rtype] - end - - # Elements to handle - GLYPHS = [ - # [ /([^\s\[{(>])?\'([dmst]\b|ll\b|ve\b|\s|:|$)/, '\1’\2' ], # single closing - # [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)\'/, '\1’' ], # single closing - # [ /\'(?=[#{PUNCT_Q}]*(s\b|[\s#{PUNCT_NOQ}]))/, '’' ], # single closing - # [ /\'/, '‘' ], # single opening - # [ //, '>' ], # greater-than - # [ /([^\s\[{(])?"(\s|:|$)/, '\1”\2' ], # double closing - # [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)"/, '\1”' ], # double closing - # [ /"(?=[#{PUNCT_Q}]*[\s#{PUNCT_NOQ}])/, '”' ], # double closing - # [ /"/, '“' ], # double opening - # [ /\b( )?\.{3}/, '\1…' ], # ellipsis - # [ /\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/, '\1' ], # 3+ uppercase acronym - # [ /(^|[^"][>\s])([A-Z][A-Z0-9 ]+[A-Z0-9])([^\2
    \3', :no_span_caps ], # 3+ uppercase caps - # [ /(\.\s)?\s?--\s?/, '\1—' ], # em dash - # [ /\s->\s/, ' → ' ], # right arrow - # [ /\s-\s/, ' – ' ], # en dash - # [ /(\d+) ?x ?(\d+)/, '\1×\2' ], # dimension sign - # [ /\b ?[(\[]TM[\])]/i, '™' ], # trademark - # [ /\b ?[(\[]R[\])]/i, '®' ], # registered - # [ /\b ?[(\[]C[\])]/i, '©' ] # copyright - ] - - H_ALGN_VALS = { - '<' => 'left', - '=' => 'center', - '>' => 'right', - '<>' => 'justify' - } - - V_ALGN_VALS = { - '^' => 'top', - '-' => 'middle', - '~' => 'bottom' - } - - # - # Flexible HTML escaping - # - def htmlesc( str, mode=:Quotes ) - if str - str.gsub!( '&', '&' ) - str.gsub!( '"', '"' ) if mode != :NoQuotes - str.gsub!( "'", ''' ) if mode == :Quotes - str.gsub!( '<', '<') - str.gsub!( '>', '>') - 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 - #end - text.gsub!(/\b([A-Z][A-Z0-9]{1,})\b(?:[(]([^)]*)[)])/) do |m| - "#{$1}" - end - end - - # Parses Textile attribute lists and builds an HTML attribute string - def pba( text_in, element = "" ) - - return '' unless text_in - - style = [] - text = text_in.dup - if element == 'td' - colspan = $1 if text =~ /\\(\d+)/ - rowspan = $1 if text =~ /\/(\d+)/ - style << "vertical-align:#{ v_align( $& ) };" if text =~ A_VLGN - end - - style << "#{ htmlesc $1 };" if text.sub!( /\{([^}]*)\}/, '' ) && !filter_styles - - lang = $1 if - text.sub!( /\[([^)]+?)\]/, '' ) - - cls = $1 if - text.sub!( /\(([^()]+?)\)/, '' ) - - style << "padding-left:#{ $1.length }em;" if - text.sub!( /([(]+)/, '' ) - - style << "padding-right:#{ $1.length }em;" if text.sub!( /([)]+)/, '' ) - - style << "text-align:#{ h_align( $& ) };" if text =~ A_HLGN - - cls, id = $1, $2 if cls =~ /^(.*?)#(.*)$/ - - atts = '' - atts << " style=\"#{ style.join }\"" unless style.empty? - atts << " class=\"#{ cls }\"" unless cls.to_s.empty? - atts << " lang=\"#{ lang }\"" if lang - atts << " id=\"#{ id }\"" if id - atts << " colspan=\"#{ colspan }\"" if colspan - atts << " rowspan=\"#{ rowspan }\"" if rowspan - - atts - end - - TABLE_RE = /^(?:table(_?#{S}#{A}#{C})\. ?\n)?^(#{A}#{C}\.? ?\|.*?\|)(\n\n|\Z)/m - - # Parses a Textile table block, building HTML from the result. - def block_textile_table( text ) - text.gsub!( TABLE_RE ) do |matches| - - tatts, fullrow = $~[1..2] - tatts = pba( tatts, 'table' ) - tatts = shelve( tatts ) if tatts - rows = [] - - fullrow.each_line do |row| - ratts, row = pba( $1, 'tr' ), $2 if row =~ /^(#{A}#{C}\. )(.*)/m - cells = [] - row.split( /(\|)(?![^\[\|]*\]\])/ )[1..-2].each do |cell| - next if cell == '|' - ctyp = 'd' - ctyp = 'h' if cell =~ /^_/ - - catts = '' - catts, cell = pba( $1, 'td' ), $2 if cell =~ /^(_?#{S}#{A}#{C}\. ?)(.*)/ - - catts = shelve( catts ) if catts - cells << "\t\t\t#{ cell }" - end - ratts = shelve( ratts ) if ratts - rows << "\t\t\n#{ cells.join( "\n" ) }\n\t\t" - end - "\t\n#{ rows.join( "\n" ) }\n\t\n\n" - end - end - - LISTS_RE = /^([#*]+?#{C} .*?)$(?![^#*])/m - LISTS_CONTENT_RE = /^([#*]+)(#{A}#{C}) (.*)$/m - - # Parses Textile lists and generates HTML - def block_textile_lists( text ) - text.gsub!( LISTS_RE ) do |match| - lines = match.split( /\n/ ) - last_line = -1 - depth = [] - lines.each_with_index do |line, line_id| - if line =~ LISTS_CONTENT_RE - tl,atts,content = $~[1..3] - if depth.last - if depth.last.length > tl.length - (depth.length - 1).downto(0) do |i| - break if depth[i].length == tl.length - lines[line_id - 1] << "\n\t\n\t" - depth.pop - end - end - if depth.last and depth.last.length == tl.length - lines[line_id - 1] << '' - end - end - unless depth.last == tl - depth << tl - atts = pba( atts ) - atts = shelve( atts ) if atts - lines[line_id] = "\t<#{ lT(tl) }l#{ atts }>\n\t
  • #{ content }" - else - lines[line_id] = "\t\t
  • #{ content }" - end - last_line = line_id - - else - last_line = line_id - end - if line_id - last_line > 1 or line_id == lines.length - 1 - depth.delete_if do |v| - lines[last_line] << "
  • \n\t" - end - end - end - lines.join( "\n" ) - end - end - - QUOTES_RE = /(^>+([^\n]*?)(\n|$))+/m - QUOTES_CONTENT_RE = /^([> ]+)(.*)$/m - - def block_textile_quotes( text ) - text.gsub!( QUOTES_RE ) do |match| - lines = match.split( /\n/ ) - quotes = '' - indent = 0 - lines.each do |line| - line =~ QUOTES_CONTENT_RE - bq,content = $1, $2 - l = bq.count('>') - if l != indent - quotes << ("\n\n" + (l>indent ? '
    ' * (l-indent) : '
    ' * (indent-l)) + "\n\n") - indent = l - end - quotes << (content + "\n") - end - quotes << ("\n" + '' * indent + "\n\n") - quotes - end - end - - CODE_RE = /(\W) - @ - (?:\|(\w+?)\|)? - (.+?) - @ - (?=\W)/x - - def inline_textile_code( text ) - text.gsub!( CODE_RE ) do |m| - before,lang,code,after = $~[1..4] - lang = " lang=\"#{ lang }\"" if lang - rip_offtags( "#{ before }#{ code }
    #{ after }", false ) - end - end - - def lT( text ) - text =~ /\#$/ ? 'o' : 'u' - end - - def hard_break( text ) - text.gsub!( /(.)\n(?!\Z| *([#*=]+(\s|$)|[{|]))/, "\\1
    " ) if hard_breaks - end - - BLOCKS_GROUP_RE = /\n{2,}(?! )/m - - def blocks( text, deep_code = false ) - text.replace( text.split( BLOCKS_GROUP_RE ).collect do |blk| - plain = blk !~ /\A[#*> ]/ - - # skip blocks that are complex HTML - if blk =~ /^<\/?(\w+).*>/ and not SIMPLE_HTML_TAGS.include? $1 - blk - else - # search for indentation levels - blk.strip! - if blk.empty? - blk - else - code_blk = nil - blk.gsub!( /((?:\n(?:\n^ +[^\n]*)+)+)/m ) do |iblk| - flush_left iblk - blocks iblk, plain - iblk.gsub( /^(\S)/, "\t\\1" ) - if plain - code_blk = iblk; "" - else - iblk - end - end - - block_applied = 0 - @rules.each do |rule_name| - block_applied += 1 if ( rule_name.to_s.match /^block_/ and method( rule_name ).call( blk ) ) - end - if block_applied.zero? - if deep_code - blk = "\t
    #{ blk }
    " - else - blk = "\t

    #{ blk }

    " - end - end - # hard_break blk - blk + "\n#{ code_blk }" - end - end - - end.join( "\n\n" ) ) - end - - def textile_bq( tag, atts, cite, content ) - cite, cite_title = check_refs( cite ) - cite = " cite=\"#{ cite }\"" if cite - atts = shelve( atts ) if atts - "\t\n\t\t#{ content }

    \n\t" - end - - def textile_p( tag, atts, cite, content ) - atts = shelve( atts ) if atts - "\t<#{ tag }#{ atts }>#{ content }" - end - - alias textile_h1 textile_p - alias textile_h2 textile_p - alias textile_h3 textile_p - alias textile_h4 textile_p - alias textile_h5 textile_p - alias textile_h6 textile_p - - def textile_fn_( tag, num, atts, cite, content ) - atts << " id=\"fn#{ num }\" class=\"footnote\"" - content = "#{ num } #{ content }" - atts = shelve( atts ) if atts - "\t#{ content }

    " - end - - BLOCK_RE = /^(([a-z]+)(\d*))(#{A}#{C})\.(?::(\S+))? (.*)$/m - - def block_textile_prefix( text ) - if text =~ BLOCK_RE - tag,tagpre,num,atts,cite,content = $~[1..6] - atts = pba( atts ) - - # pass to prefix handler - replacement = nil - if respond_to? "textile_#{ tag }", true - replacement = method( "textile_#{ tag }" ).call( tag, atts, cite, content ) - elsif respond_to? "textile_#{ tagpre }_", true - replacement = method( "textile_#{ tagpre }_" ).call( tagpre, num, atts, cite, content ) - end - text.gsub!( $& ) { replacement } if replacement - end - end - - SETEXT_RE = /\A(.+?)\n([=-])[=-]* *$/m - def block_markdown_setext( text ) - if text =~ SETEXT_RE - tag = if $2 == "="; "h1"; else; "h2"; end - blk, cont = "<#{ tag }>#{ $1 }", $' - blocks cont - text.replace( blk + cont ) - end - end - - ATX_RE = /\A(\#{1,6}) # $1 = string of #'s - [ ]* - (.+?) # $2 = Header text - [ ]* - \#* # optional closing #'s (not counted) - $/x - def block_markdown_atx( text ) - if text =~ ATX_RE - tag = "h#{ $1.length }" - blk, cont = "<#{ tag }>#{ $2 }\n\n", $' - blocks cont - text.replace( blk + cont ) - end - end - - MARKDOWN_BQ_RE = /\A(^ *> ?.+$(.+\n)*\n*)+/m - - def block_markdown_bq( text ) - text.gsub!( MARKDOWN_BQ_RE ) do |blk| - blk.gsub!( /^ *> ?/, '' ) - flush_left blk - blocks blk - blk.gsub!( /^(\S)/, "\t\\1" ) - "
    \n#{ blk }\n
    \n\n" - end - end - - MARKDOWN_RULE_RE = /^(#{ - ['*', '-', '_'].collect { |ch| ' ?(' + Regexp::quote( ch ) + ' ?){3,}' }.join( '|' ) - })$/ - - def block_markdown_rule( text ) - text.gsub!( MARKDOWN_RULE_RE ) do |blk| - "
    " - end - end - - # XXX TODO XXX - def block_markdown_lists( text ) - end - - def inline_textile_span( text ) - QTAGS.each do |qtag_rc, ht, qtag_re, rtype| - text.gsub!( qtag_re ) do |m| - - case rtype - when :limit - sta,oqs,qtag,content,oqa = $~[1..6] - atts = nil - if content =~ /^(#{C})(.+)$/ - atts, content = $~[1..2] - end - else - qtag,atts,cite,content = $~[1..4] - sta = '' - end - atts = pba( atts ) - atts = shelve( atts ) if atts - - "#{ sta }#{ oqs }<#{ ht }#{ atts }>#{ content }#{ oqa }" - - end - end - end - - LINK_RE = / - ( - ([\s\[{(]|[#{PUNCT}])? # $pre - " # start - (#{C}) # $atts - ([^"\n]+?) # $text - \s? - (?:\(([^)]+?)\)(?="))? # $title - ": - ( # $url - (\/|[a-zA-Z]+:\/\/|www\.|mailto:) # $proto - [\w\/]\S+? - ) - (\/)? # $slash - ([^\w\=\/;\(\)]*?) # $post - ) - (?=<|\s|$) - /x -#" - def inline_textile_link( text ) - text.gsub!( LINK_RE ) do |m| - all,pre,atts,text,title,url,proto,slash,post = $~[1..9] - if text.include?('
    ') - all - else - url, url_title = check_refs( url ) - title ||= url_title - - # 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 - atts = pba( atts ) - atts = " href=\"#{ htmlesc url }#{ slash }\"#{ atts }" - atts << " title=\"#{ htmlesc title }\"" if title - atts = shelve( atts ) if atts - - external = (url =~ /^https?:\/\//) ? ' class="external"' : '' - - "#{ pre }#{ text }#{ post }" - end - end - end - - MARKDOWN_REFLINK_RE = / - \[([^\[\]]+)\] # $text - [ ]? # opt. space - (?:\n[ ]*)? # one optional newline followed by spaces - \[(.*?)\] # $id - /x - - def inline_markdown_reflink( text ) - text.gsub!( MARKDOWN_REFLINK_RE ) do |m| - text, id = $~[1..2] - - if id.empty? - url, title = check_refs( text ) - else - url, title = check_refs( id ) - end - - atts = " href=\"#{ url }\"" - atts << " title=\"#{ title }\"" if title - atts = shelve( atts ) - - "#{ text }" - end - end - - MARKDOWN_LINK_RE = / - \[([^\[\]]+)\] # $text - \( # open paren - [ \t]* # opt space - ? # $href - [ \t]* # opt space - (?: # whole title - (['"]) # $quote - (.*?) # $title - \3 # matching quote - )? # title is optional - \) - /x - - def inline_markdown_link( text ) - text.gsub!( MARKDOWN_LINK_RE ) do |m| - text, url, quote, title = $~[1..4] - - atts = " href=\"#{ url }\"" - atts << " title=\"#{ title }\"" if title - atts = shelve( atts ) - - "#{ text }" - end - end - - TEXTILE_REFS_RE = /(^ *)\[([^\[\n]+?)\](#{HYPERLINK})(?=\s|$)/ - MARKDOWN_REFS_RE = /(^ *)\[([^\n]+?)\]:\s+?(?:\s+"((?:[^"]|\\")+)")?(?=\s|$)/m - - def refs( text ) - @rules.each do |rule_name| - method( rule_name ).call( text ) if rule_name.to_s.match /^refs_/ - end - end - - def refs_textile( text ) - text.gsub!( TEXTILE_REFS_RE ) do |m| - flag, url = $~[2..3] - @urlrefs[flag.downcase] = [url, nil] - nil - end - end - - def refs_markdown( text ) - text.gsub!( MARKDOWN_REFS_RE ) do |m| - flag, url = $~[2..3] - title = $~[6] - @urlrefs[flag.downcase] = [url, title] - nil - end - end - - def check_refs( text ) - ret = @urlrefs[text.downcase] if text - ret || [text, nil] - end - - IMAGE_RE = / - (>|\s|^) # start of line? - \! # opening - (\<|\=|\>)? # optional alignment atts - (#{C}) # optional style,class atts - (?:\. )? # optional dot-space - ([^\s(!]+?) # presume this is the src - \s? # optional space - (?:\(((?:[^\(\)]|\([^\)]+\))+?)\))? # optional title - \! # closing - (?::#{ HYPERLINK })? # optional href - /x - - 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=\"#{ htmlesc url.dup }\"#{ atts }" - atts << " title=\"#{ title }\"" if title - atts << " alt=\"#{ title }\"" - # size = @getimagesize($url); - # if($size) $atts.= " $size[3]"; - - href, alt_title = check_refs( href ) if href - url, url_title = check_refs( url ) - - out = '' - out << "" if href - out << "" - out << "#{ href_a1 }#{ href_a2 }" if href - - if algn - algn = h_align( algn ) - if stln == "

    " - out = "

    #{ out }" - else - out = "#{ stln }

    #{ out }
    " - end - else - out = stln + out - end - - out - end - end - - def shelve( val ) - @shelf << val - " :redsh##{ @shelf.length }:" - end - - def retrieve( text ) - @shelf.each_with_index do |r, i| - text.gsub!( " :redsh##{ i + 1 }:", r ) - end - end - - def incoming_entities( text ) - ## turn any incoming ampersands into a dummy character for now. - ## This uses a negative lookahead for alphanumerics followed by a semicolon, - ## implying an incoming html entity, to be skipped - - text.gsub!( /&(?![#a-z0-9]+;)/i, "x%x%" ) - end - - def no_textile( text ) - text.gsub!( /(^|\s)==([^=]+.*?)==(\s|$)?/, - '\1\2\3' ) - text.gsub!( /^ *==([^=]+.*?)==/m, - '\1\2\3' ) - end - - def clean_white_space( text ) - # normalize line breaks - text.gsub!( /\r\n/, "\n" ) - text.gsub!( /\r/, "\n" ) - text.gsub!( /\t/, ' ' ) - text.gsub!( /^ +$/, '' ) - text.gsub!( /\n{3,}/, "\n\n" ) - text.gsub!( /"$/, "\" " ) - - # if entire document is indented, flush - # to the left side - flush_left text - end - - def flush_left( text ) - indt = 0 - if text =~ /^ / - while text !~ /^ {#{indt}}\S/ - indt += 1 - end unless text.empty? - if indt.nonzero? - text.gsub!( /^ {#{indt}}/, '' ) - end - end - end - - def footnote_ref( text ) - text.gsub!( /\b\[([0-9]+?)\](\s)?/, - '\1\2' ) - end - - OFFTAGS = /(code|pre|kbd|notextile)/ - OFFTAG_MATCH = /(?:(<\/#{ OFFTAGS }>)|(<#{ OFFTAGS }[^>]*>))(.*?)(?=<\/?#{ OFFTAGS }\W|\Z)/mi - OFFTAG_OPEN = /<#{ OFFTAGS }/ - OFFTAG_CLOSE = /<\/?#{ OFFTAGS }/ - HASTAG_MATCH = /(<\/?\w[^\n]*?>)/m - ALLTAG_MATCH = /(<\/?\w[^\n]*?>)|.*?(?=<\/?\w[^\n]*?>|$)/m - - def glyphs_textile( text, level = 0 ) - if text !~ HASTAG_MATCH - pgl text - footnote_ref text - else - codepre = 0 - text.gsub!( ALLTAG_MATCH ) do |line| - ## matches are off if we're between ,
     etc.
    -                if $1
    -                    if line =~ OFFTAG_OPEN
    -                        codepre += 1
    -                    elsif line =~ OFFTAG_CLOSE
    -                        codepre -= 1
    -                        codepre = 0 if codepre < 0
    -                    end 
    -                elsif codepre.zero?
    -                    glyphs_textile( line, level + 1 )
    -                else
    -                    htmlesc( line, :NoQuotes )
    -                end
    -                # p [level, codepre, line]
    -
    -                line
    -            end
    -        end
    -    end
    -
    -    def rip_offtags( text, escape_aftertag=true, escape_line=true )
    -        if text =~ /<.*>/
    -            ## strip and encode 
     content
    -            codepre, used_offtags = 0, {}
    -            text.gsub!( OFFTAG_MATCH ) do |line|
    -                if $3
    -                    first, offtag, aftertag = $3, $4, $5
    -                    codepre += 1
    -                    used_offtags[offtag] = true
    -                    if codepre - used_offtags.length > 0
    -                        htmlesc( line, :NoQuotes ) if escape_line
    -                        @pre_list.last << line
    -                        line = ""
    -                    else
    -                        ### htmlesc is disabled between CODE tags which will be parsed with highlighter
    -                        ### Regexp in formatter.rb is : /\s?(.+)/m
    -                        ### NB: some changes were made not to use $N variables, because we use "match"
    -                        ###   and it breaks following lines
    -                        htmlesc( aftertag, :NoQuotes ) if aftertag && escape_aftertag && !first.match(//)
    -                        line = ""
    -                        first.match(/<#{ OFFTAGS }([^>]*)>/)
    -                        tag = $1
    -                        $2.to_s.match(/(class\=("[^"]+"|'[^']+'))/i)
    -                        tag << " #{$1}" if $1
    -                        @pre_list << "<#{ tag }>#{ aftertag }"
    -                    end
    -                elsif $1 and codepre > 0
    -                    if codepre - used_offtags.length > 0
    -                        htmlesc( line, :NoQuotes ) if escape_line
    -                        @pre_list.last << line
    -                        line = ""
    -                    end
    -                    codepre -= 1 unless codepre.zero?
    -                    used_offtags = {} if codepre.zero?
    -                end 
    -                line
    -            end
    -        end
    -        text
    -    end
    -
    -    def smooth_offtags( text )
    -        unless @pre_list.empty?
    -            ## replace 
     content
    -            text.gsub!( // ) { @pre_list[$1.to_i] }
    -        end
    -    end
    -
    -    def inline( text ) 
    -        [/^inline_/, /^glyphs_/].each do |meth_re|
    -            @rules.each do |rule_name|
    -                method( rule_name ).call( text ) if rule_name.to_s.match( meth_re )
    -            end
    -        end
    -    end
    -
    -    def h_align( text ) 
    -        H_ALGN_VALS[text]
    -    end
    -
    -    def v_align( text ) 
    -        V_ALGN_VALS[text]
    -    end
    -
    -    def textile_popup_help( name, windowW, windowH )
    -        ' ' + name + '
    ' - end - - # HTML cleansing stuff - BASIC_TAGS = { - 'a' => ['href', 'title'], - 'img' => ['src', 'alt', 'title'], - 'br' => [], - 'i' => nil, - 'u' => nil, - 'b' => nil, - 'pre' => nil, - 'kbd' => nil, - 'code' => ['lang'], - 'cite' => nil, - 'strong' => nil, - 'em' => nil, - 'ins' => nil, - 'sup' => nil, - 'sub' => nil, - 'del' => nil, - 'table' => nil, - 'tr' => nil, - 'td' => ['colspan', 'rowspan'], - 'th' => nil, - 'ol' => nil, - 'ul' => nil, - 'li' => nil, - 'p' => nil, - 'h1' => nil, - 'h2' => nil, - 'h3' => nil, - 'h4' => nil, - 'h5' => nil, - 'h6' => nil, - 'blockquote' => ['cite'] - } - - def clean_html( text, tags = BASIC_TAGS ) - text.gsub!( /]*)>/ ) do - raw = $~ - tag = raw[2].downcase - if tags.has_key? tag - pcs = [tag] - tags[tag].each do |prop| - ['"', "'", ''].each do |q| - q2 = ( q != '' ? q : '\s' ) - if raw[3] =~ /#{prop}\s*=\s*#{q}([^#{q2}]+)#{q}/i - attrv = $1 - next if prop == 'src' and attrv =~ %r{^(?!http)\w+:} - pcs << "#{prop}=\"#{$1.gsub('"', '\\"')}\"" - break - end - end - end if tags[tag] - "<#{raw[1]}#{pcs.join " "}>" - else - " " - end - end - end - - ALLOWED_TAGS = %w(redpre pre code notextile) - - def escape_html_tags(text) - text.gsub!(%r{<(\/?([!\w]+)[^<>\n]*)(>?)}) {|m| ALLOWED_TAGS.include?($2) ? "<#{$1}#{$3}" : "<#{$1}#{'>' unless $3.blank?}" } - end -end - diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/81/8152a8429a670810e05571a0d05bddd66f5b7e5e.svn-base --- a/.svn/pristine/81/8152a8429a670810e05571a0d05bddd66f5b7e5e.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,40 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../../test_helper', __FILE__) - -class PatchesTest < ActiveSupport::TestCase - include Redmine::I18n - - context "ActiveRecord::Base.human_attribute_name" do - setup do - Setting.default_language = 'en' - end - - should "transform name to field_name" do - assert_equal l('field_last_login_on'), ActiveRecord::Base.human_attribute_name('last_login_on') - end - - should "cut extra _id suffix for better validation" do - assert_equal l('field_last_login_on'), ActiveRecord::Base.human_attribute_name('last_login_on_id') - end - - should "default to humanized value if no translation has been found (useful for custom fields)" do - assert_equal 'Patch name', ActiveRecord::Base.human_attribute_name('Patch name') - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/81/81609ac3f30763970c764d86e00b3512d937facd.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/81/81609ac3f30763970c764d86e00b3512d937facd.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,76 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../../../test_helper', __FILE__) + +class Redmine::Utils::DateCalculationTest < ActiveSupport::TestCase + include Redmine::Utils::DateCalculation + + def test_working_days_without_non_working_week_days + with_settings :non_working_week_days => [] do + assert_working_days 18, '2012-10-09', '2012-10-27' + assert_working_days 6, '2012-10-09', '2012-10-15' + assert_working_days 5, '2012-10-09', '2012-10-14' + assert_working_days 3, '2012-10-09', '2012-10-12' + assert_working_days 3, '2012-10-14', '2012-10-17' + assert_working_days 16, '2012-10-14', '2012-10-30' + end + end + + def test_working_days_with_non_working_week_days + with_settings :non_working_week_days => %w(6 7) do + assert_working_days 14, '2012-10-09', '2012-10-27' + assert_working_days 4, '2012-10-09', '2012-10-15' + assert_working_days 4, '2012-10-09', '2012-10-14' + assert_working_days 3, '2012-10-09', '2012-10-12' + assert_working_days 8, '2012-10-09', '2012-10-19' + assert_working_days 8, '2012-10-11', '2012-10-23' + assert_working_days 2, '2012-10-14', '2012-10-17' + assert_working_days 11, '2012-10-14', '2012-10-30' + end + end + + def test_add_working_days_without_non_working_week_days + with_settings :non_working_week_days => [] do + assert_add_working_days '2012-10-10', '2012-10-10', 0 + assert_add_working_days '2012-10-11', '2012-10-10', 1 + assert_add_working_days '2012-10-12', '2012-10-10', 2 + assert_add_working_days '2012-10-13', '2012-10-10', 3 + assert_add_working_days '2012-10-25', '2012-10-10', 15 + end + end + + def test_add_working_days_with_non_working_week_days + with_settings :non_working_week_days => %w(6 7) do + assert_add_working_days '2012-10-10', '2012-10-10', 0 + assert_add_working_days '2012-10-11', '2012-10-10', 1 + assert_add_working_days '2012-10-12', '2012-10-10', 2 + assert_add_working_days '2012-10-15', '2012-10-10', 3 + assert_add_working_days '2012-10-31', '2012-10-10', 15 + assert_add_working_days '2012-10-19', '2012-10-09', 8 + assert_add_working_days '2012-10-23', '2012-10-11', 8 + end + end + + def assert_working_days(expected_days, from, to) + assert_equal expected_days, working_days(from.to_date, to.to_date) + end + + def assert_add_working_days(expected_date, from, working_days) + assert_equal expected_date.to_date, add_working_days(from.to_date, working_days) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/81/817f1955c70c5e3d4e1883912d5c54dd05bb15de.svn-base Binary file .svn/pristine/81/817f1955c70c5e3d4e1883912d5c54dd05bb15de.svn-base has changed diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/81/81b5162180e7da6eba9eec12f7d1c6c77f6bbb3a.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/81/81b5162180e7da6eba9eec12f7d1c6c77f6bbb3a.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,158 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class Redmine::ApiTest::VersionsTest < Redmine::ApiTest::Base + fixtures :projects, :trackers, :issue_statuses, :issues, + :enumerations, :users, :issue_categories, + :projects_trackers, + :roles, + :member_roles, + :members, + :enabled_modules, + :versions + + def setup + Setting.rest_api_enabled = '1' + end + + context "/projects/:project_id/versions" do + context "GET" do + should "return project versions" do + get '/projects/1/versions.xml' + + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_tag :tag => 'versions', + :attributes => {:type => 'array'}, + :child => { + :tag => 'version', + :child => { + :tag => 'id', + :content => '2', + :sibling => { + :tag => 'name', + :content => '1.0' + } + } + } + end + end + + context "POST" do + should "create the version" do + assert_difference 'Version.count' do + post '/projects/1/versions.xml', {:version => {:name => 'API test'}}, credentials('jsmith') + end + + version = Version.first(:order => 'id DESC') + assert_equal 'API test', version.name + + assert_response :created + assert_equal 'application/xml', @response.content_type + assert_tag 'version', :child => {:tag => 'id', :content => version.id.to_s} + end + + should "create the version with due date" do + assert_difference 'Version.count' do + post '/projects/1/versions.xml', {:version => {:name => 'API test', :due_date => '2012-01-24'}}, credentials('jsmith') + end + + version = Version.first(:order => 'id DESC') + assert_equal 'API test', version.name + assert_equal Date.parse('2012-01-24'), version.due_date + + assert_response :created + assert_equal 'application/xml', @response.content_type + assert_tag 'version', :child => {:tag => 'id', :content => version.id.to_s} + end + + should "create the version with custom fields" do + field = VersionCustomField.generate! + + assert_difference 'Version.count' do + post '/projects/1/versions.xml', { + :version => { + :name => 'API test', + :custom_fields => [ + {'id' => field.id.to_s, 'value' => 'Some value'} + ] + } + }, credentials('jsmith') + end + + version = Version.first(:order => 'id DESC') + assert_equal 'API test', version.name + assert_equal 'Some value', version.custom_field_value(field) + + assert_response :created + assert_equal 'application/xml', @response.content_type + assert_select 'version>custom_fields>custom_field[id=?]>value', field.id.to_s, 'Some value' + end + + context "with failure" do + should "return the errors" do + assert_no_difference('Version.count') do + post '/projects/1/versions.xml', {:version => {:name => ''}}, credentials('jsmith') + end + + assert_response :unprocessable_entity + assert_tag :errors, :child => {:tag => 'error', :content => "Name can't be blank"} + end + end + end + end + + context "/versions/:id" do + context "GET" do + should "return the version" do + get '/versions/2.xml' + + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_select 'version' do + assert_select 'id', :text => '2' + assert_select 'name', :text => '1.0' + assert_select 'sharing', :text => 'none' + end + end + end + + context "PUT" do + should "update the version" do + put '/versions/2.xml', {:version => {:name => 'API update'}}, credentials('jsmith') + + assert_response :ok + assert_equal '', @response.body + assert_equal 'API update', Version.find(2).name + end + end + + context "DELETE" do + should "destroy the version" do + assert_difference 'Version.count', -1 do + delete '/versions/3.xml', {}, credentials('jsmith') + end + + assert_response :ok + assert_equal '', @response.body + assert_nil Version.find_by_id(3) + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/81/81cd3726ba1b626ac8fe2e786c213679ee3b4319.svn-base --- a/.svn/pristine/81/81cd3726ba1b626ac8fe2e786c213679ee3b4319.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,24 +0,0 @@ -module CodeRay -module Scanners - - map \ - :'c++' => :cpp, - :cplusplus => :cpp, - :ecmascript => :java_script, - :ecma_script => :java_script, - :rhtml => :erb, - :eruby => :erb, - :irb => :ruby, - :javascript => :java_script, - :js => :java_script, - :pascal => :delphi, - :patch => :diff, - :plain => :text, - :plaintext => :text, - :xhtml => :html, - :yml => :yaml - - default :text - -end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/82/821d4ef82eeed4cbb18ce34368856a650d31c2d8.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/82/821d4ef82eeed4cbb18ce34368856a650d31c2d8.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,79 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class RoutingTrackersTest < ActionController::IntegrationTest + def test_trackers + assert_routing( + { :method => 'get', :path => "/trackers" }, + { :controller => 'trackers', :action => 'index' } + ) + assert_routing( + { :method => 'get', :path => "/trackers.xml" }, + { :controller => 'trackers', :action => 'index', :format => 'xml' } + ) + assert_routing( + { :method => 'post', :path => "/trackers" }, + { :controller => 'trackers', :action => 'create' } + ) + assert_routing( + { :method => 'post', :path => "/trackers.xml" }, + { :controller => 'trackers', :action => 'create', :format => 'xml' } + ) + assert_routing( + { :method => 'get', :path => "/trackers/new" }, + { :controller => 'trackers', :action => 'new' } + ) + assert_routing( + { :method => 'get', :path => "/trackers/new.xml" }, + { :controller => 'trackers', :action => 'new', :format => 'xml' } + ) + assert_routing( + { :method => 'get', :path => "/trackers/1/edit" }, + { :controller => 'trackers', :action => 'edit', :id => '1' } + ) + assert_routing( + { :method => 'put', :path => "/trackers/1" }, + { :controller => 'trackers', :action => 'update', + :id => '1' } + ) + assert_routing( + { :method => 'put', :path => "/trackers/1.xml" }, + { :controller => 'trackers', :action => 'update', + :format => 'xml', :id => '1' } + ) + assert_routing( + { :method => 'delete', :path => "/trackers/1" }, + { :controller => 'trackers', :action => 'destroy', + :id => '1' } + ) + assert_routing( + { :method => 'delete', :path => "/trackers/1.xml" }, + { :controller => 'trackers', :action => 'destroy', + :format => 'xml', :id => '1' } + ) + assert_routing( + { :method => 'get', :path => "/trackers/fields" }, + { :controller => 'trackers', :action => 'fields' } + ) + assert_routing( + { :method => 'post', :path => "/trackers/fields" }, + { :controller => 'trackers', :action => 'fields' } + ) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/82/822a99f6cd2f033a4f1b9b05b78b77e54640f573.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/82/822a99f6cd2f033a4f1b9b05b78b77e54640f573.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,31 @@ +<%= form_tag({:action => 'edit', :tab => 'issues'}, :onsubmit => 'selectAllOptions("selected_columns");') do %> + +
    +

    <%= setting_check_box :cross_project_issue_relations %>

    + +

    <%= setting_select :cross_project_subtasks, cross_project_subtasks_options %>

    + +

    <%= setting_check_box :issue_group_assignment %>

    + +

    <%= setting_check_box :default_issue_start_date_to_creation_date %>

    + +

    <%= setting_check_box :display_subprojects_issues %>

    + +

    <%= setting_select :issue_done_ratio, Issue::DONE_RATIO_OPTIONS.collect {|i| [l("setting_issue_done_ratio_#{i}"), i]} %>

    + +

    <%= setting_multiselect :non_working_week_days, (1..7).map {|d| [day_name(d), d.to_s]}, :inline => true %>

    + +

    <%= setting_text_field :issues_export_limit, :size => 6 %>

    + +

    <%= setting_text_field :gantt_items_limit, :size => 6 %>

    +
    + +
    + <%= l(:setting_issue_list_default_columns) %> + <%= render_query_columns_selection( + IssueQuery.new(:column_names => Setting.issue_list_default_columns), + :name => 'settings[issue_list_default_columns]') %> +
    + +<%= submit_tag l(:button_save) %> +<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/82/822b75d0bb3697ed3dee9071dcf61cf8a7528ac2.svn-base --- a/.svn/pristine/82/822b75d0bb3697ed3dee9071dcf61cf8a7528ac2.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,168 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) - -class MessageTest < ActiveSupport::TestCase - fixtures :projects, :roles, :members, :member_roles, :boards, :messages, :users, :watchers - - def setup - @board = Board.find(1) - @user = User.find(1) - end - - def test_create - topics_count = @board.topics_count - messages_count = @board.messages_count - - message = Message.new(:board => @board, :subject => 'Test message', - :content => 'Test message content', - :author => @user) - assert message.save - @board.reload - # topics count incremented - assert_equal topics_count+1, @board[:topics_count] - # messages count incremented - assert_equal messages_count+1, @board[:messages_count] - assert_equal message, @board.last_message - # author should be watching the message - assert message.watched_by?(@user) - end - - def test_reply - topics_count = @board.topics_count - messages_count = @board.messages_count - @message = Message.find(1) - replies_count = @message.replies_count - - reply_author = User.find(2) - reply = Message.new(:board => @board, :subject => 'Test reply', - :content => 'Test reply content', - :parent => @message, :author => reply_author) - assert reply.save - @board.reload - # same topics count - assert_equal topics_count, @board[:topics_count] - # messages count incremented - assert_equal messages_count+1, @board[:messages_count] - assert_equal reply, @board.last_message - @message.reload - # replies count incremented - assert_equal replies_count+1, @message[:replies_count] - assert_equal reply, @message.last_reply - # author should be watching the message - assert @message.watched_by?(reply_author) - end - - def test_cannot_reply_to_locked_topic - topics_count = @board.topics_count - messages_count = @board.messages_count - @message = Message.find(1) - replies_count = @message.replies_count - assert_equal false, @message.locked - @message.locked = true - assert @message.save - assert_equal true, @message.locked - - reply_author = User.find(2) - reply = Message.new(:board => @board, :subject => 'Test reply', - :content => 'Test reply content', - :parent => @message, :author => reply_author) - reply.save - assert_equal 1, reply.errors.count - end - - def test_moving_message_should_update_counters - @message = Message.find(1) - assert_no_difference 'Message.count' do - # Previous board - assert_difference 'Board.find(1).topics_count', -1 do - assert_difference 'Board.find(1).messages_count', -(1 + @message.replies_count) do - # New board - assert_difference 'Board.find(2).topics_count' do - assert_difference 'Board.find(2).messages_count', (1 + @message.replies_count) do - @message.update_attributes(:board_id => 2) - end - end - end - end - end - end - - def test_destroy_topic - message = Message.find(1) - board = message.board - topics_count, messages_count = board.topics_count, board.messages_count - - assert_difference('Watcher.count', -1) do - assert message.destroy - end - board.reload - - # Replies deleted - assert Message.find_all_by_parent_id(1).empty? - # Checks counters - assert_equal topics_count - 1, board.topics_count - assert_equal messages_count - 3, board.messages_count - # Watchers removed - end - - def test_destroy_reply - message = Message.find(5) - board = message.board - topics_count, messages_count = board.topics_count, board.messages_count - assert message.destroy - board.reload - - # Checks counters - 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.roles_for_project(message.project).first.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.roles_for_project(message.project).first.remove_permission!(:delete_own_messages) - assert !message.reload.destroyable_by?(author.reload) - end - - def test_set_sticky - message = Message.new - assert_equal 0, message.sticky - message.sticky = nil - assert_equal 0, message.sticky - message.sticky = false - assert_equal 0, message.sticky - message.sticky = true - assert_equal 1, message.sticky - message.sticky = '0' - assert_equal 0, message.sticky - message.sticky = '1' - assert_equal 1, message.sticky - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/82/8291420c5b3c01d90eab90ab6db0630cf31b5fe0.svn-base --- a/.svn/pristine/82/8291420c5b3c01d90eab90ab6db0630cf31b5fe0.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1012 +0,0 @@ -# Update to 1.1 by Michal Gebauer -# Updated by Josef LiÅ¡ka -# CZ translation by Maxim KruÅ¡ina | Massimo Filippi, s.r.o. | maxim@mxm.cz -# Based on original CZ translation by Jan KadleÄek -cs: - # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl) - direction: ltr - date: - formats: - # Use the strftime parameters for formats. - # When no format has been given, it uses default. - # You can provide other formats here if you like! - default: "%Y-%m-%d" - short: "%b %d" - long: "%B %d, %Y" - - day_names: [NedÄ›le, PondÄ›lí, Úterý, StÅ™eda, ÄŒtvrtek, Pátek, Sobota] - abbr_day_names: [Ne, Po, Út, St, ÄŒt, Pá, So] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, Leden, Únor, BÅ™ezen, Duben, KvÄ›ten, ÄŒerven, ÄŒervenec, Srpen, Září, Říjen, Listopad, Prosinec] - abbr_month_names: [~, Led, Úno, BÅ™e, Dub, KvÄ›, ÄŒer, ÄŒec, Srp, Zář, Říj, Lis, Pro] - # Used in date_select and datime_select. - order: - - :year - - :month - - :day - - time: - formats: - default: "%a, %d %b %Y %H:%M:%S %z" - time: "%H:%M" - short: "%d %b %H:%M" - long: "%B %d, %Y %H:%M" - am: "dop." - pm: "odp." - - datetime: - distance_in_words: - half_a_minute: "půl minuty" - less_than_x_seconds: - one: "ménÄ› než sekunda" - other: "ménÄ› než %{count} sekund" - x_seconds: - one: "1 sekunda" - other: "%{count} sekund" - less_than_x_minutes: - one: "ménÄ› než minuta" - other: "ménÄ› než %{count} minut" - x_minutes: - one: "1 minuta" - other: "%{count} minut" - about_x_hours: - one: "asi 1 hodina" - other: "asi %{count} hodin" - x_days: - one: "1 den" - other: "%{count} dnů" - about_x_months: - one: "asi 1 mÄ›síc" - other: "asi %{count} mÄ›síců" - x_months: - one: "1 mÄ›síc" - other: "%{count} mÄ›síců" - about_x_years: - one: "asi 1 rok" - other: "asi %{count} let" - over_x_years: - one: "více než 1 rok" - other: "více než %{count} roky" - almost_x_years: - one: "témeÅ™ 1 rok" - other: "téměř %{count} roky" - - number: - # Výchozí formát pro Äísla - format: - separator: "." - delimiter: "" - precision: 3 - human: - format: - delimiter: "" - precision: 1 - storage_units: - format: "%n %u" - units: - byte: - one: "Bajt" - other: "Bajtů" - kb: "kB" - mb: "MB" - gb: "GB" - tb: "TB" - - -# Used in array.to_sentence. - support: - array: - sentence_connector: "a" - skip_last_comma: false - - activerecord: - errors: - template: - header: - one: "1 chyba zabránila uložení %{model}" - other: "%{count} chyb zabránilo uložení %{model}" - messages: - inclusion: "není zahrnuto v seznamu" - exclusion: "je rezervováno" - invalid: "je neplatné" - confirmation: "se neshoduje s potvrzením" - accepted: "musí být akceptováno" - empty: "nemůže být prázdný" - blank: "nemůže být prázdný" - too_long: "je příliÅ¡ dlouhý" - too_short: "je příliÅ¡ krátký" - wrong_length: "má chybnou délku" - taken: "je již použito" - not_a_number: "není Äíslo" - not_a_date: "není platné datum" - greater_than: "musí být vÄ›tší než %{count}" - greater_than_or_equal_to: "musí být vÄ›tší nebo rovno %{count}" - equal_to: "musí být pÅ™esnÄ› %{count}" - less_than: "musí být ménÄ› než %{count}" - less_than_or_equal_to: "musí být ménÄ› nebo rovno %{count}" - odd: "musí být liché" - even: "musí být sudé" - greater_than_start_date: "musí být vÄ›tší než poÄáteÄní datum" - not_same_project: "nepatří stejnému projektu" - circular_dependency: "Tento vztah by vytvoÅ™il cyklickou závislost" - cant_link_an_issue_with_a_descendant: "Úkol nemůže být spojen s jedním z jeho dílÄích úkolů" - - actionview_instancetag_blank_option: Prosím vyberte - - general_text_No: 'Ne' - general_text_Yes: 'Ano' - general_text_no: 'ne' - general_text_yes: 'ano' - general_lang_name: 'ÄŒeÅ¡tina' - general_csv_separator: ',' - general_csv_decimal_separator: '.' - general_csv_encoding: UTF-8 - general_pdf_encoding: UTF-8 - general_first_day_of_week: '1' - - notice_account_updated: ÚÄet byl úspěšnÄ› zmÄ›nÄ›n. - notice_account_invalid_creditentials: Chybné jméno nebo heslo - notice_account_password_updated: Heslo bylo úspěšnÄ› zmÄ›nÄ›no. - notice_account_wrong_password: Chybné heslo - notice_account_register_done: ÚÄet byl úspěšnÄ› vytvoÅ™en. Pro aktivaci úÄtu kliknÄ›te na odkaz v emailu, který vám byl zaslán. - notice_account_unknown_email: Neznámý uživatel. - notice_can_t_change_password: Tento úÄet používá externí autentifikaci. Zde heslo zmÄ›nit nemůžete. - notice_account_lost_email_sent: Byl vám zaslán email s intrukcemi jak si nastavíte nové heslo. - notice_account_activated: Váš úÄet byl aktivován. Nyní se můžete pÅ™ihlásit. - notice_successful_create: ÚspěšnÄ› vytvoÅ™eno. - notice_successful_update: ÚspěšnÄ› aktualizováno. - notice_successful_delete: ÚspěšnÄ› odstranÄ›no. - notice_successful_connection: Úspěšné pÅ™ipojení. - notice_file_not_found: Stránka na kterou se snažíte zobrazit neexistuje nebo byla smazána. - notice_locking_conflict: Údaje byly zmÄ›nÄ›ny jiným uživatelem. - notice_not_authorized: Nemáte dostateÄná práva pro zobrazení této stránky. - notice_not_authorized_archived_project: Projekt ke kterému se snažíte pÅ™istupovat byl archivován. - notice_email_sent: "Na adresu %{value} byl odeslán email" - notice_email_error: "PÅ™i odesílání emailu nastala chyba (%{value})" - notice_feeds_access_key_reseted: Váš klÃ­Ä pro přístup k RSS byl resetován. - notice_api_access_key_reseted: Váš API přístupový klÃ­Ä byl resetován. - notice_failed_to_save_issues: "Chyba pÅ™i uložení %{count} úkolu(ů) z %{total} vybraných: %{ids}." - notice_failed_to_save_members: "NepodaÅ™ilo se uložit Älena(y): %{errors}." - notice_no_issue_selected: "Nebyl zvolen žádný úkol. Prosím, zvolte úkoly, které chcete editovat" - notice_account_pending: "Váš úÄet byl vytvoÅ™en, nyní Äeká na schválení administrátorem." - notice_default_data_loaded: Výchozí konfigurace úspěšnÄ› nahrána. - notice_unable_delete_version: Nemohu odstanit verzi - notice_unable_delete_time_entry: Nelze smazat Äas ze záznamu. - notice_issue_done_ratios_updated: Koeficienty dokonÄení úkolu byly aktualizovány. - notice_gantt_chart_truncated: Graf byl oříznut, poÄet položek pÅ™esáhl limit pro zobrazení (%{max}) - - error_can_t_load_default_data: "Výchozí konfigurace nebyla nahrána: %{value}" - error_scm_not_found: "Položka a/nebo revize neexistují v repozitáři." - error_scm_command_failed: "PÅ™i pokusu o přístup k repozitáři doÅ¡lo k chybÄ›: %{value}" - error_scm_annotate: "Položka neexistuje nebo nemůže být komentována." - error_issue_not_found_in_project: 'Úkol nebyl nalezen nebo nepatří k tomuto projektu' - error_no_tracker_in_project: Žádná fronta nebyla pÅ™iÅ™azena tomuto projektu. Prosím zkontroluje nastavení projektu. - error_no_default_issue_status: Není nastaven výchozí stav úkolu. Prosím zkontrolujte nastavení ("Administrace -> Stavy úkolů"). - error_can_not_delete_custom_field: Nelze smazat volitelné pole - error_can_not_delete_tracker: Tato fronta obsahuje úkoly a nemůže být smazán. - error_can_not_remove_role: Tato role je právÄ› používaná a nelze ji smazat. - error_can_not_reopen_issue_on_closed_version: Úkol pÅ™iÅ™azený k uzavÅ™ené verzi nemůže být znovu otevÅ™en - error_can_not_archive_project: Tento projekt nemůže být archivován - error_issue_done_ratios_not_updated: Koeficient dokonÄení úkolu nebyl aktualizován. - error_workflow_copy_source: Prosím vyberte zdrojovou frontu nebo roly - error_workflow_copy_target: Prosím vyberte cílovou frontu(y) a roly(e) - error_unable_delete_issue_status: Nelze smazat stavy úkolů - error_unable_to_connect: Nelze se pÅ™ipojit (%{value}) - warning_attachments_not_saved: "%{count} soubor(ů) nebylo možné uložit." - - mail_subject_lost_password: "VaÅ¡e heslo (%{value})" - mail_body_lost_password: 'Pro zmÄ›nu vaÅ¡eho hesla kliknÄ›te na následující odkaz:' - mail_subject_register: "Aktivace úÄtu (%{value})" - mail_body_register: 'Pro aktivaci vaÅ¡eho úÄtu kliknÄ›te na následující odkaz:' - mail_body_account_information_external: "Pomocí vaÅ¡eho úÄtu %{value} se můžete pÅ™ihlásit." - mail_body_account_information: Informace o vaÅ¡em úÄtu - mail_subject_account_activation_request: "Aktivace %{value} úÄtu" - mail_body_account_activation_request: "Byl zaregistrován nový uživatel %{value}. Aktivace jeho úÄtu závisí na vaÅ¡em potvrzení." - mail_subject_reminder: "%{count} úkol(ů) má termín bÄ›hem nÄ›kolik dní (%{days})" - mail_body_reminder: "%{count} úkol(ů), které máte pÅ™iÅ™azeny má termín bÄ›hem nÄ›kolik dní (%{days}):" - mail_subject_wiki_content_added: "'%{id}' Wiki stránka byla pÅ™idána" - mail_body_wiki_content_added: "'%{id}' Wiki stránka byla pÅ™idána od %{author}." - mail_subject_wiki_content_updated: "'%{id}' Wiki stránka byla aktualizována" - mail_body_wiki_content_updated: "'%{id}' Wiki stránka byla aktualizována od %{author}." - - gui_validation_error: 1 chyba - gui_validation_error_plural: "%{count} chyb(y)" - - field_name: Název - field_description: Popis - field_summary: PÅ™ehled - field_is_required: Povinné pole - field_firstname: Jméno - field_lastname: Příjmení - field_mail: Email - field_filename: Soubor - field_filesize: Velikost - field_downloads: Staženo - field_author: Autor - field_created_on: VytvoÅ™eno - field_updated_on: Aktualizováno - field_field_format: Formát - field_is_for_all: Pro vÅ¡echny projekty - field_possible_values: Možné hodnoty - field_regexp: Regulární výraz - field_min_length: Minimální délka - field_max_length: Maximální délka - field_value: Hodnota - field_category: Kategorie - field_title: Název - field_project: Projekt - field_issue: Úkol - field_status: Stav - field_notes: Poznámka - field_is_closed: Úkol uzavÅ™en - field_is_default: Výchozí stav - field_tracker: Fronta - field_subject: PÅ™edmÄ›t - field_due_date: Uzavřít do - field_assigned_to: PÅ™iÅ™azeno - field_priority: Priorita - field_fixed_version: Cílová verze - field_user: Uživatel - field_principal: Hlavní - field_role: Role - field_homepage: Domovská stránka - field_is_public: VeÅ™ejný - field_parent: NadÅ™azený projekt - field_is_in_roadmap: Úkoly zobrazené v plánu - field_login: PÅ™ihlášení - field_mail_notification: Emailová oznámení - field_admin: Administrátor - field_last_login_on: Poslední pÅ™ihlášení - field_language: Jazyk - field_effective_date: Datum - field_password: Heslo - field_new_password: Nové heslo - field_password_confirmation: Potvrzení - field_version: Verze - field_type: Typ - field_host: Host - field_port: Port - field_account: ÚÄet - field_base_dn: Base DN - field_attr_login: PÅ™ihlášení (atribut) - field_attr_firstname: Jméno (atribut) - field_attr_lastname: Příjemní (atribut) - field_attr_mail: Email (atribut) - field_onthefly: Automatické vytváření uživatelů - field_start_date: ZaÄátek - field_done_ratio: "% Hotovo" - field_auth_source: AutentifikaÄní mód - field_hide_mail: Nezobrazovat můj email - field_comments: Komentář - field_url: URL - field_start_page: Výchozí stránka - field_subproject: Podprojekt - field_hours: Hodiny - field_activity: Aktivita - field_spent_on: Datum - field_identifier: Identifikátor - field_is_filter: Použít jako filtr - field_issue_to: Související úkol - field_delay: ZpoždÄ›ní - field_assignable: Úkoly mohou být pÅ™iÅ™azeny této roli - field_redirect_existing_links: PÅ™esmÄ›rovat stvávající odkazy - field_estimated_hours: Odhadovaná doba - field_column_names: Sloupce - field_time_entries: Zaznamenaný Äas - field_time_zone: ÄŒasové pásmo - field_searchable: Umožnit vyhledávání - field_default_value: Výchozí hodnota - field_comments_sorting: Zobrazit komentáře - field_parent_title: RodiÄovská stránka - field_editable: Editovatelný - field_watcher: Sleduje - field_identity_url: OpenID URL - field_content: Obsah - field_group_by: Seskupovat výsledky podle - field_sharing: Sdílení - field_parent_issue: RodiÄovský úkol - field_member_of_group: Skupina pÅ™iÅ™aditele - field_assigned_to_role: Role pÅ™iÅ™aditele - field_text: Textové pole - field_visible: Viditelný - - setting_app_title: Název aplikace - setting_app_subtitle: Podtitulek aplikace - setting_welcome_text: Uvítací text - setting_default_language: Výchozí jazyk - setting_login_required: Autentifikace vyžadována - setting_self_registration: Povolena automatická registrace - setting_attachment_max_size: Maximální velikost přílohy - setting_issues_export_limit: Limit pro export úkolů - setting_mail_from: Odesílat emaily z adresy - setting_bcc_recipients: Příjemci skryté kopie (bcc) - setting_plain_text_mail: pouze prostý text (ne HTML) - setting_host_name: Jméno serveru - setting_text_formatting: Formátování textu - setting_wiki_compression: Komprese historie Wiki - setting_feeds_limit: Limit obsahu příspÄ›vků - setting_default_projects_public: Nové projekty nastavovat jako veÅ™ejné - setting_autofetch_changesets: Automaticky stahovat commity - setting_sys_api_enabled: Povolit WS pro správu repozitory - setting_commit_ref_keywords: KlíÄová slova pro odkazy - setting_commit_fix_keywords: KlíÄová slova pro uzavÅ™ení - setting_autologin: Automatické pÅ™ihlaÅ¡ování - setting_date_format: Formát data - setting_time_format: Formát Äasu - setting_cross_project_issue_relations: Povolit vazby úkolů napÅ™Ã­Ä projekty - setting_issue_list_default_columns: Výchozí sloupce zobrazené v seznamu úkolů - setting_emails_header: HlaviÄka emailů - setting_emails_footer: PatiÄka emailů - setting_protocol: Protokol - setting_per_page_options: Povolené poÄty řádků na stránce - setting_user_format: Formát zobrazení uživatele - setting_activity_days_default: Dny zobrazené v Äinnosti projektu - setting_display_subprojects_issues: Automaticky zobrazit úkoly podprojektu v hlavním projektu - setting_enabled_scm: Povolené SCM - setting_mail_handler_body_delimiters: Zkrátit e-maily po jednom z tÄ›chto řádků - setting_mail_handler_api_enabled: Povolit WS pro příchozí e-maily - setting_mail_handler_api_key: API klÃ­Ä - setting_sequential_project_identifiers: Generovat sekvenÄní identifikátory projektů - setting_gravatar_enabled: Použít uživatelské ikony Gravatar - setting_gravatar_default: Výchozí Gravatar - setting_diff_max_lines_displayed: Maximální poÄet zobrazených řádků rozdílů - setting_file_max_size_displayed: Maximální velikost textových souborů zobrazených přímo na stránce - setting_repository_log_display_limit: Maximální poÄet revizí zobrazených v logu souboru - setting_openid: Umožnit pÅ™ihlaÅ¡ování a registrace s OpenID - setting_password_min_length: Minimální délka hesla - setting_new_project_user_role_id: Role pÅ™iÅ™azená uživateli bez práv administrátora, který projekt vytvoÅ™il - setting_default_projects_modules: Výchozí zapnutné moduly pro nový projekt - setting_issue_done_ratio: SpoÄítat koeficient dokonÄení úkolu s - setting_issue_done_ratio_issue_field: Použít pole úkolu - setting_issue_done_ratio_issue_status: Použít stav úkolu - setting_start_of_week: ZaÄínat kalendáře - setting_rest_api_enabled: Zapnout službu REST - setting_cache_formatted_text: Ukládat formátovaný text do vyrovnávací pamÄ›ti - setting_default_notification_option: Výchozí nastavení oznámení - setting_commit_logtime_enabled: Povolit zapisování Äasu - setting_commit_logtime_activity_id: Aktivita pro zapsaný Äas - setting_gantt_items_limit: Maximální poÄet položek zobrazený na ganttovÄ› grafu - - permission_add_project: VytvoÅ™it projekt - permission_add_subprojects: VytvoÅ™it podprojekty - permission_edit_project: Úprava projektů - permission_select_project_modules: VýbÄ›r modulů projektu - permission_manage_members: Spravování Älenství - permission_manage_project_activities: Spravovat aktivity projektu - permission_manage_versions: Spravování verzí - permission_manage_categories: Spravování kategorií úkolů - permission_view_issues: Zobrazit úkoly - permission_add_issues: PÅ™idávání úkolů - permission_edit_issues: Upravování úkolů - permission_manage_issue_relations: Spravování vztahů mezi úkoly - permission_add_issue_notes: PÅ™idávání poznámek - permission_edit_issue_notes: Upravování poznámek - permission_edit_own_issue_notes: Upravování vlastních poznámek - permission_move_issues: PÅ™esouvání úkolů - permission_delete_issues: Mazání úkolů - permission_manage_public_queries: Správa veÅ™ejných dotazů - permission_save_queries: Ukládání dotazů - permission_view_gantt: Zobrazené Ganttova diagramu - permission_view_calendar: Prohlížení kalendáře - permission_view_issue_watchers: Zobrazení seznamu sledujícíh uživatelů - permission_add_issue_watchers: PÅ™idání sledujících uživatelů - permission_delete_issue_watchers: Smazat pÅ™ihlížející - permission_log_time: Zaznamenávání stráveného Äasu - permission_view_time_entries: Zobrazení stráveného Äasu - permission_edit_time_entries: Upravování záznamů o stráveném Äasu - permission_edit_own_time_entries: Upravování vlastních zázamů o stráveném Äase - permission_manage_news: Spravování novinek - permission_comment_news: Komentování novinek - permission_manage_documents: Správa dokumentů - permission_view_documents: Prohlížení dokumentů - permission_manage_files: Spravování souborů - permission_view_files: Prohlížení souborů - permission_manage_wiki: Spravování Wiki - permission_rename_wiki_pages: PÅ™ejmenovávání Wiki stránek - permission_delete_wiki_pages: Mazání stránek na Wiki - permission_view_wiki_pages: Prohlížení Wiki - permission_view_wiki_edits: Prohlížení historie Wiki - permission_edit_wiki_pages: Upravování stránek Wiki - permission_delete_wiki_pages_attachments: Mazání příloh - permission_protect_wiki_pages: ZabezpeÄení Wiki stránek - permission_manage_repository: Spravování repozitáře - permission_browse_repository: Procházení repozitáře - permission_view_changesets: Zobrazování sady zmÄ›n - permission_commit_access: Commit přístup - permission_manage_boards: Správa diskusních fór - permission_view_messages: Prohlížení zpráv - permission_add_messages: Posílání zpráv - permission_edit_messages: Upravování zpráv - permission_edit_own_messages: Upravit vlastní zprávy - permission_delete_messages: Mazání zpráv - permission_delete_own_messages: Smazat vlastní zprávy - permission_export_wiki_pages: Exportovat Wiki stránky - permission_manage_subtasks: Spravovat podúkoly - - project_module_issue_tracking: Sledování úkolů - project_module_time_tracking: Sledování Äasu - project_module_news: Novinky - project_module_documents: Dokumenty - project_module_files: Soubory - project_module_wiki: Wiki - project_module_repository: Repozitář - project_module_boards: Diskuse - project_module_calendar: Kalendář - project_module_gantt: Gantt - - label_user: Uživatel - label_user_plural: Uživatelé - label_user_new: Nový uživatel - label_user_anonymous: Anonymní - label_project: Projekt - label_project_new: Nový projekt - label_project_plural: Projekty - label_x_projects: - zero: žádné projekty - one: 1 projekt - other: "%{count} projekty(ů)" - label_project_all: VÅ¡echny projekty - label_project_latest: Poslední projekty - label_issue: Úkol - label_issue_new: Nový úkol - label_issue_plural: Úkoly - label_issue_view_all: VÅ¡echny úkoly - label_issues_by: "Úkoly podle %{value}" - label_issue_added: Úkol pÅ™idán - label_issue_updated: Úkol aktualizován - label_document: Dokument - label_document_new: Nový dokument - label_document_plural: Dokumenty - label_document_added: Dokument pÅ™idán - label_role: Role - label_role_plural: Role - label_role_new: Nová role - label_role_and_permissions: Role a práva - label_member: ÄŒlen - label_member_new: Nový Älen - label_member_plural: ÄŒlenové - label_tracker: Fronta - label_tracker_plural: Fronty - label_tracker_new: Nová fronta - label_workflow: PrůbÄ›h práce - label_issue_status: Stav úkolu - label_issue_status_plural: Stavy úkolů - label_issue_status_new: Nový stav - label_issue_category: Kategorie úkolu - label_issue_category_plural: Kategorie úkolů - label_issue_category_new: Nová kategorie - label_custom_field: Uživatelské pole - label_custom_field_plural: Uživatelská pole - label_custom_field_new: Nové uživatelské pole - label_enumerations: Seznamy - label_enumeration_new: Nová hodnota - label_information: Informace - label_information_plural: Informace - label_please_login: Prosím pÅ™ihlaÅ¡te se - label_register: Registrovat - label_login_with_open_id_option: nebo se pÅ™ihlaÅ¡te s OpenID - label_password_lost: Zapomenuté heslo - label_home: Úvodní - label_my_page: Moje stránka - label_my_account: Můj úÄet - label_my_projects: Moje projekty - label_my_page_block: Bloky na mé stránce - label_administration: Administrace - label_login: PÅ™ihlášení - label_logout: Odhlášení - label_help: NápovÄ›da - label_reported_issues: Nahlášené úkoly - label_assigned_to_me_issues: Mé úkoly - label_last_login: Poslední pÅ™ihlášení - label_registered_on: Registrován - label_activity: Aktivita - label_overall_activity: Celková aktivita - label_user_activity: "Aktivita uživatele: %{value}" - label_new: Nový - label_logged_as: PÅ™ihlášen jako - label_environment: ProstÅ™edí - label_authentication: Autentifikace - label_auth_source: Mód autentifikace - label_auth_source_new: Nový mód autentifikace - label_auth_source_plural: Módy autentifikace - label_subproject_plural: Podprojekty - label_subproject_new: Nový podprojekt - label_and_its_subprojects: "%{value} a jeho podprojekty" - label_min_max_length: Min - Max délka - label_list: Seznam - label_date: Datum - label_integer: Celé Äíslo - label_float: Desetinné Äíslo - label_boolean: Ano/Ne - label_string: Text - label_text: Dlouhý text - label_attribute: Atribut - label_attribute_plural: Atributy - label_download: "%{count} stažení" - label_download_plural: "%{count} stažení" - label_no_data: Žádné položky - label_change_status: ZmÄ›nit stav - label_history: Historie - label_attachment: Soubor - label_attachment_new: Nový soubor - label_attachment_delete: Odstranit soubor - label_attachment_plural: Soubory - label_file_added: Soubor pÅ™idán - label_report: PÅ™ehled - label_report_plural: PÅ™ehledy - label_news: Novinky - label_news_new: PÅ™idat novinku - label_news_plural: Novinky - label_news_latest: Poslední novinky - label_news_view_all: Zobrazit vÅ¡echny novinky - label_news_added: Novinka pÅ™idána - label_settings: Nastavení - label_overview: PÅ™ehled - label_version: Verze - label_version_new: Nová verze - label_version_plural: Verze - label_close_versions: Zavřít dokonÄené verze - label_confirmation: Potvrzení - label_export_to: 'Také k dispozici:' - label_read: NaÄítá se... - label_public_projects: VeÅ™ejné projekty - label_open_issues: otevÅ™ený - label_open_issues_plural: otevÅ™ené - label_closed_issues: uzavÅ™ený - label_closed_issues_plural: uzavÅ™ené - label_x_open_issues_abbr_on_total: - zero: 0 otevÅ™ených / %{total} - one: 1 otevÅ™ený / %{total} - other: "%{count} otevÅ™ených / %{total}" - label_x_open_issues_abbr: - zero: 0 otevÅ™ených - one: 1 otevÅ™ený - other: "%{count} otevÅ™ených" - label_x_closed_issues_abbr: - zero: 0 uzavÅ™ených - one: 1 uzavÅ™ený - other: "%{count} uzavÅ™ených" - label_total: Celkem - label_permissions: Práva - label_current_status: Aktuální stav - label_new_statuses_allowed: Nové povolené stavy - label_all: vÅ¡e - label_none: nic - label_nobody: nikdo - label_next: Další - label_previous: PÅ™edchozí - label_used_by: Použito - label_details: Detaily - label_add_note: PÅ™idat poznámku - label_per_page: Na stránku - label_calendar: Kalendář - label_months_from: mÄ›síců od - label_gantt: Ganttův graf - label_internal: Interní - label_last_changes: "posledních %{count} zmÄ›n" - label_change_view_all: Zobrazit vÅ¡echny zmÄ›ny - label_personalize_page: PÅ™izpůsobit tuto stránku - label_comment: Komentář - label_comment_plural: Komentáře - label_x_comments: - zero: žádné komentáře - one: 1 komentář - other: "%{count} komentářů" - label_comment_add: PÅ™idat komentáře - label_comment_added: Komentář pÅ™idán - label_comment_delete: Odstranit komentář - label_query: Uživatelský dotaz - label_query_plural: Uživatelské dotazy - label_query_new: Nový dotaz - label_filter_add: PÅ™idat filtr - label_filter_plural: Filtry - label_equals: je - label_not_equals: není - label_in_less_than: je měší než - label_in_more_than: je vÄ›tší než - label_greater_or_equal: '>=' - label_less_or_equal: '<=' - label_in: v - label_today: dnes - label_all_time: vÅ¡e - label_yesterday: vÄera - label_this_week: tento týden - label_last_week: minulý týden - label_last_n_days: "posledních %{count} dnů" - label_this_month: tento mÄ›síc - label_last_month: minulý mÄ›síc - label_this_year: tento rok - label_date_range: ÄŒasový rozsah - label_less_than_ago: pÅ™ed ménÄ› jak (dny) - label_more_than_ago: pÅ™ed více jak (dny) - label_ago: pÅ™ed (dny) - label_contains: obsahuje - label_not_contains: neobsahuje - label_day_plural: dny - label_repository: Repozitář - label_repository_plural: Repozitáře - label_browse: Procházet - label_modification: "%{count} zmÄ›na" - label_modification_plural: "%{count} zmÄ›n" - label_branch: VÄ›tev - label_tag: Tag - label_revision: Revize - label_revision_plural: Revizí - label_revision_id: "Revize %{value}" - label_associated_revisions: Související verze - label_added: pÅ™idáno - label_modified: zmÄ›nÄ›no - label_copied: zkopírováno - label_renamed: pÅ™ejmenováno - label_deleted: odstranÄ›no - label_latest_revision: Poslední revize - label_latest_revision_plural: Poslední revize - label_view_revisions: Zobrazit revize - label_view_all_revisions: Zobrazit vÅ¡echny revize - label_max_size: Maximální velikost - label_sort_highest: PÅ™esunout na zaÄátek - label_sort_higher: PÅ™esunout nahoru - label_sort_lower: PÅ™esunout dolů - label_sort_lowest: PÅ™esunout na konec - label_roadmap: Plán - label_roadmap_due_in: "Zbývá %{value}" - label_roadmap_overdue: "%{value} pozdÄ›" - label_roadmap_no_issues: Pro tuto verzi nejsou žádné úkoly - label_search: Hledat - label_result_plural: Výsledky - label_all_words: VÅ¡echna 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 dle názvu - label_index_by_date: Index dle data - label_current_version: Aktuální verze - label_preview: Náhled - label_feed_plural: PříspÄ›vky - label_changes_details: Detail vÅ¡ech zmÄ›n - label_issue_tracking: Sledování úkolů - label_spent_time: Strávený Äas - label_overall_spent_time: Celkem strávený Äas - label_f_hour: "%{value} hodina" - label_f_hour_plural: "%{value} hodin" - label_time_tracking: Sledování Äasu - label_change_plural: ZmÄ›ny - label_statistics: Statistiky - label_commits_per_month: Commitů za mÄ›síc - label_commits_per_author: Commitů za autora - label_view_diff: Zobrazit rozdíly - label_diff_inline: uvnitÅ™ - label_diff_side_by_side: vedle sebe - label_options: Nastavení - label_copy_workflow_from: Kopírovat průbÄ›h práce z - label_permissions_report: PÅ™ehled práv - label_watched_issues: Sledované úkoly - label_related_issues: Související úkoly - label_applied_status: Použitý stav - label_loading: Nahrávám... - label_relation_new: Nová souvislost - label_relation_delete: Odstranit souvislost - label_relates_to: související s - label_duplicates: duplikuje - label_duplicated_by: zduplikován - label_blocks: blokuje - label_blocked_by: zablokován - label_precedes: pÅ™edchází - label_follows: následuje - label_end_to_start: od konce do zaÄátku - label_end_to_end: od konce do konce - label_start_to_start: od zaÄátku do zaÄátku - label_start_to_end: od zaÄátku do konce - label_stay_logged_in: Zůstat pÅ™ihlášený - label_disabled: zakázán - label_show_completed_versions: Ukázat dokonÄené verze - label_me: já - label_board: Fórum - label_board_new: Nové fórum - label_board_plural: Fóra - label_board_locked: UzamÄeno - label_board_sticky: Nálepka - label_topic_plural: Témata - label_message_plural: Zprávy - label_message_last: Poslední zpráva - label_message_new: Nová zpráva - label_message_posted: Zpráva pÅ™idána - label_reply_plural: OdpovÄ›di - label_send_information: Zaslat informace o úÄtu uživateli - label_year: Rok - label_month: MÄ›síc - label_week: Týden - label_date_from: Od - label_date_to: Do - label_language_based: Podle výchozího jazyku - label_sort_by: "SeÅ™adit podle %{value}" - label_send_test_email: Poslat testovací email - label_feeds_access_key: Přístupový klÃ­Ä pro RSS - label_missing_feeds_access_key: Postrádá přístupový klÃ­Ä pro RSS - label_feeds_access_key_created_on: "Přístupový klÃ­Ä pro RSS byl vytvoÅ™en pÅ™ed %{value}" - label_module_plural: Moduly - label_added_time_by: "PÅ™idáno uživatelem %{author} pÅ™ed %{age}" - label_updated_time_by: "Aktualizováno uživatelem %{author} pÅ™ed %{age}" - label_updated_time: "Aktualizováno pÅ™ed %{value}" - label_jump_to_a_project: Vyberte projekt... - label_file_plural: Soubory - label_changeset_plural: Changesety - label_default_columns: Výchozí sloupce - label_no_change_option: (beze zmÄ›ny) - label_bulk_edit_selected_issues: Hromadná úprava vybraných úkolů - label_theme: Téma - label_default: Výchozí - label_search_titles_only: Vyhledávat pouze v názvech - label_user_mail_option_all: "Pro vÅ¡echny události vÅ¡ech mých projektů" - label_user_mail_option_selected: "Pro vÅ¡echny události vybraných projektů..." - label_user_mail_option_none: "Žádné události" - label_user_mail_option_only_my_events: "Jen pro vÄ›ci co sleduji nebo jsem v nich zapojen" - label_user_mail_option_only_assigned: "Jen pro vÅ¡eci kterým sem pÅ™iÅ™azen" - label_user_mail_option_only_owner: "Jen pro vÄ›ci které vlastním" - label_user_mail_no_self_notified: "Nezasílat informace o mnou vytvoÅ™ených zmÄ›nách" - label_registration_activation_by_email: aktivace úÄtu emailem - label_registration_manual_activation: manuální aktivace úÄtu - label_registration_automatic_activation: automatická aktivace úÄtu - label_display_per_page: "%{value} na stránku" - label_age: VÄ›k - label_change_properties: ZmÄ›nit vlastnosti - label_general: Obecné - label_more: Více - label_scm: SCM - label_plugins: Doplňky - label_ldap_authentication: Autentifikace LDAP - label_downloads_abbr: Staž. - label_optional_description: Volitelný popis - label_add_another_file: PÅ™idat další soubor - label_preferences: Nastavení - label_chronological_order: V chronologickém poÅ™adí - label_reverse_chronological_order: V obrácaném chronologickém poÅ™adí - label_planning: Plánování - label_incoming_emails: Příchozí e-maily - label_generate_key: Generovat klÃ­Ä - label_issue_watchers: Sledování - label_example: Příklad - label_display: Zobrazit - label_sort: Řazení - label_ascending: VzestupnÄ› - label_descending: SestupnÄ› - label_date_from_to: Od %{start} do %{end} - label_wiki_content_added: Wiki stránka pÅ™idána - label_wiki_content_updated: Wiki stránka aktualizována - label_group: Skupina - label_group_plural: Skupiny - label_group_new: Nová skupina - label_time_entry_plural: Strávený Äas - label_version_sharing_none: Nesdíleno - label_version_sharing_descendants: S podprojekty - label_version_sharing_hierarchy: S hierarchií projektu - label_version_sharing_tree: Se stromem projektu - label_version_sharing_system: Se vÅ¡emi projekty - label_update_issue_done_ratios: Aktualizovat koeficienty dokonÄení úkolů - label_copy_source: Zdroj - label_copy_target: Cíl - label_copy_same_as_target: Stejný jako cíl - label_display_used_statuses_only: Zobrazit pouze stavy které jsou použité touto frontou - label_api_access_key: API přístupový klÃ­Ä - label_missing_api_access_key: ChybÄ›jící přístupový klÃ­Ä API - label_api_access_key_created_on: API přístupový klÃ­Ä vytvoÅ™en %{value} - label_profile: Profil - label_subtask_plural: Podúkol - label_project_copy_notifications: Odeslat email oznámení v průbÄ›hu kopie projektu - label_principal_search: "Hledat uživatele nebo skupinu:" - label_user_search: "Hledat uživatele:" - - button_login: PÅ™ihlásit - button_submit: Potvrdit - button_save: Uložit - button_check_all: ZaÅ¡rtnout vÅ¡e - button_uncheck_all: OdÅ¡rtnout vÅ¡e - button_delete: Odstranit - button_create: VytvoÅ™it - button_create_and_continue: VytvoÅ™it a pokraÄovat - button_test: Testovat - button_edit: Upravit - button_edit_associated_wikipage: "Upravit pÅ™iÅ™azenou Wiki stránku: %{page_title}" - button_add: PÅ™idat - button_change: ZmÄ›nit - button_apply: Použít - button_clear: Smazat - button_lock: Zamknout - button_unlock: Odemknout - button_download: Stáhnout - button_list: Vypsat - button_view: Zobrazit - button_move: PÅ™esunout - button_move_and_follow: PÅ™esunout a následovat - button_back: ZpÄ›t - button_cancel: Storno - button_activate: Aktivovat - button_sort: SeÅ™adit - button_log_time: PÅ™idat Äas - button_rollback: ZpÄ›t k této verzi - button_watch: Sledovat - button_unwatch: Nesledovat - button_reply: OdpovÄ›dÄ›t - button_archive: Archivovat - button_unarchive: Odarchivovat - button_reset: Resetovat - button_rename: PÅ™ejmenovat - button_change_password: ZmÄ›nit heslo - button_copy: Kopírovat - button_copy_and_follow: Kopírovat a následovat - button_annotate: Komentovat - button_update: Aktualizovat - button_configure: Konfigurovat - button_quote: Citovat - button_duplicate: Duplikovat - button_show: Zobrazit - - status_active: aktivní - status_registered: registrovaný - status_locked: uzamÄený - - version_status_open: otevÅ™ený - version_status_locked: uzamÄený - version_status_closed: zavÅ™ený - - field_active: Aktivní - - text_select_mail_notifications: Vyberte akci pÅ™i které bude zasláno upozornÄ›ní emailem. - text_regexp_info: napÅ™. ^[A-Z0-9]+$ - text_min_max_length_info: 0 znamená bez limitu - text_project_destroy_confirmation: Jste si jisti, že chcete odstranit tento projekt a vÅ¡echna související data ? - text_subprojects_destroy_warning: "Jeho podprojek(y): %{value} budou také smazány." - text_workflow_edit: Vyberte roli a frontu k editaci průbÄ›hu práce - text_are_you_sure: Jste si jisti? - text_are_you_sure_with_children: Smazat úkol vÄetnÄ› vÅ¡ech podúkolů? - text_journal_changed: "%{label} zmÄ›nÄ›n z %{old} na %{new}" - text_journal_set_to: "%{label} nastaven na %{value}" - text_journal_deleted: "%{label} smazán (%{old})" - text_journal_added: "%{label} %{value} pÅ™idán" - text_tip_issue_begin_day: úkol zaÄíná v tento den - text_tip_issue_end_day: úkol konÄí v tento den - text_tip_issue_begin_end_day: úkol zaÄíná a konÄí v tento den - text_project_identifier_info: 'Jsou povolena malá písmena (a-z), Äísla a pomlÄky.
    Po uložení již není možné identifikátor zmÄ›nit.' - text_caracters_maximum: "%{count} znaků maximálnÄ›." - text_caracters_minimum: "Musí být alespoň %{count} znaků dlouhé." - text_length_between: "Délka mezi %{min} a %{max} znaky." - text_tracker_no_workflow: Pro tuto frontu není definován žádný průbÄ›h práce - text_unallowed_characters: Nepovolené znaky - text_comma_separated: Povoleno více hodnot (oddÄ›lÄ›né Äárkou). - text_line_separated: Více hodnot povoleno (jeden řádek pro každou hodnotu). - text_issues_ref_in_commit_messages: Odkazování a opravování úkolů ve zprávách commitů - text_issue_added: "Úkol %{id} byl vytvoÅ™en uživatelem %{author}." - text_issue_updated: "Úkol %{id} byl aktualizován uživatelem %{author}." - text_wiki_destroy_confirmation: Opravdu si pÅ™ejete odstranit tuto Wiki a celý její obsah? - text_issue_category_destroy_question: "NÄ›které úkoly (%{count}) jsou pÅ™iÅ™azeny k této kategorii. Co s nimi chtete udÄ›lat?" - text_issue_category_destroy_assignments: ZruÅ¡it pÅ™iÅ™azení ke kategorii - text_issue_category_reassign_to: PÅ™iÅ™adit úkoly do této kategorie - text_user_mail_option: "U projektů, které nebyly vybrány, budete dostávat oznámení pouze o vaÅ¡ich Äi o sledovaných položkách (napÅ™. o položkách jejichž jste autor nebo ke kterým jste pÅ™iÅ™azen(a))." - text_no_configuration_data: "Role, fronty, stavy úkolů ani průbÄ›h práce nebyly zatím nakonfigurovány.\nVelice doporuÄujeme nahrát výchozí konfiguraci. Po té si můžete vÅ¡e upravit" - text_load_default_configuration: Nahrát výchozí konfiguraci - text_status_changed_by_changeset: "Použito v changesetu %{value}." - text_time_logged_by_changeset: Aplikováno v changesetu %{value}. - text_issues_destroy_confirmation: 'Opravdu si pÅ™ejete odstranit vÅ¡echny zvolené úkoly?' - text_select_project_modules: 'Aktivní moduly v tomto projektu:' - text_default_administrator_account_changed: Výchozí nastavení administrátorského úÄtu zmÄ›nÄ›no - text_file_repository_writable: Povolen zápis do adresáře ukládání souborů - text_plugin_assets_writable: Možnost zápisu do adresáře plugin assets - text_rmagick_available: RMagick k dispozici (volitelné) - text_destroy_time_entries_question: "U úkolů, které chcete odstranit je evidováno %{hours} práce. Co chete udÄ›lat?" - text_destroy_time_entries: Odstranit evidované hodiny. - text_assign_time_entries_to_project: PÅ™iÅ™adit evidované hodiny projektu - text_reassign_time_entries: 'PÅ™eÅ™adit evidované hodiny k tomuto úkolu:' - text_user_wrote: "%{value} napsal:" - text_enumeration_destroy_question: "NÄ›kolik (%{count}) objektů je pÅ™iÅ™azeno k této hodnotÄ›." - text_enumeration_category_reassign_to: 'PÅ™eÅ™adit je do této:' - text_email_delivery_not_configured: "DoruÄování e-mailů není nastaveno a odesílání notifikací je zakázáno.\nNastavte Váš SMTP server v souboru config/configuration.yml a restartujte aplikaci." - text_repository_usernames_mapping: "Vybrat nebo upravit mapování mezi Redmine uživateli a uživatelskými jmény nalezenými v logu repozitáře.\nUživatelé se shodným Redmine uživatelským jménem a uživatelským jménem v repozitáři jsou mapovaní automaticky." - text_diff_truncated: '... Rozdílový soubor je zkrácen, protože jeho délka pÅ™esahuje max. limit.' - text_custom_field_possible_values_info: 'Každá hodnota na novém řádku' - text_wiki_page_destroy_question: Tato stránka má %{descendants} podstránek a potomků. Co chcete udÄ›lat? - text_wiki_page_nullify_children: Ponechat podstránky jako koÅ™enové stránky - text_wiki_page_destroy_children: Smazat podstránky a vÅ¡echny jejich potomky - text_wiki_page_reassign_children: PÅ™iÅ™adit podstránky k tomuto rodiÄi - text_own_membership_delete_confirmation: "Chystáte se odebrat si nÄ›která nebo vÅ¡echny svá oprávnÄ›ní a potom již nemusíte být schopni upravit tento projekt.\nOpravdu chcete pokraÄovat?" - text_zoom_in: PÅ™iblížit - text_zoom_out: Oddálit - - default_role_manager: Manažer - default_role_developer: Vývojář - default_role_reporter: Reportér - default_tracker_bug: Chyba - default_tracker_feature: Požadavek - default_tracker_support: Podpora - default_issue_status_new: Nový - default_issue_status_in_progress: Ve vývoji - default_issue_status_resolved: VyÅ™eÅ¡ený - default_issue_status_feedback: ÄŒeká se - default_issue_status_closed: UzavÅ™ený - default_issue_status_rejected: Odmítnutý - default_doc_category_user: Uživatelská dokumentace - default_doc_category_tech: Technická dokumentace - default_priority_low: Nízká - default_priority_normal: Normální - default_priority_high: Vysoká - default_priority_urgent: Urgentní - default_priority_immediate: Okamžitá - default_activity_design: Návhr - default_activity_development: Vývoj - - enumeration_issue_priorities: Priority úkolů - enumeration_doc_categories: Kategorie dokumentů - enumeration_activities: Aktivity (sledování Äasu) - enumeration_system_activity: Systémová aktivita - - field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text - text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. - label_my_queries: My custom queries - text_journal_changed_no_detail: "%{label} updated" - label_news_comment_added: Comment added to a news - button_expand_all: Expand all - button_collapse_all: Collapse all - label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee - label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author - label_bulk_edit_selected_time_entries: Bulk edit selected time entries - text_time_entries_destroy_confirmation: Are you sure you want to delete the selected time entr(y/ies)? - label_role_anonymous: Anonymous - label_role_non_member: Non member - label_issue_note_added: Note added - label_issue_status_updated: Status updated - label_issue_priority_updated: Priority updated - label_issues_visibility_own: Issues created by or assigned to the user - field_issues_visibility: Issues visibility - label_issues_visibility_all: All issues - permission_set_own_issues_private: Set own issues public or private - field_is_private: Private - permission_set_issues_private: Set issues public or private - label_issues_visibility_public: All non private issues - text_issues_destroy_descendants_confirmation: This will also delete %{count} subtask(s). - field_commit_logs_encoding: Kódování zpráv pÅ™i commitu - field_scm_path_encoding: Path encoding - text_scm_path_encoding_note: "Default: UTF-8" - field_path_to_repository: Path to repository - field_root_directory: Root directory - field_cvs_module: Module - field_cvsroot: CVSROOT - text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) - text_scm_command: Command - text_scm_command_version: Version - label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. - notice_issue_successful_create: Issue %{id} created. - label_between: between - setting_issue_group_assignment: Allow issue assignment to groups - label_diff: diff - text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) - description_query_sort_criteria_direction: Sort direction - description_project_scope: Search scope - description_filter: Filter - description_user_mail_notification: Mail notification settings - description_date_from: Enter start date - description_message_content: Message content - description_available_columns: Available Columns - description_date_range_interval: Choose range by selecting start and end date - description_issue_category_reassign: Choose issue category - description_search: Searchfield - description_notes: Notes - description_date_range_list: Choose range from list - description_choose_project: Projects - description_date_to: Enter end date - description_query_sort_criteria_attribute: Sort attribute - description_wiki_subpages_reassign: Choose new parent page - description_selected_columns: Selected Columns - label_parent_revision: Parent - label_child_revision: Child - error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. - setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues - button_edit_section: Edit this section - setting_repositories_encodings: Attachments and repositories encodings - description_all_columns: All Columns - button_export: Export - label_export_options: "%{export_format} export options" - error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/82/82b21e972ba488af04b89b832125d370772e13c3.svn-base --- a/.svn/pristine/82/82b21e972ba488af04b89b832125d370772e13c3.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,187 +0,0 @@ -<% @gantt.view = self %> -

    <%= @query.new_record? ? l(:label_gantt) : h(@query.name) %>

    - -<% form_tag({:controller => 'gantts', :action => 'show', :project_id => @project, :month => params[:month], :year => params[:year], :months => params[:months]}, :method => :get, :id => 'query_form') do %> -<%= hidden_field_tag 'set_filter', '1' %> -
    "> - <%= l(:label_filter_plural) %> -
    "> - <%= render :partial => 'queries/filters', :locals => {:query => @query} %> -
    -
    - -

    - <%= gantt_zoom_link(@gantt, :in) %> - <%= gantt_zoom_link(@gantt, :out) %> -

    - -

    -<%= text_field_tag 'months', @gantt.months, :size => 2 %> -<%= l(:label_months_from) %> -<%= select_month(@gantt.month_from, :prefix => "month", :discard_type => true) %> -<%= select_year(@gantt.year_from, :prefix => "year", :discard_type => true) %> -<%= hidden_field_tag 'zoom', @gantt.zoom %> - -<%= link_to_function l(:button_apply), '$("query_form").submit()', :class => 'icon icon-checked' %> -<%= link_to l(:button_clear), { :project_id => @project, :set_filter => 1 }, :class => 'icon icon-reload' %> -

    -<% end %> - -<%= error_messages_for 'query' %> -<% if @query.valid? %> -<% zoom = 1 -@gantt.zoom.times { zoom = zoom * 2 } - -subject_width = 330 -header_heigth = 18 - -headers_height = header_heigth -show_weeks = false -show_days = false - -if @gantt.zoom >1 - show_weeks = true - headers_height = 2*header_heigth - if @gantt.zoom > 2 - show_days = true - headers_height = 3*header_heigth - end -end - -# Width of the entire chart -g_width = (@gantt.date_to - @gantt.date_from + 1)*zoom - -@gantt.render(:top => headers_height + 8, :zoom => zoom, :g_width => g_width, :subject_width => subject_width) - -g_height = [(20 * (@gantt.number_of_rows + 6))+150, 206].max -t_height = g_height + headers_height - - -%> - -<% if @gantt.truncated %> -

    <%= l(:notice_gantt_chart_truncated, :max => @gantt.max_rows) %>

    -<% end %> - - - - - - -
    - -
    -
    -
    - -
    -<%= @gantt.subjects.html_safe %> -
    - -
    -
    - -
    -
     
    -<% -# -# Months headers -# -month_f = @gantt.date_from -left = 0 -height = (show_weeks ? header_heigth : header_heigth + g_height) -@gantt.months.times do - width = ((month_f >> 1) - month_f) * zoom - 1 - %> -
    - <%= link_to h("#{month_f.year}-#{month_f.month}"), @gantt.params.merge(:year => month_f.year, :month => month_f.month), :title => "#{month_name(month_f.month)} #{month_f.year}"%> -
    - <% - left = left + width + 1 - month_f = month_f >> 1 -end %> - -<% -# -# Weeks headers -# -if show_weeks - left = 0 - height = (show_days ? header_heigth-1 : header_heigth-1 + g_height) - if @gantt.date_from.cwday == 1 - # @date_from is monday - week_f = @gantt.date_from - else - # find next monday after @date_from - week_f = @gantt.date_from + (7 - @gantt.date_from.cwday + 1) - width = (7 - @gantt.date_from.cwday + 1) * zoom-1 - %> -
     
    - <% - left = left + width+1 - end %> - <% - while week_f <= @gantt.date_to - width = (week_f + 6 <= @gantt.date_to) ? 7 * zoom -1 : (@gantt.date_to - week_f + 1) * zoom-1 - %> -
    - <%= week_f.cweek if width >= 16 %> -
    - <% - left = left + width+1 - week_f = week_f+7 - end -end %> - -<% -# -# Days headers -# -if show_days - left = 0 - height = g_height + header_heigth - 1 - wday = @gantt.date_from.cwday - (@gantt.date_to - @gantt.date_from + 1).to_i.times do - width = zoom - 1 - %> -
    5 %>" class="gantt_hdr"> - <%= day_name(wday).first %> -
    - <% - left = left + width+1 - wday = wday + 1 - wday = 1 if wday > 7 - end -end %> - -<%= @gantt.lines.html_safe %> - -<% -# -# Today red line (excluded from cache) -# -if Date.today >= @gantt.date_from and Date.today <= @gantt.date_to %> -
     
    -<% end %> - -
    -
    - - - - - - -
    <%= link_to_content_update("\xc2\xab " + l(:label_previous), params.merge(@gantt.params_previous)) %><%= link_to_content_update(l(:label_next) + " \xc2\xbb", params.merge(@gantt.params_next)) %>
    - -<% other_formats_links do |f| %> - <%= f.link_to 'PDF', :url => params.merge(@gantt.params) %> - <%= f.link_to('PNG', :url => params.merge(@gantt.params)) if @gantt.respond_to?('to_image') %> -<% end %> -<% end # query.valid? %> - -<% content_for :sidebar do %> - <%= render :partial => 'issues/sidebar' %> -<% end %> - -<% html_title(l(:label_gantt)) -%> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/82/82bbb606607aa23e8d3efe2e04412a3682e044fa.svn-base --- a/.svn/pristine/82/82bbb606607aa23e8d3efe2e04412a3682e044fa.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,64 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class SettingsController < ApplicationController - layout 'admin' - - before_filter :require_admin - - def index - edit - render :action => 'edit' - end - - def edit - @notifiables = Redmine::Notifiable.all - if request.post? && params[:settings] && params[:settings].is_a?(Hash) - settings = (params[:settings] || {}).dup.symbolize_keys - settings.each do |name, value| - # remove blank values in array settings - value.delete_if {|v| v.blank? } if value.is_a?(Array) - Setting[name] = value - end - flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'edit', :tab => params[:tab] - else - @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? - - Redmine::Themes.rescan - end - end - - def plugin - @plugin = Redmine::Plugin.find(params[:id]) - if request.post? - Setting["plugin_#{@plugin.id}"] = params[:settings] - flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'plugin', :id => @plugin.id - else - @partial = @plugin.settings[:partial] - @settings = Setting["plugin_#{@plugin.id}"] - end - rescue Redmine::PluginNotFound - render_404 - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/82/82e1b9240931979d108e36ebeb7d4ae4ea18f145.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/82/82e1b9240931979d108e36ebeb7d4ae4ea18f145.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,41 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class JournalDetail < ActiveRecord::Base + belongs_to :journal + before_save :normalize_values + + private + + def normalize_values + self.value = normalize(value) + self.old_value = normalize(old_value) + end + + def normalize(v) + case v + when true + "1" + when false + "0" + when Date + v.strftime("%Y-%m-%d") + else + v + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/83/831d3316fec7fbf223f78f9fec03d2284831a5d0.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/83/831d3316fec7fbf223f78f9fec03d2284831a5d0.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,358 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class IssueNestedSetTest < ActiveSupport::TestCase + fixtures :projects, :users, :roles, + :trackers, :projects_trackers, + :issue_statuses, :issue_categories, :issue_relations, + :enumerations, + :issues + + def test_create_root_issue + issue1 = Issue.generate! + issue2 = Issue.generate! + issue1.reload + issue2.reload + + assert_equal [issue1.id, nil, 1, 2], [issue1.root_id, issue1.parent_id, issue1.lft, issue1.rgt] + assert_equal [issue2.id, nil, 1, 2], [issue2.root_id, issue2.parent_id, issue2.lft, issue2.rgt] + end + + def test_create_child_issue + parent = Issue.generate! + child = Issue.generate!(:parent_issue_id => parent.id) + parent.reload + child.reload + + assert_equal [parent.id, nil, 1, 4], [parent.root_id, parent.parent_id, parent.lft, parent.rgt] + assert_equal [parent.id, parent.id, 2, 3], [child.root_id, child.parent_id, child.lft, child.rgt] + end + + def test_creating_a_child_in_a_subproject_should_validate + issue = Issue.generate! + child = Issue.new(:project_id => 3, :tracker_id => 2, :author_id => 1, + :subject => 'child', :parent_issue_id => issue.id) + assert_save child + assert_equal issue, child.reload.parent + end + + def test_creating_a_child_in_an_invalid_project_should_not_validate + issue = Issue.generate! + child = Issue.new(:project_id => 2, :tracker_id => 1, :author_id => 1, + :subject => 'child', :parent_issue_id => issue.id) + assert !child.save + assert_not_nil child.errors[:parent_issue_id] + end + + def test_move_a_root_to_child + parent1 = Issue.generate! + parent2 = Issue.generate! + child = Issue.generate!(:parent_issue_id => parent1.id) + + parent2.parent_issue_id = parent1.id + parent2.save! + child.reload + parent1.reload + parent2.reload + + assert_equal [parent1.id, 1, 6], [parent1.root_id, parent1.lft, parent1.rgt] + assert_equal [parent1.id, 4, 5], [parent2.root_id, parent2.lft, parent2.rgt] + assert_equal [parent1.id, 2, 3], [child.root_id, child.lft, child.rgt] + end + + def test_move_a_child_to_root + parent1 = Issue.generate! + parent2 = Issue.generate! + child = Issue.generate!(:parent_issue_id => parent1.id) + + child.parent_issue_id = nil + child.save! + child.reload + parent1.reload + parent2.reload + + assert_equal [parent1.id, 1, 2], [parent1.root_id, parent1.lft, parent1.rgt] + assert_equal [parent2.id, 1, 2], [parent2.root_id, parent2.lft, parent2.rgt] + assert_equal [child.id, 1, 2], [child.root_id, child.lft, child.rgt] + end + + def test_move_a_child_to_another_issue + parent1 = Issue.generate! + parent2 = Issue.generate! + child = Issue.generate!(:parent_issue_id => parent1.id) + + child.parent_issue_id = parent2.id + child.save! + child.reload + parent1.reload + parent2.reload + + assert_equal [parent1.id, 1, 2], [parent1.root_id, parent1.lft, parent1.rgt] + assert_equal [parent2.id, 1, 4], [parent2.root_id, parent2.lft, parent2.rgt] + assert_equal [parent2.id, 2, 3], [child.root_id, child.lft, child.rgt] + end + + def test_move_a_child_with_descendants_to_another_issue + parent1 = Issue.generate! + parent2 = Issue.generate! + child = Issue.generate!(:parent_issue_id => parent1.id) + grandchild = Issue.generate!(:parent_issue_id => child.id) + + parent1.reload + parent2.reload + child.reload + grandchild.reload + + assert_equal [parent1.id, 1, 6], [parent1.root_id, parent1.lft, parent1.rgt] + assert_equal [parent2.id, 1, 2], [parent2.root_id, parent2.lft, parent2.rgt] + assert_equal [parent1.id, 2, 5], [child.root_id, child.lft, child.rgt] + assert_equal [parent1.id, 3, 4], [grandchild.root_id, grandchild.lft, grandchild.rgt] + + child.reload.parent_issue_id = parent2.id + child.save! + child.reload + grandchild.reload + parent1.reload + parent2.reload + + assert_equal [parent1.id, 1, 2], [parent1.root_id, parent1.lft, parent1.rgt] + assert_equal [parent2.id, 1, 6], [parent2.root_id, parent2.lft, parent2.rgt] + assert_equal [parent2.id, 2, 5], [child.root_id, child.lft, child.rgt] + assert_equal [parent2.id, 3, 4], [grandchild.root_id, grandchild.lft, grandchild.rgt] + end + + def test_move_a_child_with_descendants_to_another_project + parent1 = Issue.generate! + child = Issue.generate!(:parent_issue_id => parent1.id) + grandchild = Issue.generate!(:parent_issue_id => child.id) + + child.reload + child.project = Project.find(2) + assert child.save + child.reload + grandchild.reload + parent1.reload + + assert_equal [1, parent1.id, 1, 2], [parent1.project_id, parent1.root_id, parent1.lft, parent1.rgt] + assert_equal [2, child.id, 1, 4], [child.project_id, child.root_id, child.lft, child.rgt] + assert_equal [2, child.id, 2, 3], [grandchild.project_id, grandchild.root_id, grandchild.lft, grandchild.rgt] + end + + def test_moving_an_issue_to_a_descendant_should_not_validate + parent1 = Issue.generate! + parent2 = Issue.generate! + child = Issue.generate!(:parent_issue_id => parent1.id) + grandchild = Issue.generate!(:parent_issue_id => child.id) + + child.reload + child.parent_issue_id = grandchild.id + assert !child.save + assert_not_nil child.errors[:parent_issue_id] + end + + def test_destroy_should_destroy_children + issue1 = Issue.generate! + issue2 = Issue.generate! + issue3 = Issue.generate!(:parent_issue_id => issue2.id) + issue4 = Issue.generate!(:parent_issue_id => issue1.id) + + issue3.init_journal(User.find(2)) + issue3.subject = 'child with journal' + issue3.save! + + assert_difference 'Issue.count', -2 do + assert_difference 'Journal.count', -1 do + assert_difference 'JournalDetail.count', -1 do + Issue.find(issue2.id).destroy + end + end + end + + issue1.reload + issue4.reload + assert !Issue.exists?(issue2.id) + assert !Issue.exists?(issue3.id) + assert_equal [issue1.id, 1, 4], [issue1.root_id, issue1.lft, issue1.rgt] + assert_equal [issue1.id, 2, 3], [issue4.root_id, issue4.lft, issue4.rgt] + end + + def test_destroy_child_should_update_parent + issue = Issue.generate! + child1 = Issue.generate!(:parent_issue_id => issue.id) + child2 = Issue.generate!(:parent_issue_id => issue.id) + + issue.reload + assert_equal [issue.id, 1, 6], [issue.root_id, issue.lft, issue.rgt] + + child2.reload.destroy + + issue.reload + assert_equal [issue.id, 1, 4], [issue.root_id, issue.lft, issue.rgt] + end + + def test_destroy_parent_issue_updated_during_children_destroy + parent = Issue.generate! + Issue.generate!(:start_date => Date.today, :parent_issue_id => parent.id) + Issue.generate!(:start_date => 2.days.from_now, :parent_issue_id => parent.id) + + assert_difference 'Issue.count', -3 do + Issue.find(parent.id).destroy + end + end + + def test_destroy_child_issue_with_children + root = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'root') + child = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => root.id) + leaf = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'leaf', :parent_issue_id => child.id) + leaf.init_journal(User.find(2)) + leaf.subject = 'leaf with journal' + leaf.save! + + assert_difference 'Issue.count', -2 do + assert_difference 'Journal.count', -1 do + assert_difference 'JournalDetail.count', -1 do + Issue.find(child.id).destroy + end + end + end + + root = Issue.find(root.id) + assert root.leaf?, "Root issue is not a leaf (lft: #{root.lft}, rgt: #{root.rgt})" + end + + def test_destroy_issue_with_grand_child + parent = Issue.generate! + issue = Issue.generate!(:parent_issue_id => parent.id) + child = Issue.generate!(:parent_issue_id => issue.id) + grandchild1 = Issue.generate!(:parent_issue_id => child.id) + grandchild2 = Issue.generate!(:parent_issue_id => child.id) + + assert_difference 'Issue.count', -4 do + Issue.find(issue.id).destroy + parent.reload + assert_equal [1, 2], [parent.lft, parent.rgt] + end + end + + def test_parent_priority_should_be_the_highest_child_priority + parent = Issue.generate!(:priority => IssuePriority.find_by_name('Normal')) + # Create children + child1 = Issue.generate!(:priority => IssuePriority.find_by_name('High'), :parent_issue_id => parent.id) + assert_equal 'High', parent.reload.priority.name + child2 = Issue.generate!(:priority => IssuePriority.find_by_name('Immediate'), :parent_issue_id => child1.id) + assert_equal 'Immediate', child1.reload.priority.name + assert_equal 'Immediate', parent.reload.priority.name + child3 = Issue.generate!(:priority => IssuePriority.find_by_name('Low'), :parent_issue_id => parent.id) + assert_equal 'Immediate', parent.reload.priority.name + # Destroy a child + child1.destroy + assert_equal 'Low', parent.reload.priority.name + # Update a child + child3.reload.priority = IssuePriority.find_by_name('Normal') + child3.save! + assert_equal 'Normal', parent.reload.priority.name + end + + def test_parent_dates_should_be_lowest_start_and_highest_due_dates + parent = Issue.generate! + Issue.generate!(:start_date => '2010-01-25', :due_date => '2010-02-15', :parent_issue_id => parent.id) + Issue.generate!( :due_date => '2010-02-13', :parent_issue_id => parent.id) + Issue.generate!(:start_date => '2010-02-01', :due_date => '2010-02-22', :parent_issue_id => parent.id) + parent.reload + assert_equal Date.parse('2010-01-25'), parent.start_date + assert_equal Date.parse('2010-02-22'), parent.due_date + end + + def test_parent_done_ratio_should_be_average_done_ratio_of_leaves + parent = Issue.generate! + Issue.generate!(:done_ratio => 20, :parent_issue_id => parent.id) + assert_equal 20, parent.reload.done_ratio + Issue.generate!(:done_ratio => 70, :parent_issue_id => parent.id) + assert_equal 45, parent.reload.done_ratio + + child = Issue.generate!(:done_ratio => 0, :parent_issue_id => parent.id) + assert_equal 30, parent.reload.done_ratio + + Issue.generate!(:done_ratio => 30, :parent_issue_id => child.id) + assert_equal 30, child.reload.done_ratio + assert_equal 40, parent.reload.done_ratio + end + + def test_parent_done_ratio_should_be_weighted_by_estimated_times_if_any + parent = Issue.generate! + Issue.generate!(:estimated_hours => 10, :done_ratio => 20, :parent_issue_id => parent.id) + assert_equal 20, parent.reload.done_ratio + Issue.generate!(:estimated_hours => 20, :done_ratio => 50, :parent_issue_id => parent.id) + assert_equal (50 * 20 + 20 * 10) / 30, parent.reload.done_ratio + end + + def test_parent_estimate_should_be_sum_of_leaves + parent = Issue.generate! + Issue.generate!(:estimated_hours => nil, :parent_issue_id => parent.id) + assert_equal nil, parent.reload.estimated_hours + Issue.generate!(:estimated_hours => 5, :parent_issue_id => parent.id) + assert_equal 5, parent.reload.estimated_hours + Issue.generate!(:estimated_hours => 7, :parent_issue_id => parent.id) + assert_equal 12, parent.reload.estimated_hours + end + + def test_move_parent_updates_old_parent_attributes + first_parent = Issue.generate! + second_parent = Issue.generate! + child = Issue.generate!(:estimated_hours => 5, :parent_issue_id => first_parent.id) + assert_equal 5, first_parent.reload.estimated_hours + child.update_attributes(:estimated_hours => 7, :parent_issue_id => second_parent.id) + assert_equal 7, second_parent.reload.estimated_hours + assert_nil first_parent.reload.estimated_hours + end + + def test_reschuling_a_parent_should_reschedule_subtasks + parent = Issue.generate! + c1 = Issue.generate!(:start_date => '2010-05-12', :due_date => '2010-05-18', :parent_issue_id => parent.id) + c2 = Issue.generate!(:start_date => '2010-06-03', :due_date => '2010-06-10', :parent_issue_id => parent.id) + parent.reload + parent.reschedule_on!(Date.parse('2010-06-02')) + c1.reload + assert_equal [Date.parse('2010-06-02'), Date.parse('2010-06-08')], [c1.start_date, c1.due_date] + c2.reload + assert_equal [Date.parse('2010-06-03'), Date.parse('2010-06-10')], [c2.start_date, c2.due_date] # no change + parent.reload + assert_equal [Date.parse('2010-06-02'), Date.parse('2010-06-10')], [parent.start_date, parent.due_date] + end + + def test_project_copy_should_copy_issue_tree + p = Project.create!(:name => 'Tree copy', :identifier => 'tree-copy', :tracker_ids => [1, 2]) + i1 = Issue.generate!(:project => p, :subject => 'i1') + i2 = Issue.generate!(:project => p, :subject => 'i2', :parent_issue_id => i1.id) + i3 = Issue.generate!(:project => p, :subject => 'i3', :parent_issue_id => i1.id) + i4 = Issue.generate!(:project => p, :subject => 'i4', :parent_issue_id => i2.id) + i5 = Issue.generate!(:project => p, :subject => 'i5') + c = Project.new(:name => 'Copy', :identifier => 'copy', :tracker_ids => [1, 2]) + c.copy(p, :only => 'issues') + c.reload + + assert_equal 5, c.issues.count + ic1, ic2, ic3, ic4, ic5 = c.issues.order('subject').all + assert ic1.root? + assert_equal ic1, ic2.parent + assert_equal ic1, ic3.parent + assert_equal ic2, ic4.parent + assert ic5.root? + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/83/834d9cf4dced150304b5d64254144f6c7a1f3283.svn-base --- a/.svn/pristine/83/834d9cf4dced150304b5d64254144f6c7a1f3283.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,107 +0,0 @@ - - -
    -<% form_tag({:action => "add_block"}, :id => "block-form") do %> -<%= label_tag('block-select', l(:label_my_page_block)) %>: -<%= select_tag 'block', "" + options_for_select(@block_options), :id => "block-select" %> -<%= link_to_remote l(:button_add), - {:url => { :action => "add_block" }, - :with => "Form.serialize('block-form')", - :update => "list-top", - :position => :top, - :complete => "afterAddBlock();" - }, :class => 'icon icon-add' - %> -<% end %> -<%= link_to l(:button_back), {:action => 'page'}, :class => 'icon icon-cancel' %> -
    - -

    <%=l(:label_my_page)%>

    - -
    - <% @blocks['top'].each do |b| - next unless MyController::BLOCKS.keys.include? b %> - <%= render :partial => 'block', :locals => {:user => @user, :block_name => b} %> - <% end if @blocks['top'] %> -
    - -
    - <% @blocks['left'].each do |b| - next unless MyController::BLOCKS.keys.include? b %> - <%= render :partial => 'block', :locals => {:user => @user, :block_name => b} %> - <% end if @blocks['left'] %> -
    - -
    - <% @blocks['right'].each do |b| - next unless MyController::BLOCKS.keys.include? b %> - <%= render :partial => 'block', :locals => {:user => @user, :block_name => b} %> - <% end if @blocks['right'] %> -
    - -<%= sortable_element 'list-top', - :tag => 'div', - :only => 'mypage-box', - :handle => "handle", - :dropOnEmpty => true, - :containment => ['list-top', 'list-left', 'list-right'], - :constraint => false, - :url => { :action => "order_blocks", :group => "top" } - %> - -<%= sortable_element 'list-left', - :tag => 'div', - :only => 'mypage-box', - :handle => "handle", - :dropOnEmpty => true, - :containment => ['list-top', 'list-left', 'list-right'], - :constraint => false, - :url => { :action => "order_blocks", :group => "left" } - %> - -<%= sortable_element 'list-right', - :tag => 'div', - :only => 'mypage-box', - :handle => "handle", - :dropOnEmpty => true, - :containment => ['list-top', 'list-left', 'list-right'], - :constraint => false, - :url => { :action => "order_blocks", :group => "right" } - %> - -<%= javascript_tag "updateSelect()" %> -<% html_title(l(:label_my_page)) -%> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/83/8360f4a19732e489ca5ff590b12c243a5092c5f6.svn-base --- a/.svn/pristine/83/8360f4a19732e489ca5ff590b12c243a5092c5f6.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,56 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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 SyntaxHighlighting - - class << self - attr_reader :highlighter - delegate :highlight_by_filename, :highlight_by_language, :to => :highlighter - - def highlighter=(name) - if name.is_a?(Module) - @highlighter = name - else - @highlighter = const_get(name) - end - end - end - - module CodeRay - require 'coderay' - require 'coderay/helpers/file_type' - - class << self - # Highlights +text+ as the content of +filename+ - # Should not return line numbers nor outer pre tag - def highlight_by_filename(text, filename) - language = ::CodeRay::FileType[filename] - language ? ::CodeRay.scan(text, language).html : ERB::Util.h(text) - end - - # Highlights +text+ using +language+ syntax - # Should not return outer pre tag - def highlight_by_language(text, language) - ::CodeRay.scan(text, language).html(:line_numbers => :inline, :line_number_anchors => false, :wrap => :span) - end - end - end - end - - SyntaxHighlighting.highlighter = 'CodeRay' -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/83/83a71fa0994c919b743d5f425de3265db363c4cc.svn-base --- a/.svn/pristine/83/83a71fa0994c919b743d5f425de3265db363c4cc.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,28 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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 ApiTemplateHandler < ActionView::TemplateHandler - include ActionView::TemplateHandlers::Compilable - - def compile(template) - "Redmine::Views::Builders.for(params[:format]) do |api|; #{template.source}; self.output_buffer = api.output; end" - end - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/83/83aea0eb2f9d845231bab48205698f5fdab2c21a.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/83/83aea0eb2f9d845231bab48205698f5fdab2c21a.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,7 @@ +

    <%=l(:label_auth_source_new)%> (<%= h(@auth_source.auth_method_name) %>)

    + +<%= labelled_form_for @auth_source, :as => :auth_source, :url => auth_sources_path, :html => {:id => 'auth_source_form'} do |f| %> + <%= hidden_field_tag 'type', @auth_source.type %> + <%= render :partial => auth_source_partial_name(@auth_source), :locals => { :f => f } %> + <%= submit_tag l(:button_create) %> +<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/83/83b13898f7eab0943573424303e9d171d7a1a13a.svn-base --- a/.svn/pristine/83/83b13898f7eab0943573424303e9d171d7a1a13a.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) - -class DocumentTest < ActiveSupport::TestCase - fixtures :projects, :enumerations, :documents, :attachments - - 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_should_send_email_notification - ActionMailer::Base.deliveries.clear - Setting.notified_events << 'document_added' - doc = Document.new(:project => Project.find(1), :title => 'New document', :category => Enumeration.find_by_name('User documentation')) - - assert doc.save - assert_equal 1, ActionMailer::Base.deliveries.size - 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 - - def test_updated_on_with_attachments - d = Document.find(1) - assert d.attachments.any? - assert_equal d.attachments.map(&:created_on).max, d.updated_on - end - - def test_updated_on_without_attachments - d = Document.find(2) - assert d.attachments.empty? - assert_equal d.created_on, d.updated_on - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/83/83c3e4ac7042c55f0008c2a13f02fb3cd6cd5e71.svn-base --- a/.svn/pristine/83/83c3e4ac7042c55f0008c2a13f02fb3cd6cd5e71.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,140 +0,0 @@ -# Taken from Rails 2.1 -module CollectiveIdea #:nodoc: - module NamedScope #:nodoc: - # All subclasses of ActiveRecord::Base have two named_scopes: - # * all, which is similar to a find(:all) query, and - # * scoped, 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 :conditions => {:color => :red}, :select => 'shirts.*', :include => :washing_instructions. - # - # 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 named_scope define class methods Shirt.red and Shirt.dry_clean_only. Shirt.red, - # in effect, represents the query Shirt.find(:all, :conditions => {:color => 'red'}). - # - # Unlike Shirt.find(...), however, the object returned by Shirt.red is not an Array; it resembles the association object - # constructed by a has_many declaration. For instance, you can invoke Shirt.red.find(:first), Shirt.red.count, - # Shirt.red.find(:all, :conditions => {:size => 'small'}). Also, just - # as with the association objects, name scopes acts like an Array, implementing Enumerable; Shirt.red.each(&block), - # Shirt.red.first, and Shirt.red.inject(memo, &block) all behave as if Shirt.red really were an Array. - # - # These named scopes are composable. For instance, Shirt.red.dry_clean_only will produce all shirts that are both red and dry clean only. - # Nested finds and calculations also work with these compositions: Shirt.red.dry_clean_only.count returns the number of garments - # for which these criteria obtain. Similarly with Shirt.red.dry_clean_only.average(:thread_count). - # - # All scopes are available as class methods on the ActiveRecord descendent upon which the scopes were defined. But they are also available to - # has_many associations. If, - # - # class Person < ActiveRecord::Base - # has_many :shirts - # end - # - # then elton.shirts.red.dry_clean_only 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, Shirt.colored('puce') finds all puce shirts. - # - # Named scopes can also have extensions, just as with has_many 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 - # proxy_options 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 -r 0a574315af3e -r 4f746d8966dd .svn/pristine/83/83e9bc8b42104b913718f6e8799f58710545ff76.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/83/83e9bc8b42104b913718f6e8799f58710545ff76.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,58 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class RoutingDocumentsTest < ActionController::IntegrationTest + def test_documents_scoped_under_project + assert_routing( + { :method => 'get', :path => "/projects/567/documents" }, + { :controller => 'documents', :action => 'index', :project_id => '567' } + ) + assert_routing( + { :method => 'get', :path => "/projects/567/documents/new" }, + { :controller => 'documents', :action => 'new', :project_id => '567' } + ) + assert_routing( + { :method => 'post', :path => "/projects/567/documents" }, + { :controller => 'documents', :action => 'create', :project_id => '567' } + ) + end + + def test_documents + assert_routing( + { :method => 'get', :path => "/documents/22" }, + { :controller => 'documents', :action => 'show', :id => '22' } + ) + assert_routing( + { :method => 'get', :path => "/documents/22/edit" }, + { :controller => 'documents', :action => 'edit', :id => '22' } + ) + assert_routing( + { :method => 'put', :path => "/documents/22" }, + { :controller => 'documents', :action => 'update', :id => '22' } + ) + assert_routing( + { :method => 'delete', :path => "/documents/22" }, + { :controller => 'documents', :action => 'destroy', :id => '22' } + ) + assert_routing( + { :method => 'post', :path => "/documents/22/add_attachment" }, + { :controller => 'documents', :action => 'add_attachment', :id => '22' } + ) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/84/84091f2a3428fd01f5592918d3e84fd8efa86bdf.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/84/84091f2a3428fd01f5592918d3e84fd8efa86bdf.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,40 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 DocumentCategory < Enumeration + has_many :documents, :foreign_key => 'category_id' + + OptionName = :enumeration_doc_categories + + def option_name + OptionName + end + + def objects_count + documents.count + end + + def transfer_relations(to) + documents.update_all("category_id = #{to.id}") + end + + def self.default + d = super + d = first if d.nil? + d + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/84/841186dcecffe41ae1f674267a63d3d2f8a4647b.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/84/841186dcecffe41ae1f674267a63d3d2f8a4647b.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,61 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class RoutingWatchersTest < ActionController::IntegrationTest + def test_watchers + assert_routing( + { :method => 'get', :path => "/watchers/new" }, + { :controller => 'watchers', :action => 'new' } + ) + assert_routing( + { :method => 'post', :path => "/watchers/append" }, + { :controller => 'watchers', :action => 'append' } + ) + assert_routing( + { :method => 'post', :path => "/watchers" }, + { :controller => 'watchers', :action => 'create' } + ) + assert_routing( + { :method => 'delete', :path => "/watchers" }, + { :controller => 'watchers', :action => 'destroy' } + ) + assert_routing( + { :method => 'get', :path => "/watchers/autocomplete_for_user" }, + { :controller => 'watchers', :action => 'autocomplete_for_user' } + ) + assert_routing( + { :method => 'post', :path => "/watchers/watch" }, + { :controller => 'watchers', :action => 'watch' } + ) + assert_routing( + { :method => 'delete', :path => "/watchers/watch" }, + { :controller => 'watchers', :action => 'unwatch' } + ) + assert_routing( + { :method => 'post', :path => "/issues/12/watchers.xml" }, + { :controller => 'watchers', :action => 'create', + :object_type => 'issue', :object_id => '12', :format => 'xml' } + ) + assert_routing( + { :method => 'delete', :path => "/issues/12/watchers/3.xml" }, + { :controller => 'watchers', :action => 'destroy', + :object_type => 'issue', :object_id => '12', :user_id => '3', :format => 'xml'} + ) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/84/842d236f9067b05ac81fdd2d952a30deb65e8f8c.svn-base --- a/.svn/pristine/84/842d236f9067b05ac81fdd2d952a30deb65e8f8c.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1049 +0,0 @@ -# Swedish translation for Ruby on Rails -# by Johan Lundström (johanlunds@gmail.com), -# with parts taken from http://github.com/daniel/swe_rails - -sv: - number: - # Used in number_with_delimiter() - # These are also the defaults for 'currency', 'percentage', 'precision', and 'human' - format: - # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5) - separator: "," - # Delimets thousands (e.g. 1,000,000 is a million) (always in groups of three) - delimiter: "." - # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00) - precision: 2 - - # Used in number_to_currency() - currency: - format: - # Where is the currency sign? %u is the currency unit, %n the number (default: $5.00) - format: "%n %u" - unit: "kr" - # These three are to override number.format and are optional - # separator: "." - # delimiter: "," - # precision: 2 - - # Used in number_to_percentage() - percentage: - format: - # These three are to override number.format and are optional - # separator: - delimiter: "" - # precision: - - # Used in number_to_precision() - precision: - format: - # These three are to override number.format and are optional - # separator: - delimiter: "" - # precision: - - # Used in number_to_human_size() - human: - format: - # These three are to override number.format and are optional - # separator: - delimiter: "" - # precision: 1 - storage_units: - format: "%n %u" - units: - byte: - one: "Byte" - other: "Bytes" - kb: "kB" - mb: "MB" - gb: "GB" - tb: "TB" - - # Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words() - datetime: - distance_in_words: - half_a_minute: "en halv minut" - less_than_x_seconds: - one: "mindre än en sekund" - other: "mindre än %{count} sekunder" - x_seconds: - one: "en sekund" - other: "%{count} sekunder" - less_than_x_minutes: - one: "mindre än en minut" - other: "mindre än %{count} minuter" - x_minutes: - one: "en minut" - other: "%{count} minuter" - about_x_hours: - one: "ungefär en timme" - other: "ungefär %{count} timmar" - x_days: - one: "en dag" - other: "%{count} dagar" - about_x_months: - one: "ungefär en månad" - other: "ungefär %{count} månader" - x_months: - one: "en månad" - other: "%{count} månader" - about_x_years: - one: "ungefär ett år" - other: "ungefär %{count} år" - over_x_years: - one: "mer än ett år" - other: "mer än %{count} år" - almost_x_years: - one: "nästan 1 år" - other: "nästan %{count} år" - - activerecord: - errors: - template: - header: - one: "Ett fel förhindrade denna %{model} från att sparas" - other: "%{count} fel förhindrade denna %{model} från att sparas" - # The variable :count is also available - body: "Det var problem med följande fält:" - # The values :model, :attribute and :value are always available for interpolation - # The value :count is available when applicable. Can be used for pluralization. - messages: - inclusion: "finns inte i listan" - exclusion: "är reserverat" - invalid: "är ogiltigt" - confirmation: "stämmer inte överens" - accepted : "måste vara accepterad" - empty: "får ej vara tom" - blank: "måste anges" - too_long: "är för lång (maximum är %{count} tecken)" - too_short: "är för kort (minimum är %{count} tecken)" - wrong_length: "har fel längd (ska vara %{count} tecken)" - taken: "har redan tagits" - not_a_number: "är inte ett nummer" - greater_than: "måste vara större än %{count}" - greater_than_or_equal_to: "måste vara större än eller lika med %{count}" - equal_to: "måste vara samma som" - less_than: "måste vara mindre än %{count}" - less_than_or_equal_to: "måste vara mindre än eller lika med %{count}" - odd: "måste vara udda" - even: "måste vara jämnt" - greater_than_start_date: "måste vara senare än startdatumet" - not_same_project: "tillhör inte samma projekt" - circular_dependency: "Denna relation skulle skapa ett cirkulärt beroende" - cant_link_an_issue_with_a_descendant: "Ett ärende kan inte länkas till ett av dess underärenden" - - direction: ltr - date: - formats: - # Use the strftime parameters for formats. - # When no format has been given, it uses default. - # You can provide other formats here if you like! - default: "%Y-%m-%d" - short: "%e %b" - long: "%e %B, %Y" - - day_names: [söndag, måndag, tisdag, onsdag, torsdag, fredag, lördag] - abbr_day_names: [sön, mån, tis, ons, tor, fre, lör] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, januari, februari, mars, april, maj, juni, juli, augusti, september, oktober, november, december] - abbr_month_names: [~, jan, feb, mar, apr, maj, jun, jul, aug, sep, okt, nov, dec] - # Used in date_select and datime_select. - order: - - :day - - :month - - :year - - time: - formats: - default: "%Y-%m-%d %H:%M" - time: "%H:%M" - short: "%d %b %H:%M" - long: "%d %B, %Y %H:%M" - am: "" - pm: "" - -# Used in array.to_sentence. - support: - array: - sentence_connector: "och" - skip_last_comma: true - - actionview_instancetag_blank_option: Var god välj - - general_text_No: 'Nej' - general_text_Yes: 'Ja' - general_text_no: 'nej' - general_text_yes: 'ja' - general_lang_name: 'Svenska' - general_csv_separator: ',' - general_csv_decimal_separator: '.' - general_csv_encoding: ISO-8859-1 - general_pdf_encoding: UTF-8 - 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. 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: Skapades korrekt. - 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: Du saknar behörighet att komma åt den här sidan. - notice_not_authorized_archived_project: Projektet du försöker komma åt har arkiverats. - notice_email_sent: "Ett mail skickades till %{value}" - notice_email_error: "Ett fel inträffade när mail skickades (%{value})" - notice_feeds_access_key_reseted: Din RSS-nyckel återställdes. - notice_api_access_key_reseted: Din API-nyckel återställdes. - notice_failed_to_save_issues: "Misslyckades med att spara %{count} ärende(n) på %{total} valt: %{ids}." - notice_failed_to_save_members: "Misslyckades med att spara medlem(mar): %{errors}." - 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. - notice_unable_delete_time_entry: Tidloggning kunde inte tas bort. - notice_issue_done_ratios_updated: "% klart uppdaterade." - notice_gantt_chart_truncated: "Schemat förminskades eftersom det överskrider det maximala antalet aktiviteter som får visas (%{max})" - notice_issue_successful_create: Ärende %{id} skapades. - - error_can_t_load_default_data: "Standardkonfiguration gick inte att läsa in: %{value}" - error_scm_not_found: "Inlägg och/eller revision finns inte i detta versionsarkiv." - error_scm_command_failed: "Ett fel inträffade vid försök att nå versionsarkivet: %{value}" - 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' - error_no_tracker_in_project: 'Ingen ärendetyp är associerad med projektet. Vänligen kontrollera projektinställningarna.' - error_no_default_issue_status: 'Ingen status är definierad som standard för nya ärenden. Vänligen kontrollera din konfiguration (Gå till "Administration -> Ärendestatus").' - error_can_not_delete_custom_field: Kan inte ta bort användardefinerat fält - error_can_not_delete_tracker: "Det finns ärenden av denna typ och den är därför inte möjlig att ta bort." - error_can_not_remove_role: "Denna roll används och den är därför inte möjlig att ta bort." - error_can_not_reopen_issue_on_closed_version: 'Ett ärende tilldelat en stängd version kan inte öppnas på nytt' - error_can_not_archive_project: Detta projekt kan inte arkiveras - error_issue_done_ratios_not_updated: "% klart inte uppdaterade." - error_workflow_copy_source: 'Vänligen välj källans ärendetyp eller roll' - error_workflow_copy_target: 'Vänligen välj ärendetyp(er) och roll(er) för mål' - error_unable_delete_issue_status: 'Ärendestatus kunde inte tas bort' - error_unable_to_connect: "Kan inte ansluta (%{value})" - - warning_attachments_not_saved: "%{count} fil(er) kunde inte sparas." - - mail_subject_lost_password: "Ditt %{value} lösenord" - mail_body_lost_password: 'För att ändra ditt lösenord, klicka på följande länk:' - mail_subject_register: "Din %{value} 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 %{value}-konto för att logga in." - mail_body_account_information: Din kontoinformation - mail_subject_account_activation_request: "%{value} begäran om kontoaktivering" - mail_body_account_activation_request: "En ny användare (%{value}) har registrerat sig och avvaktar ditt godkännande:" - mail_subject_reminder: "%{count} ärende(n) har deadline under de kommande %{days} dagarna" - mail_body_reminder: "%{count} ärende(n) som är tilldelat dig har deadline under de %{days} dagarna:" - mail_subject_wiki_content_added: "'%{id}' wikisida has lagts till" - mail_body_wiki_content_added: "The '%{id}' wikisida has lagts till av %{author}." - mail_subject_wiki_content_updated: "'%{id}' wikisida har uppdaterats" - mail_body_wiki_content_updated: "The '%{id}' wikisida har uppdaterats av %{author}." - - gui_validation_error: 1 fel - gui_validation_error_plural: "%{count} fel" - - field_name: Namn - field_description: Beskrivning - field_summary: Sammanfattning - field_is_required: Obligatorisk - field_firstname: Förnamn - field_lastname: Efternamn - field_mail: Mail - field_filename: Fil - field_filesize: Storlek - field_downloads: Nerladdningar - field_author: Författare - field_created_on: Skapad - 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: Reguljärt uttryck - field_min_length: Minimilängd - field_max_length: Maxlängd - field_value: Värde - field_category: Kategori - field_title: Titel - field_project: Projekt - field_issue: Ärende - field_status: Status - field_notes: Anteckningar - 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: Versionsmål - field_user: Användare - field_principal: Principal - field_role: Roll - field_homepage: Hemsida - field_is_public: Publik - field_parent: Underprojekt till - 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 lösenord - field_version: Version - field_type: Typ - field_host: Värddator - field_port: Port - field_account: Konto - field_base_dn: Bas-DN - field_attr_login: Inloggningsattribut - field_attr_firstname: Förnamnsattribut - field_attr_lastname: Efternamnsattribut - field_attr_mail: Mailattribut - field_onthefly: Skapa användare on-the-fly - field_start_date: Startdatum - 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: Underprojekt - field_hours: Timmar - field_activity: Aktivitet - field_spent_on: Datum - field_identifier: Identifierare - field_is_filter: Använd som filter - field_issue_to: 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_entries: Spenderad tid - 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 - field_watcher: Bevakare - field_identity_url: OpenID URL - field_content: Innehåll - field_group_by: Gruppera resultat efter - field_sharing: Delning - field_parent_issue: Förälderaktivitet - field_member_of_group: "Tilldelad användares grupp" - field_assigned_to_role: "Tilldelad användares roll" - field_text: Textfält - field_visible: Synlig - field_warn_on_leaving_unsaved: Varna om jag lämnar en sida med osparad text - field_issues_visibility: Ärendesynlighet - field_is_private: Privat - field_commit_logs_encoding: Teckenuppsättning för commit-meddelanden - field_scm_path_encoding: Sökvägskodning - field_path_to_repository: Sökväg till versionsarkiv - field_root_directory: Rotmapp - field_cvsroot: CVSROOT - field_cvs_module: Modul - - 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: Avsändaradress - 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: Textformatering - setting_wiki_compression: Komprimering av wikihistorik - setting_feeds_limit: Innehållsgräns för Feed - setting_default_projects_public: Nya projekt är publika - setting_autofetch_changesets: Automatisk hämtning av commits - setting_sys_api_enabled: Aktivera WS för versionsarkivhantering - 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_emails_header: Mail-header - setting_emails_footer: Signatur - 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 - setting_enabled_scm: Aktivera SCM - setting_mail_handler_body_delimiters: "Trunkera mail efter en av följande rader" - setting_mail_handler_api_enabled: Aktivera WS för inkommande mail - setting_mail_handler_api_key: API-nyckel - setting_sequential_project_identifiers: Generera projektidentifierare sekventiellt - setting_gravatar_enabled: Använd Gravatar-avatarer - setting_gravatar_default: Förvald Gravatar-bild - setting_diff_max_lines_displayed: Maximalt antal synliga rader i diff - setting_file_max_size_displayed: Maxstorlek på textfiler som visas inline - setting_repository_log_display_limit: Maximalt antal revisioner i filloggen - setting_openid: Tillåt inloggning och registrering med OpenID - setting_password_min_length: Minsta tillåtna lösenordslängd - setting_new_project_user_role_id: Tilldelad roll för en icke-administratör som skapar ett projekt - setting_default_projects_modules: Aktiverade moduler för nya projekt - setting_issue_done_ratio: Beräkna % klart med - setting_issue_done_ratio_issue_field: Använd ärendefältet - setting_issue_done_ratio_issue_status: Använd ärendestatus - setting_start_of_week: Första dagen i veckan - setting_rest_api_enabled: Aktivera REST webbtjänst - setting_cache_formatted_text: Cacha formaterad text - setting_default_notification_option: Standard notifieringsalternativ - setting_commit_logtime_enabled: Aktivera tidloggning - setting_commit_logtime_activity_id: Aktivitet för loggad tid - setting_gantt_items_limit: Maximalt antal aktiviteter som visas i gantt-schemat - setting_issue_group_assignment: Tillåt att ärenden tilldelas till grupper - - permission_add_project: Skapa projekt - permission_add_subprojects: Skapa underprojekt - permission_edit_project: Ändra projekt - permission_select_project_modules: Välja projektmoduler - permission_manage_members: Hantera medlemmar - permission_manage_project_activities: Hantera projektaktiviteter - permission_manage_versions: Hantera versioner - permission_manage_categories: Hantera ärendekategorier - permission_add_issues: Lägga till ärenden - permission_edit_issues: Ändra ärenden - permission_view_issues: Visa ärenden - permission_manage_issue_relations: Hantera ärenderelationer - permission_set_issues_private: Sätta ärenden publika eller privata - permission_set_own_issues_private: Sätta egna ärenden publika eller privata - permission_add_issue_notes: Lägga till ärendenotering - permission_edit_issue_notes: Ändra ärendenoteringar - permission_edit_own_issue_notes: Ändra egna ärendenoteringar - 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_delete_issue_watchers: Ta bort bevakare - permission_log_time: Logga spenderad tid - permission_view_time_entries: Visa spenderad tid - permission_edit_time_entries: Ändra tidloggningar - permission_edit_own_time_entries: Ändra egna tidloggningar - 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 versionsarkiv - permission_browse_repository: Bläddra i versionsarkiv - permission_view_changesets: Visa changesets - permission_commit_access: Commit-åtkomst - 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 - permission_export_wiki_pages: Exportera wikisidor - permission_manage_subtasks: Hantera underaktiviteter - - 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: Versionsarkiv - project_module_boards: Forum - project_module_calendar: Kalender - project_module_gantt: Gantt - - label_user: Användare - label_user_plural: Användare - label_user_new: Ny användare - label_user_anonymous: Anonym - label_project: Projekt - label_project_new: Nytt projekt - label_project_plural: Projekt - label_x_projects: - zero: inga projekt - one: 1 projekt - other: "%{count} projekt" - label_project_all: Alla projekt - label_project_latest: Senaste projekt - label_issue: Ärende - label_issue_new: Nytt ärende - label_issue_plural: Ärenden - label_issue_view_all: Visa alla ärenden - label_issues_by: "Ärenden %{value}" - label_issue_added: Ärende tillagt - label_issue_updated: Ärende uppdaterat - label_issue_note_added: Anteckning tillagd - label_issue_status_updated: Status uppdaterad - label_issue_priority_updated: Prioritet uppdaterad - 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 behörigheter - label_role_anonymous: Anonym - label_role_non_member: Icke-medlem - label_member: Medlem - label_member_new: Ny medlem - label_member_plural: Medlemmar - label_tracker: Ärendetyp - label_tracker_plural: Ärendetyper - label_tracker_new: Ny ärendetyp - label_workflow: Arbetsflöde - label_issue_status: Ärendestatus - label_issue_status_plural: Ärendestatus - label_issue_status_new: Ny status - 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_enumeration_new: Nytt värde - label_information: Information - label_information_plural: Information - label_please_login: Var god logga in - label_register: Registrera - label_login_with_open_id_option: eller logga in med OpenID - label_password_lost: Glömt lösenord - label_home: Hem - label_my_page: Min sida - label_my_account: Mitt konto - label_my_projects: Mina projekt - label_my_page_block: '"Min sida"-block' - label_administration: Administration - label_login: Logga in - label_logout: Logga ut - label_help: Hjälp - label_reported_issues: Rapporterade ärenden - label_assigned_to_me_issues: Ärenden tilldelade till mig - label_last_login: Senaste inloggning - label_registered_on: Registrerad - label_activity: Aktivitet - label_overall_activity: All aktivitet - label_user_activity: "Aktiviteter för %{value}" - label_new: Ny - label_logged_as: Inloggad som - label_environment: Miljö - 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_subproject_new: Nytt underprojekt - label_and_its_subprojects: "%{value} 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: Lång text - label_attribute: Attribut - label_attribute_plural: Attribut - label_download: "%{count} Nerladdning" - label_download_plural: "%{count} Nerladdningar" - label_no_data: Ingen data att visa - label_change_status: Ändra status - label_history: Historia - 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 nyheterna - label_news_view_all: Visa alla nyheter - label_news_added: Nyhet tillagd - label_news_comment_added: Kommentar tillagd till en nyhet - label_settings: Inställningar - label_overview: Översikt - label_version: Version - label_version_new: Ny version - label_version_plural: Versioner - label_close_versions: Stäng klara versioner - label_confirmation: Bekräftelse - label_export_to: 'Finns även som:' - label_read: Läs... - 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_x_open_issues_abbr_on_total: - zero: 0 öppna av %{total} - one: 1 öppen av %{total} - other: "%{count} öppna av %{total}" - label_x_open_issues_abbr: - zero: 0 öppna - one: 1 öppen - other: "%{count} öppna" - label_x_closed_issues_abbr: - zero: 0 stängda - one: 1 stängd - other: "%{count} stängda" - label_total: Total - label_permissions: Behörigheter - label_current_status: Nuvarande status - label_new_statuses_allowed: Nya tillåtna statusvärden - label_all: alla - label_none: ingen - label_nobody: ingen - label_next: Nästa - label_previous: Föregående - label_used_by: Använd av - label_details: Detaljer - label_add_note: Lägg till anteckning - label_per_page: Per sida - label_calendar: Kalender - label_months_from: månader från - label_gantt: Gantt - label_internal: Intern - label_last_changes: "senaste %{count} ändringar" - label_change_view_all: Visa alla ändringar - label_personalize_page: Anpassa denna sida - label_comment: Kommentar - label_comment_plural: Kommentarer - label_x_comments: - zero: inga kommentarer - one: 1 kommentar - other: "%{count} kommentarer" - label_comment_add: Lägg till kommentar - label_comment_added: Kommentar tillagd - label_comment_delete: Ta bort kommentar - label_query: Användardefinerad fråga - label_query_plural: Användardefinerade frågor - label_query_new: Ny fråga - label_my_queries: Mina egna frågor - label_filter_add: Lägg till filter - label_filter_plural: Filter - label_equals: är - label_not_equals: är inte - label_in_less_than: om mindre än - label_in_more_than: om mer än - label_greater_or_equal: '>=' - label_less_or_equal: '<=' - label_between: mellan - label_in: om - label_today: idag - label_all_time: närsom - label_yesterday: igår - label_this_week: denna vecka - label_last_week: senaste veckan - label_last_n_days: "senaste %{count} 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: Versionsarkiv - label_repository_plural: Versionsarkiv - label_browse: Bläddra - label_modification: "%{count} ändring" - label_modification_plural: "%{count} ändringar" - label_branch: Branch - label_tag: Tag - label_revision: Revision - label_revision_plural: Revisioner - label_revision_id: "Revision %{value}" - 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_view_all_revisions: Visa alla revisioner - label_max_size: Maxstorlek - 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 %{value}" - label_roadmap_overdue: "%{value} 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ä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: Förhandsgranska - label_feed_plural: Feeds - label_changes_details: Detaljer om alla ändringar - label_issue_tracking: Ärendeuppföljning - label_spent_time: Spenderad tid - label_overall_spent_time: Total tid spenderad - label_f_hour: "%{value} timme" - label_f_hour_plural: "%{value} timmar" - label_time_tracking: Tidsuppföljning - label_change_plural: Ändringar - label_statistics: Statistik - label_commits_per_month: Commits per månad - label_commits_per_author: Commits per författare - label_view_diff: Visa skillnader - label_diff_inline: i texten - label_diff_side_by_side: sida vid sida - label_options: Inställningar - 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: Nytt forum - label_board_plural: Forum - label_board_locked: Låst - label_board_sticky: Sticky - 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å %{value}" - label_send_test_email: Skicka testmail - label_feeds_access_key: RSS-nyckel - label_missing_feeds_access_key: Saknar en RSS-nyckel - label_feeds_access_key_created_on: "RSS-nyckel skapad för %{value} sedan" - label_module_plural: Moduler - label_added_time_by: "Tillagd av %{author} för %{age} sedan" - label_updated_time_by: "Uppdaterad av %{author} för %{age} sedan" - label_updated_time: "Uppdaterad för %{value} 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_bulk_edit_selected_time_entries: Gruppredigera valda tidloggningar - 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: "Inga händelser" - label_user_mail_option_only_my_events: "Endast för saker jag bevakar eller är inblandad i" - label_user_mail_option_only_assigned: "Endast för saker jag är tilldelad" - label_user_mail_option_only_owner: "Endast för saker jag äger" - label_user_mail_no_self_notified: "Jag vill inte bli underrättad 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: %{value}" - 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 - label_sort: Sortera - label_descending: Fallande - label_ascending: Stigande - label_date_from_to: Från %{start} till %{end} - label_wiki_content_added: Wikisida tillagd - label_wiki_content_updated: Wikisida uppdaterad - label_group: Grupp - label_group_plural: Grupper - label_group_new: Ny grupp - label_time_entry_plural: Spenderad tid - label_version_sharing_none: Inte delad - label_version_sharing_descendants: Med underprojekt - label_version_sharing_hierarchy: Med projekthierarki - label_version_sharing_tree: Med projektträd - label_version_sharing_system: Med alla projekt - label_update_issue_done_ratios: Uppdatera % klart - label_copy_source: Källa - label_copy_target: Mål - label_copy_same_as_target: Samma som mål - label_display_used_statuses_only: Visa endast status som används av denna ärendetyp - label_api_access_key: API-nyckel - label_missing_api_access_key: Saknar en API-nyckel - label_api_access_key_created_on: "API-nyckel skapad för %{value} sedan" - label_profile: Profil - label_subtask_plural: Underaktiviteter - label_project_copy_notifications: Skicka mailnotifieringar när projektet kopieras - label_principal_search: "Sök efter användare eller grupp:" - label_user_search: "Sök efter användare:" - label_additional_workflow_transitions_for_author: Ytterligare övergångar tillåtna när användaren är den som skapat ärendet - label_additional_workflow_transitions_for_assignee: Ytterligare övergångar tillåtna när användaren är den som tilldelats ärendet - label_issues_visibility_all: Alla ärenden - label_issues_visibility_public: Alla icke-privata ärenden - label_issues_visibility_own: Ärenden skapade av eller tilldelade till användaren - label_git_report_last_commit: Rapportera senaste commit av filer och mappar - - button_login: Logga in - button_submit: Skicka - button_save: Spara - button_check_all: Markera alla - button_uncheck_all: Avmarkera alla - button_collapse_all: Kollapsa alla - button_expand_all: Expandera alla - button_delete: Ta bort - button_create: Skapa - button_create_and_continue: Skapa och fortsätt - button_test: Testa - button_edit: Ändra - button_edit_associated_wikipage: "Ändra associerad Wikisida: %{page_title}" - button_add: Lägg till - button_change: Ändra - button_apply: Verkställ - button_clear: Återställ - button_lock: Lås - button_unlock: Lås upp - button_download: Ladda ner - button_list: Lista - button_view: Visa - button_move: Flytta - button_move_and_follow: Flytta och följ efter - button_back: Tillbaka - button_cancel: Avbryt - button_activate: Aktivera - button_sort: Sortera - button_log_time: Logga tid - 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_copy_and_follow: Kopiera och följ efter - button_annotate: Kommentera - button_update: Uppdatera - button_configure: Konfigurera - button_quote: Citera - button_duplicate: Duplicera - button_show: Visa - - status_active: aktiv - status_registered: registrerad - status_locked: låst - - version_status_open: öppen - version_status_locked: låst - version_status_closed: stängd - - field_active: Aktiv - - 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_subprojects_destroy_warning: "Alla underprojekt: %{value} 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_are_you_sure_with_children: Ta bort ärende och alla underärenden? - text_journal_changed: "%{label} ändrad från %{old} till %{new}" - text_journal_changed_no_detail: "%{label} uppdaterad" - text_journal_set_to: "%{label} satt till %{value}" - text_journal_deleted: "%{label} borttagen (%{old})" - text_journal_added: "%{label} %{value} tillagd" - text_tip_issue_begin_day: ärende som börjar denna dag - text_tip_issue_end_day: ärende som slutar denna dag - text_tip_issue_begin_end_day: ärende som börjar och slutar denna dag - text_project_identifier_info: 'Små bokstäver (a-z), siffror och streck tillåtna.
    När den är sparad kan identifieraren inte ändras.' - text_caracters_maximum: "max %{count} tecken." - text_caracters_minimum: "Måste vara minst %{count} tecken lång." - text_length_between: "Längd mellan %{min} och %{max} tecken." - 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_line_separated: Flera värden tillåtna (ett värde per rad). - text_issues_ref_in_commit_messages: Referera och fixa ärenden i commit-meddelanden - text_issue_added: "Ärende %{id} har rapporterats (av %{author})." - text_issue_updated: "Ärende %{id} har uppdaterats (av %{author})." - 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 (%{count}) ä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 bli underrättad om saker du bevakar eller är inblandad i (T.ex. ärenden du skapat eller tilldelats)." - text_no_configuration_data: "Roller, ärendetyper, ärendestatus 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 %{value}." - text_time_logged_by_changeset: "Tilldelad i changeset %{value}." - text_issues_destroy_confirmation: 'Är du säker på att du vill radera markerade ärende(n) ?' - text_issues_destroy_descendants_confirmation: Detta kommer även ta bort %{count} underaktivitet(er). - text_time_entries_destroy_confirmation: Är du säker på att du vill ta bort valda tidloggningar? - 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: Arkivet för bifogade filer är skrivbart - text_plugin_assets_writable: Arkivet för plug-ins är skrivbart - text_rmagick_available: RMagick tillgängligt (ej obligatoriskt) - text_destroy_time_entries_question: "%{hours} 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: "%{value} skrev:" - text_enumeration_destroy_question: "%{count} 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 via mail kan därför inte skickas.\nKonfigurera din SMTP-server i config/configuration.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 versionarkivloggen.\nAnvändare med samma användarnamn eller mailadress i både Redmine och versionsarkivet 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: 'Ett värde per rad' - text_wiki_page_destroy_question: "Denna sida har %{descendants} underliggande sidor. Vad vill du göra?" - text_wiki_page_nullify_children: "Behåll undersidor som rotsidor" - text_wiki_page_destroy_children: "Ta bort alla underliggande sidor" - text_wiki_page_reassign_children: "Flytta undersidor till denna föräldersida" - text_own_membership_delete_confirmation: "Några av, eller alla, dina behörigheter kommer att tas bort och du kanske inte längre kommer kunna göra ändringar i det här projektet.\nVill du verkligen fortsätta?" - text_zoom_out: Zooma ut - text_zoom_in: Zooma in - text_warn_on_leaving_unsaved: Nuvarande sida innehåller osparad text som kommer försvinna om du lämnar sidan. - text_scm_path_encoding_note: "Standard: UTF-8" - text_mercurial_repository_note: Lokalt versionsarkiv (t.ex. /hgrepo, c:\hgrepo) - text_scm_command: Kommando - text_scm_command_version: Version - text_scm_config: Du kan konfigurera dina scm-kommando i config/configuration.yml. Vänligen starta om applikationen när ändringar gjorts. - text_scm_command_not_available: Scm-kommando är inte tillgängligt. Vänligen kontrollera inställningarna i administratörspanelen. - - default_role_manager: Projektledare - default_role_developer: Utvecklare - default_role_reporter: Rapportör - default_tracker_bug: Bugg - default_tracker_feature: Funktionalitet - default_tracker_support: Support - default_issue_status_new: Ny - default_issue_status_in_progress: Pågår - default_issue_status_resolved: Löst - default_issue_status_feedback: Återkoppling - default_issue_status_closed: Stängd - default_issue_status_rejected: Avslagen - default_doc_category_user: Användardokumentation - default_doc_category_tech: Teknisk dokumentation - default_priority_low: Låg - default_priority_normal: Normal - default_priority_high: Hög - default_priority_urgent: Brådskande - default_priority_immediate: Omedelbar - default_activity_design: Design - default_activity_development: Utveckling - - enumeration_issue_priorities: Ärendeprioriteter - enumeration_doc_categories: Dokumentkategorier - enumeration_activities: Aktiviteter (tidsuppföljning) - enumeration_system_activity: Systemaktivitet - label_diff: diff - text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) - description_query_sort_criteria_direction: Sort direction - description_project_scope: Search scope - description_filter: Filter - description_user_mail_notification: Mail notification settings - description_date_from: Enter start date - description_message_content: Message content - description_available_columns: Available Columns - description_date_range_interval: Choose range by selecting start and end date - description_issue_category_reassign: Choose issue category - description_search: Searchfield - description_notes: Notes - description_date_range_list: Choose range from list - description_choose_project: Projects - description_date_to: Enter end date - description_query_sort_criteria_attribute: Sort attribute - description_wiki_subpages_reassign: Choose new parent page - description_selected_columns: Selected Columns - label_parent_revision: Parent - label_child_revision: Child - error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. - setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues - button_edit_section: Edit this section - setting_repositories_encodings: Attachments and repositories encodings - description_all_columns: All Columns - button_export: Export - label_export_options: "%{export_format} export options" - error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/84/842d23caf63cf174cbb0a8bccbdb2b32e5fa17e5.svn-base --- a/.svn/pristine/84/842d23caf63cf174cbb0a8bccbdb2b32e5fa17e5.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,248 +0,0 @@ -Return-Path: -Received: from osiris ([127.0.0.1]) - by OSIRIS - with hMailServer ; Sat, 21 Jun 2008 15:53:25 +0200 -Message-ID: <002301c8d3a6$2cdf6950$0a00a8c0@osiris> -From: "John Smith" -To: -Subject: Ticket created by email with attachment -Date: Sat, 21 Jun 2008 15:53:25 +0200 -MIME-Version: 1.0 -Content-Type: multipart/mixed; - boundary="----=_NextPart_000_001F_01C8D3B6.F05C5270" -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 - -This is a multi-part message in MIME format. - -------=_NextPart_000_001F_01C8D3B6.F05C5270 -Content-Type: multipart/alternative; - boundary="----=_NextPart_001_0020_01C8D3B6.F05C5270" - - -------=_NextPart_001_0020_01C8D3B6.F05C5270 -Content-Type: text/plain; - charset="iso-8859-1" -Content-Transfer-Encoding: quoted-printable - -This is a new ticket with attachments -------=_NextPart_001_0020_01C8D3B6.F05C5270 -Content-Type: text/html; - charset="iso-8859-1" -Content-Transfer-Encoding: quoted-printable - - - - - - - - -
    This is  a new ticket with=20 -attachments
    - -------=_NextPart_001_0020_01C8D3B6.F05C5270-- - -------=_NextPart_000_001F_01C8D3B6.F05C5270 -Content-Type: image/jpeg; - name="Paella.jpg" -Content-Transfer-Encoding: base64 -Content-Disposition: attachment; - filename="Paella.jpg" - -/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcU -FhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgo -KCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCACmAMgDASIA -AhEBAxEB/8QAHQAAAgMBAQEBAQAAAAAAAAAABQYABAcDCAIBCf/EADsQAAEDAwMCBQIDBQcFAQAA -AAECAwQABREGEiExQQcTIlFhcYEUMpEVI0Kh0QhSYrHB4fAWJCUzQ3L/xAAaAQADAQEBAQAAAAAA -AAAAAAADBAUCAQYA/8QAKhEAAgIBBAICAgIDAAMAAAAAAQIAAxEEEiExIkEFE1FhMnFCkaEjwdH/ -2gAMAwEAAhEDEQA/ACTUdSsdhRCNE54GTRaBaXHiBtNOVo0wEpSt8BKfmpWCZRPHcVbdZ3X1J9Jx -Tla9OBpIU8Noo7Gjx4qdrCBkfxGupUSck13GJjeT1ObEdthOG04/zpX8SNXjR1njym46ZMmQ+llp -pStuc9T9hRq/X22afhKl3iazEYHdxWCfgDqT9K83eKfiFG1RfIEi3tuC3W9KlNh0YLqyeuO3QV0D -MznM9O2uai4QI8psYQ8gLA9virY615P034xX+zNNslLDsMKOG1J5HuAa3nQPiBZ9WtpUy4lmcE4U -ypXP2rmMHmcI/EealD7te7ZZ2S7dLhGiN9cvOBP+dIF18btHw3C1DkSbi7nATGZJBPwTitTIyZp9 -SsCun9oJaEFUDTy0oyQFyXSOfoB/rQOL466huE9LIagxW1A48tkuKJxwBlQrm4YzNhGPE9Mmua8Y -JrzsrXPiQ42y7+KtsZt4kpS8ltK0p91J5IzXGFr3xFef8pMqE4vJABZT6se3FDNyEZzNCh89Tfbv -aoV2iKj3GO2+0eyh0+h7VkWq/CqTDUqXpp0uJHPkKOFj6HofvQRzxZ1bbwFTG7c+jO0lKeh+cGi8 -bxrebZZVMtjDqljKgw4Rt9uuea5vEIEceoL09ZnHQoyGy3KaOFhxO0j6g0J8QNPr3tzorHmsJSUv -NgdQeprTIuqbfqdtD7MRxh7HO/H6ZHWlnW0e5tQnv2WgupAyEg8p9xUl7WGowpzKCoDXyJ5nvMdK -Uuho4bSv057CqK2stIWrgEZp2kWtE+O5+MC0OKUchHFCbnaWVNeW1KU3tTtwtAUkj6jkfpXoK7gQ -AZLsqYEmJ0mUBlLeCfeqHKl5PqJopNhriupQWyoqPpKeQfpTXYPDW+3ZlEhTTcVpXI8w+oj6Cmty -qMxTazHAi1ZLG/PXuKClv3Ip7t2n4yI3lKZSsEc7hmicXwfu5ThN22fCUH+tXB4QX1KdzN6WVjth -Q/1oDuG/yjCIV/xgWLouQFfiLK/5LqejbnKT9D1FStX05DRaYrTN8K232wEl1aMJV856VKF9hPc3 -9QPM32HEjxEjykBSh/ERSd4s61uGjLbBnQrcie2t4pfClEFKAM8Y704uvtsMrdfcQ20gZUtZAAHu -SawHxt8V7PKt/wCytPp/aLrToW7JAPlNkAjAPfOfpQ0JY4E42B3Nf09ruwXvTQvjM9lmGkfvvOWE -llXdKvn/ADrONZeNwU28zo2Ml1tHpXc5Y2spP+EHlR/5ivOzYkPPKdjMechRDjrCUHy1Ec9Aa1Lw -l0VF10pcy4XJC0RlbTFTgKbHwnokfSibFXkzAJbiJ0tN81jc1yHXplzkEEqkPA7UjvtR2H1/SrOl -rGu6NvP7Q8yhaWkDruVj/n616Lvl20n4Z2cpeS02tSfRHbAU69/t8nivOGoNXzNQSVRbFAbtsFal -FESEjBOepUR1rBs3D8CFVMHjmXNYW+wWtsMrlMvyyOW4h3FB9irpn70lx7k9AeDttW4w70DgWd3+ -1NmlvDi7XpL0iShcWG0dqllO5SlHsB35NG7l4PSRG823z0YbGFqkDaFK+MZx7d6XOu09Z2M8MKHb -OBM1vBuAkJcuUgyHXRu3KfDp+5ycVTaeU36kKUlYOQQcEVrehvC5l1Mh/VClISHFMttIVgL45VnH -TkEH4rQbjpHTbyGWVQIzL7bYabc2AnaMfYnAxk0K35Smo7e/2IRdC7eXUwfT5m6pfbtC/wARIlLW -VNu7yoN9MlQ9h3NO+n9Cwo8rzZU1Sm2Mlx9YLaUkHjaOv3Nc7zd7FoyY5D07HR56SfMl7961ZGNo -9gKXrtd77dnkssoSwt7K9rZG8jHU44Tkc9q0rvbyvipnNgT9kTRLvqKy2JDgS/8AiH3hjecKXjv2 -/SkG8akmRyhqG+hKSQ4dpyofBxxV2w+Hkuda27pMW5tcSpWxati1HJGQTkYp70xoS2MW1pp+ImXN -koJLi+UtfP1FAt1dFPHcPXQ9nPUy+/3pu4usrYZS16MOKCAkuLJypRxX5aG5ExX4VlfC/Vt98e3z -WvL8M9NsNMtyFyVyGx6h5uPMPyMcV9Q9HQbbdWwzHQGFHKVhStw+uTQTr6tu1IQad85M46baVarV -uVkJ/mDVCVqWUll59t4FxlW0ocOA4k+1P8uLGU35UgAhQ2kgdRWUeIMi2WyKqASFLJJbWchQI7Ul -pWWyw5GSYZ1IXA4Ez7U12mR7q95jCWgTuCQeoPsaGqntylbCpIdxnaSM/wBK56lujtydZS4UkNIw -CBzQO4RURywWnUupcQF7knoT1BHYg5r0lFY2DIwZKvYq5x1DjUo26WzJKEuIQoFSFDIP+9bzaL0x -+HZcZcQpC0ggewIrzYzNJQGpGVt+/cUw2PU8+0vqWEJnW8q/9KzgpHslXb6UV6yw4gBZg8z1NZbj -Ek43LQDjkZFMLbkMcJW3+orKvDq86T1SUssrEef3iPq2rz8f3vtTZrtizaR0pOvD8XephOG2959a -ycJH60HBBxDBhjMB+L9/RY7WpT7jam3kkNNJwSs+/NSss0Bpi4+Jmpfxl7kPOQ2k7iCfyI/hQOwz -/vUroqrUnceZ8LnIG2Cdaa61Dq54i7SVJi5ymGwdjSf/ANe/86s6W0TLvkNySp5pcVjBUy0oAD5x -1P1NbDbPALTQjp/aC5bj+OS27tH+VOmjPDqw6QEv9lNPFcpIQ4p5zeSB0A/WtNYoXCwK1nOWgjwk -sFrg2wuJjtKl5IJUBwPakLxDXbNI6/alaGW6b87uL1vjJCmAogjcvHTrnb8DpVnxj1q1oOS7b9PP -j9qSEErA58gHuf8AF7CsStOurpBjKZioQqS6sqU+vlayepPvQytu3cgz/fEPWaXfFjYEfLlo5+bM -/aurr+X33vW6lIJUD/dyen2p80zboMNG6NBEGOygJLy04cdAGRjjn5NYRD1NcjMMme8XpST6Q4Mp -H0HStstF4kO2lMS5vAlTfq9O04PQZ+KifILaqg3PnPodS5o0S3I0q4x2T3Kr+obzH1HsjuFFpeUU -B5s5Snck4ST0z0p502w5HZW86qW5lXLbpSeMfHFZH4gpFutbDlrmNtujlxvzc705HAHfB5qknVSI -VliuWK7STcHVBL7Ticc8c8f70IaMaipWq4z+oo6jT2sr8ma3qCfBky48be4zvcAOB6gR/CMd6EXF -m9EPKhx3Vx92EJdADmOmQKJ2y5xVpiJlW+OzPSj1LbSBtURyoGjFzWqPbHljClFBLbiBnHHUmpeT -WdqiPISuDM/e0bark4YzkEJkJ9RebGF7u+T/AKVeg6DbVdXHJ6U/hi35KAlRGU44zj/WrtpdfSlt -D7m54jKznr/WnOAVKa9Y7cGtDVWodhaH1WnVlD7cZxPhq3NMobbeBeZQnalKlZ47cUQDSGtvlqwn -GEp7AVQdbddWQHkp2dOea6qWHQlPmJSscEE9aET/AJCK/X+JFxUtuKecHnKxx8VXRKiBSkuKII55 -PSvq4yUQmf3qspxwc8is71fqZMeKtTO0AHn3V8UaitrDgdmcdtoyZ215q1USShq0bZClghTYPqFL -Vr0xH1otbt1XKZkpT6cccfOaF6SZkz7q7dZYWHjz0ykJp2Yvi4YaYVHdUXjs2eSUlR7HPt89KoW5 -p8af5D3OVLldz9GLmsNLR1WZiI+oJlRB5aHgBuKe2cdaxd5tVsuy0OJbdWwvkKGUq+or0PqiyXVy -IJ7za1NlIJbz6m/fgdv61lN000qWJ09EWQ8++6lqM01k8geokY5p/wCK1RXK2Nn/AOz75PS1vStt -Y594iCUnOauWi5SLXMDzIQ4g8ONOp3IcT7KHcVduWn7nbWg5OgSI6SopBcQUjPtzXK1RX1OqkMtb -0xcPO9PSkHrzV0WKRkHM86a2BwZqFm0da9c2pdw0asM3JgBT9qdd2uNH+8y51x7A/rSjrXUmq129 -Om9TuyvKhu70NyUYd4GBlX8QofG1hcLbrBF/tZ/DvtqGEDhJQONpA6gjrXq61f8AS/jDo9mXNhNu -nGxxPR2O5jkBXX+tY3bcFhPtoPAin4H6gsMTQgLEhtM7eoyGioBYI4Tx7Yx+pqUr668ILjZXDOtS -XZsdvlMiGkJlND/GgYDg+Rg1KwUDHIM2r7Bgiei5NwiQo635cllllAypbiwAPvWO678c4UJuRH0y -gSHkDBkrHpz2CR3+prHbXJ1L4o6matwkKaYP7xzkhthsdVEf8NLWrzbo94fh2RKjAjqLSHFnKniO -Cs/X/KuLSAcN3OfYW5HUD3SXJutxfnTnVOyn1lbi1HJJNPnh9otyfbJF5lLabjpJQ0FjlZHUis9C -lDOO9bdHkS4WkbXBlIMdaGUnyhwkjqFfU5pf5K566gqe+I98TpBqb9pnB/Q9wu7kdyOGUNNp3oWp -Owq7+3P1r9uQmqllqS+S+ghClFWR+vtT/Z7goWGOopbjodwEltQOcdR16/WrcrTFmW4tyYZHmuDc -dhwkDHSvNvq2BC2+up6PThdIzDvMypelJN2lI8+M9JKxsZS1/Cfcn2+tF9K6Oh6ZeW5fYS5VwKgl -locpR3Cvk0+zJTdtioi2htDe5OVL/KAPcn3r5j3ZtdmkrKFTFJ3EDG7BAzgH9a+XX2sNi8CJXaZW -c3GIN7u0u931+KwhaGGspKQMKcKepVV5UmU1DZZtzspMVKQXm3F5B+gHIH0zQCBImKuiJMeCuEH1 -YCfVkjv+bqSKr6t1U7a7uxEgurS0yMLBASc/arlenBULiSGtOSSY6WKJKXckJU2tplSt6FA7gfvW -gxA/sUBggDGSayGya5ed8tkNqSlXVYOVVpEZydIablRFF6ORgjGFJPyKga3Tuj5Il2rVC6sKT1L9 -tiuPTnDI3eSfc/lqrqWOuHFK4qlF1HIX7j2NWIkyQ8XEApSUcD/Ea5TmZj2SggqUMKSrp9KUByQM -T45U5mSS9UzJMtMZ93GFcqJ7UL8Q3UOOww24Bx6h3V8/Sqev0sx7u4IqkB5w8tJ4KFfNBXG3Fuo/ -FPqLxA3FXXHtXp9PQiBXXiTGZrmIjTo68qh+Y2ygPhYSAlXIBz1rYHp04RkNRnWDOA5KyEgDrgVh -mmSmPcCfQpWCACnINFdRXOW3GQ4+60GgcJKDgr+R70lqdP8AZaAvuUK3woDY4mqyrjeFWppZZUXW -lnzUlYCVp+K+LLeYEoLLG5lGdxQk4wcfyrOourlyIzbDhcKVNhHB7e9XYlxatbam0dVDOAOT96Rf -TEDBHMMpU9dTQpVxiTWXGUqDy1n0hxCSAPvXnfWVtnWO9TI8lpLHnZOGxhKkE54+K1K1XhLj4S4j -GOnxX5qiNZ7wlpd1Di30ZS0hKtu4kdCaN8fqG0luxhwYtrdOtqZXsTA1dTWh+B+unNG6tbTIWTap -hDUhGeE56L+oP8qSbtBXDnyWSB+7WUnadwH3rgYT6IQmEpS0VbU5WNyj8DrXr/F1/ueXIZT1P6Hh -aVoSpJBSoZBB4IqVjPgP4ii72eHZLsSJrCPKadP8YA4B+cfrUpMgg4jK8jMybw5vUfT/AIXatujD -iRc5S24DX95KVAkn/P8ASstODk9asPSXvwZbUEoQpzhtIwkYHt9z1q3NZiO2uNMhFLbif3chkryc -9lAHsabbAbP5i6DI/qctPSokW9w3p0cvsIcBLY7+2fituuVxYvDbAMZ2VIUkeX5I5x3Tgdqznwz0 -xbb/ADZQuy3w2y2FISycHJz3+MVtWnNLwNMb3G0SZDvlgb3DlWPgf86V5/5e+oOAc7l/9y18WLK/ -IdH/AHB+l23bLPLMl0RkyQS22r1eWQO/tR178NEju3GS8ZahyVIc7ewA4qpKKfxzTMOGHCsBZSob -ueveitut+XGo8tpDacEp2DAP69ahNYHO4yo1rMxJgt22RLy0l5bYQ04jckLWfM+o7frVPUMpdg0a -65EfXvaX5XOArnp9hTtGgRbcyhL6PPbaG1ClnJAPvWeeMl0FogwnWGYkqKHSFxnUkpSojgkD79aJ -pQbblr9ZgNRcAhMzli9zZYfS27NkPBIKAFKVnnkn2pf1PaZbMNm4PpkDzeV+c0UEK+p6/WtX8H5M -GXDm3OS22Jq3P/W2AlIHwOgFVPF+VBfjqKi4sEHBKSAVfFegXWsmo+pV4zJZ0wareTFbw71Y1Ab/ -AAjbcNh1Q/8Ae9yaYU33VESW5KdK1wucuMpwgj3FYq4S456E7VDjimGHqa6wYqIS5HmMq42LOQBT -Wo0AYll5z+YCjV7MA+puVmuDkgh7evZt3bsdK46s1uiNZSY6iHwSj82CPnFC7PcbdbdOxkPTiqaB -5iQlXCf61mV9uC79dn39oDIVztGAajafRK9pPoSrZezKAOzKclyXcLgue8VLUo7sHrUaVIfeCloG -T0Uo9qstKdbcBLZUg9DiuzkbY4VDIBGQkdBVkuBxOrRtAwf7naKlyMoqQ4pRI9RHH2qtc1/i/KS+ -p3yWchtKwcIzX7HnoQv1nbgYUR7+9NESXCmR1xdjexxOXCTg9ODSzO1bBiJvCsCBFu3eahwltCnA -O6ATj6082K2rlltyXGSsIGEhzPP1xQa1QJNngLmMuNPMrPKE5BwKuzrw6Yu6JJVGWkZSkHIXn274 -pe8m0+H+51G2DBlu4J/DzFKbWhICiS2EgH7H2FD3JTMuclt7B2ArBzgJPvQNF1lSUFoON5JyST1P -tmgEu5yY0wgJ2uoUd27nPtRKdEzHk8xezVLUnHudtXsRYc4rt8pxZdKvMSpWcH60M07a03W5JZcW -UtgFSj8Dt96orKnVKUQVK6nv966R5b0dCksLLe4gkp68dOatKjBNgPMiM4Z9xHE1fwCkQx4pqYdC -vJcC1RwT0WkZH8s1KVPDm+Psa208ogAtysqWOqyo4JP2qUtanPM2jDEL+OWn49u8R5UK0MbGClDg -bSOApYyQPvSzM0rKt9qiXCRs8uSSlCeQoHnII+1aJ/aAZWjxImL3FILTSwR/+RX7bhqJ561XC5Jj -O20pSnyFYJWMZypJ6djWLdSa1BzxDUaYWnaOzH/RlmZ0nYWPJab9SQqS5t/eLV2+wzj7UfZmouM8 -MNtlsNoKlFZAV8H4FULPfmrmtyCtwJfQjKggFIVx2orHsbUZ1TzCktFwfvVKJJUB05968jqHaxyz -y3t+sBeiJJTLSXA6hAWscFSTjke561yfkAlte4h88BIJwB3q5Hjx297RUpWfUD+YYqs5Gjx3HJJK -ywRylIGM+/vShBMIrDMtpKiyVKcWtvaP3aRnn3HevOfi9eZM/UEiEv8A7eOHgkhfT0jg4+5r0JJu -ENLad0plpWM9c8dqUtTaMtGoJS37gyXH3UANyEHH6iqXx99entD2CK31m1CqmZZomd+HjORbXte8 -hOVLSk4USeTRm4xrvqbTjseUGmozTmVPLH5fgfNNNhYtWmJardbw3tf59XqIwepNM2poyJVpdKEt -+SRuCR/EfemLdWou3oO/cJXVmsI08z3BiFp7UakMuonR0jk47+31oG7iTM/dkNoWvCdx/KCe9P8A -dIzR1PAZfjtI3gx3QsAJHznFKOqbfbbXKSzbriZrwJ8390UJRjpgnrXpdNeLAM9kSDqKDWT+AYcu -1ivcK2x1KdiyYSejrCgSnPZXehTLqou7cghKRkgd6Px9SWp2xsMT23HF7QgpaOCFDoaCxFee4UKC -gCT14P3oKs5B+xccx+kIpG0wlaJKZLB9KglB5Uo9KsLeDj2GzjI+1AjmPLH4ZzCVEApPAIopGCFR -1rSpW4naaFbWB5DqUabMnaYEuTGyc40le4deO1fMZam17krwAOua7yYjyZCiG8hZ65ya57WW3W2y -lS3FDkFW0CmgdygdydZ4MT1HezzUy4iCwVKLKcFtSuD74r9uVtRJabLZ8obckpTlP60ItSLXOeDT -KlR1spG9W7clw/ejN4mXa0MDYA9FLn7olIxtxyFCprVkWbU7/cY+0FNx6/UU70GYDBQw6FrUcAgH -ke9Lq3FHkkk980xXedHuYWt6D5L4A2rQrCQO4xV+yaaiTrW5JL29GRgflUCOoJ5wPmqaOKUy/cl3 -Zufw6itbriuAJHloSVPNlvJ/hB61RCwVAKPHc1YubQZmvNpSlKUqIACtwH371Tzk/FOKAeR7ibEj -g+o06QWy7riziG2pDf4lsJCjknnrUrv4TtIe1/ZQ50Q+Fk/TkfzxUpW7ggQ1a7xmbF/aGsKEX83N -U4IU8wFJZWMbtvBwf04pOieITadOMxXmWRJR6CsD1HHTH2xWx/2irAu9aJTIjJJkQXgsYHJSrg/6 -V5os1rjsynVXOQY8uMsER1t8r+M9j0pSymu1P/J6j+ktatxtE23QtvmwYar3cX0JjyE+hhQ9ROeC -a0CJJaLTe+Uhfm/l7/YUhWKUxfbKxCztdQkJStWdySf7o/rTHZLC7bW3g5M819Y2pLiPy/TmvLak -AsSeCPUp7i1hB6h+Ytbnl+US2AfVx/nXyWg4kpeOQ4CPT2FVX0JacS6qWpASnC0qIINDLlKKGyGp -QaLmADgYA74xzSY7zDpWW4Eq2e0N2yXMdmKS6twlCUO4IQj3+po86RGWzGjtNgO4AATwlPXNAmPK -dLanH15K04SEE5x7GrsGWLnclJ9SHGuCrOCU+1E2s5zNfSE/7mJniFFciyHJ6XEktoIylWBjPPHv -SnC1HKlFK25Kls7cBpSvy4PtWwXHSsCXIUqUt15Tg2qStfpx7kUIc0JZIqHlpGwqTgFJxgZzx809 -XfWE22DJgwQD49TGr0pN2nlL7i2JKjvC1DCc9qUtRR47sjLQWiYkYdbX0PyDWwax09bZpcZtpdbl -FJO5aztJxkD46Vl83TclMT8SlDjh28lIJwfY/NXdDqK8Ag4iGsosYHK8QVKiRIztv/BqccWUhT6l -jASruBVpEoKkOAYLhJO0D9KGIUoqQ2vucYPaidptb0i6lCMNt8lSlq/N8VRcDblz1J9Tbf4CEGYb -rzbjiEBLqQQAtQAzUs7jrqnGFNJy0fUMcA/WjlutUySrLT0dLGw5C08hQ6fbNCrTBuVlubjjkJ58 -pJwU5Lef72B1pQMLFYZGY0bHQggS7KYUw35ivUlXU9xSfdCp5QWltSUp/iPfNaBLtv4KGiVOkYcf -X5imS2dyE9uM8DvjrQc2hyYsg+WGSfSQKxRatfJMLepvXA7iilxtKmlMJcQ4nlSlKzn7U4wbou7Y -RK9SGeUpzjJPciuLmi5ayDF8t3nsrHFfFx0lcbeSptYWhKUlS0EjBP8ADR2votx5DMSFF1eRjiGF -OWuK4mO+y2lTyFIWpw5SCeivgZpNuCzBU4zEmBbTnUtq4UP+ZoxaNIXG6So5ebX5C3NillXQd/pV -zWlmYtEJmEiARLz6XEerf78jrXy3VK4XO4mDsSzbwMYiQI8iQlx5tpa2kfmWBwK4BKVdDiicpq5t -NGItl1DbbYdUgDgAjO40JZSpxwBA5zVBDnn1EnGD+5rn9n+1pXeZlzcQFIYbCEEjoo9x9galN/hp -BFn06wwQA89+9cPfJ7fpUpG072zHql2Libtf225NukRX+WnWyhX0Iry9drM3ar2i4XN0h6BKS28r -O5TiByleD8Yr0ldJyHWtyOD0UKzHW9taloXM8jzkhBbkN4yVt+4HunqPvQXBxkTqH1E2dck2u5wp -9rUW0yiVPKCdwQgkYJx361pca9NSGG3C5kIR6nkD0g/Ws5uMMT4DJtFyZTCdSlAjlsJKTnHpP+hr -hapk+yxP2fNW7+DeSrAIyN3uP0qJfQtij8/9lPTlkznmPNwdh3FgILzgcK/3bqSfUfZQpW1BMuNr -hKeeQlCyrCWeu0DjdXL9oW2NAadjuLbdj4UFBQIWoe6Scg/NEo5cu81h+5JAQtvcgdE++Tmlvr+o -5YZEbpvstyvRlPSGtFvNJjzox4JKHknHP0pq03c2GlTAp5j8Spw7d5CVEYHANL9xsrTbMibHUCUJ -IKEt8JPvxSey4ZylLX/8yOSMbqIK67stXwIT0NxyZubSDKUX1lbawkAZ9u+KHXeez5ja3HwhpPxy -D2HNZu1rG7W5zeqS0EgbUggHA+nvVaNqOXdr5HVNcQhCV71BKQNx7ZzxQxoW7PUIgGcmNs6SqW+W -2hvdc53qRgkHgc0YsdpVGgluSGygrUdqQClJ+TXVu2sSSu4x3PxD20qDa14yccAe2KruPvNw23Lg -z+HDytqh1Chjoo9utAJ9LC22h0CqMRc15omyXhCnLc0mLc0c7mcBKiBnCk/PuKy646YvkCU0qLuL -iWylQUPyE9cH5/WtkRLs0VhTLzqW22sEqLm5xXPTjtV2bLt88sttrCSpQxsOSCPeqGn191ACnyH7 -k27RI/K8TFdFOOYcTcAWENqIcUpJBz23DvTqvWMRElm3uQiUpIQ08BgJV259qdFWjzorsd8RXQ7k -KJHCh7E9yBWWatszVpmsKRuCRgJTn0g5P9KKt9WrtJYYM+q07IgQGWpsNN/lsTH5W7yF7H22+Nqc -ZJz84r8sMda284IRztBHal19yRbslgltMjKVA01abvCmLamK6AprbtGeoo1ysKwF5Eao0TsxK9xu -03BS6hS9gU4DzkUWj26G4osKbSpRysBQJGaE2W822NHDbyngM7s4wM/avmZqdhrelhorSoEbxknn -5qVtctnEOdLZnkQvKjIhuNojNZyraQMYTx1PtXzeYMZtDS30IS4lQWhWMkH4+tIxvz8GT5iQt1Bz -vSoHBPbNVjPvGo33HWnSEsgqTgcE9NtMJpWyGJwJ9dQVGOxAGt9QruazbYxQGMAOOjBUo9hn4pf0 -vYiu7AvEKQ0rcQOh9hX47bJMW5qjlrCyohKSoEgfOKboflWmIhhsb5S+Sfk16SsCmsLX1PLWoXsz -Z2I6QZ3kBKc5dPGPapSw28qMn1q3PK/Mc9PipQ4YVMwyJt2oHV2uZuGVML/mKoKWlwbkHchQ4qkN -ZaevsQxzcmQsj0byUkH71TgOvRVqbeG6Ks+l5PqSD9RXxBioihqTS8Vm7JlNyHGIqlZWWujDmQQr -H9339q/bihUVLqVvh1ak7S6g8KHwO1OshQIIUAoHg96z7VdpkxIEw2chTDqTmOr/AOZ90Ht9KWv0 -7WkYMf0Oqr075sXIgLTkZl7Uy1zZCQhpsuDOOuQOa05NvYkS0J8h1UUDd5w5UOOAfisK026yJZj3 -YOR3i56XRzkn+EitUsN4uEvEeCpDCGlEOL67ldMikfk6HUg54Ef02pS9i6jEcLpcGUMLSW9iU43J -6EjH+VZ9NuLDmQqCIsdxR7e30rQWNPKaebmOTVrdXysq5C+OhFfcm129Y/7ptghJ3JKU8j6VLqtS -rvmNFNx4mNXGMy6jEQqeUF5V8D2oS63JalpaQdrhxjdyQK2O6Ls8SOGm0hO7ohKeVH2FIl205Pdd -cmMskrICkNg+pIz0IqrptWGGDwP3M3VhFye4w2hmVGYaUmUUsrwcpOSn5xTpcpUJu1vOmQpwObUK -S6njfnjjtzWOu6iu3luRnIhQGTtJHBB/pRq1u3G5hhKFlIVneVdz9+lKXaRgdzkCdRxYMg9S9qB+ -A/MS0tpYIVudaZTgOqwAPtUdjTkORXGmhHbKgltKVBJSMd+9Mtv/ABrcWRFLUdxATl0lGFlWOx7/ -AAaEOJhuLZipYdksr6BokraVnnd7VhbOl7xBfWwctnj8T9m39strVFa9aMggZKlK+lLGpXLhc47d -smsKjlSgpJWg5A65B7dfrWk2vTdus8p+clS1vYyEurB2H+pqs9erVc32zJIbeZXtS2oZO8fH+tap -sVH3VrnHucXftIeZf/0zdZDYbKlPlpJWVnkZ7D704WLRhTbkOzg6XVpxsB2+Wfr3p0hzIylPPtth -KEr2uFQxuI7ChV61IhaTGay24okBST0J6GutrLLPACMJY6DxMze/Ldtdzcik7gnlJ+DVJF2KTlVO -0O2M3WK8mQ0h5/HoIOFdepPalq5aTuapziQhptrPUkHA609VZW3i3cbHyRVfKU03RLishXIpfVqe -Q2lyJC/dZWQpfzmqF5f/AGdcSw08hwJxnb3V7CqcNl5qWp6U2lKRnYnOefeqlOjQDcw4kX5D5g2Y -Wn13GOKsQklxR8yU51UecUSt+5GX3vU8rue1CbeypxfnO/YUWB9jRGIHAiVNZc72lgLJVzzUrmg1 -KFiOjjqIwUpPKSR96KWnUl1tLoXCmOt+4CuD9qFlOe9fm3nrT5wexPN5I6msWHxHjzili+Nhlw4A -faGBn5HSmicCI6X2loeiufkeb5Sf6GvPqknrTJpPVs2wPbMh+EvhxhzlKh9KA1XtYZbM9xj1Laos -/K1ICHv74/1qnbryuwBtCIYQgDatbayQv5wehpnu8NiXaBebK6X7csgOIPK4yj/Cr49jSbJXwQel -BesWLseGrsNTbkjx/wBWQ4FvYfdntLW8NwZC8qT9RQ9Gq3bo8ERlBDajgrJ/KPekB1ltLqZCAlK0 -HcCUgjP0NfIuy1Tg+yw2y4kEL8kYSv52nj9KSPxNQ/jyZRr+UYfyGJt+nm7Kje95pflEAFxR6H/C -DQW+OSocpBjL/EFZOHmzyR7GkzSl9ZLr5uE2LFBOPLWlWSPccYFaxpS8WZlP4aEpDri8OKO4KBP+ -lTL9NZQ/kMxg21agBi3MXo9ulOvB1uC8p0j1LV0PH86JQ7QpiSh94mO3tUFBSeMn2zTsJjKFrde8 -g8DbsIJA78VzbuEd6MVLaSWFZSCUZI985pRnJjCviI2nbncJNzXDUhL7aSU5C8J2/OKcbTaodsU7 -K8hLL6zuUndkA/GaU7tM/ZUlQjBlu3bdzbkdHKTnkE+59qU77q+4zISmGY8lbyVH96hKjlPHHFGG -me0+HAM7bcmMxv1V/wCQkLFvcdxzktd6RbNDC71lDgbS2dy3F9sHmh8PVF5ZQtEdteFDar0eof0o -8q7abXHYNxdDEhgYUUnYpffkdxmqFelspGMZz+Io2qQ+51v9/wDw7KkwZflxlElIKgTnPJNcH7mz -Asjbi1smU8QouE/PBH2pd1DreyOwnojMGPIK8+tLe3HGAfrSE9cVrjtJjFfozwv1bfpnj+VOaf40 -so3DETv+RReF5m53LUNis0Bp9ExK3QkAoQ5nPfisq1druXd3CmMVtsDITlXOPn3pcMGS/HW84VKd -zwF9SKFKCs7T27U/pvjqaju7Mm6jW2uMdCE4tsukyI5cmY77sdtYSt4DICuoBNMFoWiapJcVhY6o -V7138N9XK0/JWw42l+BIT5cmMv8AK6jv9COxpi1XpBtE2LctJvfi7bOBdbAI8xrH5krHYj370zaf -R4gqCQwxzOCMJGE9K6A4rm20ttnDysuJ4OBxmq0uWllv08rNIjyOBPRsCg5GJLnODDZQg+s/yqUs -zJKlqUVHJNSmkqGOZOt1TBvGfZIxkVwWsg1KlaEmT8DhxX7u3dqlStTka/D3Ur2nrylKkfiIEr9z -IjK/K4g9fvR/xBsyLDqF+IwsrjqSl5rd1CFjcAfkZqVKHYIZOonyclpZz0oeygoUpWetSpWVmz1O -c6Ol9o9lDoaBIkPMOZS4obTg4URUqUzWAeDE7SVPEYrXrSZb30ORGwhwDG4rUr/M0SXri+SpYcYu -EiMMcJbVx9alSgtpad27aMw6ai0pjdKFz1nqJuSn/wAtIJIznj+lfQu11VueVdJm9weohwjNSpWj -UigYAmfsck8wPPlPKz5jzyz33LJoOt1SieSB7VKlGQQDk5n2w35qwCaYLbEQEBwgY7CpUrlphaAC -3MIkBKc0DuUUKC5CcJIPI96lSh18GH1AyINiI8x9CM4x3Fat4f6okWOY0qKkFv8AKpCgCFp75qVK -xqfUY+MUENmMmv7bHbDV5tqPJjTFcsK6pVgE4+Kz68xy41vZUEKPvUqUovDyufKjmfrVmYbiHd6n -cbis+/WpUqUcMZKdF44n/9k= - -------=_NextPart_000_001F_01C8D3B6.F05C5270-- - diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/84/844e1f7efcdb7d2e09fc680929a5a6cad80561f3.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/84/844e1f7efcdb7d2e09fc680929a5a6cad80561f3.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,141 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class AuthSourceLdapTest < ActiveSupport::TestCase + include Redmine::I18n + fixtures :auth_sources + + def setup + end + + def test_create + a = AuthSourceLdap.new(:name => 'My LDAP', :host => 'ldap.example.net', :port => 389, :base_dn => 'dc=example,dc=net', :attr_login => 'sAMAccountName') + assert a.save + end + + def test_should_strip_ldap_attributes + a = AuthSourceLdap.new(:name => 'My LDAP', :host => 'ldap.example.net', :port => 389, :base_dn => 'dc=example,dc=net', :attr_login => 'sAMAccountName', + :attr_firstname => 'givenName ') + assert a.save + assert_equal 'givenName', a.reload.attr_firstname + end + + def test_replace_port_zero_to_389 + a = AuthSourceLdap.new( + :name => 'My LDAP', :host => 'ldap.example.net', :port => 0, + :base_dn => 'dc=example,dc=net', :attr_login => 'sAMAccountName', + :attr_firstname => 'givenName ') + assert a.save + assert_equal 389, a.port + end + + def test_filter_should_be_validated + set_language_if_valid 'en' + + a = AuthSourceLdap.new(:name => 'My LDAP', :host => 'ldap.example.net', :port => 389, :attr_login => 'sn') + a.filter = "(mail=*@redmine.org" + assert !a.valid? + assert_include "LDAP filter is invalid", a.errors.full_messages + + a.filter = "(mail=*@redmine.org)" + assert a.valid? + end + + if ldap_configured? + test '#authenticate with a valid LDAP user should return the user attributes' do + auth = AuthSourceLdap.find(1) + auth.update_attribute :onthefly_register, true + + attributes = auth.authenticate('example1','123456') + assert attributes.is_a?(Hash), "An hash was not returned" + assert_equal 'Example', attributes[:firstname] + assert_equal 'One', attributes[:lastname] + assert_equal 'example1@redmine.org', attributes[:mail] + assert_equal auth.id, attributes[:auth_source_id] + attributes.keys.each do |attribute| + assert User.new.respond_to?("#{attribute}="), "Unexpected :#{attribute} attribute returned" + end + end + + test '#authenticate with an invalid LDAP user should return nil' do + auth = AuthSourceLdap.find(1) + assert_equal nil, auth.authenticate('nouser','123456') + end + + test '#authenticate without a login should return nil' do + auth = AuthSourceLdap.find(1) + assert_equal nil, auth.authenticate('','123456') + end + + test '#authenticate without a password should return nil' do + auth = AuthSourceLdap.find(1) + assert_equal nil, auth.authenticate('edavis','') + end + + test '#authenticate without filter should return any user' do + auth = AuthSourceLdap.find(1) + assert auth.authenticate('example1','123456') + assert auth.authenticate('edavis', '123456') + end + + test '#authenticate with filter should return user who matches the filter only' do + auth = AuthSourceLdap.find(1) + auth.filter = "(mail=*@redmine.org)" + + assert auth.authenticate('example1','123456') + assert_nil auth.authenticate('edavis', '123456') + end + + def test_authenticate_should_timeout + auth_source = AuthSourceLdap.find(1) + auth_source.timeout = 1 + def auth_source.initialize_ldap_con(*args); sleep(5); end + + assert_raise AuthSourceTimeoutException do + auth_source.authenticate 'example1', '123456' + end + end + + def test_search_should_return_matching_entries + results = AuthSource.search("exa") + assert_equal 1, results.size + result = results.first + assert_kind_of Hash, result + assert_equal "example1", result[:login] + assert_equal "Example", result[:firstname] + assert_equal "One", result[:lastname] + assert_equal "example1@redmine.org", result[:mail] + assert_equal 1, result[:auth_source_id] + end + + def test_search_with_no_match_should_return_an_empty_array + results = AuthSource.search("wro") + assert_equal [], results + end + + def test_search_with_exception_should_return_an_empty_array + Net::LDAP.stubs(:new).raises(Net::LDAP::LdapError, 'Cannot connect') + + results = AuthSource.search("exa") + assert_equal [], results + end + else + puts '(Test LDAP server not configured)' + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/84/84675d4c5913f4d4787034973ce3aa888eb2ae42.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/84/84675d4c5913f4d4787034973ce3aa888eb2ae42.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,36 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class WikisController < ApplicationController + menu_item :settings + before_filter :find_project, :authorize + + # Create or update a project's wiki + def edit + @wiki = @project.wiki || Wiki.new(:project => @project) + @wiki.safe_attributes = params[:wiki] + @wiki.save if request.post? + end + + # Delete a project's wiki + def destroy + if request.post? && params[:confirm] && @project.wiki + @project.wiki.destroy + redirect_to settings_project_path(@project, :tab => 'wiki') + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/84/849f87222b3ad4c6513f88d2c270ccbbefd56465.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/84/849f87222b3ad4c6513f88d2c270ccbbefd56465.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,86 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +module RedmineMenuTestHelper + # Assertions + def assert_number_of_items_in_menu(menu_name, count) + assert Redmine::MenuManager.items(menu_name).size >= count, "Menu has less than #{count} items" + end + + def assert_menu_contains_item_named(menu_name, item_name) + assert Redmine::MenuManager.items(menu_name).collect(&:name).include?(item_name.to_sym), "Menu did not have an item named #{item_name}" + end + + # Helpers + def get_menu_item(menu_name, item_name) + Redmine::MenuManager.items(menu_name).find {|item| item.name == item_name.to_sym} + end +end + +class RedmineTest < ActiveSupport::TestCase + include RedmineMenuTestHelper + + def test_top_menu + assert_number_of_items_in_menu :top_menu, 5 + assert_menu_contains_item_named :top_menu, :home + assert_menu_contains_item_named :top_menu, :my_page + assert_menu_contains_item_named :top_menu, :projects + assert_menu_contains_item_named :top_menu, :administration + assert_menu_contains_item_named :top_menu, :help + end + + def test_account_menu + assert_number_of_items_in_menu :account_menu, 4 + assert_menu_contains_item_named :account_menu, :login + assert_menu_contains_item_named :account_menu, :register + assert_menu_contains_item_named :account_menu, :my_account + assert_menu_contains_item_named :account_menu, :logout + end + + def test_application_menu + assert_number_of_items_in_menu :application_menu, 0 + end + + def test_admin_menu + assert_number_of_items_in_menu :admin_menu, 0 + end + + def test_project_menu + assert_number_of_items_in_menu :project_menu, 14 + assert_menu_contains_item_named :project_menu, :overview + assert_menu_contains_item_named :project_menu, :activity + assert_menu_contains_item_named :project_menu, :roadmap + assert_menu_contains_item_named :project_menu, :issues + assert_menu_contains_item_named :project_menu, :new_issue + assert_menu_contains_item_named :project_menu, :calendar + assert_menu_contains_item_named :project_menu, :gantt + assert_menu_contains_item_named :project_menu, :news + assert_menu_contains_item_named :project_menu, :documents + assert_menu_contains_item_named :project_menu, :wiki + assert_menu_contains_item_named :project_menu, :boards + assert_menu_contains_item_named :project_menu, :files + assert_menu_contains_item_named :project_menu, :repository + assert_menu_contains_item_named :project_menu, :settings + end + + def test_new_issue_should_have_root_as_a_parent + new_issue = get_menu_item(:project_menu, :new_issue) + assert_equal :root, new_issue.parent.name + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/84/84aef8daed0663b1abb2c50b1715fa8c859e4a68.svn-base --- a/.svn/pristine/84/84aef8daed0663b1abb2c50b1715fa8c859e4a68.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,512 +0,0 @@ -# redMine - project management software -# Copyright (C) 2006-2007 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -desc 'Mantis migration script' - -require 'active_record' -require 'iconv' -require 'pp' - -namespace :redmine do -task :migrate_from_mantis => :environment do - - module MantisMigrate - - DEFAULT_STATUS = IssueStatus.default - assigned_status = IssueStatus.find_by_position(2) - resolved_status = IssueStatus.find_by_position(3) - feedback_status = IssueStatus.find_by_position(4) - closed_status = IssueStatus.find :first, :conditions => { :is_closed => true } - STATUS_MAPPING = {10 => DEFAULT_STATUS, # new - 20 => feedback_status, # feedback - 30 => DEFAULT_STATUS, # acknowledged - 40 => DEFAULT_STATUS, # confirmed - 50 => assigned_status, # assigned - 80 => resolved_status, # resolved - 90 => closed_status # closed - } - - priorities = IssuePriority.all - DEFAULT_PRIORITY = priorities[2] - PRIORITY_MAPPING = {10 => priorities[1], # none - 20 => priorities[1], # low - 30 => priorities[2], # normal - 40 => priorities[3], # high - 50 => priorities[4], # urgent - 60 => priorities[5] # immediate - } - - TRACKER_BUG = Tracker.find_by_position(1) - TRACKER_FEATURE = Tracker.find_by_position(2) - - roles = Role.find(:all, :conditions => {:builtin => 0}, :order => 'position ASC') - manager_role = roles[0] - developer_role = roles[1] - DEFAULT_ROLE = roles.last - ROLE_MAPPING = {10 => DEFAULT_ROLE, # viewer - 25 => DEFAULT_ROLE, # reporter - 40 => DEFAULT_ROLE, # updater - 55 => developer_role, # developer - 70 => manager_role, # manager - 90 => manager_role # administrator - } - - CUSTOM_FIELD_TYPE_MAPPING = {0 => 'string', # String - 1 => 'int', # Numeric - 2 => 'int', # Float - 3 => 'list', # Enumeration - 4 => 'string', # Email - 5 => 'bool', # Checkbox - 6 => 'list', # List - 7 => 'list', # Multiselection list - 8 => 'date', # Date - } - - RELATION_TYPE_MAPPING = {1 => IssueRelation::TYPE_RELATES, # related to - 2 => IssueRelation::TYPE_RELATES, # parent of - 3 => IssueRelation::TYPE_RELATES, # child of - 0 => IssueRelation::TYPE_DUPLICATES, # duplicate of - 4 => IssueRelation::TYPE_DUPLICATES # has duplicate - } - - class MantisUser < ActiveRecord::Base - set_table_name :mantis_user_table - - def firstname - @firstname = realname.blank? ? username : realname.split.first[0..29] - @firstname - end - - def lastname - @lastname = realname.blank? ? '-' : realname.split[1..-1].join(' ')[0..29] - @lastname = '-' if @lastname.blank? - @lastname - end - - def email - if read_attribute(:email).match(/^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i) && - !User.find_by_mail(read_attribute(:email)) - @email = read_attribute(:email) - else - @email = "#{username}@foo.bar" - end - end - - def username - read_attribute(:username)[0..29].gsub(/[^a-zA-Z0-9_\-@\.]/, '-') - end - end - - class MantisProject < ActiveRecord::Base - set_table_name :mantis_project_table - has_many :versions, :class_name => "MantisVersion", :foreign_key => :project_id - has_many :categories, :class_name => "MantisCategory", :foreign_key => :project_id - has_many :news, :class_name => "MantisNews", :foreign_key => :project_id - has_many :members, :class_name => "MantisProjectUser", :foreign_key => :project_id - - def identifier - read_attribute(:name).gsub(/[^a-z0-9\-]+/, '-').slice(0, Project::IDENTIFIER_MAX_LENGTH) - end - end - - class MantisVersion < ActiveRecord::Base - set_table_name :mantis_project_version_table - - def version - read_attribute(:version)[0..29] - end - - def description - read_attribute(:description)[0..254] - end - end - - class MantisCategory < ActiveRecord::Base - set_table_name :mantis_project_category_table - end - - class MantisProjectUser < ActiveRecord::Base - set_table_name :mantis_project_user_list_table - end - - class MantisBug < ActiveRecord::Base - set_table_name :mantis_bug_table - belongs_to :bug_text, :class_name => "MantisBugText", :foreign_key => :bug_text_id - has_many :bug_notes, :class_name => "MantisBugNote", :foreign_key => :bug_id - has_many :bug_files, :class_name => "MantisBugFile", :foreign_key => :bug_id - has_many :bug_monitors, :class_name => "MantisBugMonitor", :foreign_key => :bug_id - end - - class MantisBugText < ActiveRecord::Base - set_table_name :mantis_bug_text_table - - # Adds Mantis steps_to_reproduce and additional_information fields - # to description if any - def full_description - full_description = description - full_description += "\n\n*Steps to reproduce:*\n\n#{steps_to_reproduce}" unless steps_to_reproduce.blank? - full_description += "\n\n*Additional information:*\n\n#{additional_information}" unless additional_information.blank? - full_description - end - end - - class MantisBugNote < ActiveRecord::Base - set_table_name :mantis_bugnote_table - belongs_to :bug, :class_name => "MantisBug", :foreign_key => :bug_id - belongs_to :bug_note_text, :class_name => "MantisBugNoteText", :foreign_key => :bugnote_text_id - end - - class MantisBugNoteText < ActiveRecord::Base - set_table_name :mantis_bugnote_text_table - end - - class MantisBugFile < ActiveRecord::Base - set_table_name :mantis_bug_file_table - - def size - filesize - end - - def original_filename - MantisMigrate.encode(filename) - end - - def content_type - file_type - end - - def read(*args) - if @read_finished - nil - else - @read_finished = true - content - end - end - end - - class MantisBugRelationship < ActiveRecord::Base - set_table_name :mantis_bug_relationship_table - end - - class MantisBugMonitor < ActiveRecord::Base - set_table_name :mantis_bug_monitor_table - end - - class MantisNews < ActiveRecord::Base - set_table_name :mantis_news_table - end - - class MantisCustomField < ActiveRecord::Base - set_table_name :mantis_custom_field_table - set_inheritance_column :none - has_many :values, :class_name => "MantisCustomFieldString", :foreign_key => :field_id - has_many :projects, :class_name => "MantisCustomFieldProject", :foreign_key => :field_id - - def format - read_attribute :type - end - - def name - read_attribute(:name)[0..29] - end - end - - class MantisCustomFieldProject < ActiveRecord::Base - set_table_name :mantis_custom_field_project_table - end - - class MantisCustomFieldString < ActiveRecord::Base - set_table_name :mantis_custom_field_string_table - end - - - def self.migrate - - # Users - print "Migrating users" - User.delete_all "login <> 'admin'" - users_map = {} - users_migrated = 0 - MantisUser.find(:all).each do |user| - u = User.new :firstname => encode(user.firstname), - :lastname => encode(user.lastname), - :mail => user.email, - :last_login_on => user.last_visit - u.login = user.username - u.password = 'mantis' - u.status = User::STATUS_LOCKED if user.enabled != 1 - u.admin = true if user.access_level == 90 - next unless u.save! - users_migrated += 1 - users_map[user.id] = u.id - print '.' - end - puts - - # Projects - print "Migrating projects" - Project.destroy_all - projects_map = {} - versions_map = {} - categories_map = {} - MantisProject.find(:all).each do |project| - p = Project.new :name => encode(project.name), - :description => encode(project.description) - p.identifier = project.identifier - next unless p.save - projects_map[project.id] = p.id - p.enabled_module_names = ['issue_tracking', 'news', 'wiki'] - p.trackers << TRACKER_BUG - p.trackers << TRACKER_FEATURE - print '.' - - # Project members - project.members.each do |member| - m = Member.new :user => User.find_by_id(users_map[member.user_id]), - :roles => [ROLE_MAPPING[member.access_level] || DEFAULT_ROLE] - m.project = p - m.save - end - - # Project versions - project.versions.each do |version| - v = Version.new :name => encode(version.version), - :description => encode(version.description), - :effective_date => (version.date_order ? version.date_order.to_date : nil) - v.project = p - v.save - versions_map[version.id] = v.id - end - - # Project categories - project.categories.each do |category| - g = IssueCategory.new :name => category.category[0,30] - g.project = p - g.save - categories_map[category.category] = g.id - end - end - puts - - # Bugs - print "Migrating bugs" - Issue.destroy_all - issues_map = {} - keep_bug_ids = (Issue.count == 0) - MantisBug.find_each(:batch_size => 200) do |bug| - next unless projects_map[bug.project_id] && users_map[bug.reporter_id] - i = Issue.new :project_id => projects_map[bug.project_id], - :subject => encode(bug.summary), - :description => encode(bug.bug_text.full_description), - :priority => PRIORITY_MAPPING[bug.priority] || DEFAULT_PRIORITY, - :created_on => bug.date_submitted, - :updated_on => bug.last_updated - i.author = User.find_by_id(users_map[bug.reporter_id]) - i.category = IssueCategory.find_by_project_id_and_name(i.project_id, bug.category[0,30]) unless bug.category.blank? - i.fixed_version = Version.find_by_project_id_and_name(i.project_id, bug.fixed_in_version) unless bug.fixed_in_version.blank? - i.status = STATUS_MAPPING[bug.status] || DEFAULT_STATUS - i.tracker = (bug.severity == 10 ? TRACKER_FEATURE : TRACKER_BUG) - i.id = bug.id if keep_bug_ids - next unless i.save - issues_map[bug.id] = i.id - print '.' - STDOUT.flush - - # Assignee - # Redmine checks that the assignee is a project member - if (bug.handler_id && users_map[bug.handler_id]) - i.assigned_to = User.find_by_id(users_map[bug.handler_id]) - i.save_with_validation(false) - end - - # Bug notes - bug.bug_notes.each do |note| - next unless users_map[note.reporter_id] - n = Journal.new :notes => encode(note.bug_note_text.note), - :created_on => note.date_submitted - n.user = User.find_by_id(users_map[note.reporter_id]) - n.journalized = i - n.save - end - - # Bug files - bug.bug_files.each do |file| - a = Attachment.new :created_on => file.date_added - a.file = file - a.author = User.find :first - a.container = i - a.save - end - - # Bug monitors - bug.bug_monitors.each do |monitor| - next unless users_map[monitor.user_id] - i.add_watcher(User.find_by_id(users_map[monitor.user_id])) - end - 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 - - # Bug relationships - print "Migrating bug relations" - MantisBugRelationship.find(:all).each do |relation| - next unless issues_map[relation.source_bug_id] && issues_map[relation.destination_bug_id] - r = IssueRelation.new :relation_type => RELATION_TYPE_MAPPING[relation.relationship_type] - r.issue_from = Issue.find_by_id(issues_map[relation.source_bug_id]) - r.issue_to = Issue.find_by_id(issues_map[relation.destination_bug_id]) - pp r unless r.save - print '.' - STDOUT.flush - end - puts - - # News - print "Migrating news" - News.destroy_all - MantisNews.find(:all, :conditions => 'project_id > 0').each do |news| - next unless projects_map[news.project_id] - n = News.new :project_id => projects_map[news.project_id], - :title => encode(news.headline[0..59]), - :description => encode(news.body), - :created_on => news.date_posted - n.author = User.find_by_id(users_map[news.poster_id]) - n.save - print '.' - STDOUT.flush - end - puts - - # Custom fields - print "Migrating custom fields" - IssueCustomField.destroy_all - MantisCustomField.find(:all).each do |field| - f = IssueCustomField.new :name => field.name[0..29], - :field_format => CUSTOM_FIELD_TYPE_MAPPING[field.format], - :min_length => field.length_min, - :max_length => field.length_max, - :regexp => field.valid_regexp, - :possible_values => field.possible_values.split('|'), - :is_required => field.require_report? - next unless f.save - print '.' - STDOUT.flush - # Trackers association - f.trackers = Tracker.find :all - - # Projects association - field.projects.each do |project| - f.projects << Project.find_by_id(projects_map[project.project_id]) if projects_map[project.project_id] - end - - # Values - field.values.each do |value| - v = CustomValue.new :custom_field_id => f.id, - :value => value.value - v.customized = Issue.find_by_id(issues_map[value.bug_id]) if issues_map[value.bug_id] - v.save - end unless f.new_record? - end - puts - - puts - puts "Users: #{users_migrated}/#{MantisUser.count}" - puts "Projects: #{Project.count}/#{MantisProject.count}" - puts "Memberships: #{Member.count}/#{MantisProjectUser.count}" - puts "Versions: #{Version.count}/#{MantisVersion.count}" - puts "Categories: #{IssueCategory.count}/#{MantisCategory.count}" - puts "Bugs: #{Issue.count}/#{MantisBug.count}" - puts "Bug notes: #{Journal.count}/#{MantisBugNote.count}" - puts "Bug files: #{Attachment.count}/#{MantisBugFile.count}" - puts "Bug relations: #{IssueRelation.count}/#{MantisBugRelationship.count}" - puts "Bug monitors: #{Watcher.count}/#{MantisBugMonitor.count}" - puts "News: #{News.count}/#{MantisNews.count}" - puts "Custom fields: #{IssueCustomField.count}/#{MantisCustomField.count}" - end - - def self.encoding(charset) - @ic = Iconv.new('UTF-8', charset) - rescue Iconv::InvalidEncoding - return false - end - - def self.establish_connection(params) - constants.each do |const| - klass = const_get(const) - next unless klass.respond_to? 'establish_connection' - klass.establish_connection params - end - end - - def self.encode(text) - @ic.iconv text - rescue - text - end - end - - puts - if Redmine::DefaultData::Loader.no_data? - puts "Redmine configuration need to be loaded before importing data." - puts "Please, run this first:" - puts - puts " rake redmine:load_default_data RAILS_ENV=\"#{ENV['RAILS_ENV']}\"" - exit - end - - puts "WARNING: Your Redmine data will be deleted during this process." - print "Are you sure you want to continue ? [y/N] " - STDOUT.flush - break unless STDIN.gets.match(/^y$/i) - - # Default Mantis database settings - db_params = {:adapter => 'mysql', - :database => 'bugtracker', - :host => 'localhost', - :username => 'root', - :password => '' } - - puts - puts "Please enter settings for your Mantis database" - [:adapter, :host, :database, :username, :password].each do |param| - print "#{param} [#{db_params[param]}]: " - value = STDIN.gets.chomp! - db_params[param] = value unless value.blank? - end - - while true - print "encoding [UTF-8]: " - STDOUT.flush - encoding = STDIN.gets.chomp! - encoding = 'UTF-8' if encoding.blank? - break if MantisMigrate.encoding encoding - puts "Invalid encoding!" - end - puts - - # Make sure bugs can refer bugs in other projects - Setting.cross_project_issue_relations = 1 if Setting.respond_to? 'cross_project_issue_relations' - - # Turn off email notifications - Setting.notified_events = [] - - MantisMigrate.establish_connection db_params - MantisMigrate.migrate -end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/84/84bb124f9c56a0e127eefcadf426b28c77093316.svn-base --- a/.svn/pristine/84/84bb124f9c56a0e127eefcadf426b28c77093316.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,6 +0,0 @@ -

    <%=l(:label_version_new)%>

    - -<% labelled_tabular_form_for @version, :url => project_versions_path(@project) do |f| %> -<%= render :partial => 'versions/form', :locals => { :f => f } %> -<%= submit_tag l(:button_create) %> -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/85/856339cef1bdf5f11b116c77c2ac5a76171899ce.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/85/856339cef1bdf5f11b116c77c2ac5a76171899ce.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,165 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class RepositoriesDarcsControllerTest < ActionController::TestCase + tests RepositoriesController + + fixtures :projects, :users, :roles, :members, :member_roles, + :repositories, :enabled_modules + + REPOSITORY_PATH = Rails.root.join('tmp/test/darcs_repository').to_s + PRJ_ID = 3 + NUM_REV = 6 + + def setup + User.current = nil + @project = Project.find(PRJ_ID) + @repository = Repository::Darcs.create( + :project => @project, + :url => REPOSITORY_PATH, + :log_encoding => 'UTF-8' + ) + assert @repository + end + + if File.directory?(REPOSITORY_PATH) + def test_get_new + @request.session[:user_id] = 1 + @project.repository.destroy + get :new, :project_id => 'subproject1', :repository_scm => 'Darcs' + assert_response :success + assert_template 'new' + assert_kind_of Repository::Darcs, assigns(:repository) + assert assigns(:repository).new_record? + end + + def test_browse_root + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + get :show, :id => PRJ_ID + assert_response :success + assert_template 'show' + assert_not_nil assigns(:entries) + assert_equal 3, assigns(:entries).size + assert assigns(:entries).detect {|e| e.name == 'images' && e.kind == 'dir'} + assert assigns(:entries).detect {|e| e.name == 'sources' && e.kind == 'dir'} + assert assigns(:entries).detect {|e| e.name == 'README' && e.kind == 'file'} + end + + def test_browse_directory + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + get :show, :id => PRJ_ID, :path => repository_path_hash(['images'])[:param] + assert_response :success + assert_template 'show' + assert_not_nil assigns(:entries) + assert_equal ['delete.png', 'edit.png'], assigns(:entries).collect(&:name) + entry = assigns(:entries).detect {|e| e.name == 'edit.png'} + assert_not_nil entry + assert_equal 'file', entry.kind + assert_equal 'images/edit.png', entry.path + end + + def test_browse_at_given_revision + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + get :show, :id => PRJ_ID, :path => repository_path_hash(['images'])[:param], + :rev => 1 + assert_response :success + assert_template 'show' + assert_not_nil assigns(:entries) + assert_equal ['delete.png'], assigns(:entries).collect(&:name) + end + + def test_changes + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + get :changes, :id => PRJ_ID, + :path => repository_path_hash(['images', 'edit.png'])[:param] + assert_response :success + assert_template 'changes' + assert_tag :tag => 'h2', :content => 'edit.png' + end + + def test_diff + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + # Full diff of changeset 5 + ['inline', 'sbs'].each do |dt| + get :diff, :id => PRJ_ID, :rev => 5, :type => dt + assert_response :success + assert_template 'diff' + # Line 22 removed + assert_tag :tag => 'th', + :content => '22', + :sibling => { :tag => 'td', + :attributes => { :class => /diff_out/ }, + :content => /def remove/ } + end + end + + def test_destroy_valid_repository + @request.session[:user_id] = 1 # admin + assert_equal 0, @repository.changesets.count + @repository.fetch_changesets + @project.reload + assert_equal NUM_REV, @repository.changesets.count + + assert_difference 'Repository.count', -1 do + delete :destroy, :id => @repository.id + end + assert_response 302 + @project.reload + assert_nil @project.repository + end + + def test_destroy_invalid_repository + @request.session[:user_id] = 1 # admin + @project.repository.destroy + @repository = Repository::Darcs.create!( + :project => @project, + :url => "/invalid", + :log_encoding => 'UTF-8' + ) + @repository.fetch_changesets + @project.reload + assert_equal 0, @repository.changesets.count + + assert_difference 'Repository.count', -1 do + delete :destroy, :id => @repository.id + end + assert_response 302 + @project.reload + assert_nil @project.repository + end + else + puts "Darcs test repository NOT FOUND. Skipping functional tests !!!" + def test_fake; assert true end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/85/85848dd34249f60ad32d28944cb4a9546d489175.svn-base --- a/.svn/pristine/85/85848dd34249f60ad32d28944cb4a9546d489175.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -require File.dirname(__FILE__) + '/string/conversions' -require File.dirname(__FILE__) + '/string/inflections' - -class String #:nodoc: - include Redmine::CoreExtensions::String::Conversions - include Redmine::CoreExtensions::String::Inflections -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/85/85f72634b1fa7115c56cde5496c8d210d2e64d32.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/85/85f72634b1fa7115c56cde5496c8d210d2e64d32.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,36 @@ +api.user do + api.id @user.id + api.login @user.login if User.current.admin? || (User.current == @user) + api.firstname @user.firstname + api.lastname @user.lastname + api.mail @user.mail if User.current.admin? || !@user.pref.hide_mail + api.created_on @user.created_on + api.last_login_on @user.last_login_on + api.api_key @user.api_key if User.current.admin? || (User.current == @user) + + render_api_custom_values @user.visible_custom_field_values, api + + api.array :groups do |groups| + @user.groups.each do |group| + api.group :id => group.id, :name => group.name + end + end if User.current.admin? && include_in_api_response?('groups') + + api.array :memberships do + @memberships.each do |membership| + api.membership do + api.id membership.id + api.project :id => membership.project.id, :name => membership.project.name + api.array :roles do + membership.member_roles.each do |member_role| + if member_role.role + attrs = {:id => member_role.role.id, :name => member_role.role.name} + attrs.merge!(:inherited => true) if member_role.inherited_from.present? + api.role attrs + end + end + end + end if membership.project + end + end if include_in_api_response?('memberships') && @memberships +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/86/860e2f012a8a16391f9ed2054fc730df3d67d9c9.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/86/860e2f012a8a16391f9ed2054fc730df3d67d9c9.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,27 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +module AdminHelper + def project_status_options_for_select(selected) + options_for_select([[l(:label_all), ''], + [l(:project_status_active), '1'], + [l(:project_status_closed), '5'], + [l(:project_status_archived), '9']], selected.to_s) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/86/86143f66fe1f5d10a7a618e2496577aaa346c237.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/86/86143f66fe1f5d10a7a618e2496577aaa346c237.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,33 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class RoutingProjectEnumerationsTest < ActionController::IntegrationTest + def test_project_enumerations + assert_routing( + { :method => 'put', :path => "/projects/64/enumerations" }, + { :controller => 'project_enumerations', :action => 'update', + :project_id => '64' } + ) + assert_routing( + { :method => 'delete', :path => "/projects/64/enumerations" }, + { :controller => 'project_enumerations', :action => 'destroy', + :project_id => '64' } + ) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/86/8626420aabc42f1c5c5c9fca490181efb3ed2f66.svn-base --- a/.svn/pristine/86/8626420aabc42f1c5c5c9fca490181efb3ed2f66.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ -# Settings specified here will take precedence over those in config/environment.rb - -# The production environment is meant for finished, "live" apps. -# Code is not reloaded between requests -config.cache_classes = true - -##### -# Customize the default logger (http://ruby-doc.org/core/classes/Logger.html) -# -# Use a different logger for distributed setups -# config.logger = SyslogLogger.new -# -# Rotate logs bigger than 1MB, keeps no more than 7 rotated logs around. -# When setting a new Logger, make sure to set it's log level too. -# -# config.logger = Logger.new(config.log_path, 7, 1048576) -# config.logger.level = Logger::INFO - -# Full error reports are disabled and caching is turned on -config.action_controller.consider_all_requests_local = false -config.action_controller.perform_caching = true - -# Enable serving of images, stylesheets, and javascripts from an asset server -# config.action_controller.asset_host = "http://assets.example.com" - -# Disable delivery errors if you bad email addresses should just be ignored -config.action_mailer.raise_delivery_errors = false - -# No email in production log -config.action_mailer.logger = nil diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/86/862a776ed4eda62592dadbb92180115063e923b2.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/86/862a776ed4eda62592dadbb92180115063e923b2.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,115 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../../test_helper', __FILE__) + +class Redmine::CodesetUtilTest < ActiveSupport::TestCase + + def test_to_utf8_by_setting_from_latin1 + with_settings :repositories_encodings => 'UTF-8,ISO-8859-1' do + s1 = "Texte encod\xc3\xa9" + s2 = "Texte encod\xe9" + s3 = s2.dup + if s1.respond_to?(:force_encoding) + s1.force_encoding("UTF-8") + s2.force_encoding("ASCII-8BIT") + s3.force_encoding("UTF-8") + end + assert_equal s1, Redmine::CodesetUtil.to_utf8_by_setting(s2) + assert_equal s1, Redmine::CodesetUtil.to_utf8_by_setting(s3) + end + end + + def test_to_utf8_by_setting_from_euc_jp + with_settings :repositories_encodings => 'UTF-8,EUC-JP' do + s1 = "\xe3\x83\xac\xe3\x83\x83\xe3\x83\x89\xe3\x83\x9e\xe3\x82\xa4\xe3\x83\xb3" + s2 = "\xa5\xec\xa5\xc3\xa5\xc9\xa5\xde\xa5\xa4\xa5\xf3" + s3 = s2.dup + if s1.respond_to?(:force_encoding) + s1.force_encoding("UTF-8") + s2.force_encoding("ASCII-8BIT") + s3.force_encoding("UTF-8") + end + assert_equal s1, Redmine::CodesetUtil.to_utf8_by_setting(s2) + assert_equal s1, Redmine::CodesetUtil.to_utf8_by_setting(s3) + end + end + + def test_to_utf8_by_setting_should_be_converted_all_latin1 + with_settings :repositories_encodings => 'ISO-8859-1' do + s1 = "\xc3\x82\xc2\x80" + s2 = "\xC2\x80" + s3 = s2.dup + if s1.respond_to?(:force_encoding) + s1.force_encoding("UTF-8") + s2.force_encoding("ASCII-8BIT") + s3.force_encoding("UTF-8") + end + assert_equal s1, Redmine::CodesetUtil.to_utf8_by_setting(s2) + assert_equal s1, Redmine::CodesetUtil.to_utf8_by_setting(s3) + end + end + + def test_to_utf8_by_setting_blank_string + assert_equal "", Redmine::CodesetUtil.to_utf8_by_setting("") + assert_equal nil, Redmine::CodesetUtil.to_utf8_by_setting(nil) + end + + def test_to_utf8_by_setting_returns_ascii_as_utf8 + s1 = "ASCII" + s2 = s1.dup + if s1.respond_to?(:force_encoding) + s1.force_encoding("UTF-8") + s2.force_encoding("ISO-8859-1") + end + str1 = Redmine::CodesetUtil.to_utf8_by_setting(s1) + str2 = Redmine::CodesetUtil.to_utf8_by_setting(s2) + assert_equal s1, str1 + assert_equal s1, str2 + if s1.respond_to?(:force_encoding) + assert_equal "UTF-8", str1.encoding.to_s + assert_equal "UTF-8", str2.encoding.to_s + end + end + + def test_to_utf8_by_setting_invalid_utf8_sequences_should_be_stripped + with_settings :repositories_encodings => '' do + # s1 = File.read("#{RAILS_ROOT}/test/fixtures/encoding/iso-8859-1.txt") + s1 = "Texte encod\xe9 en ISO-8859-1." + s1.force_encoding("ASCII-8BIT") if s1.respond_to?(:force_encoding) + str = Redmine::CodesetUtil.to_utf8_by_setting(s1) + if str.respond_to?(:force_encoding) + assert str.valid_encoding? + assert_equal "UTF-8", str.encoding.to_s + end + assert_equal "Texte encod? en ISO-8859-1.", str + end + end + + def test_to_utf8_by_setting_invalid_utf8_sequences_should_be_stripped_ja_jis + with_settings :repositories_encodings => 'ISO-2022-JP' do + s1 = "test\xb5\xfetest\xb5\xfe" + s1.force_encoding("ASCII-8BIT") if s1.respond_to?(:force_encoding) + str = Redmine::CodesetUtil.to_utf8_by_setting(s1) + if str.respond_to?(:force_encoding) + assert str.valid_encoding? + assert_equal "UTF-8", str.encoding.to_s + end + assert_equal "test??test??", str + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/86/863064fb5000e258cd2d7b4e4e73c65dd4fbbfc5.svn-base --- a/.svn/pristine/86/863064fb5000e258cd2d7b4e4e73c65dd4fbbfc5.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,190 +0,0 @@ -# $Id: testldap.rb 65 2006-04-23 01:17:49Z blackhedd $ -# -# - - -$:.unshift "lib" - -require 'test/unit' - -require 'net/ldap' -require 'stringio' - - -class TestLdapClient < Test::Unit::TestCase - - # TODO: these tests crash and burn if the associated - # LDAP testserver isn't up and running. - # We rely on being able to read a file with test data - # in LDIF format. - # TODO, WARNING: for the moment, this data is in a file - # whose name and location are HARDCODED into the - # instance method load_test_data. - - def setup - @host = "127.0.0.1" - @port = 3890 - @auth = { - :method => :simple, - :username => "cn=bigshot,dc=bayshorenetworks,dc=com", - :password => "opensesame" - } - - @ldif = load_test_data - end - - - - # Get some test data which will be used to validate - # the responses from the test LDAP server we will - # connect to. - # TODO, Bogus: we are HARDCODING the location of the file for now. - # - def load_test_data - ary = File.readlines( "tests/testdata.ldif" ) - hash = {} - while line = ary.shift and line.chomp! - if line =~ /^dn:[\s]*/i - dn = $' - hash[dn] = {} - while attr = ary.shift and attr.chomp! and attr =~ /^([\w]+)[\s]*:[\s]*/ - hash[dn][$1.downcase.intern] ||= [] - hash[dn][$1.downcase.intern] << $' - end - end - end - hash - end - - - - # Binding tests. - # Need tests for all kinds of network failures and incorrect auth. - # TODO: Implement a class-level timeout for operations like bind. - # Search has a timeout defined at the protocol level, other ops do not. - # TODO, use constants for the LDAP result codes, rather than hardcoding them. - def test_bind - ldap = Net::LDAP.new :host => @host, :port => @port, :auth => @auth - assert_equal( true, ldap.bind ) - assert_equal( 0, ldap.get_operation_result.code ) - assert_equal( "Success", ldap.get_operation_result.message ) - - bad_username = @auth.merge( {:username => "cn=badguy,dc=imposters,dc=com"} ) - ldap = Net::LDAP.new :host => @host, :port => @port, :auth => bad_username - assert_equal( false, ldap.bind ) - assert_equal( 48, ldap.get_operation_result.code ) - assert_equal( "Inappropriate Authentication", ldap.get_operation_result.message ) - - bad_password = @auth.merge( {:password => "cornhusk"} ) - ldap = Net::LDAP.new :host => @host, :port => @port, :auth => bad_password - assert_equal( false, ldap.bind ) - assert_equal( 49, ldap.get_operation_result.code ) - assert_equal( "Invalid Credentials", ldap.get_operation_result.message ) - end - - - - def test_search - ldap = Net::LDAP.new :host => @host, :port => @port, :auth => @auth - - search = {:base => "dc=smalldomain,dc=com"} - assert_equal( false, ldap.search( search )) - assert_equal( 32, ldap.get_operation_result.code ) - - search = {:base => "dc=bayshorenetworks,dc=com"} - assert_equal( true, ldap.search( search )) - assert_equal( 0, ldap.get_operation_result.code ) - - ldap.search( search ) {|res| - assert_equal( res, @ldif ) - } - end - - - - - # This is a helper routine for test_search_attributes. - def internal_test_search_attributes attrs_to_search - ldap = Net::LDAP.new :host => @host, :port => @port, :auth => @auth - assert( ldap.bind ) - - search = { - :base => "dc=bayshorenetworks,dc=com", - :attributes => attrs_to_search - } - - ldif = @ldif - ldif.each {|dn,entry| - entry.delete_if {|attr,value| - ! attrs_to_search.include?(attr) - } - } - - assert_equal( true, ldap.search( search )) - ldap.search( search ) {|res| - res_keys = res.keys.sort - ldif_keys = ldif.keys.sort - assert( res_keys, ldif_keys ) - res.keys.each {|rk| - assert( res[rk], ldif[rk] ) - } - } - end - - - def test_search_attributes - internal_test_search_attributes [:mail] - internal_test_search_attributes [:cn] - internal_test_search_attributes [:ou] - internal_test_search_attributes [:hasaccessprivilege] - internal_test_search_attributes ["mail"] - internal_test_search_attributes ["cn"] - internal_test_search_attributes ["ou"] - internal_test_search_attributes ["hasaccessrole"] - - internal_test_search_attributes [:mail, :cn, :ou, :hasaccessrole] - internal_test_search_attributes [:mail, "cn", :ou, "hasaccessrole"] - end - - - def test_search_filters - ldap = Net::LDAP.new :host => @host, :port => @port, :auth => @auth - search = { - :base => "dc=bayshorenetworks,dc=com", - :filter => Net::LDAP::Filter.eq( "sn", "Fosse" ) - } - - ldap.search( search ) {|res| - p res - } - end - - - - def test_open - ldap = Net::LDAP.new :host => @host, :port => @port, :auth => @auth - ldap.open {|ldap| - 10.times { - rc = ldap.search( :base => "dc=bayshorenetworks,dc=com" ) - assert_equal( true, rc ) - } - } - end - - - def test_ldap_open - Net::LDAP.open( :host => @host, :port => @port, :auth => @auth ) {|ldap| - 10.times { - rc = ldap.search( :base => "dc=bayshorenetworks,dc=com" ) - assert_equal( true, rc ) - } - } - end - - - - - -end - - diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/86/86cf2abe4f8b8142381e7211a838d3b30b929a54.svn-base --- a/.svn/pristine/86/86cf2abe4f8b8142381e7211a838d3b30b929a54.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,127 +0,0 @@ -// ** I18N - -// Calendar EN language -// Author: Mihai Bazon, -// 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 -("ì¼ìš”ì¼", - "월요ì¼", - "화요ì¼", - "수요ì¼", - "목요ì¼", - "금요ì¼", - "토요ì¼", - "ì¼ìš”ì¼"); - -// 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 = 0; - -// full month names -Calendar._MN = new Array -("1ì›”", - "2ì›”", - "3ì›”", - "4ì›”", - "5ì›”", - "6ì›”", - "7ì›”", - "8ì›”", - "9ì›”", - "10ì›”", - "11ì›”", - "12ì›”"); - -// short month names -Calendar._SMN = new Array -("1ì›”", - "2ì›”", - "3ì›”", - "4ì›”", - "5ì›”", - "6ì›”", - "7ì›”", - "8ì›”", - "9ì›”", - "10ì›”", - "11ì›”", - "12ì›”"); - -// tooltips -Calendar._TT = {}; -Calendar._TT["INFO"] = "ì´ ë‹¬ë ¥ì€ ... & ë„움ë§"; - -Calendar._TT["ABOUT"] = -"DHTML ë‚ ì§œ/시간 ì„ íƒê¸°\n" + -"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) -"최신 ë²„ì „ì„ êµ¬í•˜ë ¤ë©´ 여기로: http://www.dynarch.com/projects/calendar/\n" + -"ë°°í¬ë¼ì´ì„¼ìФ:GNU LGPL. 참조:http://gnu.org/licenses/lgpl.html for details." + -"\n\n" + -"ë‚ ì§œ ì„ íƒ:\n" + -"- 해를 ì„ íƒí•˜ë ¤ë©´ \xab, \xbb ë²„íŠ¼ì„ ì‚¬ìš©í•˜ì„¸ìš”.\n" + -"- ë‹¬ì„ ì„ íƒí•˜ë ¤ë©´ " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " ë²„íŠ¼ì„ ì‚¬ìš©í•˜ì„¸ìš”.\n" + -"- 좀 ë” ë¹ ë¥´ê²Œ ì„ íƒí•˜ë ¤ë©´ ìœ„ì˜ ë²„íŠ¼ì„ ê¾¹ 눌러주세요."; -Calendar._TT["ABOUT_TIME"] = "\n\n" + -"시간 ì„ íƒ:\n" + -"- 시, ë¶„ì„ ë”하려면 í´ë¦­í•˜ì„¸ìš”.\n" + -"- 시, ë¶„ì„ ë¹¼ë ¤ë©´ 쉬프트 누르고 í´ë¦­í•˜ì„¸ìš”.\n" + -"- 좀 ë” ë¹ ë¥´ê²Œ ì„ íƒí•˜ë ¤ë©´ í´ë¦­í•˜ê³  드래그하세요."; - -Calendar._TT["PREV_YEAR"] = "ì´ì „ í•´"; -Calendar._TT["PREV_MONTH"] = "ì´ì „ 달"; -Calendar._TT["GO_TODAY"] = "오늘로 ì´ë™"; -Calendar._TT["NEXT_MONTH"] = "ë‹¤ìŒ ë‹¬"; -Calendar._TT["NEXT_YEAR"] = "ë‹¤ìŒ í•´"; -Calendar._TT["SEL_DATE"] = "ë‚ ì§œ ì„ íƒ"; -Calendar._TT["DRAG_TO_MOVE"] = "ì´ë™(드래그)"; -Calendar._TT["PART_TODAY"] = " (오늘)"; - -// the following is to inform that "%s" is to be the first day of week -// %s will be replaced with the day name. -Calendar._TT["DAY_FIRST"] = "[%s]ì„ ì²˜ìŒìœ¼ë¡œ"; - -// This may be locale-dependent. It specifies the week-end days, as an array -// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 -// means Monday, etc. -Calendar._TT["WEEKEND"] = "0,6"; - -Calendar._TT["CLOSE"] = "닫기"; -Calendar._TT["TODAY"] = "오늘"; -Calendar._TT["TIME_PART"] = "í´ë¦­(+),쉬프트+í´ë¦­(-),드래그"; - -// date formats -Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; -Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; - -Calendar._TT["WK"] = "주"; -Calendar._TT["TIME"] = "시간:"; diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/86/86d46b11f53bf924d634fb6a310d55c059a3a986.svn-base --- a/.svn/pristine/86/86d46b11f53bf924d634fb6a310d55c059a3a986.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +0,0 @@ -
    -<%= link_to l(:label_issue_status_new), new_issue_status_path, :class => 'icon icon-add' %> -<%= link_to(l(:label_update_issue_done_ratios), update_issue_done_ratio_issue_statuses_path, :class => 'icon icon-multiple', :method => 'post', :confirm => l(:text_are_you_sure)) if Issue.use_status_for_done_ratio? %> -
    - -

    <%=l(:label_issue_status_plural)%>

    - - - - - <% if Issue.use_status_for_done_ratio? %> - - <% end %> - - - - - - -<% for status in @issue_statuses %> - "> - - <% if Issue.use_status_for_done_ratio? %> - - <% end %> - - - - - -<% end %> - -
    <%=l(:field_status)%><%=l(:field_done_ratio)%><%=l(:field_is_default)%><%=l(:field_is_closed)%><%=l(:button_sort)%>
    <%= link_to h(status.name), edit_issue_status_path(status) %><%= h status.default_done_ratio %><%= checked_image status.is_default? %><%= checked_image status.is_closed? %><%= reorder_links('issue_status', {:action => 'update', :id => status}, :put) %> - <%= link_to(l(:button_delete), issue_status_path(status), - :method => :delete, - :confirm => l(:text_are_you_sure), - :class => 'icon icon-del') %> -
    - -

    <%= pagination_links_full @issue_status_pages %>

    - -<% html_title(l(:label_issue_status_plural)) -%> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/86/86f993136841831dd0360fe42ff275f20185c30f.svn-base --- a/.svn/pristine/86/86f993136841831dd0360fe42ff275f20185c30f.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,6 +0,0 @@ -

    <%=l(:label_issue_category)%>

    - -<% labelled_tabular_form_for :issue_category, @category, :url => issue_category_path(@category), :html => {:method => :put} do |f| %> -<%= render :partial => 'issue_categories/form', :locals => { :f => f } %> -<%= submit_tag l(:button_save) %> -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/87/8719788cc5feae1f6d13316748ca22d737c8a820.svn-base --- a/.svn/pristine/87/8719788cc5feae1f6d13316748ca22d737c8a820.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,18 +0,0 @@ -
    -<% for attachment in attachments %> -

    <%= link_to_attachment attachment, :class => 'icon icon-attachment' -%> -<%= h(" - #{attachment.description}") unless attachment.description.blank? %> - (<%= number_to_human_size attachment.filesize %>) - <% if options[:deletable] %> - <%= link_to image_tag('delete.png'), attachment_path(attachment), - :confirm => l(:text_are_you_sure), - :method => :delete, - :class => 'delete', - :title => l(:button_delete) %> - <% end %> - <% if options[:author] %> - <%= h(attachment.author) %>, <%= format_time(attachment.created_on) %> - <% end %> -

    -<% end %> -
    diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/87/871ab69c9430fd870a684b73b2e5c6c68ed7f0f1.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/87/871ab69c9430fd870a684b73b2e5c6c68ed7f0f1.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,58 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2013 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 CalendarsHelper + def link_to_previous_month(year, month, options={}) + target_year, target_month = if month == 1 + [year - 1, 12] + else + [year, month - 1] + end + + name = if target_month == 12 + "#{month_name(target_month)} #{target_year}" + else + "#{month_name(target_month)}" + end + + # \xc2\xab(utf-8) = « + link_to_month(("\xc2\xab " + name), target_year, target_month, options) + end + + def link_to_next_month(year, month, options={}) + target_year, target_month = if month == 12 + [year + 1, 1] + else + [year, month + 1] + end + + name = if target_month == 1 + "#{month_name(target_month)} #{target_year}" + else + "#{month_name(target_month)}" + end + + # \xc2\xbb(utf-8) = » + link_to_month((name + " \xc2\xbb"), target_year, target_month, options) + end + + def link_to_month(link_name, year, month, options={}) + link_to_content_update(h(link_name), params.merge(:year => year, :month => month)) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/87/87b8296b76d4848fd3e88cf880e805a657f96574.svn-base --- a/.svn/pristine/87/87b8296b76d4848fd3e88cf880e805a657f96574.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,48 +0,0 @@ -Return-Path: -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> -In-Reply-To: -From: "John Smith" -To: -Subject: Re: update to issue 2 -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 - -An update to the issue by the sender. - -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. - ->> > --- Reply above. Do not remove this line. --- ->> > ->> > Issue #6779 has been updated by Eric Davis. ->> > ->> > Subject changed from Projects with JSON to Project JSON API ->> > Status changed from New to Assigned ->> > Assignee set to Eric Davis ->> > Priority changed from Low to Normal ->> > Estimated time deleted (1.00) ->> > ->> > Looks like the JSON api for projects was missed. I'm going to be ->> > reviewing the existing APIs and trying to clean them up over the next ->> > few weeks. diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/87/87cf5e349a5bc29bdc53d5a9546c92b6fc428743.svn-base --- a/.svn/pristine/87/87cf5e349a5bc29bdc53d5a9546c92b6fc428743.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1031 +0,0 @@ -pt-BR: - # formatos de data e hora - direction: ltr - date: - formats: - default: "%d/%m/%Y" - short: "%d de %B" - long: "%d de %B de %Y" - only_day: "%d" - - day_names: [Domingo, Segunda, Terça, Quarta, Quinta, Sexta, Sábado] - abbr_day_names: [Dom, Seg, Ter, Qua, Qui, Sex, Sáb] - month_names: [~, Janeiro, Fevereiro, Março, Abril, Maio, Junho, Julho, Agosto, Setembro, Outubro, Novembro, Dezembro] - abbr_month_names: [~, Jan, Fev, Mar, Abr, Mai, Jun, Jul, Ago, Set, Out, Nov, Dez] - order: - - :day - - :month - - :year - - time: - formats: - default: "%A, %d de %B de %Y, %H:%M hs" - time: "%H:%M hs" - short: "%d/%m, %H:%M hs" - long: "%A, %d de %B de %Y, %H:%M hs" - only_second: "%S" - datetime: - formats: - default: "%Y-%m-%dT%H:%M:%S%Z" - am: '' - pm: '' - - # date helper distancia em palavras - datetime: - distance_in_words: - half_a_minute: 'meio minuto' - less_than_x_seconds: - one: 'menos de 1 segundo' - other: 'menos de %{count} segundos' - - x_seconds: - one: '1 segundo' - other: '%{count} segundos' - - less_than_x_minutes: - one: 'menos de um minuto' - other: 'menos de %{count} minutos' - - x_minutes: - one: '1 minuto' - other: '%{count} minutos' - - about_x_hours: - one: 'aproximadamente 1 hora' - other: 'aproximadamente %{count} horas' - - x_days: - one: '1 dia' - other: '%{count} dias' - - about_x_months: - one: 'aproximadamente 1 mês' - other: 'aproximadamente %{count} meses' - - x_months: - one: '1 mês' - other: '%{count} meses' - - about_x_years: - one: 'aproximadamente 1 ano' - other: 'aproximadamente %{count} anos' - - over_x_years: - one: 'mais de 1 ano' - other: 'mais de %{count} anos' - almost_x_years: - one: "almost 1 year" - other: "almost %{count} years" - - # numeros - number: - format: - precision: 3 - separator: ',' - delimiter: '.' - currency: - format: - unit: 'R$' - precision: 2 - format: '%u %n' - separator: ',' - delimiter: '.' - percentage: - format: - delimiter: '.' - precision: - format: - delimiter: '.' - human: - format: - precision: 1 - delimiter: '.' - storage_units: - format: "%n %u" - units: - byte: - one: "Byte" - other: "Bytes" - kb: "KB" - mb: "MB" - gb: "GB" - tb: "TB" - support: - array: - sentence_connector: "e" - skip_last_comma: true - - # Active Record - activerecord: - errors: - template: - header: - one: "model não pode ser salvo: 1 erro" - other: "model não pode ser salvo: %{count} erros." - body: "Por favor, verifique os seguintes campos:" - messages: - inclusion: "não está incluso na lista" - exclusion: "não está disponível" - invalid: "não é válido" - confirmation: "não está de acordo com a confirmação" - accepted: "precisa ser aceito" - empty: "não pode ficar vazio" - blank: "não pode ficar vazio" - too_long: "é muito longo (máximo: %{count} caracteres)" - too_short: "é muito curto (mínimon: %{count} caracteres)" - wrong_length: "deve ter %{count} caracteres" - taken: "não está disponível" - not_a_number: "não é um número" - greater_than: "precisa ser maior do que %{count}" - greater_than_or_equal_to: "precisa ser maior ou igual a %{count}" - equal_to: "precisa ser igual a %{count}" - less_than: "precisa ser menor do que %{count}" - less_than_or_equal_to: "precisa ser menor ou igual a %{count}" - odd: "precisa ser ímpar" - even: "precisa ser par" - greater_than_start_date: "deve ser maior que a data inicial" - not_same_project: "não pertence ao mesmo projeto" - circular_dependency: "Esta relação geraria uma dependência circular" - cant_link_an_issue_with_a_descendant: "Uma tarefa não pode ser relaciona a uma de suas subtarefas" - - actionview_instancetag_blank_option: Selecione - - general_text_No: 'Não' - 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_encoding: ISO-8859-1 - general_pdf_encoding: UTF-8 - general_first_day_of_week: '1' - - notice_account_updated: Conta atualizada com sucesso. - notice_account_invalid_creditentials: Usuário ou senha inválido. - notice_account_password_updated: Senha alterada com sucesso. - notice_account_wrong_password: Senha inválida. - notice_account_register_done: Conta criada com sucesso. Para ativar sua conta, clique no link que lhe foi enviado por e-mail. - notice_account_unknown_email: Usuário desconhecido. - notice_can_t_change_password: Esta conta utiliza autenticação externa. Não é possível alterar a senha. - notice_account_lost_email_sent: Um e-mail com instruções para escolher uma nova senha foi enviado para você. - notice_account_activated: Sua conta foi ativada. Você pode acessá-la agora. - notice_successful_create: Criado com sucesso. - notice_successful_update: Alterado com sucesso. - notice_successful_delete: Excluído com sucesso. - notice_successful_connection: Conectado com sucesso. - notice_file_not_found: A página que você está tentando acessar não existe ou foi excluída. - notice_locking_conflict: Os dados foram atualizados por outro usuário. - notice_not_authorized: Você não está autorizado a acessar esta página. - notice_email_sent: "Um e-mail foi enviado para %{value}" - notice_email_error: "Ocorreu um erro ao enviar o e-mail (%{value})" - notice_feeds_access_key_reseted: Sua chave RSS foi reconfigurada. - notice_failed_to_save_issues: "Problema ao salvar %{count} tarefa(s) de %{total} selecionadas: %{ids}." - notice_no_issue_selected: "Nenhuma tarefa selecionada! Por favor, marque as tarefas que você deseja editar." - notice_account_pending: "Sua conta foi criada e está aguardando aprovação do administrador." - notice_default_data_loaded: Configuração padrão carregada com sucesso. - - error_can_t_load_default_data: "A configuração padrão não pode ser carregada: %{value}" - error_scm_not_found: "A entrada e/ou a revisão não existe no repositório." - error_scm_command_failed: "Ocorreu um erro ao tentar acessar o repositório: %{value}" - error_scm_annotate: "Esta 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 projeto' - error_no_tracker_in_project: 'Não há um tipo de tarefa associado a este projeto. Favor verificar as configurações do projeto.' - error_no_default_issue_status: 'A situação padrão para tarefa não está definida. Favor verificar sua configuração (Vá em "Administração -> Situação da tarefa").' - - mail_subject_lost_password: "Sua senha do %{value}." - mail_body_lost_password: 'Para mudar sua senha, clique no link abaixo:' - mail_subject_register: "Ativação de conta do %{value}." - mail_body_register: 'Para ativar sua conta, clique no link abaixo:' - mail_body_account_information_external: "Você pode usar sua conta do %{value} para entrar." - mail_body_account_information: Informações sobre sua conta - mail_subject_account_activation_request: "%{value} - Requisição de ativação de conta" - mail_body_account_activation_request: "Um novo usuário (%{value}) se registrou. A conta está aguardando sua aprovação:" - mail_subject_reminder: "%{count} tarefa(s) com data prevista para os próximos %{days} dias" - mail_body_reminder: "%{count} tarefa(s) para você com data prevista para os próximos %{days} dias:" - - gui_validation_error: 1 erro - gui_validation_error_plural: "%{count} erros" - - field_name: Nome - field_description: Descrição - field_summary: Resumo - field_is_required: Obrigatório - field_firstname: Nome - field_lastname: Sobrenome - field_mail: E-mail - field_filename: Arquivo - field_filesize: Tamanho - field_downloads: Downloads - field_author: Autor - field_created_on: Criado em - field_updated_on: Alterado em - field_field_format: Formato - field_is_for_all: Para todos os projetos - field_possible_values: Possíveis valores - field_regexp: Expressão regular - field_min_length: Tamanho mínimo - field_max_length: Tamanho máximo - field_value: Valor - field_category: Categoria - field_title: Título - field_project: Projeto - field_issue: Tarefa - field_status: Situação - field_notes: Notas - field_is_closed: Tarefa fechada - field_is_default: Situação padrão - field_tracker: Tipo - field_subject: Título - field_due_date: Data prevista - field_assigned_to: Atribuído para - field_priority: Prioridade - field_fixed_version: Versão - field_user: Usuário - field_role: Cargo - field_homepage: Página do projeto - field_is_public: Público - field_parent: Sub-projeto de - field_is_in_roadmap: Exibir no planejamento - field_login: Usuário - field_mail_notification: Notificações por e-mail - field_admin: Administrador - field_last_login_on: Última conexão - field_language: Idioma - field_effective_date: Data - field_password: Senha - field_new_password: Nova senha - field_password_confirmation: Confirmação - field_version: Versão - field_type: Tipo - field_host: Servidor - field_port: Porta - field_account: Conta - field_base_dn: DN Base - field_attr_login: Atributo para nome de usuário - field_attr_firstname: Atributo para nome - field_attr_lastname: Atributo para sobrenome - field_attr_mail: Atributo para e-mail - field_onthefly: Criar usuários dinamicamente ("on-the-fly") - field_start_date: Início - field_done_ratio: "% Terminado" - field_auth_source: Modo de autenticação - field_hide_mail: Ocultar meu e-mail - field_comments: Comentário - field_url: URL - field_start_page: Página inicial - field_subproject: Sub-projeto - field_hours: Horas - field_activity: Atividade - field_spent_on: Data - field_identifier: Identificador - field_is_filter: É um filtro - field_issue_to: Tarefa relacionada - field_delay: Atraso - field_assignable: Tarefas podem ser atribuídas a este papel - field_redirect_existing_links: Redirecionar links existentes - field_estimated_hours: Tempo estimado - field_column_names: Colunas - field_time_zone: Fuso-horário - field_searchable: Pesquisável - field_default_value: Padrão - field_comments_sorting: Visualizar comentários - field_parent_title: Página pai - - setting_app_title: Título da aplicação - setting_app_subtitle: Sub-título da aplicação - setting_welcome_text: Texto de boas-vindas - setting_default_language: Idioma padrão - setting_login_required: Exigir autenticação - setting_self_registration: Permitido Auto-registro - setting_attachment_max_size: Tamanho máximo do anexo - setting_issues_export_limit: Limite de exportação das tarefas - setting_mail_from: E-mail enviado de - setting_bcc_recipients: Enviar com cópia oculta (cco) - setting_host_name: Nome do Servidor e subdomínio - setting_text_formatting: Formatação do texto - setting_wiki_compression: Compactação de histórico do Wiki - setting_feeds_limit: Número de registros por Feed - setting_default_projects_public: Novos projetos são públicos por padrão - setting_autofetch_changesets: Obter commits automaticamente - setting_sys_api_enabled: Ativa WS para gerenciamento do repositório (SVN) - setting_commit_ref_keywords: Palavras de referência - setting_commit_fix_keywords: Palavras de fechamento - setting_autologin: Auto-login - setting_date_format: Formato da data - setting_time_format: Formato de hora - setting_cross_project_issue_relations: Permitir relacionar tarefas entre projetos - setting_issue_list_default_columns: Colunas padrão visíveis na lista de tarefas - setting_emails_footer: Rodapé do e-mail - setting_protocol: Protocolo - setting_per_page_options: Número de itens exibidos por página - setting_user_format: Formato de exibição de nome de usuário - setting_activity_days_default: Dias visualizados na atividade do projeto - setting_display_subprojects_issues: Visualizar tarefas dos subprojetos nos projetos principais por padrão - setting_enabled_scm: SCM habilitados - setting_mail_handler_api_enabled: Habilitar WS para e-mails de entrada - setting_mail_handler_api_key: Chave de API - setting_sequential_project_identifiers: Gerar identificadores sequenciais de projeto - - project_module_issue_tracking: Gerenciamento de Tarefas - project_module_time_tracking: Gerenciamento de tempo - project_module_news: Notícias - project_module_documents: Documentos - project_module_files: Arquivos - project_module_wiki: Wiki - project_module_repository: Repositório - project_module_boards: Fóruns - - label_user: Usuário - label_user_plural: Usuários - label_user_new: Novo usuário - label_project: Projeto - label_project_new: Novo projeto - label_project_plural: Projetos - label_x_projects: - zero: nenhum projeto - one: 1 projeto - other: "%{count} projetos" - label_project_all: Todos os projetos - label_project_latest: Últimos projetos - 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 %{value}" - label_issue_added: Tarefa adicionada - label_issue_updated: Tarefa atualizada - label_issue_note_added: Nota adicionada - label_issue_status_updated: Situação atualizada - label_issue_priority_updated: Prioridade atualizada - label_document: Documento - label_document_new: Novo documento - label_document_plural: Documentos - 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 - label_tracker: Tipo de tarefa - label_tracker_plural: Tipos de tarefas - label_tracker_new: Novo tipo - label_workflow: Fluxo de trabalho - label_issue_status: Situação da tarefa - label_issue_status_plural: Situação das tarefas - label_issue_status_new: Nova situação - label_issue_category: Categoria da tarefa - label_issue_category_plural: Categorias das tarefas - 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: 'Tipos & Categorias' - label_enumeration_new: Novo - label_information: Informação - label_information_plural: Informações - label_please_login: Efetue o login - label_register: Cadastre-se - label_password_lost: Perdi minha senha - label_home: Página inicial - label_my_page: Minha página - label_my_account: Minha conta - label_my_projects: Meus projetos - label_administration: Administração - label_login: Entrar - label_logout: Sair - label_help: Ajuda - label_reported_issues: Tarefas reportadas - label_assigned_to_me_issues: Minhas tarefas - label_last_login: Última conexão - label_registered_on: Registrado em - label_activity: Atividade - label_overall_activity: Atividades gerais - label_new: Novo - label_logged_as: "Acessando como:" - label_environment: Ambiente - label_authentication: Autenticação - label_auth_source: Modo de autenticação - label_auth_source_new: Novo modo de autenticação - label_auth_source_plural: Modos de autenticação - label_subproject_plural: Sub-projetos - label_and_its_subprojects: "%{value} e seus sub-projetos" - label_min_max_length: Tamanho mín-máx - label_list: Lista - label_date: Data - label_integer: Inteiro - label_float: Decimal - label_boolean: Boleano - label_string: Texto - label_text: Texto longo - label_attribute: Atributo - label_attribute_plural: Atributos - label_download: "%{count} Download" - label_download_plural: "%{count} Downloads" - label_no_data: Nenhuma informação disponível - label_change_status: Alterar situação - label_history: Histórico - label_attachment: Arquivo - label_attachment_new: Novo arquivo - label_attachment_delete: Excluir arquivo - label_attachment_plural: Arquivos - label_file_added: Arquivo adicionado - label_report: Relatório - label_report_plural: Relatório - label_news: Notícia - label_news_new: Adicionar notícia - label_news_plural: Notícias - label_news_latest: Últimas notícias - label_news_view_all: Ver todas as notícias - label_news_added: Notícia adicionada - label_settings: Configurações - label_overview: Visão geral - label_version: Versão - label_version_new: Nova versão - label_version_plural: Versões - label_confirmation: Confirmação - label_export_to: Exportar para - label_read: Ler... - label_public_projects: Projetos públicos - label_open_issues: Aberta - label_open_issues_plural: Abertas - label_closed_issues: Fechada - label_closed_issues_plural: Fechadas - label_x_open_issues_abbr_on_total: - zero: 0 aberta / %{total} - one: 1 aberta / %{total} - other: "%{count} abertas / %{total}" - label_x_open_issues_abbr: - zero: 0 aberta - one: 1 aberta - other: "%{count} abertas" - label_x_closed_issues_abbr: - zero: 0 fechada - one: 1 fechada - other: "%{count} fechadas" - label_total: Total - label_permissions: Permissões - label_current_status: Situação atual - label_new_statuses_allowed: Nova situação permitida - label_all: todos - label_none: nenhum - label_nobody: ninguém - label_next: Próximo - label_previous: Anterior - label_used_by: Usado por - label_details: Detalhes - label_add_note: Adicionar nota - label_per_page: Por página - label_calendar: Calendário - label_months_from: meses a partir de - label_gantt: Gantt - label_internal: Interno - label_last_changes: "últimas %{count} alterações" - label_change_view_all: Mostrar todas as alterações - label_personalize_page: Personalizar esta página - label_comment: Comentário - label_comment_plural: Comentários - label_x_comments: - zero: nenhum comentário - one: 1 comentário - other: "%{count} comentários" - label_comment_add: Adicionar comentário - label_comment_added: Comentário adicionado - label_comment_delete: Excluir comentário - label_query: Consulta personalizada - label_query_plural: Consultas personalizadas - label_query_new: Nova consulta - label_filter_add: Adicionar filtro - label_filter_plural: Filtros - label_equals: igual a - label_not_equals: diferente de - label_in_less_than: maior que - label_in_more_than: menor que - label_in: em - label_today: hoje - label_all_time: tudo - label_yesterday: ontem - label_this_week: esta semana - label_last_week: última semana - label_last_n_days: "últimos %{count} dias" - label_this_month: este mês - label_last_month: último mês - label_this_year: este ano - label_date_range: Período - label_less_than_ago: menos de - label_more_than_ago: mais de - 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_repository_plural: Repositórios - label_browse: Procurar - label_modification: "%{count} alteração" - label_modification_plural: "%{count} alterações" - label_revision: Revisão - label_revision_plural: Revisões - label_associated_revisions: Revisões associadas - label_added: adicionada - label_modified: alterada - label_deleted: excluída - 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_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: Planejamento - label_roadmap_due_in: "Previsto para %{value}" - label_roadmap_overdue: "%{value} atrasado" - label_roadmap_no_issues: Sem tarefas para esta versão - label_search: Busca - label_result_plural: Resultados - label_all_words: Todas as palavras - label_wiki: Wiki - label_wiki_edit: Editar Wiki - label_wiki_edit_plural: Edições Wiki - label_wiki_page: Página Wiki - label_wiki_page_plural: páginas Wiki - label_index_by_title: Ãndice por título - label_index_by_date: Ãndice por data - label_current_version: Versão atual - label_preview: Pré-visualizar - label_feed_plural: Feeds - label_changes_details: Detalhes de todas as alterações - label_issue_tracking: Tarefas - label_spent_time: Tempo gasto - label_f_hour: "%{value} hora" - label_f_hour_plural: "%{value} horas" - label_time_tracking: Registro de horas - label_change_plural: Alterações - label_statistics: Estatísticas - label_commits_per_month: Commits por mês - label_commits_per_author: Commits por autor - label_view_diff: Ver diferenças - label_diff_inline: inline - label_diff_side_by_side: lado a lado - label_options: Opções - label_copy_workflow_from: Copiar fluxo de trabalho de - label_permissions_report: Relatório de permissões - label_watched_issues: Tarefas observadas - label_related_issues: Tarefas relacionadas - label_applied_status: Situação alterada - label_loading: Carregando... - label_relation_new: Nova relação - label_relation_delete: Excluir relação - label_relates_to: relacionado a - label_duplicates: duplica - label_duplicated_by: duplicado por - label_blocks: bloqueia - label_blocked_by: bloqueado por - label_precedes: precede - label_follows: segue - label_end_to_start: fim para o início - label_end_to_end: fim para fim - label_start_to_start: início para início - label_start_to_end: início para fim - label_stay_logged_in: Permanecer logado - label_disabled: desabilitado - label_show_completed_versions: Exibir versões completas - label_me: mim - label_board: Fórum - label_board_new: Novo fórum - label_board_plural: Fóruns - label_topic_plural: Tópicos - label_message_plural: Mensagens - label_message_last: Última mensagem - label_message_new: Nova mensagem - label_message_posted: Mensagem enviada - label_reply_plural: Respostas - label_send_information: Enviar informação da nova conta para o usuário - label_year: Ano - label_month: Mês - label_week: Semana - label_date_from: De - label_date_to: Para - label_language_based: Com base no idioma do usuário - label_sort_by: "Ordenar por %{value}" - label_send_test_email: Enviar um e-mail de teste - label_feeds_access_key_created_on: "chave de acesso RSS criada %{value} atrás" - label_module_plural: Módulos - label_added_time_by: "Adicionado por %{author} %{age} atrás" - label_updated_time: "Atualizado %{value} atrás" - label_jump_to_a_project: Ir para o projeto... - label_file_plural: Arquivos - label_changeset_plural: Changesets - label_default_columns: Colunas padrão - label_no_change_option: (Sem alteração) - label_bulk_edit_selected_issues: Edição em massa das tarefas selecionados. - label_theme: Tema - label_default: Padrão - label_search_titles_only: Pesquisar somente títulos - label_user_mail_option_all: "Para qualquer evento em todos os meus projetos" - label_user_mail_option_selected: "Para qualquer evento somente no(s) projeto(s) selecionado(s)..." - label_user_mail_no_self_notified: "Eu não quero ser notificado de minhas próprias modificações" - label_registration_activation_by_email: ativação de conta por e-mail - label_registration_manual_activation: ativação manual de conta - label_registration_automatic_activation: ativação automática de conta - label_display_per_page: "Por página: %{value}" - label_age: Idade - label_change_properties: Alterar propriedades - label_general: Geral - label_more: Mais - label_scm: 'Controle de versão:' - label_plugins: Plugins - label_ldap_authentication: Autenticação LDAP - label_downloads_abbr: D/L - label_optional_description: Descrição opcional - label_add_another_file: Adicionar outro arquivo - label_preferences: Preferências - label_chronological_order: Em ordem cronológica - label_reverse_chronological_order: Em ordem cronológica inversa - label_planning: Planejamento - label_incoming_emails: E-mails recebidos - label_generate_key: Gerar uma chave - label_issue_watchers: Observadores - - button_login: Entrar - button_submit: Enviar - button_save: Salvar - button_check_all: Marcar todos - button_uncheck_all: Desmarcar todos - button_delete: Excluir - button_create: Criar - button_test: Testar - button_edit: Editar - button_add: Adicionar - button_change: Alterar - button_apply: Aplicar - button_clear: Limpar - button_lock: Bloquear - button_unlock: Desbloquear - button_download: Baixar - button_list: Listar - button_view: Ver - button_move: Mover - button_back: Voltar - button_cancel: Cancelar - button_activate: Ativar - button_sort: Ordenar - button_log_time: Tempo de trabalho - button_rollback: Voltar para esta versão - button_watch: Observar - button_unwatch: Parar de observar - button_reply: Responder - button_archive: Arquivar - button_unarchive: Desarquivar - button_reset: Redefinir - button_rename: Renomear - button_change_password: Alterar senha - button_copy: Copiar - button_annotate: Anotar - button_update: Atualizar - button_configure: Configurar - button_quote: Responder - - status_active: ativo - status_registered: registrado - status_locked: bloqueado - - text_select_mail_notifications: Ações a serem notificadas por e-mail - text_regexp_info: ex. ^[A-Z0-9]+$ - text_min_max_length_info: 0 = sem restrição - text_project_destroy_confirmation: Você tem certeza que deseja excluir este projeto e todos os dados relacionados? - text_subprojects_destroy_warning: "Seu(s) subprojeto(s): %{value} também serão excluídos." - text_workflow_edit: Selecione um papel e um tipo de tarefa para editar o fluxo de trabalho - text_are_you_sure: Você tem certeza? - text_tip_issue_begin_day: tarefa inicia neste dia - text_tip_issue_end_day: tarefa termina neste dia - text_tip_issue_begin_end_day: tarefa inicia e termina neste dia - text_project_identifier_info: 'Letras minúsculas (a-z), números e hífens permitidos.
    Uma vez salvo, o identificador não poderá ser alterado.' - text_caracters_maximum: "máximo %{count} caracteres" - text_caracters_minimum: "deve ter ao menos %{count} caracteres." - text_length_between: "deve ter entre %{min} e %{max} caracteres." - text_tracker_no_workflow: Sem fluxo de trabalho definido para este tipo. - text_unallowed_characters: Caracteres não permitidos - text_comma_separated: Múltiplos valores são permitidos (separados por vírgula). - text_issues_ref_in_commit_messages: Referenciando tarefas nas mensagens de commit - text_issue_added: "Tarefa %{id} incluída (por %{author})." - text_issue_updated: "Tarefa %{id} alterada (por %{author})." - text_wiki_destroy_confirmation: Você tem certeza que deseja excluir este wiki e TODO o seu conteúdo? - text_issue_category_destroy_question: "Algumas tarefas (%{count}) estão atribuídas a esta categoria. O que você deseja fazer?" - text_issue_category_destroy_assignments: Remover atribuições da categoria - text_issue_category_reassign_to: Redefinir tarefas para esta categoria - text_user_mail_option: "Para projetos (não selecionados), você somente receberá notificações sobre o que você está observando ou está envolvido (ex. tarefas das quais você é o autor ou que estão atribuídas a você)" - text_no_configuration_data: "Os Papéis, tipos de tarefas, situação de tarefas e fluxos de trabalho não foram configurados ainda.\nÉ altamente recomendado carregar as configurações padrão. Você poderá modificar estas configurações assim que carregadas." - text_load_default_configuration: Carregar a configuração padrão - text_status_changed_by_changeset: "Aplicado no changeset %{value}." - text_issues_destroy_confirmation: 'Você tem certeza que deseja excluir a(s) tarefa(s) selecionada(s)?' - text_select_project_modules: 'Selecione módulos para habilitar para este projeto:' - text_default_administrator_account_changed: Conta padrão do administrador alterada - text_file_repository_writable: Repositório com permissão de escrita - text_rmagick_available: RMagick disponível (opcional) - text_destroy_time_entries_question: "%{hours} horas de trabalho foram registradas nas tarefas que você está excluindo. O que você deseja fazer?" - text_destroy_time_entries: Excluir horas de trabalho - text_assign_time_entries_to_project: Atribuir estas horas de trabalho para outro projeto - text_reassign_time_entries: 'Atribuir horas reportadas para esta tarefa:' - text_user_wrote: "%{value} escreveu:" - text_enumeration_destroy_question: "%{count} objetos estão atribuídos a este valor." - text_enumeration_category_reassign_to: 'Reatribuí-los ao valor:' - text_email_delivery_not_configured: "O envio de e-mail não está configurado, e as notificações estão inativas.\nConfigure seu servidor SMTP no arquivo config/configuration.yml e reinicie a aplicação para ativá-las." - - default_role_manager: Gerente - default_role_developer: Desenvolvedor - default_role_reporter: Informante - default_tracker_bug: Defeito - default_tracker_feature: Funcionalidade - default_tracker_support: Suporte - default_issue_status_new: Nova - default_issue_status_in_progress: Em andamento - default_issue_status_resolved: Resolvida - default_issue_status_feedback: Feedback - default_issue_status_closed: Fechada - default_issue_status_rejected: Rejeitada - default_doc_category_user: Documentação do usuário - default_doc_category_tech: Documentação técnica - default_priority_low: Baixa - default_priority_normal: Normal - default_priority_high: Alta - default_priority_urgent: Urgente - default_priority_immediate: Imediata - default_activity_design: Design - default_activity_development: Desenvolvimento - - enumeration_issue_priorities: Prioridade das tarefas - enumeration_doc_categories: Categorias de documento - enumeration_activities: Atividades (registro de horas) - notice_unable_delete_version: Não foi possível excluir a versão - label_renamed: renomeado - label_copied: copiado - setting_plain_text_mail: Usar mensagem sem formatação HTML - permission_view_files: Ver arquivos - permission_edit_issues: Editar tarefas - permission_edit_own_time_entries: Editar o próprio tempo de trabalho - permission_manage_public_queries: Gerenciar consultas publicas - permission_add_issues: Adicionar tarefas - 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 tarefas - 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 observadores - permission_view_gantt: Ver gráfico gantt - permission_move_issues: Mover tarefas - permission_manage_issue_relations: Gerenciar relacionamentos de tarefas - 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 calendário - permission_manage_members: Gerenciar membros - permission_edit_messages: Editar mensagens - permission_delete_issues: Excluir tarefas - permission_view_issue_watchers: Ver lista de observadores - permission_manage_repository: Gerenciar repositório - permission_commit_access: Acesso de commit - permission_browse_repository: Pesquisar repositório - 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 suas 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 e-mail 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 %{value}" - label_updated_time_by: "Atualizado por %{author} há %{age}" - 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: "%{count} 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 - setting_file_max_size_displayed: Tamanho máximo dos arquivos textos exibidos inline - field_identity_urler: Observador - setting_openid: Permitir Login e Registro via OpenID - field_identity_url: OpenID URL - label_login_with_open_id_option: ou use o OpenID - field_content: Conteúdo - label_descending: Descendente - label_sort: Ordenar - label_ascending: Ascendente - label_date_from_to: De %{start} até %{end} - label_greater_or_equal: ">=" - label_less_or_equal: <= - text_wiki_page_destroy_question: Esta página tem %{descendants} página(s) filha(s) e descendente(s). O que você quer fazer? - text_wiki_page_reassign_children: Reatribuir páginas filhas para esta página pai - text_wiki_page_nullify_children: Manter as páginas filhas como páginas raízes - text_wiki_page_destroy_children: Excluir páginas filhas e todas suas descendentes - setting_password_min_length: Comprimento mínimo para senhas - field_group_by: Agrupar por - mail_subject_wiki_content_updated: "A página wiki '%{id}' foi atualizada" - label_wiki_content_added: Página wiki adicionada - mail_subject_wiki_content_added: "A página wiki '%{id}' foi adicionada" - mail_body_wiki_content_added: A página wiki '%{id}' foi adicionada por %{author}. - label_wiki_content_updated: Página wiki atualizada - mail_body_wiki_content_updated: A página wiki '%{id}' foi atualizada por %{author}. - permission_add_project: Criar projeto - setting_new_project_user_role_id: Papel atribuído a um usuário não-administrador que cria um projeto - label_view_all_revisions: Ver todas as revisões - label_tag: Etiqueta - label_branch: Ramo - text_journal_changed: "%{label} alterado de %{old} para %{new}" - text_journal_set_to: "%{label} ajustado para %{value}" - text_journal_deleted: "%{label} excluído (%{old})" - label_group_plural: Grupos - label_group: Grupo - label_group_new: Novo grupo - label_time_entry_plural: Tempos gastos - text_journal_added: "%{label} %{value} adicionado" - field_active: Ativo - enumeration_system_activity: Atividade do sistema - permission_delete_issue_watchers: Excluir observadores - version_status_closed: fechado - version_status_locked: travado - version_status_open: aberto - error_can_not_reopen_issue_on_closed_version: Uma tarefa atribuída a uma versão fechada não pode ser reaberta - label_user_anonymous: Anônimo - button_move_and_follow: Mover e seguir - setting_default_projects_modules: Módulos habilitados por padrão para novos projetos - setting_gravatar_default: Imagem-padrão de Gravatar - field_sharing: Compartilhamento - label_version_sharing_hierarchy: Com a hierarquia do projeto - label_version_sharing_system: Com todos os projetos - label_version_sharing_descendants: Com sub-projetos - label_version_sharing_tree: Com a árvore do projeto - label_version_sharing_none: Sem compartilhamento - error_can_not_archive_project: Este projeto não pode ser arquivado - button_duplicate: Duplicar - button_copy_and_follow: Copiar e seguir - label_copy_source: Origem - setting_issue_done_ratio: Calcular o percentual de conclusão da tarefa - setting_issue_done_ratio_issue_status: Usar a situação da tarefa - error_issue_done_ratios_not_updated: O pecentual de conclusão das tarefas não foi atualizado. - error_workflow_copy_target: Por favor, selecione os tipos de tarefa e os papéis alvo - setting_issue_done_ratio_issue_field: Use the issue field - label_copy_same_as_target: Mesmo alvo - label_copy_target: Alvo - notice_issue_done_ratios_updated: Percentual de conslusão atualizados. - error_workflow_copy_source: Por favor, selecione um tipo de tarefa e papel de origem - label_update_issue_done_ratios: Atualizar percentual de conclusão das tarefas - setting_start_of_week: Início da semana - field_watcher: Observador - permission_view_issues: Ver tarefas - label_display_used_statuses_only: Somente exibir situações que são usadas por este tipo de tarefa - label_revision_id: Revisão %{value} - label_api_access_key: Chave de acesso a API - button_show: Exibir - label_api_access_key_created_on: Chave de acesso a API criado a %{value} atrás - label_feeds_access_key: Chave de acesso ao RSS - notice_api_access_key_reseted: Sua chave de acesso a API foi redefinida. - setting_rest_api_enabled: Habilitar REST web service - label_missing_api_access_key: Chave de acesso a API faltando - label_missing_feeds_access_key: Chave de acesso ao RSS faltando - text_line_separated: Múltiplos valores permitidos (uma linha para cada valor). - setting_mail_handler_body_delimiters: Truncar e-mails após uma destas linhas - permission_add_subprojects: Criar subprojetos - label_subproject_new: Novo subprojeto - text_own_membership_delete_confirmation: |- - Você está para excluir algumas de suas próprias permissões e pode não mais estar apto a editar este projeto após esta operação. - Você tem certeza que deseja continuar? - label_close_versions: Fechar versões concluídas - label_board_sticky: Marcado - label_board_locked: Travado - label_change_log: Registro de alterações - permission_export_wiki_pages: Exportar páginas wiki - setting_cache_formatted_text: Realizar cache de texto formatado - permission_manage_project_activities: Gerenciar atividades do projeto - error_unable_delete_issue_status: Não foi possível excluir situação da tarefa - label_profile: Perfil - permission_manage_subtasks: Gerenciar subtarefas - field_parent_issue: Tarefa pai - label_subtask_plural: Subtarefas - label_project_copy_notifications: Enviar notificações por e-mail ao copiar projeto - error_can_not_delete_custom_field: Não foi possível excluir o campo personalizado - error_unable_to_connect: Não foi possível conectar (%{value}) - error_can_not_remove_role: Este papel está em uso e não pode ser excluído. - error_can_not_delete_tracker: Este tipo de tarefa está atribuído a alguma(s) tarefa(s) e não pode ser excluído. - field_principal: Principal - label_my_page_block: Meu bloco de página - notice_failed_to_save_members: "Falha ao gravar membro(s): %{errors}." - text_zoom_out: Afastar zoom - text_zoom_in: Aproximar zoom - notice_unable_delete_time_entry: Não foi possível excluir a entrada no registro de horas trabalhadas. - label_overall_spent_time: Tempo gasto geral - field_time_entries: Registro de horas - project_module_gantt: Gantt - project_module_calendar: Calendário - button_edit_associated_wikipage: "Editar página wiki relacionada: %{page_title}" - text_are_you_sure_with_children: Excluir a tarefa e suas subtarefas? - field_text: Campo de texto - label_user_mail_option_only_owner: Somente para as coisas que eu criei - setting_default_notification_option: Opção padrão de notificação - label_user_mail_option_only_my_events: Somente para as coisas que eu esteja observando ou esteja envolvido - label_user_mail_option_only_assigned: Somente para as coisas que estejam atribuídas a mim - label_user_mail_option_none: Sem eventos - field_member_of_group: Grupo do responsável - field_assigned_to_role: Papel do responsável - notice_not_authorized_archived_project: O projeto que você está tentando acessar foi arquivado. - label_principal_search: "Pesquisar por usuários ou grupos:" - label_user_search: "Pesquisar por usuário:" - field_visible: Visível - setting_emails_header: Cabeçalho do e-mail - setting_commit_logtime_activity_id: Atividade para registrar horas - text_time_logged_by_changeset: Aplicado no changeset %{value}. - setting_commit_logtime_enabled: Habilitar registro de horas - notice_gantt_chart_truncated: O gráfico foi cortado por exceder o tamanho máximo de linhas que podem ser exibidas (%{max}) - setting_gantt_items_limit: Número máximo de itens exibidos no gráfico gatt - field_warn_on_leaving_unsaved: Alertar-me ao sair de uma página sem salvar o texto - text_warn_on_leaving_unsaved: A página atual contem texto que não foi salvo e será perdido se você sair desta página. - label_my_queries: Minhas consultas personalizadas - text_journal_changed_no_detail: "%{label} atualizado(a)" - label_news_comment_added: Notícia recebeu um comentário - button_expand_all: Expandir tudo - button_collapse_all: Recolher tudo - label_additional_workflow_transitions_for_assignee: Transições adicionais permitidas quando o usuário é o responsável pela tarefa - label_additional_workflow_transitions_for_author: Transições adicionais permitidas quando o usuário é o autor - - label_bulk_edit_selected_time_entries: Alteração em massa do registro de horas - text_time_entries_destroy_confirmation: Tem certeza que quer excluir o(s) registro(s) de horas selecionado(s)? - label_role_anonymous: Anônimo - label_role_non_member: Não Membro - label_issues_visibility_own: Tarefas criadas ou atribuídas ao usuário - field_issues_visibility: Visibilidade das tarefas - label_issues_visibility_all: Todas as tarefas - permission_set_own_issues_private: Alterar as próprias tarefas para públicas ou privadas - field_is_private: Privado - permission_set_issues_private: Alterar tarefas para públicas ou privadas - label_issues_visibility_public: Todas as tarefas não privadas - text_issues_destroy_descendants_confirmation: Isto também irá excluir %{count} subtarefa(s). - field_commit_logs_encoding: Codificação das mensagens de commit - field_scm_path_encoding: Codificação do caminho - text_scm_path_encoding_note: "Padrão: UTF-8" - field_path_to_repository: Caminho para o repositório - field_root_directory: Diretório raiz - field_cvs_module: Módulo - field_cvsroot: CVSROOT - text_mercurial_repository_note: "Repositório local (ex.: /hgrepo, c:\\hgrepo)" - text_scm_command: Comando - text_scm_command_version: Versão - label_git_report_last_commit: Relatar última alteração para arquivos e diretórios - text_scm_config: Você pode configurar seus comandos de versionamento em config/configurations.yml. Por favor reinicie a aplicação após alterá-lo. - text_scm_command_not_available: Comando de versionamento não disponível. Por favor verifique as configurações no painel de administração. - notice_issue_successful_create: Issue %{id} created. - label_between: between - setting_issue_group_assignment: Allow issue assignment to groups - label_diff: diff - text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) - - description_query_sort_criteria_direction: Sort direction - description_project_scope: Escopo da pesquisa - description_filter: Filtro - description_user_mail_notification: Configuração de notificações por e-mail - description_date_from: Digita a data inicial - description_message_content: Conteúdo da mensagem - description_available_columns: Colunas disponíveis - description_date_range_interval: Escolha um período selecionando a data de início e fim - description_issue_category_reassign: Escolha uma categoria de tarefas - description_search: Searchfield - description_notes: Notas - description_date_range_list: Escolha um período a partira da lista - description_choose_project: Projetos - description_date_to: Digite a data final - description_query_sort_criteria_attribute: Sort attribute - description_wiki_subpages_reassign: Escolha uma nova página pai - description_selected_columns: Colunas selecionadas - - label_parent_revision: Parent - label_child_revision: Child - error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. - setting_default_issue_start_date_to_creation_date: Usar data corrente como data inicial para novas tarefas - button_edit_section: Edit this section - setting_repositories_encodings: Attachments and repositories encodings - description_all_columns: All Columns - button_export: Export - label_export_options: "%{export_format} export options" - error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/89/89021103ecb16e23dc5cc611fa652e5e895e947c.svn-base --- a/.svn/pristine/89/89021103ecb16e23dc5cc611fa652e5e895e947c.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,86 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../../test_helper', __FILE__) - -module RedmineMenuTestHelper - # Assertions - def assert_number_of_items_in_menu(menu_name, count) - assert Redmine::MenuManager.items(menu_name).size >= count, "Menu has less than #{count} items" - end - - def assert_menu_contains_item_named(menu_name, item_name) - assert Redmine::MenuManager.items(menu_name).collect(&:name).include?(item_name.to_sym), "Menu did not have an item named #{item_name}" - end - - # Helpers - def get_menu_item(menu_name, item_name) - Redmine::MenuManager.items(menu_name).find {|item| item.name == item_name.to_sym} - end -end - -class RedmineTest < ActiveSupport::TestCase - include RedmineMenuTestHelper - - def test_top_menu - assert_number_of_items_in_menu :top_menu, 5 - assert_menu_contains_item_named :top_menu, :home - assert_menu_contains_item_named :top_menu, :my_page - assert_menu_contains_item_named :top_menu, :projects - assert_menu_contains_item_named :top_menu, :administration - assert_menu_contains_item_named :top_menu, :help - end - - def test_account_menu - assert_number_of_items_in_menu :account_menu, 4 - assert_menu_contains_item_named :account_menu, :login - assert_menu_contains_item_named :account_menu, :register - assert_menu_contains_item_named :account_menu, :my_account - assert_menu_contains_item_named :account_menu, :logout - end - - def test_application_menu - assert_number_of_items_in_menu :application_menu, 0 - end - - def test_admin_menu - assert_number_of_items_in_menu :admin_menu, 0 - end - - def test_project_menu - assert_number_of_items_in_menu :project_menu, 14 - assert_menu_contains_item_named :project_menu, :overview - assert_menu_contains_item_named :project_menu, :activity - assert_menu_contains_item_named :project_menu, :roadmap - assert_menu_contains_item_named :project_menu, :issues - assert_menu_contains_item_named :project_menu, :new_issue - assert_menu_contains_item_named :project_menu, :calendar - assert_menu_contains_item_named :project_menu, :gantt - assert_menu_contains_item_named :project_menu, :news - assert_menu_contains_item_named :project_menu, :documents - assert_menu_contains_item_named :project_menu, :wiki - assert_menu_contains_item_named :project_menu, :boards - assert_menu_contains_item_named :project_menu, :files - assert_menu_contains_item_named :project_menu, :repository - assert_menu_contains_item_named :project_menu, :settings - end - - def test_new_issue_should_have_root_as_a_parent - new_issue = get_menu_item(:project_menu, :new_issue) - assert_equal :root, new_issue.parent.name - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/89/8937afa1653ec0053686c11b0489477d9550a47d.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/89/8937afa1653ec0053686c11b0489477d9550a47d.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,39 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 WorkflowTransition < WorkflowRule + validates_presence_of :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 #{table_name} WHERE type = 'WorkflowTransition' GROUP BY role_id, tracker_id") + roles = Role.sorted.all + trackers = Tracker.sorted.all + + result = [] + trackers.each do |tracker| + t = [] + roles.each do |role| + row = counts.detect {|c| c['role_id'].to_s == role.id.to_s && c['tracker_id'].to_s == tracker.id.to_s} + t << [role, (row.nil? ? 0 : row['c'].to_i)] + end + result << [tracker, t] + end + + result + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/89/894f61e1593e5111f70c0b60e768f70d33aa6d50.svn-base --- a/.svn/pristine/89/894f61e1593e5111f70c0b60e768f70d33aa6d50.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,221 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) - -class RepositorySubversionTest < ActiveSupport::TestCase - fixtures :projects, :repositories, :enabled_modules, :users, :roles - - NUM_REV = 11 - - def setup - @project = Project.find(3) - @repository = Repository::Subversion.create(:project => @project, - :url => self.class.subversion_repository_url) - assert @repository - end - - if repository_configured?('subversion') - def test_fetch_changesets_from_scratch - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - - assert_equal NUM_REV, @repository.changesets.count - assert_equal 20, @repository.changes.count - assert_equal 'Initial import.', @repository.changesets.find_by_revision('1').comments - end - - def test_fetch_changesets_incremental - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - - # Remove changesets with revision > 5 - @repository.changesets.find(:all).each {|c| c.destroy if c.revision.to_i > 5} - @project.reload - assert_equal 5, @repository.changesets.count - - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - end - - def test_latest_changesets - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - - # with limit - changesets = @repository.latest_changesets('', nil, 2) - assert_equal 2, changesets.size - assert_equal @repository.latest_changesets('', nil).slice(0,2), changesets - - # with path - changesets = @repository.latest_changesets('subversion_test/folder', nil) - assert_equal ["10", "9", "7", "6", "5", "2"], changesets.collect(&:revision) - - # with path and revision - changesets = @repository.latest_changesets('subversion_test/folder', 8) - assert_equal ["7", "6", "5", "2"], changesets.collect(&:revision) - end - - def test_directory_listing_with_square_brackets_in_path - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - - entries = @repository.entries('subversion_test/[folder_with_brackets]') - assert_not_nil entries, 'Expect to find entries in folder_with_brackets' - assert_equal 1, entries.size, 'Expect one entry in folder_with_brackets' - assert_equal 'README.txt', entries.first.name - end - - def test_directory_listing_with_square_brackets_in_base - @project = Project.find(3) - @repository = Repository::Subversion.create( - :project => @project, - :url => "file:///#{self.class.repository_path('subversion')}/subversion_test/[folder_with_brackets]") - - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - - assert_equal 1, @repository.changesets.count, 'Expected to see 1 revision' - assert_equal 2, @repository.changes.count, 'Expected to see 2 changes, dir add and file add' - - entries = @repository.entries('') - assert_not_nil entries, 'Expect to find entries' - assert_equal 1, entries.size, 'Expect a single entry' - assert_equal 'README.txt', entries.first.name - end - - def test_identifier - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - c = @repository.changesets.find_by_revision('1') - assert_equal c.revision, c.identifier - end - - def test_find_changeset_by_empty_name - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - ['', ' ', nil].each do |r| - assert_nil @repository.find_changeset_by_name(r) - end - end - - def test_identifier_nine_digit - c = Changeset.new(:repository => @repository, :committed_on => Time.now, - :revision => '123456789', :comments => 'test') - assert_equal c.identifier, c.revision - end - - def test_format_identifier - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - c = @repository.changesets.find_by_revision('1') - assert_equal c.format_identifier, c.revision - end - - def test_format_identifier_nine_digit - c = Changeset.new(:repository => @repository, :committed_on => Time.now, - :revision => '123456789', :comments => 'test') - assert_equal c.format_identifier, c.revision - end - - def test_activities - c = Changeset.new(:repository => @repository, :committed_on => Time.now, - :revision => '1', :comments => 'test') - assert c.event_title.include?('1:') - assert_equal '1', c.event_url[:rev] - end - - def test_activities_nine_digit - c = Changeset.new(:repository => @repository, :committed_on => Time.now, - :revision => '123456789', :comments => 'test') - assert c.event_title.include?('123456789:') - assert_equal '123456789', c.event_url[:rev] - end - - def test_log_encoding_ignore_setting - with_settings :commit_logs_encoding => 'windows-1252' do - s1 = "\xC2\x80" - s2 = "\xc3\x82\xc2\x80" - if s1.respond_to?(:force_encoding) - s1.force_encoding('ISO-8859-1') - s2.force_encoding('UTF-8') - assert_equal s1.encode('UTF-8'), s2 - end - c = Changeset.new(:repository => @repository, - :comments => s2, - :revision => '123', - :committed_on => Time.now) - assert c.save - assert_equal s2, c.comments - end - end - - def test_previous - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - changeset = @repository.find_changeset_by_name('3') - assert_equal @repository.find_changeset_by_name('2'), changeset.previous - end - - def test_previous_nil - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - changeset = @repository.find_changeset_by_name('1') - assert_nil changeset.previous - end - - def test_next - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - changeset = @repository.find_changeset_by_name('2') - assert_equal @repository.find_changeset_by_name('3'), changeset.next - end - - def test_next_nil - assert_equal 0, @repository.changesets.count - @repository.fetch_changesets - @project.reload - assert_equal NUM_REV, @repository.changesets.count - changeset = @repository.find_changeset_by_name('11') - assert_nil changeset.next - end - else - puts "Subversion test repository NOT FOUND. Skipping unit tests !!!" - def test_fake; assert true end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/89/896d2497486a7dfbefa5146aaa83a0fc9dd4aa0c.svn-base --- a/.svn/pristine/89/896d2497486a7dfbefa5146aaa83a0fc9dd4aa0c.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,179 +0,0 @@ -module CodeRay - module Encoders - - # Outputs code highlighted for a color terminal. - # - # Note: This encoder is in beta. It currently doesn't use the Styles. - # - # Alias: +term+ - # - # == Authors & License - # - # By Rob Aldred (http://robaldred.co.uk) - # - # Based on idea by Nathan Weizenbaum (http://nex-3.com) - # - # MIT License (http://www.opensource.org/licenses/mit-license.php) - class Terminal < Encoder - - register_for :terminal - - TOKEN_COLORS = { - :annotation => '35', - :attribute_name => '33', - :attribute_value => '31', - :binary => '1;35', - :char => { - :self => '36', :delimiter => '34' - }, - :class => '1;35', - :class_variable => '36', - :color => '32', - :comment => '37', - :complex => '34', - :constant => ['34', '4'], - :decoration => '35', - :definition => '1;32', - :directive => ['32', '4'], - :doc => '46', - :doctype => '1;30', - :doc_string => ['31', '4'], - :entity => '33', - :error => ['1;33', '41'], - :exception => '1;31', - :float => '1;35', - :function => '1;34', - :global_variable => '42', - :hex => '1;36', - :include => '33', - :integer => '1;34', - :key => '35', - :label => '1;15', - :local_variable => '33', - :octal => '1;35', - :operator_name => '1;29', - :predefined_constant => '1;36', - :predefined_type => '1;30', - :predefined => ['4', '1;34'], - :preprocessor => '36', - :pseudo_class => '34', - :regexp => { - :self => '31', - :content => '31', - :delimiter => '1;29', - :modifier => '35', - :function => '1;29' - }, - :reserved => '1;31', - :shell => { - :self => '42', - :content => '1;29', - :delimiter => '37', - }, - :string => { - :self => '32', - :modifier => '1;32', - :escape => '1;36', - :delimiter => '1;32', - }, - :symbol => '1;32', - :tag => '34', - :type => '1;34', - :value => '36', - :variable => '34', - - :insert => '42', - :delete => '41', - :change => '44', - :head => '45' - } - TOKEN_COLORS[:keyword] = TOKEN_COLORS[:reserved] - TOKEN_COLORS[:method] = TOKEN_COLORS[:function] - TOKEN_COLORS[:imaginary] = TOKEN_COLORS[:complex] - TOKEN_COLORS[:begin_group] = TOKEN_COLORS[:end_group] = - TOKEN_COLORS[:escape] = TOKEN_COLORS[:delimiter] - - protected - - def setup(options) - super - @opened = [] - @subcolors = nil - end - - public - - def text_token text, kind - if color = (@subcolors || TOKEN_COLORS)[kind] - if Hash === color - if color[:self] - color = color[:self] - else - @out << text - return - end - end - - @out << ansi_colorize(color) - @out << text.gsub("\n", ansi_clear + "\n" + ansi_colorize(color)) - @out << ansi_clear - @out << ansi_colorize(@subcolors[:self]) if @subcolors && @subcolors[:self] - else - @out << text - end - end - - def begin_group kind - @opened << kind - @out << open_token(kind) - end - alias begin_line begin_group - - def end_group kind - if @opened.empty? - # nothing to close - else - @opened.pop - @out << ansi_clear - @out << open_token(@opened.last) - end - end - - def end_line kind - if @opened.empty? - # nothing to close - else - @opened.pop - # whole lines to be highlighted, - # eg. added/modified/deleted lines in a diff - @out << "\t" * 100 + ansi_clear - @out << open_token(@opened.last) - end - end - - private - - def open_token kind - if color = TOKEN_COLORS[kind] - if Hash === color - @subcolors = color - ansi_colorize(color[:self]) if color[:self] - else - @subcolors = {} - ansi_colorize(color) - end - else - @subcolors = nil - '' - end - end - - def ansi_colorize(color) - Array(color).map { |c| "\e[#{c}m" }.join - end - def ansi_clear - ansi_colorize(0) - end - end - end -end \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/89/899fc5d76ba5b79062018e92150433654216dd83.svn-base --- a/.svn/pristine/89/899fc5d76ba5b79062018e92150433654216dd83.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +0,0 @@ -module CodeRay - VERSION = '1.0.0' -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/89/89a6f5df70247b9cdb732addfa4529a1a5a8007f.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/89/89a6f5df70247b9cdb732addfa4529a1a5a8007f.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,43 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +module ReportsHelper + + def aggregate(data, criteria) + a = 0 + data.each { |row| + match = 1 + criteria.each { |k, v| + match = 0 unless (row[k].to_s == v.to_s) || (k == 'closed' && row[k] == (v == 0 ? "f" : "t")) + } unless criteria.nil? + a = a + row["total"].to_i if match == 1 + } unless data.nil? + a + end + + def aggregate_link(data, criteria, *args) + a = aggregate data, criteria + a > 0 ? link_to(h(a), *args) : '-' + end + + def aggregate_path(project, field, row, options={}) + parameters = {:set_filter => 1, :subproject_id => '!*', field => row.id}.merge(options) + project_issues_path(row.is_a?(Project) ? row : project, parameters) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/89/89cb2d66d28a3e37299698980c2cd1f25e832b60.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/89/89cb2d66d28a3e37299698980c2cd1f25e832b60.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,108 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class Message < ActiveRecord::Base + include Redmine::SafeAttributes + 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" + acts_as_attachable + belongs_to :last_reply, :class_name => 'Message', :foreign_key => 'last_reply_id' + + acts_as_searchable :columns => ['subject', 'content'], + :include => {:board => :project}, + :project_key => "#{Board.table_name}.project_id", + :date_column => "#{table_name}.created_on" + acts_as_event :title => Proc.new {|o| "#{o.board.name}: #{o.subject}"}, + :description => :content, + :group => :parent, + :type => Proc.new {|o| o.parent_id.nil? ? 'message' : 'reply'}, + :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, :r => o.id, :anchor => "message-#{o.id}"})} + + acts_as_activity_provider :find_options => {:include => [{:board => :project}, :author]}, + :author_key => :author_id + acts_as_watchable + + validates_presence_of :board, :subject, :content + validates_length_of :subject, :maximum => 255 + validate :cannot_reply_to_locked_topic, :on => :create + + after_create :add_author_as_watcher, :reset_counters! + after_update :update_messages_board + after_destroy :reset_counters! + + scope :visible, lambda {|*args| + includes(:board => :project).where(Project.allowed_to_condition(args.shift || User.current, :view_messages, *args)) + } + + safe_attributes 'subject', 'content' + safe_attributes 'locked', 'sticky', 'board_id', + :if => lambda {|message, user| + user.allowed_to?(:edit_messages, message.project) + } + + def visible?(user=User.current) + !user.nil? && user.allowed_to?(:view_messages, project) + end + + def cannot_reply_to_locked_topic + # Can not reply to a locked topic + errors.add :base, 'Topic is locked' if root.locked? && self != root + end + + def update_messages_board + if board_id_changed? + Message.update_all({:board_id => board_id}, ["id = ? OR parent_id = ?", root.id, root.id]) + Board.reset_counters!(board_id_was) + Board.reset_counters!(board_id) + end + end + + def reset_counters! + if parent && parent.id + Message.update_all({:last_reply_id => parent.children.maximum(:id)}, {:id => parent.id}) + end + board.reset_counters! + end + + def sticky=(arg) + write_attribute :sticky, (arg == true || arg.to_s == '1' ? 1 : 0) + end + + def sticky? + sticky == 1 + end + + 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 + + def add_author_as_watcher + Watcher.create(:watchable => self.root, :user => author) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/89/89d051df634d7daf82a0124fc1261d1139a675b3.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/89/89d051df634d7daf82a0124fc1261d1139a675b3.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,26 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class Comment < ActiveRecord::Base + include Redmine::SafeAttributes + belongs_to :commented, :polymorphic => true, :counter_cache => true + belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' + + validates_presence_of :commented, :author, :comments + + safe_attributes 'comments' +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/89/89f2f1de1b0c1a744af1b4d7a2c5c156ca193d25.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/89/89f2f1de1b0c1a744af1b4d7a2c5c156ca193d25.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,90 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class Board < ActiveRecord::Base + include Redmine::SafeAttributes + belongs_to :project + has_many :topics, :class_name => 'Message', :conditions => "#{Message.table_name}.parent_id IS NULL", :order => "#{Message.table_name}.created_on DESC" + has_many :messages, :dependent => :destroy, :order => "#{Message.table_name}.created_on DESC" + belongs_to :last_message, :class_name => 'Message', :foreign_key => :last_message_id + acts_as_tree :dependent => :nullify + acts_as_list :scope => '(project_id = #{project_id} AND parent_id #{parent_id ? "= #{parent_id}" : "IS NULL"})' + acts_as_watchable + + validates_presence_of :name, :description + validates_length_of :name, :maximum => 30 + validates_length_of :description, :maximum => 255 + validate :validate_board + + scope :visible, lambda {|*args| + includes(:project).where(Project.allowed_to_condition(args.shift || User.current, :view_messages, *args)) + } + + safe_attributes 'name', 'description', 'parent_id', 'move_to' + + def visible?(user=User.current) + !user.nil? && user.allowed_to?(:view_messages, project) + end + + def reload(*args) + @valid_parents = nil + super + end + + def to_s + name + end + + def valid_parents + @valid_parents ||= project.boards - self_and_descendants + end + + def reset_counters! + self.class.reset_counters!(id) + end + + # Updates topics_count, messages_count and last_message_id attributes for +board_id+ + def self.reset_counters!(board_id) + board_id = board_id.to_i + update_all("topics_count = (SELECT COUNT(*) FROM #{Message.table_name} WHERE board_id=#{board_id} AND parent_id IS NULL)," + + " messages_count = (SELECT COUNT(*) FROM #{Message.table_name} WHERE board_id=#{board_id})," + + " last_message_id = (SELECT MAX(id) FROM #{Message.table_name} WHERE board_id=#{board_id})", + ["id = ?", board_id]) + end + + def self.board_tree(boards, parent_id=nil, level=0) + tree = [] + boards.select {|board| board.parent_id == parent_id}.sort_by(&:position).each do |board| + tree << [board, level] + tree += board_tree(boards, board.id, level+1) + end + if block_given? + tree.each do |board, level| + yield board, level + end + end + tree + end + + protected + + def validate_board + if parent_id && parent_id_changed? + errors.add(:parent_id, :invalid) unless valid_parents.include?(parent) + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/89/89f5504f7426bd8b866f9fb941e83b8b3b055e7a.svn-base --- a/.svn/pristine/89/89f5504f7426bd8b866f9fb941e83b8b3b055e7a.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,104 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -module Redmine - class CustomFieldFormat - include Redmine::I18n - - cattr_accessor :available - @@available = {} - - attr_accessor :name, :order, :label, :edit_as, :class_names - - def initialize(name, options={}) - self.name = name - self.label = options[:label] - self.order = options[:order] - self.edit_as = options[:edit_as] || name - self.class_names = options[:only] - end - - def format(value) - send "format_as_#{name}", value - end - - def format_as_date(value) - begin; format_date(value.to_date); rescue; value end - end - - def format_as_bool(value) - l(value == "1" ? :general_text_Yes : :general_text_No) - end - - ['string','text','int','float','list'].each do |name| - define_method("format_as_#{name}") {|value| - return value - } - end - - ['user', 'version'].each do |name| - define_method("format_as_#{name}") {|value| - return value.blank? ? "" : name.classify.constantize.find_by_id(value.to_i).to_s - } - end - - class << self - def map(&block) - yield self - end - - # Registers a custom field format - def register(custom_field_format, options={}) - @@available[custom_field_format.name] = custom_field_format unless @@available.keys.include?(custom_field_format.name) - end - - def available_formats - @@available.keys - end - - def find_by_name(name) - @@available[name.to_s] - end - - def label_for(name) - format = @@available[name.to_s] - format.label if format - end - - # Return an array of custom field formats which can be used in select_tag - def as_select(class_name=nil) - fields = @@available.values - fields = fields.select {|field| field.class_names.nil? || field.class_names.include?(class_name)} - fields.sort {|a,b| - a.order <=> b.order - }.collect {|custom_field_format| - [ l(custom_field_format.label), custom_field_format.name ] - } - end - - def format_value(value, field_format) - return "" unless value && !value.empty? - - if format_type = find_by_name(field_format) - format_type.format(value) - else - value - end - end - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/8a/8a22be8cc9fbaa89ce238ad133a0232eb8b7d395.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/8a/8a22be8cc9fbaa89ce238ad133a0232eb8b7d395.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,84 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class CalendarsControllerTest < ActionController::TestCase + fixtures :projects, + :trackers, + :projects_trackers, + :roles, + :member_roles, + :members, + :enabled_modules + + def test_show + get :show, :project_id => 1 + assert_response :success + assert_template 'calendar' + assert_not_nil assigns(:calendar) + end + + def test_show_should_run_custom_queries + @query = IssueQuery.create!(:name => 'Calendar', :is_public => true) + + get :show, :query_id => @query.id + assert_response :success + end + + def test_cross_project_calendar + get :show + assert_response :success + assert_template 'calendar' + assert_not_nil assigns(:calendar) + end + + def test_week_number_calculation + Setting.start_of_week = 7 + + get :show, :month => '1', :year => '2010' + assert_response :success + + assert_select 'tr' do + assert_select 'td.week-number', :text => '53' + assert_select 'td.odd', :text => '27' + assert_select 'td.even', :text => '2' + end + + assert_select 'tr' do + assert_select 'td.week-number', :text => '1' + assert_select 'td.odd', :text => '3' + assert_select 'td.even', :text => '9' + end + + Setting.start_of_week = 1 + get :show, :month => '1', :year => '2010' + assert_response :success + + assert_select 'tr' do + assert_select 'td.week-number', :text => '53' + assert_select 'td.even', :text => '28' + assert_select 'td.even', :text => '3' + end + + assert_select 'tr' do + assert_select 'td.week-number', :text => '1' + assert_select 'td.even', :text => '4' + assert_select 'td.even', :text => '10' + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/8a/8a339bcd7d20e6ca60eea513a81446d12d132459.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/8a/8a339bcd7d20e6ca60eea513a81446d12d132459.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,25 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +module IssueRelationsHelper + def collection_for_relation_type_select + values = IssueRelation::TYPES + values.keys.sort{|x,y| values[x][:order] <=> values[y][:order]}.collect{|k| [l(values[k][:name]), k]} + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/8a/8a4b8b250e38c9c5c65661973d49cb661d84e614.svn-base --- a/.svn/pristine/8a/8a4b8b250e38c9c5c65661973d49cb661d84e614.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,158 +0,0 @@ -module CodeRay -module Encoders - - class HTML - - # This module is included in the output String of the HTML Encoder. - # - # It provides methods like wrap, div, page etc. - # - # Remember to use #clone instead of #dup to keep the modules the object was - # extended with. - # - # TODO: Rewrite this without monkey patching. - module Output - - attr_accessor :css - - class << self - - # Raises an exception if an object that doesn't respond to to_str is extended by Output, - # to prevent users from misuse. Use Module#remove_method to disable. - def extended o # :nodoc: - warn "The Output module is intended to extend instances of String, not #{o.class}." unless o.respond_to? :to_str - end - - def make_stylesheet css, in_tag = false # :nodoc: - sheet = css.stylesheet - sheet = <<-'CSS' if in_tag - - CSS - sheet - end - - def page_template_for_css css # :nodoc: - sheet = make_stylesheet css - PAGE.apply 'CSS', sheet - end - - end - - def wrapped_in? element - wrapped_in == element - end - - def wrapped_in - @wrapped_in ||= nil - end - attr_writer :wrapped_in - - def wrap_in! template - Template.wrap! self, template, 'CONTENT' - self - end - - def apply_title! title - self.sub!(/()(<\/title>)/) { $1 + title + $2 } - self - end - - def wrap! element, *args - return self if not element or element == wrapped_in - case element - when :div - raise "Can't wrap %p in %p" % [wrapped_in, element] unless wrapped_in? nil - wrap_in! DIV - when :span - raise "Can't wrap %p in %p" % [wrapped_in, element] unless wrapped_in? nil - wrap_in! SPAN - when :page - wrap! :div if wrapped_in? nil - raise "Can't wrap %p in %p" % [wrapped_in, element] unless wrapped_in? :div - wrap_in! Output.page_template_for_css(@css) - if args.first.is_a?(Hash) && title = args.first[:title] - apply_title! title - end - self - when nil - return self - else - raise "Unknown value %p for :wrap" % element - end - @wrapped_in = element - self - end - - def stylesheet in_tag = false - Output.make_stylesheet @css, in_tag - end - -#-- don't include the templates in docu - - class Template < String # :nodoc: - - def self.wrap! str, template, target - target = Regexp.new(Regexp.escape("<%#{target}%>")) - if template =~ target - str[0,0] = $` - str << $' - else - raise "Template target <%%%p%%> not found" % target - end - end - - def apply target, replacement - target = Regexp.new(Regexp.escape("<%#{target}%>")) - if self =~ target - Template.new($` + replacement + $') - else - raise "Template target <%%%p%%> not found" % target - end - end - - end - - SPAN = Template.new '<span class="CodeRay"><%CONTENT%></span>' - - DIV = Template.new <<-DIV -<div class="CodeRay"> - <div class="code"><pre><%CONTENT%></pre></div> -</div> - DIV - - TABLE = Template.new <<-TABLE -<table class="CodeRay"><tr> - <td class="line-numbers" title="double click to toggle" ondblclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre><%LINE_NUMBERS%></pre></td> - <td class="code"><pre><%CONTENT%></pre></td> -</tr></table> - TABLE - - PAGE = Template.new <<-PAGE -<!DOCTYPE html> -<html> -<head> - <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> - <title> - - - - -<%CONTENT%> - - - PAGE - - end - - end - -end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/8a/8a5fb0079b7bc3191dfd37b1677f3f459fe6d66e.svn-base --- a/.svn/pristine/8a/8a5fb0079b7bc3191dfd37b1677f3f459fe6d66e.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,241 +0,0 @@ -require 'SVG/Graph/Plot' -require 'parsedate' - -module SVG - module Graph - # === For creating SVG plots of scalar temporal data - # - # = Synopsis - # - # require 'SVG/Graph/TimeSeriess' - # - # # Data sets are x,y pairs - # data1 = ["6/17/72", 11, "1/11/72", 7, "4/13/04 17:31", 11, - # "9/11/01", 9, "9/1/85", 2, "9/1/88", 1, "1/15/95", 13] - # data2 = ["8/1/73", 18, "3/1/77", 15, "10/1/98", 4, - # "5/1/02", 14, "3/1/95", 6, "8/1/91", 12, "12/1/87", 6, - # "5/1/84", 17, "10/1/80", 12] - # - # graph = SVG::Graph::TimeSeries.new( { - # :width => 640, - # :height => 480, - # :graph_title => title, - # :show_graph_title => true, - # :no_css => true, - # :key => true, - # :scale_x_integers => true, - # :scale_y_integers => true, - # :min_x_value => 0, - # :min_y_value => 0, - # :show_data_labels => true, - # :show_x_guidelines => true, - # :show_x_title => true, - # :x_title => "Time", - # :show_y_title => true, - # :y_title => "Ice Cream Cones", - # :y_title_text_direction => :bt, - # :stagger_x_labels => true, - # :x_label_format => "%m/%d/%y", - # }) - # - # graph.add_data({ - # :data => projection - # :title => 'Projected', - # }) - # - # graph.add_data({ - # :data => actual, - # :title => 'Actual', - # }) - # - # print graph.burn() - # - # = Description - # - # Produces a graph of temporal scalar data. - # - # = Examples - # - # http://www.germane-software/repositories/public/SVG/test/timeseries.rb - # - # = Notes - # - # The default stylesheet handles upto 10 data sets, if you - # use more you must create your own stylesheet and add the - # additional settings for the extra data sets. You will know - # if you go over 10 data sets as they will have no style and - # be in black. - # - # Unlike the other types of charts, data sets must contain x,y pairs: - # - # [ "12:30", 2 ] # A data set with 1 point: ("12:30",2) - # [ "01:00",2, "14:20",6] # A data set with 2 points: ("01:00",2) and - # # ("14:20",6) - # - # Note that multiple data sets within the same chart can differ in length, - # and that the data in the datasets needn't be in order; they will be ordered - # by the plot along the X-axis. - # - # The dates must be parseable by ParseDate, but otherwise can be - # any order of magnitude (seconds within the hour, or years) - # - # = See also - # - # * SVG::Graph::Graph - # * SVG::Graph::BarHorizontal - # * SVG::Graph::Bar - # * SVG::Graph::Line - # * SVG::Graph::Pie - # * SVG::Graph::Plot - # - # == Author - # - # Sean E. Russell - # - # Copyright 2004 Sean E. Russell - # This software is available under the Ruby license[LICENSE.txt] - # - class TimeSeries < Plot - # In addition to the defaults set by Graph::initialize and - # Plot::set_defaults, sets: - # [x_label_format] '%Y-%m-%d %H:%M:%S' - # [popup_format] '%Y-%m-%d %H:%M:%S' - def set_defaults - super - init_with( - #:max_time_span => '', - :x_label_format => '%Y-%m-%d %H:%M:%S', - :popup_format => '%Y-%m-%d %H:%M:%S' - ) - end - - # The format string use do format the X axis labels. - # See Time::strformat - attr_accessor :x_label_format - # Use this to set the spacing between dates on the axis. The value - # must be of the form - # "\d+ ?(days|weeks|months|years|hours|minutes|seconds)?" - # - # EG: - # - # graph.timescale_divisions = "2 weeks" - # - # will cause the chart to try to divide the X axis up into segments of - # two week periods. - attr_accessor :timescale_divisions - # The formatting used for the popups. See x_label_format - attr_accessor :popup_format - - # Add data to the plot. - # - # d1 = [ "12:30", 2 ] # A data set with 1 point: ("12:30",2) - # d2 = [ "01:00",2, "14:20",6] # A data set with 2 points: ("01:00",2) and - # # ("14:20",6) - # graph.add_data( - # :data => d1, - # :title => 'One' - # ) - # graph.add_data( - # :data => d2, - # :title => 'Two' - # ) - # - # Note that the data must be in time,value pairs, and that the date format - # may be any date that is parseable by ParseDate. - def add_data data - @data = [] unless @data - - raise "No data provided by #{@data.inspect}" unless data[:data] and - data[:data].kind_of? Array - raise "Data supplied must be x,y pairs! "+ - "The data provided contained an odd set of "+ - "data points" unless data[:data].length % 2 == 0 - return if data[:data].length == 0 - - - x = [] - y = [] - data[:data].each_index {|i| - if i%2 == 0 - arr = ParseDate.parsedate( data[:data][i] ) - t = Time.local( *arr[0,6].compact ) - x << t.to_i - else - y << data[:data][i] - end - } - sort( x, y ) - data[:data] = [x,y] - @data << data - end - - - protected - - def min_x_value=(value) - arr = ParseDate.parsedate( value ) - @min_x_value = Time.local( *arr[0,6].compact ).to_i - end - - - def format x, y - Time.at( x ).strftime( popup_format ) - end - - def get_x_labels - get_x_values.collect { |v| Time.at(v).strftime( x_label_format ) } - end - - private - def get_x_values - rv = [] - min, max, scale_division = x_range - if timescale_divisions - timescale_divisions =~ /(\d+) ?(day|week|month|year|hour|minute|second)?/ - division_units = $2 ? $2 : "day" - amount = $1.to_i - if amount - step = nil - case division_units - when "month" - cur = min - while cur < max - rv << cur - arr = Time.at( cur ).to_a - arr[4] += amount - if arr[4] > 12 - arr[5] += (arr[4] / 12).to_i - arr[4] = (arr[4] % 12) - end - cur = Time.local(*arr).to_i - end - when "year" - cur = min - while cur < max - rv << cur - arr = Time.at( cur ).to_a - arr[5] += amount - cur = Time.local(*arr).to_i - end - when "week" - step = 7 * 24 * 60 * 60 * amount - when "day" - step = 24 * 60 * 60 * amount - when "hour" - step = 60 * 60 * amount - when "minute" - step = 60 * amount - when "second" - step = amount - end - min.step( max, step ) {|v| rv << v} if step - - return rv - end - end - min.step( max, scale_division ) {|v| rv << v} - return rv - end - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/8a/8a773d20103a10a7c96107249009800080c2940c.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/8a/8a773d20103a10a7c96107249009800080c2940c.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,4349 @@ +#============================================================+ +# File name : tcpdf.rb +# Begin : 2002-08-03 +# Last Update : 2007-03-20 +# Author : Nicola Asuni +# Version : 1.53.0.TC031 +# License : GNU LGPL (http://www.gnu.org/copyleft/lesser.html) +# +# Description : This is a Ruby class for generating PDF files +# on-the-fly without requiring external +# extensions. +# +# IMPORTANT: +# This class is an extension and improvement of the Public Domain +# FPDF class by Olivier Plathey (http://www.fpdf.org). +# +# Main changes by Nicola Asuni: +# Ruby porting; +# UTF-8 Unicode support; +# code refactoring; +# source code clean up; +# code style and formatting; +# source code documentation using phpDocumentor (www.phpdoc.org); +# All ISO page formats were included; +# image scale factor; +# includes methods to parse and printsome XHTML code, supporting the following elements: h1, h2, h3, h4, h5, h6, b, u, i, a, img, p, br, strong, em, font, blockquote, li, ul, ol, hr, td, th, tr, table, sup, sub, small; +# includes a method to print various barcode formats using an improved version of "Generic Barcode Render Class" by Karim Mribti (http://www.mribti.com/barcode/) (require GD library: http://www.boutell.com/gd/); +# defines standard Header() and Footer() methods. +# +# Ported to Ruby by Ed Moss 2007-08-06 +# +#============================================================+ + +require 'tempfile' +require 'core/rmagick' + +# +# TCPDF Class. +# @package com.tecnick.tcpdf +# + +PDF_PRODUCER = 'TCPDF via RFPDF 1.53.0.TC031 (http://tcpdf.sourceforge.net)' + +module TCPDFFontDescriptor + @@descriptors = { 'freesans' => {} } + @@font_name = 'freesans' + + def self.font(font_name) + @@descriptors[font_name.gsub(".rb", "")] + end + + def self.define(font_name = 'freesans') + @@descriptors[font_name] ||= {} + yield @@descriptors[font_name] + end +end + +# This is a Ruby class for generating PDF files on-the-fly without requiring external extensions.
    +# This class is an extension and improvement of the FPDF class by Olivier Plathey (http://www.fpdf.org).
    +# This version contains some changes: [porting to Ruby, support for UTF-8 Unicode, code style and formatting, php documentation (www.phpdoc.org), ISO page formats, minor improvements, image scale factor]
    +# TCPDF project (http://tcpdf.sourceforge.net) is based on the Public Domain FPDF class by Olivier Plathey (http://www.fpdf.org).
    +# To add your own TTF fonts please read /fonts/README.TXT +# @name TCPDF +# @package com.tecnick.tcpdf +# @@version 1.53.0.TC031 +# @author Nicola Asuni +# @link http://tcpdf.sourceforge.net +# @license http://www.gnu.org/copyleft/lesser.html LGPL +# +class TCPDF + include RFPDF + include Core::RFPDF + include RFPDF::Math + + def logger + Rails.logger + end + + @@version = "1.53.0.TC031" + @@fpdf_charwidths = {} + + cattr_accessor :k_cell_height_ratio + @@k_cell_height_ratio = 1.25 + + cattr_accessor :k_blank_image + @@k_blank_image = "" + + cattr_accessor :k_small_ratio + @@k_small_ratio = 2/3.0 + + cattr_accessor :k_path_cache + @@k_path_cache = Rails.root.join('tmp') + + cattr_accessor :k_path_url_cache + @@k_path_url_cache = Rails.root.join('tmp') + + attr_accessor :barcode + + attr_accessor :buffer + + attr_accessor :diffs + + attr_accessor :color_flag + + attr_accessor :default_table_columns + + attr_accessor :max_table_columns + + attr_accessor :default_font + + attr_accessor :draw_color + + attr_accessor :encoding + + attr_accessor :fill_color + + attr_accessor :fonts + + attr_accessor :font_family + + attr_accessor :font_files + + cattr_accessor :font_path + + attr_accessor :font_style + + attr_accessor :font_size_pt + + attr_accessor :header_width + + attr_accessor :header_logo + + attr_accessor :header_logo_width + + attr_accessor :header_title + + attr_accessor :header_string + + attr_accessor :images + + attr_accessor :img_scale + + attr_accessor :in_footer + + attr_accessor :is_unicode + + attr_accessor :lasth + + attr_accessor :links + + attr_accessor :list_ordered + + attr_accessor :list_count + + attr_accessor :li_spacer + + attr_accessor :n + + attr_accessor :offsets + + attr_accessor :orientation_changes + + attr_accessor :page + + attr_accessor :page_links + + attr_accessor :pages + + attr_accessor :pdf_version + + attr_accessor :prevfill_color + + attr_accessor :prevtext_color + + attr_accessor :print_header + + attr_accessor :print_footer + + attr_accessor :state + + attr_accessor :tableborder + + attr_accessor :tdbegin + + attr_accessor :tdwidth + + attr_accessor :tdheight + + attr_accessor :tdalign + + attr_accessor :tdfill + + attr_accessor :tempfontsize + + attr_accessor :text_color + + attr_accessor :underline + + attr_accessor :ws + + # + # This is the class constructor. + # It allows to set up the page format, the orientation and + # the measure unit used in all the methods (except for the font sizes). + # @since 1.0 + # @param string :orientation page orientation. Possible values are (case insensitive):
    • P or Portrait (default)
    • L or Landscape
    + # @param string :unit User measure unit. Possible values are:
    • pt: point
    • mm: millimeter (default)
    • cm: centimeter
    • in: inch

    A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit. + # @param mixed :format The format used for pages. It can be either one of the following values (case insensitive) or a custom format in the form of a two-element array containing the width and the height (expressed in the unit given by unit).
    • 4A0
    • 2A0
    • A0
    • A1
    • A2
    • A3
    • A4 (default)
    • A5
    • A6
    • A7
    • A8
    • A9
    • A10
    • B0
    • B1
    • B2
    • B3
    • B4
    • B5
    • B6
    • B7
    • B8
    • B9
    • B10
    • C0
    • C1
    • C2
    • C3
    • C4
    • C5
    • C6
    • C7
    • C8
    • C9
    • C10
    • RA0
    • RA1
    • RA2
    • RA3
    • RA4
    • SRA0
    • SRA1
    • SRA2
    • SRA3
    • SRA4
    • LETTER
    • LEGAL
    • EXECUTIVE
    • FOLIO
    + # @param boolean :unicode TRUE means that the input text is unicode (default = true) + # @param String :encoding charset encoding; default is UTF-8 + # + def initialize(orientation = 'P', unit = 'mm', format = 'A4', unicode = true, encoding = "UTF-8") + + # Set internal character encoding to ASCII# + #FIXME 2007-05-25 (EJM) Level=0 - + # if (respond_to?("mb_internal_encoding") and mb_internal_encoding()) + # @internal_encoding = mb_internal_encoding(); + # mb_internal_encoding("ASCII"); + # } + + #Some checks + dochecks(); + + #Initialization of properties + @barcode ||= false + @buffer ||= '' + @diffs ||= [] + @color_flag ||= false + @default_table_columns ||= 4 + @table_columns ||= 0 + @max_table_columns ||= [] + @tr_id ||= 0 + @max_td_page ||= [] + @max_td_y ||= [] + @t_columns ||= 0 + @default_font ||= "FreeSans" if unicode + @default_font ||= "Helvetica" + @draw_color ||= '0 G' + @encoding ||= "UTF-8" + @fill_color ||= '0 g' + @fonts ||= {} + @font_family ||= '' + @font_files ||= {} + @font_style ||= '' + @font_size ||= 12 + @font_size_pt ||= 12 + @header_width ||= 0 + @header_logo ||= "" + @header_logo_width ||= 30 + @header_title ||= "" + @header_string ||= "" + @images ||= {} + @img_scale ||= 1 + @in_footer ||= false + @is_unicode = unicode + @lasth ||= 0 + @links ||= [] + @list_ordered ||= [] + @list_count ||= [] + @li_spacer ||= "" + @li_count ||= 0 + @spacer ||= "" + @quote_count ||= 0 + @prevquote_count ||= 0 + @quote_top ||= [] + @quote_page ||= [] + @n ||= 2 + @offsets ||= [] + @orientation_changes ||= [] + @page ||= 0 + @page_links ||= {} + @pages ||= [] + @pdf_version ||= "1.3" + @prevfill_color ||= [255,255,255] + @prevtext_color ||= [0,0,0] + @print_header ||= false + @print_footer ||= false + @state ||= 0 + @tableborder ||= 0 + @tdbegin ||= false + @tdtext ||= '' + @tdwidth ||= 0 + @tdheight ||= 0 + @tdalign ||= "L" + @tdfill ||= 0 + @tempfontsize ||= 10 + @text_color ||= '0 g' + @underline ||= false + @deleted ||= false + @ws ||= 0 + + #Standard Unicode fonts + @core_fonts = { + 'courier'=>'Courier', + 'courierB'=>'Courier-Bold', + 'courierI'=>'Courier-Oblique', + 'courierBI'=>'Courier-BoldOblique', + 'helvetica'=>'Helvetica', + 'helveticaB'=>'Helvetica-Bold', + 'helveticaI'=>'Helvetica-Oblique', + 'helveticaBI'=>'Helvetica-BoldOblique', + 'times'=>'Times-Roman', + 'timesB'=>'Times-Bold', + 'timesI'=>'Times-Italic', + 'timesBI'=>'Times-BoldItalic', + 'symbol'=>'Symbol', + 'zapfdingbats'=>'ZapfDingbats'} + + #Scale factor + case unit.downcase + when 'pt' ; @k=1 + when 'mm' ; @k=72/25.4 + when 'cm' ; @k=72/2.54 + when 'in' ; @k=72 + else Error("Incorrect unit: #{unit}") + end + + #Page format + if format.is_a?(String) + # Page formats (45 standard ISO paper formats and 4 american common formats). + # Paper cordinates are calculated in this way: (inches# 72) where (1 inch = 2.54 cm) + case (format.upcase) + when '4A0' ; format = [4767.87,6740.79] + when '2A0' ; format = [3370.39,4767.87] + when 'A0' ; format = [2383.94,3370.39] + when 'A1' ; format = [1683.78,2383.94] + when 'A2' ; format = [1190.55,1683.78] + when 'A3' ; format = [841.89,1190.55] + when 'A4' ; format = [595.28,841.89] # ; default + when 'A5' ; format = [419.53,595.28] + when 'A6' ; format = [297.64,419.53] + when 'A7' ; format = [209.76,297.64] + when 'A8' ; format = [147.40,209.76] + when 'A9' ; format = [104.88,147.40] + when 'A10' ; format = [73.70,104.88] + when 'B0' ; format = [2834.65,4008.19] + when 'B1' ; format = [2004.09,2834.65] + when 'B2' ; format = [1417.32,2004.09] + when 'B3' ; format = [1000.63,1417.32] + when 'B4' ; format = [708.66,1000.63] + when 'B5' ; format = [498.90,708.66] + when 'B6' ; format = [354.33,498.90] + when 'B7' ; format = [249.45,354.33] + when 'B8' ; format = [175.75,249.45] + when 'B9' ; format = [124.72,175.75] + when 'B10' ; format = [87.87,124.72] + when 'C0' ; format = [2599.37,3676.54] + when 'C1' ; format = [1836.85,2599.37] + when 'C2' ; format = [1298.27,1836.85] + when 'C3' ; format = [918.43,1298.27] + when 'C4' ; format = [649.13,918.43] + when 'C5' ; format = [459.21,649.13] + when 'C6' ; format = [323.15,459.21] + when 'C7' ; format = [229.61,323.15] + when 'C8' ; format = [161.57,229.61] + when 'C9' ; format = [113.39,161.57] + when 'C10' ; format = [79.37,113.39] + when 'RA0' ; format = [2437.80,3458.27] + when 'RA1' ; format = [1729.13,2437.80] + when 'RA2' ; format = [1218.90,1729.13] + when 'RA3' ; format = [864.57,1218.90] + when 'RA4' ; format = [609.45,864.57] + when 'SRA0' ; format = [2551.18,3628.35] + when 'SRA1' ; format = [1814.17,2551.18] + when 'SRA2' ; format = [1275.59,1814.17] + when 'SRA3' ; format = [907.09,1275.59] + when 'SRA4' ; format = [637.80,907.09] + when 'LETTER' ; format = [612.00,792.00] + when 'LEGAL' ; format = [612.00,1008.00] + when 'EXECUTIVE' ; format = [521.86,756.00] + when 'FOLIO' ; format = [612.00,936.00] + #else then Error("Unknown page format: #{format}" + end + @fw_pt = format[0] + @fh_pt = format[1] + else + @fw_pt = format[0]*@k + @fh_pt = format[1]*@k + end + + @fw = @fw_pt/@k + @fh = @fh_pt/@k + + #Page orientation + orientation = orientation.downcase + if orientation == 'p' or orientation == 'portrait' + @def_orientation = 'P' + @w_pt = @fw_pt + @h_pt = @fh_pt + elsif orientation == 'l' or orientation == 'landscape' + @def_orientation = 'L' + @w_pt = @fh_pt + @h_pt = @fw_pt + else + Error("Incorrect orientation: #{orientation}") + end + + @fw = @w_pt/@k + @fh = @h_pt/@k + + @cur_orientation = @def_orientation + @w = @w_pt/@k + @h = @h_pt/@k + #Page margins (1 cm) + margin = 28.35/@k + SetMargins(margin, margin) + #Interior cell margin (1 mm) + @c_margin = margin / 10 + #Line width (0.2 mm) + @line_width = 0.567 / @k + #Automatic page break + SetAutoPageBreak(true, 2 * margin) + #Full width display mode + SetDisplayMode('fullwidth') + #Compression + SetCompression(true) + #Set default PDF version number + @pdf_version = "1.3" + + @encoding = encoding + @b = 0 + @i = 0 + @u = 0 + @href = '' + @fontlist = ["arial", "times", "courier", "helvetica", "symbol"] + @issetfont = false + @issetcolor = false + + SetFillColor(200, 200, 200, true) + SetTextColor(0, 0, 0, true) + end + + # + # Set the image scale. + # @param float :scale image scale. + # @author Nicola Asuni + # @since 1.5.2 + # + def SetImageScale(scale) + @img_scale = scale; + end + alias_method :set_image_scale, :SetImageScale + + # + # Returns the image scale. + # @return float image scale. + # @author Nicola Asuni + # @since 1.5.2 + # + def GetImageScale() + return @img_scale; + end + alias_method :get_image_scale, :GetImageScale + + # + # Returns the page width in units. + # @return int page width. + # @author Nicola Asuni + # @since 1.5.2 + # + def GetPageWidth() + return @w; + end + alias_method :get_page_width, :GetPageWidth + + # + # Returns the page height in units. + # @return int page height. + # @author Nicola Asuni + # @since 1.5.2 + # + def GetPageHeight() + return @h; + end + alias_method :get_page_height, :GetPageHeight + + # + # Returns the page break margin. + # @return int page break margin. + # @author Nicola Asuni + # @since 1.5.2 + # + def GetBreakMargin() + return @b_margin; + end + alias_method :get_break_margin, :GetBreakMargin + + # + # Returns the scale factor (number of points in user unit). + # @return int scale factor. + # @author Nicola Asuni + # @since 1.5.2 + # + def GetScaleFactor() + return @k; + end + alias_method :get_scale_factor, :GetScaleFactor + + # + # Defines the left, top and right margins. By default, they equal 1 cm. Call this method to change them. + # @param float :left Left margin. + # @param float :top Top margin. + # @param float :right Right margin. Default value is the left one. + # @since 1.0 + # @see SetLeftMargin(), SetTopMargin(), SetRightMargin(), SetAutoPageBreak() + # + def SetMargins(left, top, right=-1) + #Set left, top and right margins + @l_margin = left + @t_margin = top + if (right == -1) + right = left + end + @r_margin = right + end + alias_method :set_margins, :SetMargins + + # + # Defines the left margin. The method can be called before creating the first page. If the current abscissa gets out of page, it is brought back to the margin. + # @param float :margin The margin. + # @since 1.4 + # @see SetTopMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins() + # + def SetLeftMargin(margin) + #Set left margin + @l_margin = margin + if ((@page>0) and (@x < margin)) + @x = margin + end + end + alias_method :set_left_margin, :SetLeftMargin + + # + # Defines the top margin. The method can be called before creating the first page. + # @param float :margin The margin. + # @since 1.5 + # @see SetLeftMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins() + # + def SetTopMargin(margin) + #Set top margin + @t_margin = margin + end + alias_method :set_top_margin, :SetTopMargin + + # + # Defines the right margin. The method can be called before creating the first page. + # @param float :margin The margin. + # @since 1.5 + # @see SetLeftMargin(), SetTopMargin(), SetAutoPageBreak(), SetMargins() + # + def SetRightMargin(margin) + #Set right margin + @r_margin = margin + end + alias_method :set_right_margin, :SetRightMargin + + # + # Enables or disables the automatic page breaking mode. When enabling, the second parameter is the distance from the bottom of the page that defines the triggering limit. By default, the mode is on and the margin is 2 cm. + # @param boolean :auto Boolean indicating if mode should be on or off. + # @param float :margin Distance from the bottom of the page. + # @since 1.0 + # @see Cell(), MultiCell(), AcceptPageBreak() + # + def SetAutoPageBreak(auto, margin=0) + #Set auto page break mode and triggering margin + @auto_page_break = auto + @b_margin = margin + @page_break_trigger = @h - margin + end + alias_method :set_auto_page_break, :SetAutoPageBreak + + # + # Defines the way the document is to be displayed by the viewer. The zoom level can be set: pages can be displayed entirely on screen, occupy the full width of the window, use real size, be scaled by a specific zooming factor or use viewer default (configured in the Preferences menu of Acrobat). The page layout can be specified too: single at once, continuous display, two columns or viewer default. By default, documents use the full width mode with continuous display. + # @param mixed :zoom The zoom to use. It can be one of the following string values or a number indicating the zooming factor to use.
    • fullpage: displays the entire page on screen
    • fullwidth: uses maximum width of window
    • real: uses real size (equivalent to 100% zoom)
    • default: uses viewer default mode
    + # @param string :layout The page layout. Possible values are:
    • single: displays one page at once
    • continuous: displays pages continuously (default)
    • two: displays two pages on two columns
    • default: uses viewer default mode
    + # @since 1.2 + # + def SetDisplayMode(zoom, layout = 'continuous') + #Set display mode in viewer + if (zoom == 'fullpage' or zoom == 'fullwidth' or zoom == 'real' or zoom == 'default' or !zoom.is_a?(String)) + @zoom_mode = zoom + else + Error("Incorrect zoom display mode: #{zoom}") + end + if (layout == 'single' or layout == 'continuous' or layout == 'two' or layout == 'default') + @layout_mode = layout + else + Error("Incorrect layout display mode: #{layout}") + end + end + alias_method :set_display_mode, :SetDisplayMode + + # + # Activates or deactivates page compression. When activated, the internal representation of each page is compressed, which leads to a compression ratio of about 2 for the resulting document. Compression is on by default. + # Note: the Zlib extension is required for this feature. If not present, compression will be turned off. + # @param boolean :compress Boolean indicating if compression must be enabled. + # @since 1.4 + # + def SetCompression(compress) + #Set page compression + if (respond_to?('gzcompress')) + @compress = compress + else + @compress = false + end + end + alias_method :set_compression, :SetCompression + + # + # Defines the title of the document. + # @param string :title The title. + # @since 1.2 + # @see SetAuthor(), SetCreator(), SetKeywords(), SetSubject() + # + def SetTitle(title) + #Title of document + @title = title + end + alias_method :set_title, :SetTitle + + # + # Defines the subject of the document. + # @param string :subject The subject. + # @since 1.2 + # @see SetAuthor(), SetCreator(), SetKeywords(), SetTitle() + # + def SetSubject(subject) + #Subject of document + @subject = subject + end + alias_method :set_subject, :SetSubject + + # + # Defines the author of the document. + # @param string :author The name of the author. + # @since 1.2 + # @see SetCreator(), SetKeywords(), SetSubject(), SetTitle() + # + def SetAuthor(author) + #Author of document + @author = author + end + alias_method :set_author, :SetAuthor + + # + # Associates keywords with the document, generally in the form 'keyword1 keyword2 ...'. + # @param string :keywords The list of keywords. + # @since 1.2 + # @see SetAuthor(), SetCreator(), SetSubject(), SetTitle() + # + def SetKeywords(keywords) + #Keywords of document + @keywords = keywords + end + alias_method :set_keywords, :SetKeywords + + # + # Defines the creator of the document. This is typically the name of the application that generates the PDF. + # @param string :creator The name of the creator. + # @since 1.2 + # @see SetAuthor(), SetKeywords(), SetSubject(), SetTitle() + # + def SetCreator(creator) + #Creator of document + @creator = creator + end + alias_method :set_creator, :SetCreator + + # + # Defines an alias for the total number of pages. It will be substituted as the document is closed.
    + # Example:
    + #
    +	# class PDF extends TCPDF {
    +	# 	def Footer()
    +	# 		#Go to 1.5 cm from bottom
    +	# 		SetY(-15);
    +	# 		#Select Arial italic 8
    +	# 		SetFont('Arial','I',8);
    +	# 		#Print current and total page numbers
    +	# 		Cell(0,10,'Page '.PageNo().'/{nb}',0,0,'C');
    +	# 	end
    +	# }
    +	# :pdf=new PDF();
    +	# :pdf->alias_nb_pages();
    +	# 
    + # @param string :alias The alias. Default valuenb}. + # @since 1.4 + # @see PageNo(), Footer() + # + def AliasNbPages(alias_nb ='{nb}') + #Define an alias for total number of pages + @alias_nb_pages = escapetext(alias_nb) + end + alias_method :alias_nb_pages, :AliasNbPages + + # + # This method is automatically called in case of fatal error; it simply outputs the message and halts the execution. An inherited class may override it to customize the error handling but should always halt the script, or the resulting document would probably be invalid. + # 2004-06-11 :: Nicola Asuni : changed bold tag with strong + # @param string :msg The error message + # @since 1.0 + # + def Error(msg) + #Fatal error + raise ("TCPDF error: #{msg}") + end + alias_method :error, :Error + + # + # This method begins the generation of the PDF document. It is not necessary to call it explicitly because AddPage() does it automatically. + # Note: no page is created by this method + # @since 1.0 + # @see AddPage(), Close() + # + def Open() + #Begin document + @state = 1 + end + # alias_method :open, :Open + + # + # Terminates the PDF document. It is not necessary to call this method explicitly because Output() does it automatically. If the document contains no page, AddPage() is called to prevent from getting an invalid document. + # @since 1.0 + # @see Open(), Output() + # + def Close() + #Terminate document + if (@state==3) + return; + end + if (@page==0) + AddPage(); + end + #Page footer + @in_footer=true; + Footer(); + @in_footer=false; + #Close page + endpage(); + #Close document + enddoc(); + end + # alias_method :close, :Close + + # + # Adds a new page to the document. If a page is already present, the Footer() method is called first to output the footer. Then the page is added, the current position set to the top-left corner according to the left and top margins, and Header() is called to display the header. + # The font which was set before calling is automatically restored. There is no need to call SetFont() again if you want to continue with the same font. The same is true for colors and line width. + # The origin of the coordinate system is at the top-left corner and increasing ordinates go downwards. + # @param string :orientation Page orientation. Possible values are (case insensitive):
    • P or Portrait
    • L or Landscape
    The default value is the one passed to the constructor. + # @since 1.0 + # @see TCPDF(), Header(), Footer(), SetMargins() + # + def AddPage(orientation='') + #Start a new page + if (@state==0) + Open(); + end + family=@font_family; + style=@font_style + (@underline ? 'U' : '') + (@deleted ? 'D' : ''); + size=@font_size_pt; + lw=@line_width; + dc=@draw_color; + fc=@fill_color; + tc=@text_color; + cf=@color_flag; + if (@page>0) + #Page footer + @in_footer=true; + Footer(); + @in_footer=false; + #Close page + endpage(); + end + #Start new page + beginpage(orientation); + #Set line cap style to square + out('2 J'); + #Set line width + @line_width = lw; + out(sprintf('%.2f w', lw*@k)); + #Set font + if (family) + SetFont(family, style, size); + end + #Set colors + @draw_color = dc; + if (dc!='0 G') + out(dc); + end + @fill_color = fc; + if (fc!='0 g') + out(fc); + end + @text_color = tc; + @color_flag = cf; + #Page header + Header(); + #Restore line width + if (@line_width != lw) + @line_width = lw; + out(sprintf('%.2f w', lw*@k)); + end + #Restore font + if (family) + SetFont(family, style, size); + end + #Restore colors + if (@draw_color != dc) + @draw_color = dc; + out(dc); + end + if (@fill_color != fc) + @fill_color = fc; + out(fc); + end + @text_color = tc; + @color_flag = cf; + end + alias_method :add_page, :AddPage + + # + # Rotate object. + # @param float :angle angle in degrees for counter-clockwise rotation + # @param int :x abscissa of the rotation center. Default is current x position + # @param int :y ordinate of the rotation center. Default is current y position + # + def Rotate(angle, x="", y="") + + if (x == '') + x = @x; + end + + if (y == '') + y = @y; + end + + if (@rtl) + x = @w - x; + angle = -@angle; + end + + y = (@h - y) * @k; + x *= @k; + + # calculate elements of transformation matrix + tm = [] + tm[0] = ::Math::cos(deg2rad(angle)); + tm[1] = ::Math::sin(deg2rad(angle)); + tm[2] = -tm[1]; + tm[3] = tm[0]; + tm[4] = x + tm[1] * y - tm[0] * x; + tm[5] = y - tm[0] * y - tm[1] * x; + + # generate the transformation matrix + Transform(tm); + end + alias_method :rotate, :Rotate + + # + # Starts a 2D tranformation saving current graphic state. + # This function must be called before scaling, mirroring, translation, rotation and skewing. + # Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior. + # + def StartTransform + out('q'); + end + alias_method :start_transform, :StartTransform + + # + # Stops a 2D tranformation restoring previous graphic state. + # This function must be called after scaling, mirroring, translation, rotation and skewing. + # Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior. + # + def StopTransform + out('Q'); + end + alias_method :stop_transform, :StopTransform + + # + # Apply graphic transformations. + # @since 2.1.000 (2008-01-07) + # @see StartTransform(), StopTransform() + # + def Transform(tm) + x = out(sprintf('%.3f %.3f %.3f %.3f %.3f %.3f cm', tm[0], tm[1], tm[2], tm[3], tm[4], tm[5])); + end + alias_method :transform, :Transform + + # + # Set header data. + # @param string :ln header image logo + # @param string :lw header image logo width in mm + # @param string :ht string to print as title on document header + # @param string :hs string to print on document header + # + def SetHeaderData(ln="", lw=0, ht="", hs="") + @header_logo = ln || "" + @header_logo_width = lw || 0 + @header_title = ht || "" + @header_string = hs || "" + end + alias_method :set_header_data, :SetHeaderData + + # + # Set header margin. + # (minimum distance between header and top page margin) + # @param int :hm distance in millimeters + # + def SetHeaderMargin(hm=10) + @header_margin = hm; + end + alias_method :set_header_margin, :SetHeaderMargin + + # + # Set footer margin. + # (minimum distance between footer and bottom page margin) + # @param int :fm distance in millimeters + # + def SetFooterMargin(fm=10) + @footer_margin = fm; + end + alias_method :set_footer_margin, :SetFooterMargin + + # + # Set a flag to print page header. + # @param boolean :val set to true to print the page header (default), false otherwise. + # + def SetPrintHeader(val=true) + @print_header = val; + end + alias_method :set_print_header, :SetPrintHeader + + # + # Set a flag to print page footer. + # @param boolean :value set to true to print the page footer (default), false otherwise. + # + def SetPrintFooter(val=true) + @print_footer = val; + end + alias_method :set_print_footer, :SetPrintFooter + + # + # This method is used to render the page header. + # It is automatically called by AddPage() and could be overwritten in your own inherited class. + # + def Header() + if (@print_header) + if (@original_l_margin.nil?) + @original_l_margin = @l_margin; + end + if (@original_r_margin.nil?) + @original_r_margin = @r_margin; + end + + #set current position + SetXY(@original_l_margin, @header_margin); + + if ((@header_logo) and (@header_logo != @@k_blank_image)) + Image(@header_logo, @original_l_margin, @header_margin, @header_logo_width); + else + @img_rb_y = GetY(); + end + + cell_height = ((@@k_cell_height_ratio * @header_font[2]) / @k).round(2) + + header_x = @original_l_margin + (@header_logo_width * 1.05); #set left margin for text data cell + + # header title + SetFont(@header_font[0], 'B', @header_font[2] + 1); + SetX(header_x); + Cell(@header_width, cell_height, @header_title, 0, 1, 'L'); + + # header string + SetFont(@header_font[0], @header_font[1], @header_font[2]); + SetX(header_x); + MultiCell(@header_width, cell_height, @header_string, 0, 'L', 0); + + # print an ending header line + if (@header_width) + #set style for cell border + SetLineWidth(0.3); + SetDrawColor(0, 0, 0); + SetY(1 + (@img_rb_y > GetY() ? @img_rb_y : GetY())); + SetX(@original_l_margin); + Cell(0, 0, '', 'T', 0, 'C'); + end + + #restore position + SetXY(@original_l_margin, @t_margin); + end + end + alias_method :header, :Header + + # + # This method is used to render the page footer. + # It is automatically called by AddPage() and could be overwritten in your own inherited class. + # + def Footer() + if (@print_footer) + + if (@original_l_margin.nil?) + @original_l_margin = @l_margin; + end + if (@original_r_margin.nil?) + @original_r_margin = @r_margin; + end + + #set font + SetFont(@footer_font[0], @footer_font[1] , @footer_font[2]); + #set style for cell border + line_width = 0.3; + SetLineWidth(line_width); + SetDrawColor(0, 0, 0); + + footer_height = ((@@k_cell_height_ratio * @footer_font[2]) / @k).round; #footer height, was , 2) + #get footer y position + footer_y = @h - @footer_margin - footer_height; + #set current position + SetXY(@original_l_margin, footer_y); + + #print document barcode + if (@barcode) + Ln(); + barcode_width = ((@w - @original_l_margin - @original_r_margin)).round; #max width + writeBarcode(@original_l_margin, footer_y + line_width, barcode_width, footer_height - line_width, "C128B", false, false, 2, @barcode); + end + + SetXY(@original_l_margin, footer_y); + + #Print page number + Cell(0, footer_height, @l['w_page'] + " " + PageNo().to_s + ' / {nb}', 'T', 0, 'R'); + end + end + alias_method :footer, :Footer + + # + # Returns the current page number. + # @return int page number + # @since 1.0 + # @see alias_nb_pages() + # + def PageNo() + #Get current page number + return @page; + end + alias_method :page_no, :PageNo + + # + # Defines the color used for all drawing operations (lines, rectangles and cell borders). It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page. + # @param int :r If g et b are given, red component; if not, indicates the gray level. Value between 0 and 255 + # @param int :g Green component (between 0 and 255) + # @param int :b Blue component (between 0 and 255) + # @since 1.3 + # @see SetFillColor(), SetTextColor(), Line(), Rect(), Cell(), MultiCell() + # + def SetDrawColor(r, g=-1, b=-1) + #Set color for all stroking operations + if ((r==0 and g==0 and b==0) or g==-1) + @draw_color=sprintf('%.3f G', r/255.0); + else + @draw_color=sprintf('%.3f %.3f %.3f RG', r/255.0, g/255.0, b/255.0); + end + if (@page>0) + out(@draw_color); + end + end + alias_method :set_draw_color, :SetDrawColor + + # + # Defines the color used for all filling operations (filled rectangles and cell backgrounds). It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page. + # @param int :r If g et b are given, red component; if not, indicates the gray level. Value between 0 and 255 + # @param int :g Green component (between 0 and 255) + # @param int :b Blue component (between 0 and 255) + # @param boolean :storeprev if true stores the RGB array on :prevfill_color variable. + # @since 1.3 + # @see SetDrawColor(), SetTextColor(), Rect(), Cell(), MultiCell() + # + def SetFillColor(r, g=-1, b=-1, storeprev=false) + #Set color for all filling operations + if ((r==0 and g==0 and b==0) or g==-1) + @fill_color=sprintf('%.3f g', r/255.0); + else + @fill_color=sprintf('%.3f %.3f %.3f rg', r/255.0, g/255.0, b/255.0); + end + @color_flag=(@fill_color!=@text_color); + if (@page>0) + out(@fill_color); + end + if (storeprev) + # store color as previous value + @prevfill_color = [r, g, b] + end + end + alias_method :set_fill_color, :SetFillColor + + # This hasn't been ported from tcpdf, it's a variation on SetTextColor for setting cmyk colors + def SetCmykFillColor(c, m, y, k, storeprev=false) + #Set color for all filling operations + @fill_color=sprintf('%.3f %.3f %.3f %.3f k', c, m, y, k); + @color_flag=(@fill_color!=@text_color); + if (storeprev) + # store color as previous value + @prevtext_color = [c, m, y, k] + end + if (@page>0) + out(@fill_color); + end + end + alias_method :set_cmyk_fill_color, :SetCmykFillColor + + # + # Defines the color used for text. It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page. + # @param int :r If g et b are given, red component; if not, indicates the gray level. Value between 0 and 255 + # @param int :g Green component (between 0 and 255) + # @param int :b Blue component (between 0 and 255) + # @param boolean :storeprev if true stores the RGB array on :prevtext_color variable. + # @since 1.3 + # @see SetDrawColor(), SetFillColor(), Text(), Cell(), MultiCell() + # + def SetTextColor(r, g=-1, b=-1, storeprev=false) + #Set color for text + if ((r==0 and :g==0 and :b==0) or :g==-1) + @text_color=sprintf('%.3f g', r/255.0); + else + @text_color=sprintf('%.3f %.3f %.3f rg', r/255.0, g/255.0, b/255.0); + end + @color_flag=(@fill_color!=@text_color); + if (storeprev) + # store color as previous value + @prevtext_color = [r, g, b] + end + end + alias_method :set_text_color, :SetTextColor + + # This hasn't been ported from tcpdf, it's a variation on SetTextColor for setting cmyk colors + def SetCmykTextColor(c, m, y, k, storeprev=false) + #Set color for text + @text_color=sprintf('%.3f %.3f %.3f %.3f k', c, m, y, k); + @color_flag=(@fill_color!=@text_color); + if (storeprev) + # store color as previous value + @prevtext_color = [c, m, y, k] + end + end + alias_method :set_cmyk_text_color, :SetCmykTextColor + + # + # Returns the length of a string in user unit. A font must be selected.
    + # Support UTF-8 Unicode [Nicola Asuni, 2005-01-02] + # @param string :s The string whose length is to be computed + # @return int + # @since 1.2 + # + def GetStringWidth(s) + #Get width of a string in the current font + s = s.to_s; + cw = @current_font['cw'] + w = 0; + if (@is_unicode) + unicode = UTF8StringToArray(s); + unicode.each do |char| + if (!cw[char].nil?) + w += cw[char]; + # This should not happen. UTF8StringToArray should guarentee the array is ascii values. + # elsif (c!cw[char[0]].nil?) + # w += cw[char[0]]; + # elsif (!cw[char.chr].nil?) + # w += cw[char.chr]; + elsif (!@current_font['desc']['MissingWidth'].nil?) + w += @current_font['desc']['MissingWidth']; # set default size + else + w += 500; + end + end + else + s.each_byte do |c| + if cw[c.chr] + w += cw[c.chr]; + elsif cw[?c.chr] + w += cw[?c.chr] + end + end + end + return (w * @font_size / 1000.0); + end + alias_method :get_string_width, :GetStringWidth + + # + # Defines the line width. By default, the value equals 0.2 mm. The method can be called before the first page is created and the value is retained from page to page. + # @param float :width The width. + # @since 1.0 + # @see Line(), Rect(), Cell(), MultiCell() + # + def SetLineWidth(width) + #Set line width + @line_width = width; + if (@page>0) + out(sprintf('%.2f w', width*@k)); + end + end + alias_method :set_line_width, :SetLineWidth + + # + # Draws a line between two points. + # @param float :x1 Abscissa of first point + # @param float :y1 Ordinate of first point + # @param float :x2 Abscissa of second point + # @param float :y2 Ordinate of second point + # @since 1.0 + # @see SetLineWidth(), SetDrawColor() + # + def Line(x1, y1, x2, y2) + #Draw a line + out(sprintf('%.2f %.2f m %.2f %.2f l S', x1 * @k, (@h - y1) * @k, x2 * @k, (@h - y2) * @k)); + end + alias_method :line, :Line + + def Circle(mid_x, mid_y, radius, style='') + mid_y = (@h-mid_y)*@k + out(sprintf("q\n")) # postscript content in pdf + # init line type etc. with /GSD gs G g (grey) RG rg (RGB) w=line witdh etc. + out(sprintf("1 j\n")) # line join + # translate ("move") circle to mid_y, mid_y + out(sprintf("1 0 0 1 %f %f cm", mid_x, mid_y)) + kappa = 0.5522847498307933984022516322796 + # Quadrant 1 + x_s = 0.0 # 12 o'clock + y_s = 0.0 + radius + x_e = 0.0 + radius # 3 o'clock + y_e = 0.0 + out(sprintf("%f %f m\n", x_s, y_s)) # move to 12 o'clock + # cubic bezier control point 1, start height and kappa * radius to the right + bx_e1 = x_s + (radius * kappa) + by_e1 = y_s + # cubic bezier control point 2, end and kappa * radius above + bx_e2 = x_e + by_e2 = y_e + (radius * kappa) + # draw cubic bezier from current point to x_e/y_e with bx_e1/by_e1 and bx_e2/by_e2 as bezier control points + out(sprintf("%f %f %f %f %f %f c\n", bx_e1, by_e1, bx_e2, by_e2, x_e, y_e)) + # Quadrant 2 + x_s = x_e + y_s = y_e # 3 o'clock + x_e = 0.0 + y_e = 0.0 - radius # 6 o'clock + bx_e1 = x_s # cubic bezier point 1 + by_e1 = y_s - (radius * kappa) + bx_e2 = x_e + (radius * kappa) # cubic bezier point 2 + by_e2 = y_e + out(sprintf("%f %f %f %f %f %f c\n", bx_e1, by_e1, bx_e2, by_e2, x_e, y_e)) + # Quadrant 3 + x_s = x_e + y_s = y_e # 6 o'clock + x_e = 0.0 - radius + y_e = 0.0 # 9 o'clock + bx_e1 = x_s - (radius * kappa) # cubic bezier point 1 + by_e1 = y_s + bx_e2 = x_e # cubic bezier point 2 + by_e2 = y_e - (radius * kappa) + out(sprintf("%f %f %f %f %f %f c\n", bx_e1, by_e1, bx_e2, by_e2, x_e, y_e)) + # Quadrant 4 + x_s = x_e + y_s = y_e # 9 o'clock + x_e = 0.0 + y_e = 0.0 + radius # 12 o'clock + bx_e1 = x_s # cubic bezier point 1 + by_e1 = y_s + (radius * kappa) + bx_e2 = x_e - (radius * kappa) # cubic bezier point 2 + by_e2 = y_e + out(sprintf("%f %f %f %f %f %f c\n", bx_e1, by_e1, bx_e2, by_e2, x_e, y_e)) + if style=='F' + op='f' + elsif style=='FD' or style=='DF' + op='b' + else + op='s' + end + out(sprintf("#{op}\n")) # stroke circle, do not fill and close path + # for filling etc. b, b*, f, f* + out(sprintf("Q\n")) # finish postscript in PDF + end + alias_method :circle, :Circle + + # + # Outputs a rectangle. It can be drawn (border only), filled (with no border) or both. + # @param float :x Abscissa of upper-left corner + # @param float :y Ordinate of upper-left corner + # @param float :w Width + # @param float :h Height + # @param string :style Style of rendering. Possible values are:
    • D or empty string: draw (default)
    • F: fill
    • DF or FD: draw and fill
    + # @since 1.0 + # @see SetLineWidth(), SetDrawColor(), SetFillColor() + # + def Rect(x, y, w, h, style='') + #Draw a rectangle + if (style=='F') + op='f'; + elsif (style=='FD' or style=='DF') + op='B'; + else + op='S'; + end + out(sprintf('%.2f %.2f %.2f %.2f re %s', x * @k, (@h - y) * @k, w * @k, -h * @k, op)); + end + alias_method :rect, :Rect + + # + # Imports a TrueType or Type1 font and makes it available. It is necessary to generate a font definition file first with the makefont.rb utility. The definition file (and the font file itself when embedding) must be present either in the current directory or in the one indicated by FPDF_FONTPATH if the constant is defined. If it could not be found, the error "Could not include font definition file" is generated. + # Support UTF-8 Unicode [Nicola Asuni, 2005-01-02]. + # Example:
    + #
    +	# :pdf->AddFont('Comic','I');
    +	# # is equivalent to:
    +	# :pdf->AddFont('Comic','I','comici.rb');
    +	# 
    + # @param string :family Font family. The name can be chosen arbitrarily. If it is a standard family name, it will override the corresponding font. + # @param string :style Font style. Possible values are (case insensitive):
    • empty string: regular (default)
    • B: bold
    • I: italic
    • BI or IB: bold italic
    + # @param string :file The font definition file. By default, the name is built from the family and style, in lower case with no space. + # @since 1.5 + # @see SetFont() + # + def AddFont(family, style='', file='') + if (family.empty?) + return; + end + + #Add a TrueType or Type1 font + family = family.downcase + if ((!@is_unicode) and (family == 'arial')) + family = 'helvetica'; + end + + style=style.upcase + style=style.gsub('U',''); + style=style.gsub('D',''); + if (style == 'IB') + style = 'BI'; + end + + fontkey = family + style; + # check if the font has been already added + if !@fonts[fontkey].nil? + return; + end + + if (file=='') + file = family.gsub(' ', '') + style.downcase + '.rb'; + end + font_file_name = getfontpath(file) + if (font_file_name.nil?) + # try to load the basic file without styles + file = family.gsub(' ', '') + '.rb'; + font_file_name = getfontpath(file) + end + if font_file_name.nil? + Error("Could not find font #{file}.") + end + require(getfontpath(file)) + font_desc = TCPDFFontDescriptor.font(file) + + if (font_desc[:name].nil? and @@fpdf_charwidths.nil?) + Error('Could not include font definition file'); + end + + i = @fonts.length+1; + if (@is_unicode) + @fonts[fontkey] = {'i' => i, 'type' => font_desc[:type], 'name' => font_desc[:name], 'desc' => font_desc[:desc], 'up' => font_desc[:up], 'ut' => font_desc[:ut], 'cw' => font_desc[:cw], 'enc' => font_desc[:enc], 'file' => font_desc[:file], 'ctg' => font_desc[:ctg], 'cMap' => font_desc[:cMap], 'registry' => font_desc[:registry]} + @@fpdf_charwidths[fontkey] = font_desc[:cw]; + else + @fonts[fontkey]={'i' => i, 'type'=>'core', 'name'=>@core_fonts[fontkey], 'up'=>-100, 'ut'=>50, 'cw' => font_desc[:cw]} + @@fpdf_charwidths[fontkey] = font_desc[:cw]; + end + + if (!font_desc[:diff].nil? and (!font_desc[:diff].empty?)) + #Search existing encodings + d=0; + nb=@diffs.length; + 1.upto(nb) do |i| + if (@diffs[i]== font_desc[:diff]) + d = i; + break; + end + end + if (d==0) + d = nb+1; + @diffs[d] = font_desc[:diff]; + end + @fonts[fontkey]['diff'] = d; + end + if (font_desc[:file] and font_desc[:file].length > 0) + if (font_desc[:type] == "TrueType") or (font_desc[:type] == "TrueTypeUnicode") + @font_files[font_desc[:file]] = {'length1' => font_desc[:originalsize]} + else + @font_files[font_desc[:file]] = {'length1' => font_desc[:size1], 'length2' => font_desc[:size2]} + end + end + end + alias_method :add_font, :AddFont + + # + # Sets the font used to print character strings. It is mandatory to call this method at least once before printing text or the resulting document would not be valid. + # The font can be either a standard one or a font added via the AddFont() method. Standard fonts use Windows encoding cp1252 (Western Europe). + # The method can be called before the first page is created and the font is retained from page to page. + # If you just wish to change the current font size, it is simpler to call SetFontSize(). + # Note: for the standard fonts, the font metric files must be accessible. There are three possibilities for this:
    • They are in the current directory (the one where the running script lies)
    • They are in one of the directories defined by the include_path parameter
    • They are in the directory defined by the FPDF_FONTPATH constant

    + # Example for the last case (note the trailing slash):
    + #
    +	# define('FPDF_FONTPATH','/home/www/font/');
    +	# require('tcpdf.rb');
    +	#
    +	# #Times regular 12
    +	# :pdf->SetFont('Times');
    +	# #Arial bold 14
    +	# :pdf->SetFont('Arial','B',14);
    +	# #Removes bold
    +	# :pdf->SetFont('');
    +	# #Times bold, italic and underlined 14
    +	# :pdf->SetFont('Times','BIUD');
    +	# 

    + # If the file corresponding to the requested font is not found, the error "Could not include font metric file" is generated. + # @param string :family Family font. It can be either a name defined by AddFont() or one of the standard families (case insensitive):
    • Courier (fixed-width)
    • Helvetica or Arial (synonymous; sans serif)
    • Times (serif)
    • Symbol (symbolic)
    • ZapfDingbats (symbolic)
    It is also possible to pass an empty string. In that case, the current family is retained. + # @param string :style Font style. Possible values are (case insensitive):
    • empty string: regular
    • B: bold
    • I: italic
    • U: underline
    or any combination. The default value is regular. Bold and italic styles do not apply to Symbol and ZapfDingbats + # @param float :size Font size in points. The default value is the current size. If no size has been specified since the beginning of the document, the value taken is 12 + # @since 1.0 + # @see AddFont(), SetFontSize(), Cell(), MultiCell(), Write() + # + def SetFont(family, style='', size=0) + # save previous values + @prevfont_family = @font_family; + @prevfont_style = @font_style; + + family=family.downcase; + if (family=='') + family=@font_family; + end + if ((!@is_unicode) and (family == 'arial')) + family = 'helvetica'; + elsif ((family=="symbol") or (family=="zapfdingbats")) + style=''; + end + + style=style.upcase; + + if (style.include?('U')) + @underline=true; + style= style.gsub('U',''); + else + @underline=false; + end + if (style.include?('D')) + @deleted=true; + style= style.gsub('D',''); + else + @deleted=false; + end + if (style=='IB') + style='BI'; + end + if (size==0) + size=@font_size_pt; + end + + # try to add font (if not already added) + AddFont(family, style); + + #Test if font is already selected + if ((@font_family == family) and (@font_style == style) and (@font_size_pt == size)) + return; + end + + fontkey = family + style; + style = '' if (@fonts[fontkey].nil? and !@fonts[family].nil?) + + #Test if used for the first time + if (@fonts[fontkey].nil?) + #Check if one of the standard fonts + if (!@core_fonts[fontkey].nil?) + if @@fpdf_charwidths[fontkey].nil? + #Load metric file + file = family; + if ((family!='symbol') and (family!='zapfdingbats')) + file += style.downcase; + end + if (getfontpath(file + '.rb').nil?) + # try to load the basic file without styles + file = family; + fontkey = family; + end + require(getfontpath(file + '.rb')); + font_desc = TCPDFFontDescriptor.font(file) + if ((@is_unicode and ctg.nil?) or ((!@is_unicode) and (@@fpdf_charwidths[fontkey].nil?)) ) + Error("Could not include font metric file [" + fontkey + "]: " + getfontpath(file + ".rb")); + end + end + i = @fonts.length + 1; + + if (@is_unicode) + @fonts[fontkey] = {'i' => i, 'type' => font_desc[:type], 'name' => font_desc[:name], 'desc' => font_desc[:desc], 'up' => font_desc[:up], 'ut' => font_desc[:ut], 'cw' => font_desc[:cw], 'enc' => font_desc[:enc], 'file' => font_desc[:file], 'ctg' => font_desc[:ctg]} + @@fpdf_charwidths[fontkey] = font_desc[:cw]; + else + @fonts[fontkey] = {'i' => i, 'type'=>'core', 'name'=>@core_fonts[fontkey], 'up'=>-100, 'ut'=>50, 'cw' => font_desc[:cw]} + @@fpdf_charwidths[fontkey] = font_desc[:cw]; + end + else + Error('Undefined font: ' + family + ' ' + style); + end + end + #Select it + @font_family = family; + @font_style = style; + @font_size_pt = size; + @font_size = size / @k; + @current_font = @fonts[fontkey]; # was & may need deep copy? + if (@page>0) + out(sprintf('BT /F%d %.2f Tf ET', @current_font['i'], @font_size_pt)); + end + end + alias_method :set_font, :SetFont + + # + # Defines the size of the current font. + # @param float :size The size (in points) + # @since 1.0 + # @see SetFont() + # + def SetFontSize(size) + #Set font size in points + if (@font_size_pt== size) + return; + end + @font_size_pt = size; + @font_size = size.to_f / @k; + if (@page > 0) + out(sprintf('BT /F%d %.2f Tf ET', @current_font['i'], @font_size_pt)); + end + end + alias_method :set_font_size, :SetFontSize + + # + # Creates a new internal link and returns its identifier. An internal link is a clickable area which directs to another place within the document.
    + # The identifier can then be passed to Cell(), Write(), Image() or Link(). The destination is defined with SetLink(). + # @since 1.5 + # @see Cell(), Write(), Image(), Link(), SetLink() + # + def AddLink() + #Create a new internal link + n=@links.length+1; + @links[n]=[0,0]; + return n; + end + alias_method :add_link, :AddLink + + # + # Defines the page and position a link points to + # @param int :link The link identifier returned by AddLink() + # @param float :y Ordinate of target position; -1 indicates the current position. The default value is 0 (top of page) + # @param int :page Number of target page; -1 indicates the current page. This is the default value + # @since 1.5 + # @see AddLink() + # + def SetLink(link, y=0, page=-1) + #Set destination of internal link + if (y==-1) + y=@y; + end + if (page==-1) + page=@page; + end + @links[link] = [page, y] + end + alias_method :set_link, :SetLink + + # + # Puts a link on a rectangular area of the page. Text or image links are generally put via Cell(), Write() or Image(), but this method can be useful for instance to define a clickable area inside an image. + # @param float :x Abscissa of the upper-left corner of the rectangle + # @param float :y Ordinate of the upper-left corner of the rectangle + # @param float :w Width of the rectangle + # @param float :h Height of the rectangle + # @param mixed :link URL or identifier returned by AddLink() + # @since 1.5 + # @see AddLink(), Cell(), Write(), Image() + # + def Link(x, y, w, h, link) + #Put a link on the page + @page_links ||= Array.new + @page_links[@page] ||= Array.new + @page_links[@page].push([x * @k, @h_pt - y * @k, w * @k, h*@k, link]); + end + alias_method :link, :Link + + # + # Prints a character string. The origin is on the left of the first charcter, on the baseline. This method allows to place a string precisely on the page, but it is usually easier to use Cell(), MultiCell() or Write() which are the standard methods to print text. + # @param float :x Abscissa of the origin + # @param float :y Ordinate of the origin + # @param string :txt String to print + # @since 1.0 + # @see SetFont(), SetTextColor(), Cell(), MultiCell(), Write() + # + def Text(x, y, txt) + #Output a string + s=sprintf('BT %.2f %.2f Td (%s) Tj ET', x * @k, (@h-y) * @k, escapetext(txt)); + if (@underline and (txt!='')) + s += ' ' + dolinetxt(x, y, txt); + end + if (@color_flag) + s='q ' + @text_color + ' ' + s + ' Q'; + end + out(s); + end + alias_method :text, :Text + + # + # Whenever a page break condition is met, the method is called, and the break is issued or not depending on the returned value. The default implementation returns a value according to the mode selected by SetAutoPageBreak().
    + # This method is called automatically and should not be called directly by the application.
    + # Example:
    + # The method is overriden in an inherited class in order to obtain a 3 column layout:
    + #
    +	# class PDF extends TCPDF {
    +	# 	var :col=0;
    +	#
    +	# 	def SetCol(col)
    +	# 		#Move position to a column
    +	# 		@col = col;
    +	# 		:x=10+:col*65;
    +	# 		SetLeftMargin(x);
    +	# 		SetX(x);
    +	# 	end
    +	#
    +	# 	def AcceptPageBreak()
    +	# 		if (@col<2)
    +	# 			#Go to next column
    +	# 			SetCol(@col+1);
    +	# 			SetY(10);
    +	# 			return false;
    +	# 		end
    +	# 		else
    +	# 			#Go back to first column and issue page break
    +	# 			SetCol(0);
    +	# 			return true;
    +	# 		end
    +	# 	end
    +	# }
    +	#
    +	# :pdf=new PDF();
    +	# :pdf->Open();
    +	# :pdf->AddPage();
    +	# :pdf->SetFont('Arial','',12);
    +	# for(i=1;:i<=300;:i++)
    +	#     :pdf->Cell(0,5,"Line :i",0,1);
    +	# }
    +	# :pdf->Output();
    +	# 
    + # @return boolean + # @since 1.4 + # @see SetAutoPageBreak() + # + def AcceptPageBreak() + #Accept automatic page break or not + return @auto_page_break; + end + alias_method :accept_page_break, :AcceptPageBreak + + def BreakThePage?(h) + if ((@y + h) > @page_break_trigger and !@in_footer and AcceptPageBreak()) + true + else + false + end + end + alias_method :break_the_page?, :BreakThePage? + # + # Prints a cell (rectangular area) with optional borders, background color and character string. The upper-left corner of the cell corresponds to the current position. The text can be aligned or centered. After the call, the current position moves to the right or to the next line. It is possible to put a link on the text.
    + # If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting. + # @param float :w Cell width. If 0, the cell extends up to the right margin. + # @param float :h Cell height. Default value: 0. + # @param string :txt String to print. Default value: empty string. + # @param mixed :border Indicates if borders must be drawn around the cell. The value can be either a number:
    • 0: no border (default)
    • 1: frame
    or a string containing some or all of the following characters (in any order):
    • L: left
    • T: top
    • R: right
    • B: bottom
    + # @param int :ln Indicates where the current position should go after the call. Possible values are:
    • 0: to the right
    • 1: to the beginning of the next line
    • 2: below
    + # Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0. + # @param string :align Allows to center or align the text. Possible values are:
    • L or empty string: left align (default value)
    • C: center
    • R: right align
    + # @param int :fill Indicates if the cell background must be painted (1) or transparent (0). Default value: 0. + # @param mixed :link URL or identifier returned by AddLink(). + # @since 1.0 + # @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), AddLink(), Ln(), MultiCell(), Write(), SetAutoPageBreak() + # + def Cell(w, h=0, txt='', border=0, ln=0, align='', fill=0, link=nil) + #Output a cell + k=@k; + if ((@y + h) > @page_break_trigger and !@in_footer and AcceptPageBreak()) + #Automatic page break + if @pages[@page+1].nil? + x = @x; + ws = @ws; + if (ws > 0) + @ws = 0; + out('0 Tw'); + end + AddPage(@cur_orientation); + @x = x; + if (ws > 0) + @ws = ws; + out(sprintf('%.3f Tw', ws * k)); + end + else + @page += 1; + @y=@t_margin; + end + end + + if (w == 0) + w = @w - @r_margin - @x; + end + s = ''; + if ((fill.to_i == 1) or (border.to_i == 1)) + if (fill.to_i == 1) + op = (border.to_i == 1) ? 'B' : 'f'; + else + op = 'S'; + end + s = sprintf('%.2f %.2f %.2f %.2f re %s ', @x * k, (@h - @y) * k, w * k, -h * k, op); + end + if (border.is_a?(String)) + x=@x; + y=@y; + if (border.include?('L')) + s<0) + # Go to next line + @y += h; + if (ln == 1) + @x = @l_margin; + end + else + @x += w; + end + end + alias_method :cell, :Cell + + # + # This method allows printing text with line breaks. They can be automatic (as soon as the text reaches the right border of the cell) or explicit (via the \n character). As many cells as necessary are output, one below the other.
    + # Text can be aligned, centered or justified. The cell block can be framed and the background painted. + # @param float :w Width of cells. If 0, they extend up to the right margin of the page. + # @param float :h Height of cells. + # @param string :txt String to print + # @param mixed :border Indicates if borders must be drawn around the cell block. The value can be either a number:
    • 0: no border (default)
    • 1: frame
    or a string containing some or all of the following characters (in any order):
    • L: left
    • T: top
    • R: right
    • B: bottom
    + # @param string :align Allows to center or align the text. Possible values are:
    • L or empty string: left align
    • C: center
    • R: right align
    • J: justification (default value)
    + # @param int :fill Indicates if the cell background must be painted (1) or transparent (0). Default value: 0. + # @param int :ln Indicates where the current position should go after the call. Possible values are:
    • 0: to the right
    • 1: to the beginning of the next line [DEFAULT]
    • 2: below
    + # @since 1.3 + # @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), Cell(), Write(), SetAutoPageBreak() + # + def MultiCell(w, h, txt, border=0, align='J', fill=0, ln=1) + + # save current position + prevx = @x; + prevy = @y; + prevpage = @page; + + #Output text with automatic or explicit line breaks + + if (w == 0) + w = @w - @r_margin - @x; + end + + wmax = (w - 3 * @c_margin); + + s = txt.gsub("\r", ''); # remove carriage returns + nb = s.length; + + b=0; + if (border) + if (border==1) + border='LTRB'; + b='LRT'; + b2='LR'; + elsif border.is_a?(String) + b2=''; + if (border.include?('L')) + b2<<'L'; + end + if (border.include?('R')) + b2<<'R'; + end + b=(border.include?('T')) ? b2 + 'T' : b2; + end + end + sep=-1; + to_index=0; + from_j=0; + l=0; + ns=0; + nl=1; + + while to_index < nb + #Get next character + c = s[to_index]; + if c == "\n"[0] + #Explicit line break + if @ws > 0 + @ws = 0 + out('0 Tw') + end + #Ed Moss - change begin + end_i = to_index == 0 ? 0 : to_index - 1 + # Changed from s[from_j..to_index] to fix bug reported by Hans Allis. + from_j = to_index == 0 ? 1 : from_j + Cell(w, h, s[from_j..end_i], b, 2, align, fill) + #change end + to_index += 1 + sep=-1 + from_j=to_index + l=0 + ns=0 + nl += 1 + b = b2 if border and nl==2 + next + end + if (c == " "[0]) + sep = to_index; + ls = l; + ns += 1; + end + + l = GetStringWidth(s[from_j, to_index - from_j]); + + if (l > wmax) + #Automatic line break + if (sep == -1) + if (to_index == from_j) + to_index += 1; + end + if (@ws > 0) + @ws = 0; + out('0 Tw'); + end + Cell(w, h, s[from_j..to_index-1], b, 2, align, fill) # my FPDF version + else + if (align=='J' || align=='justify' || align=='justified') + @ws = (ns>1) ? (wmax-ls)/(ns-1) : 0; + out(sprintf('%.3f Tw', @ws * @k)); + end + Cell(w, h, s[from_j..sep], b, 2, align, fill); + to_index = sep + 1; + end + sep=-1; + from_j = to_index; + l=0; + ns=0; + nl += 1; + if (border and (nl==2)) + b = b2; + end + else + to_index += 1; + end + end + #Last chunk + if (@ws>0) + @ws=0; + out('0 Tw'); + end + if (border.is_a?(String) and border.include?('B')) + b<<'B'; + end + Cell(w, h, s[from_j, to_index-from_j], b, 2, align, fill); + + # move cursor to specified position + # since 2007-03-03 + if (ln == 1) + # go to the beginning of the next line + @x = @l_margin; + elsif (ln == 0) + # go to the top-right of the cell + @page = prevpage; + @y = prevy; + @x = prevx + w; + elsif (ln == 2) + # go to the bottom-left of the cell + @x = prevx; + end + end + alias_method :multi_cell, :MultiCell + + # + # This method prints text from the current position. When the right margin is reached (or the \n character is met) a line break occurs and text continues from the left margin. Upon method exit, the current position is left just at the end of the text. It is possible to put a link on the text.
    + # Example:
    + #
    +	# #Begin with regular font
    +	# :pdf->SetFont('Arial','',14);
    +	# :pdf->Write(5,'Visit ');
    +	# #Then put a blue underlined link
    +	# :pdf->SetTextColor(0,0,255);
    +	# :pdf->SetFont('','U');
    +	# :pdf->Write(5,'www.tecnick.com','http://www.tecnick.com');
    +	# 
    + # @param float :h Line height + # @param string :txt String to print + # @param mixed :link URL or identifier returned by AddLink() + # @param int :fill Indicates if the background must be painted (1) or transparent (0). Default value: 0. + # @since 1.5 + # @see SetFont(), SetTextColor(), AddLink(), MultiCell(), SetAutoPageBreak() + # + def Write(h, txt, link=nil, fill=0) + + #Output text in flowing mode + w = @w - @r_margin - @x; + wmax = (w - 3 * @c_margin); + + s = txt.gsub("\r", ''); + nb = s.length; + + # handle single space character + if ((nb==1) and (s == " ")) + @x += GetStringWidth(s); + return; + end + + sep=-1; + i=0; + j=0; + l=0; + nl=1; + while(i wmax) + #Automatic line break (word wrapping) + if (sep == -1) + if (@x > @l_margin) + #Move to next line + @x = @l_margin; + @y += h; + w=@w - @r_margin - @x; + wmax=(w - 3 * @c_margin); + i += 1 + nl += 1 + next + end + if (i == j) + i += 1 + end + Cell(w, h, s[j, (i-1)], 0, 2, '', fill, link); + else + Cell(w, h, s[j, (sep-j)], 0, 2, '', fill, link); + i = sep+1; + end + sep = -1; + j = i; + l = 0; + if (nl==1) + @x = @l_margin; + w = @w - @r_margin - @x; + wmax = (w - 3 * @c_margin); + end + nl += 1; + else + i += 1; + end + end + #Last chunk + if (i != j) + Cell(GetStringWidth(s[j..i]), h, s[j..i], 0, 0, '', fill, link); + end + end + alias_method :write, :Write + + # + # Puts an image in the page. The upper-left corner must be given. The dimensions can be specified in different ways:
    • explicit width and height (expressed in user unit)
    • one explicit dimension, the other being calculated automatically in order to keep the original proportions
    • no explicit dimension, in which case the image is put at 72 dpi
    + # Supported formats are JPEG and PNG. + # For JPEG, all flavors are allowed:
    • gray scales
    • true colors (24 bits)
    • CMYK (32 bits)
    + # For PNG, are allowed:
    • gray scales on at most 8 bits (256 levels)
    • indexed colors
    • true colors (24 bits)
    + # but are not supported:
    • Interlacing
    • Alpha channel
    + # If a transparent color is defined, it will be taken into account (but will be only interpreted by Acrobat 4 and above).
    + # The format can be specified explicitly or inferred from the file extension.
    + # It is possible to put a link on the image.
    + # Remark: if an image is used several times, only one copy will be embedded in the file.
    + # @param string :file Name of the file containing the image. + # @param float :x Abscissa of the upper-left corner. + # @param float :y Ordinate of the upper-left corner. + # @param float :w Width of the image in the page. If not specified or equal to zero, it is automatically calculated. + # @param float :h Height of the image in the page. If not specified or equal to zero, it is automatically calculated. + # @param string :type Image format. Possible values are (case insensitive): JPG, JPEG, PNG. If not specified, the type is inferred from the file extension. + # @param mixed :link URL or identifier returned by AddLink(). + # @since 1.1 + # @see AddLink() + # + def Image(file, x, y, w=0, h=0, type='', link=nil) + #Put an image on the page + if (@images[file].nil?) + #First use of image, get info + if (type == '') + pos = File::basename(file).rindex('.'); + if (pos.nil? or pos == 0) + Error('Image file has no extension and no type was specified: ' + file); + end + pos = file.rindex('.'); + type = file[pos+1..-1]; + end + type.downcase! + if (type == 'jpg' or type == 'jpeg') + info=parsejpg(file); + elsif (type == 'png') + info=parsepng(file); + elsif (type == 'gif') + tmpFile = imageToPNG(file); + info=parsepng(tmpFile.path); + tmpFile.delete + else + #Allow for additional formats + mtd='parse' + type; + if (!self.respond_to?(mtd)) + Error('Unsupported image type: ' + type); + end + info=send(mtd, file); + end + info['i']=@images.length+1; + @images[file] = info; + else + info=@images[file]; + end + #Automatic width and height calculation if needed + if ((w == 0) and (h == 0)) + rescale_x = (@w - @r_margin - x) / (info['w'] / (@img_scale * @k)) + rescale_x = 1 if rescale_x >= 1 + if (y + info['h'] * rescale_x / (@img_scale * @k) > @page_break_trigger and !@in_footer and AcceptPageBreak()) + #Automatic page break + if @pages[@page+1].nil? + ws = @ws; + if (ws > 0) + @ws = 0; + out('0 Tw'); + end + AddPage(@cur_orientation); + if (ws > 0) + @ws = ws; + out(sprintf('%.3f Tw', ws * @k)); + end + else + @page += 1; + end + y=@t_margin; + end + rescale_y = (@page_break_trigger - y) / (info['h'] / (@img_scale * @k)) + rescale_y = 1 if rescale_y >= 1 + rescale = rescale_y >= rescale_x ? rescale_x : rescale_y + + #Put image at 72 dpi + # 2004-06-14 :: Nicola Asuni, scale factor where added + w = info['w'] * rescale / (@img_scale * @k); + h = info['h'] * rescale / (@img_scale * @k); + elsif (w == 0) + w = h * info['w'] / info['h']; + elsif (h == 0) + h = w * info['h'] / info['w']; + end + out(sprintf('q %.2f 0 0 %.2f %.2f %.2f cm /I%d Do Q', w*@k, h*@k, x*@k, (@h-(y+h))*@k, info['i'])); + if (link) + Link(x, y, w, h, link); + end + + #2002-07-31 - Nicola Asuni + # set right-bottom corner coordinates + @img_rb_x = x + w; + @img_rb_y = y + h; + end + alias_method :image, :Image + + # + # Performs a line break. The current abscissa goes back to the left margin and the ordinate increases by the amount passed in parameter. + # @param float :h The height of the break. By default, the value equals the height of the last printed cell. + # @since 1.0 + # @see Cell() + # + def Ln(h='') + #Line feed; default value is last cell height + @x=@l_margin; + if (h.is_a?(String)) + @y += @lasth; + else + @y += h; + end + + k=@k; + if (@y > @page_break_trigger and !@in_footer and AcceptPageBreak()) + #Automatic page break + if @pages[@page+1].nil? + x = @x; + ws = @ws; + if (ws > 0) + @ws = 0; + out('0 Tw'); + end + AddPage(@cur_orientation); + @x = x; + if (ws > 0) + @ws = ws; + out(sprintf('%.3f Tw', ws * k)); + end + else + @page += 1; + @y=@t_margin; + end + end + + end + alias_method :ln, :Ln + + # + # Returns the abscissa of the current position. + # @return float + # @since 1.2 + # @see SetX(), GetY(), SetY() + # + def GetX() + #Get x position + return @x; + end + alias_method :get_x, :GetX + + # + # Defines the abscissa of the current position. If the passed value is negative, it is relative to the right of the page. + # @param float :x The value of the abscissa. + # @since 1.2 + # @see GetX(), GetY(), SetY(), SetXY() + # + def SetX(x) + #Set x position + if (x>=0) + @x = x; + else + @x=@w+x; + end + end + alias_method :set_x, :SetX + + # + # Returns the ordinate of the current position. + # @return float + # @since 1.0 + # @see SetY(), GetX(), SetX() + # + def GetY() + #Get y position + return @y; + end + alias_method :get_y, :GetY + + # + # Moves the current abscissa back to the left margin and sets the ordinate. If the passed value is negative, it is relative to the bottom of the page. + # @param float :y The value of the ordinate. + # @since 1.0 + # @see GetX(), GetY(), SetY(), SetXY() + # + def SetY(y) + #Set y position and reset x + @x=@l_margin; + if (y>=0) + @y = y; + else + @y=@h+y; + end + end + alias_method :set_y, :SetY + + # + # Defines the abscissa and ordinate of the current position. If the passed values are negative, they are relative respectively to the right and bottom of the page. + # @param float :x The value of the abscissa + # @param float :y The value of the ordinate + # @since 1.2 + # @see SetX(), SetY() + # + def SetXY(x, y) + #Set x and y positions + SetY(y); + SetX(x); + end + alias_method :set_xy, :SetXY + + # + # Send the document to a given destination: string, local file or browser. In the last case, the plug-in may be used (if present) or a download ("Save as" dialog box) may be forced.
    + # The method first calls Close() if necessary to terminate the document. + # @param string :name The name of the file. If not given, the document will be sent to the browser (destination I) with the name doc.pdf. + # @param string :dest Destination where to send the document. It can take one of the following values:
    • I: send the file inline to the browser. The plug-in is used if available. The name given by name is used when one selects the "Save as" option on the link generating the PDF.
    • D: send to the browser and force a file download with the name given by name.
    • F: save to a local file with the name given by name.
    • S: return the document as a string. name is ignored.
    If the parameter is not specified but a name is given, destination is F. If no parameter is specified at all, destination is I.
    + # @since 1.0 + # @see Close() + # + def Output(name='', dest='') + #Output PDF to some destination + #Finish document if necessary + if (@state < 3) + Close(); + end + #Normalize parameters + # Boolean no longer supported + # if (dest.is_a?(Boolean)) + # dest = dest ? 'D' : 'F'; + # end + dest = dest.upcase + if (dest=='') + if (name=='') + name='doc.pdf'; + dest='I'; + else + dest='F'; + end + end + case (dest) + when 'I' + # This is PHP specific code + ##Send to standard output + # if (ob_get_contents()) + # Error('Some data has already been output, can\'t send PDF file'); + # end + # if (php_sapi_name()!='cli') + # #We send to a browser + # header('Content-Type: application/pdf'); + # if (headers_sent()) + # Error('Some data has already been output to browser, can\'t send PDF file'); + # end + # header('Content-Length: ' + @buffer.length); + # header('Content-disposition: inline; filename="' + name + '"'); + # end + return @buffer; + + when 'D' + # PHP specific + #Download file + # if (ob_get_contents()) + # Error('Some data has already been output, can\'t send PDF file'); + # end + # if (!_SERVER['HTTP_USER_AGENT'].nil? && SERVER['HTTP_USER_AGENT'].include?('MSIE')) + # header('Content-Type: application/force-download'); + # else + # header('Content-Type: application/octet-stream'); + # end + # if (headers_sent()) + # Error('Some data has already been output to browser, can\'t send PDF file'); + # end + # header('Content-Length: '+ @buffer.length); + # header('Content-disposition: attachment; filename="' + name + '"'); + return @buffer; + + when 'F' + open(name,'wb') do |f| + f.write(@buffer) + end + # PHP code + # #Save to local file + # f=open(name,'wb'); + # if (!f) + # Error('Unable to create output file: ' + name); + # end + # fwrite(f,@buffer,@buffer.length); + # f.close + + when 'S' + #Return as a string + return @buffer; + else + Error('Incorrect output destination: ' + dest); + + end + return ''; + end + alias_method :output, :Output + + # Protected methods + + # + # Check for locale-related bug + # @access protected + # + def dochecks() + #Check for locale-related bug + if (1.1==1) + Error('Don\'t alter the locale before including class file'); + end + #Check for decimal separator + if (sprintf('%.1f',1.0)!='1.0') + setlocale(LC_NUMERIC,'C'); + end + end + + # + # Return fonts path + # @access protected + # + def getfontpath(file) + # Is it in the @@font_path? + if @@font_path + fpath = File.join @@font_path, file + if File.exists?(fpath) + return fpath + end + end + # Is it in this plugin's font folder? + fpath = File.join File.dirname(__FILE__), 'fonts', file + if File.exists?(fpath) + return fpath + end + # Could not find it. + nil + end + + # + # Start document + # @access protected + # + def begindoc() + #Start document + @state=1; + out('%PDF-1.3'); + end + + # + # putpages + # @access protected + # + def putpages() + nb = @page; + if (@alias_nb_pages) + nbstr = UTF8ToUTF16BE(nb.to_s, false); + #Replace number of pages + 1.upto(nb) do |n| + @pages[n].gsub!(@alias_nb_pages, nbstr) + end + end + if @def_orientation=='P' + w_pt=@fw_pt + h_pt=@fh_pt + else + w_pt=@fh_pt + h_pt=@fw_pt + end + filter=(@compress) ? '/Filter /FlateDecode ' : '' + 1.upto(nb) do |n| + #Page + newobj + out('<>>>'; + else + l=@links[pl[4]]; + h=!@orientation_changes[l[0]].nil? ? w_pt : h_pt; + annots<>',1+2*l[0], h-l[1]*@k); + end + end + out(annots + ']'); + end + out('/Contents ' + (@n+1).to_s + ' 0 R>>'); + out('endobj'); + #Page content + p=(@compress) ? gzcompress(@pages[n]) : @pages[n]; + newobj(); + out('<<' + filter + '/Length '+ p.length.to_s + '>>'); + putstream(p); + out('endobj'); + end + #Pages root + @offsets[1]=@buffer.length; + out('1 0 obj'); + out('<>'); + out('endobj'); + end + + # + # Adds fonts + # putfonts + # @access protected + # + def putfonts() + nf=@n; + @diffs.each do |diff| + #Encodings + newobj(); + out('<>'); + out('endobj'); + end + @font_files.each do |file, info| + #Font file embedding + newobj(); + @font_files[file]['n']=@n; + font=''; + open(getfontpath(file),'rb') do |f| + font = f.read(); + end + compressed=(file[-2,2]=='.z'); + if (!compressed && !info['length2'].nil?) + header=((font[0][0])==128); + if (header) + #Strip first binary header + font=font[6]; + end + if header && (font[info['length1']][0] == 128) + #Strip second binary header + font=font[0..info['length1']] + font[info['length1']+6]; + end + end + out('<>'); + open(getfontpath(file),'rb') do |f| + putstream(font) + end + out('endobj'); + end + @fonts.each do |k, font| + #Font objects + @fonts[k]['n']=@n+1; + type = font['type']; + name = font['name']; + if (type=='core') + #Standard font + newobj(); + out('<>'); + out('endobj'); + elsif type == 'Type0' + putType0(font) + elsif (type=='Type1' || type=='TrueType') + #Additional Type1 or TrueType font + newobj(); + out('<>'); + out('endobj'); + #Widths + newobj(); + cw=font['cw']; # & + s='['; + 32.upto(255) do |i| + s << cw[i.chr] + ' '; + end + out(s + ']'); + out('endobj'); + #Descriptor + newobj(); + s='<>'); + out('endobj'); + else + #Allow for additional types + mtd='put' + type.downcase; + if (!self.respond_to?(mtd)) + Error('Unsupported font type: ' + type) + else + self.send(mtd,font) + end + end + end + end + + def putType0(font) + #Type0 + newobj(); + out('<>') + out('endobj') + #CIDFont + newobj() + out('<>') + out('/FontDescriptor '+(@n+1).to_s+' 0 R') + w='/W [1 [' + font['cw'].keys.sort.each {|key| + w+=font['cw'][key].to_s + " " +# ActionController::Base::logger.debug key.to_s +# ActionController::Base::logger.debug font['cw'][key].to_s + } + out(w+'] 231 325 500 631 [500] 326 389 500]') + out('>>') + out('endobj') + #Font descriptor + newobj() + out('<>') + out('endobj') + end + + # + # putimages + # @access protected + # + def putimages() + filter=(@compress) ? '/Filter /FlateDecode ' : ''; + @images.each do |file, info| # was while(list(file, info)=each(@images)) + newobj(); + @images[file]['n']=@n; + out('<>'); + putstream(info['data']); + @images[file]['data']=nil + out('endobj'); + #Palette + if (info['cs']=='Indexed') + newobj(); + pal=(@compress) ? gzcompress(info['pal']) : info['pal']; + out('<<' + filter + '/Length ' + pal.length.to_s + '>>'); + putstream(pal); + out('endobj'); + end + end + end + + # + # putxobjectdict + # @access protected + # + def putxobjectdict() + @images.each_value do |image| + out('/I' + image['i'].to_s + ' ' + image['n'].to_s + ' 0 R'); + end + end + + # + # putresourcedict + # @access protected + # + def putresourcedict() + out('/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]'); + out('/Font <<'); + @fonts.each_value do |font| + out('/F' + font['i'].to_s + ' ' + font['n'].to_s + ' 0 R'); + end + out('>>'); + out('/XObject <<'); + putxobjectdict(); + out('>>'); + end + + # + # putresources + # @access protected + # + def putresources() + putfonts(); + putimages(); + #Resource dictionary + @offsets[2]=@buffer.length; + out('2 0 obj'); + out('<<'); + putresourcedict(); + out('>>'); + out('endobj'); + end + + # + # putinfo + # @access protected + # + def putinfo() + out('/Producer ' + textstring(PDF_PRODUCER)); + if (!@title.nil?) + out('/Title ' + textstring(@title)); + end + if (!@subject.nil?) + out('/Subject ' + textstring(@subject)); + end + if (!@author.nil?) + out('/Author ' + textstring(@author)); + end + if (!@keywords.nil?) + out('/Keywords ' + textstring(@keywords)); + end + if (!@creator.nil?) + out('/Creator ' + textstring(@creator)); + end + out('/CreationDate ' + textstring('D:' + Time.now.strftime('%Y%m%d%H%M%S'))); + end + + # + # putcatalog + # @access protected + # + def putcatalog() + out('/Type /Catalog'); + out('/Pages 1 0 R'); + if (@zoom_mode=='fullpage') + out('/OpenAction [3 0 R /Fit]'); + elsif (@zoom_mode=='fullwidth') + out('/OpenAction [3 0 R /FitH null]'); + elsif (@zoom_mode=='real') + out('/OpenAction [3 0 R /XYZ null null 1]'); + elsif (!@zoom_mode.is_a?(String)) + out('/OpenAction [3 0 R /XYZ null null ' + (@zoom_mode/100) + ']'); + end + if (@layout_mode=='single') + out('/PageLayout /SinglePage'); + elsif (@layout_mode=='continuous') + out('/PageLayout /OneColumn'); + elsif (@layout_mode=='two') + out('/PageLayout /TwoColumnLeft'); + end + end + + # + # puttrailer + # @access protected + # + def puttrailer() + out('/Size ' + (@n+1).to_s); + out('/Root ' + @n.to_s + ' 0 R'); + out('/Info ' + (@n-1).to_s + ' 0 R'); + end + + # + # putheader + # @access protected + # + def putheader() + out('%PDF-' + @pdf_version); + end + + # + # enddoc + # @access protected + # + def enddoc() + putheader(); + putpages(); + putresources(); + #Info + newobj(); + out('<<'); + putinfo(); + out('>>'); + out('endobj'); + #Catalog + newobj(); + out('<<'); + putcatalog(); + out('>>'); + out('endobj'); + #Cross-ref + o=@buffer.length; + out('xref'); + out('0 ' + (@n+1).to_s); + out('0000000000 65535 f '); + 1.upto(@n) do |i| + out(sprintf('%010d 00000 n ',@offsets[i])); + end + #Trailer + out('trailer'); + out('<<'); + puttrailer(); + out('>>'); + out('startxref'); + out(o); + out('%%EOF'); + @state=3; + end + + # + # beginpage + # @access protected + # + def beginpage(orientation) + @page += 1; + @pages[@page]=''; + @state=2; + @x=@l_margin; + @y=@t_margin; + @font_family=''; + #Page orientation + if (orientation.empty?) + orientation=@def_orientation; + else + orientation.upcase! + if (orientation!=@def_orientation) + @orientation_changes[@page]=true; + end + end + if (orientation!=@cur_orientation) + #Change orientation + if (orientation=='P') + @w_pt=@fw_pt; + @h_pt=@fh_pt; + @w=@fw; + @h=@fh; + else + @w_pt=@fh_pt; + @h_pt=@fw_pt; + @w=@fh; + @h=@fw; + end + @page_break_trigger=@h-@b_margin; + @cur_orientation = orientation; + end + end + + # + # End of page contents + # @access protected + # + def endpage() + @state=1; + end + + # + # Begin a new object + # @access protected + # + def newobj() + @n += 1; + @offsets[@n]=@buffer.length; + out(@n.to_s + ' 0 obj'); + end + + # + # Underline and Deleted text + # @access protected + # + def dolinetxt(x, y, txt) + up = @current_font['up']; + ut = @current_font['ut']; + w = GetStringWidth(txt) + @ws * txt.count(' '); + sprintf('%.2f %.2f %.2f %.2f re f', x * @k, (@h - (y - up / 1000.0 * @font_size)) * @k, w * @k, -ut / 1000.0 * @font_size_pt); + end + + # + # Extract info from a JPEG file + # @access protected + # + def parsejpg(file) + a=getimagesize(file); + if (a.empty?) + Error('Missing or incorrect image file: ' + file); + end + if (!a[2].nil? and a[2]!='JPEG') + Error('Not a JPEG file: ' + file); + end + if (a['channels'].nil? or a['channels']==3) + colspace='DeviceRGB'; + elsif (a['channels']==4) + colspace='DeviceCMYK'; + else + colspace='DeviceGray'; + end + bpc=!a['bits'].nil? ? a['bits'] : 8; + #Read whole file + data=''; + + open(file,'rb') do |f| + data< a[0],'h' => a[1],'cs' => colspace,'bpc' => bpc,'f'=>'DCTDecode','data' => data} + end + + def imageToPNG(file) + return unless Object.const_defined?(:Magick) + + img = Magick::ImageList.new(file) + img.format = 'PNG' # convert to PNG from gif + img.opacity = 0 # PNG alpha channel delete + + #use a temporary file.... + tmpFile = Tempfile.new(['', '_' + File::basename(file) + '.png'], @@k_path_cache); + tmpFile.binmode + tmpFile.print img.to_blob + tmpFile + ensure + tmpFile.close + end + + # + # Extract info from a PNG file + # @access protected + # + def parsepng(file) + f=open(file,'rb'); + #Check signature + if (f.read(8)!=137.chr + 'PNG' + 13.chr + 10.chr + 26.chr + 10.chr) + Error('Not a PNG file: ' + file); + end + #Read header chunk + f.read(4); + if (f.read(4)!='IHDR') + Error('Incorrect PNG file: ' + file); + end + w=freadint(f); + h=freadint(f); + bpc=f.read(1).unpack('C')[0]; + if (bpc>8) + Error('16-bit depth not supported: ' + file); + end + ct=f.read(1).unpack('C')[0]; + if (ct==0) + colspace='DeviceGray'; + elsif (ct==2) + colspace='DeviceRGB'; + elsif (ct==3) + colspace='Indexed'; + else + Error('Alpha channel not supported: ' + file); + end + if (f.read(1).unpack('C')[0] != 0) + Error('Unknown compression method: ' + file); + end + if (f.read(1).unpack('C')[0] != 0) + Error('Unknown filter method: ' + file); + end + if (f.read(1).unpack('C')[0] != 0) + Error('Interlacing not supported: ' + file); + end + f.read(4); + parms='/DecodeParms <>'; + #Scan chunks looking for palette, transparency and image data + pal=''; + trns=''; + data=''; + begin + n=freadint(f); + type=f.read(4); + if (type=='PLTE') + #Read palette + pal=f.read( n); + f.read(4); + elsif (type=='tRNS') + #Read transparency info + t=f.read( n); + if (ct==0) + trns = t[1].unpack('C')[0] + elsif (ct==2) + trns = t[[1].unpack('C')[0], t[3].unpack('C')[0], t[5].unpack('C')[0]] + else + pos=t.index(0.chr); + unless (pos.nil?) + trns = [pos] + end + end + f.read(4); + elsif (type=='IDAT') + #Read image data block + data< w, 'h' => h, 'cs' => colspace, 'bpc' => bpc, 'f'=>'FlateDecode', 'parms' => parms, 'pal' => pal, 'trns' => trns, 'data' => data} + ensure + f.close + end + + # + # Read a 4-byte integer from file + # @access protected + # + def freadint(f) + # Read a 4-byte integer from file + a = f.read(4).unpack('N') + return a[0] + end + + # + # Format a text string + # @access protected + # + def textstring(s) + if (@is_unicode) + #Convert string to UTF-16BE + s = UTF8ToUTF16BE(s, true); + end + return '(' + escape(s) + ')'; + end + + # + # Format a text string + # @access protected + # + def escapetext(s) + if (@is_unicode) + #Convert string to UTF-16BE + s = UTF8ToUTF16BE(s, false); + end + return escape(s); + end + + # + # Add \ before \, ( and ) + # @access protected + # + def escape(s) + # Add \ before \, ( and ) + s.gsub('\\','\\\\\\').gsub('(','\\(').gsub(')','\\)').gsub(13.chr, '\r') + end + + # + # + # @access protected + # + def putstream(s) + out('stream'); + out(s); + out('endstream'); + end + + # + # Add a line to the document + # @access protected + # + def out(s) + if (@state==2) + @pages[@page] << s.to_s + "\n"; + else + @buffer << s.to_s + "\n"; + end + end + + # + # Adds unicode fonts.
    + # Based on PDF Reference 1.3 (section 5) + # @access protected + # @author Nicola Asuni + # @since 1.52.0.TC005 (2005-01-05) + # + def puttruetypeunicode(font) + # Type0 Font + # A composite font composed of other fonts, organized hierarchically + newobj(); + out('<>'); + out('endobj'); + + # CIDFontType2 + # A CIDFont whose glyph descriptions are based on TrueType font technology + newobj(); + out('<>'); + out('endobj'); + + # ToUnicode + # is a stream object that contains the definition of the CMap + # (PDF Reference 1.3 chap. 5.9) + newobj(); + out('<>'); + out('stream'); + out('/CIDInit /ProcSet findresource begin'); + out('12 dict begin'); + out('begincmap'); + out('/CIDSystemInfo'); + out('<> def'); + out('/CMapName /Adobe-Identity-UCS def'); + out('/CMapType 2 def'); + out('1 begincodespacerange'); + out('<0000> '); + out('endcodespacerange'); + out('1 beginbfrange'); + out('<0000> <0000>'); + out('endbfrange'); + out('endcmap'); + out('CMapName currentdict /CMap defineresource pop'); + out('end'); + out('end'); + out('endstream'); + out('endobj'); + + # CIDSystemInfo dictionary + # A dictionary containing entries that define the character collection of the CIDFont. + newobj(); + out('<>'); + out('endobj'); + + # Font descriptor + # A font descriptor describing the CIDFont default metrics other than its glyph widths + newobj(); + out('<>'); + out('endobj'); + + # Embed CIDToGIDMap + # A specification of the mapping from CIDs to glyph indices + newobj(); + ctgfile = getfontpath(font['ctg']) + if (!ctgfile) + Error('Font file not found: ' + ctgfile); + end + size = File.size(ctgfile); + out('<>'); + open(ctgfile, "rb") do |f| + putstream(f.read()) + end + out('endobj'); + end + + # + # Converts UTF-8 strings to codepoints array.
    + # Invalid byte sequences will be replaced with 0xFFFD (replacement character)
    + # Based on: http://www.faqs.org/rfcs/rfc3629.html + #
    +	# 	  Char. number range  |        UTF-8 octet sequence
    +	#       (hexadecimal)    |              (binary)
    +	#    --------------------+-----------------------------------------------
    +	#    0000 0000-0000 007F | 0xxxxxxx
    +	#    0000 0080-0000 07FF | 110xxxxx 10xxxxxx
    +	#    0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
    +	#    0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
    +	#    ---------------------------------------------------------------------
    +	#
    +	#   ABFN notation:
    +	#   ---------------------------------------------------------------------
    +	#   UTF8-octets =#( UTF8-char )
    +	#   UTF8-char   = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
    +	#   UTF8-1      = %x00-7F
    +	#   UTF8-2      = %xC2-DF UTF8-tail
    +	#
    +	#   UTF8-3      = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
    +	#                 %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
    +	#   UTF8-4      = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
    +	#                 %xF4 %x80-8F 2( UTF8-tail )
    +	#   UTF8-tail   = %x80-BF
    +	#   ---------------------------------------------------------------------
    +	# 
    + # @param string :str string to process. + # @return array containing codepoints (UTF-8 characters values) + # @access protected + # @author Nicola Asuni + # @since 1.53.0.TC005 (2005-01-05) + # + def UTF8StringToArray(str) + if (!@is_unicode) + return str; # string is not in unicode + end + + unicode = [] # array containing unicode values + bytes = [] # array containing single character byte sequences + numbytes = 1; # number of octetc needed to represent the UTF-8 character + + str = str.to_s; # force :str to be a string + + str.each_byte do |char| + if (bytes.length == 0) # get starting octect + if (char <= 0x7F) + unicode << char # use the character "as is" because is ASCII + numbytes = 1 + elsif ((char >> 0x05) == 0x06) # 2 bytes character (0x06 = 110 BIN) + bytes << ((char - 0xC0) << 0x06) + numbytes = 2 + elsif ((char >> 0x04) == 0x0E) # 3 bytes character (0x0E = 1110 BIN) + bytes << ((char - 0xE0) << 0x0C) + numbytes = 3 + elsif ((char >> 0x03) == 0x1E) # 4 bytes character (0x1E = 11110 BIN) + bytes << ((char - 0xF0) << 0x12) + numbytes = 4 + else + # use replacement character for other invalid sequences + unicode << 0xFFFD + bytes = [] + numbytes = 1 + end + elsif ((char >> 0x06) == 0x02) # bytes 2, 3 and 4 must start with 0x02 = 10 BIN + bytes << (char - 0x80) + if (bytes.length == numbytes) + # compose UTF-8 bytes to a single unicode value + char = bytes[0] + 1.upto(numbytes-1) do |j| + char += (bytes[j] << ((numbytes - j - 1) * 0x06)) + end + if (((char >= 0xD800) and (char <= 0xDFFF)) or (char >= 0x10FFFF)) + # The definition of UTF-8 prohibits encoding character numbers between + # U+D800 and U+DFFF, which are reserved for use with the UTF-16 + # encoding form (as surrogate pairs) and do not directly represent + # characters + unicode << 0xFFFD; # use replacement character + else + unicode << char; # add char to array + end + # reset data for next char + bytes = [] + numbytes = 1; + end + else + # use replacement character for other invalid sequences + unicode << 0xFFFD; + bytes = [] + numbytes = 1; + end + end + return unicode; + end + + # + # Converts UTF-8 strings to UTF16-BE.
    + # Based on: http://www.faqs.org/rfcs/rfc2781.html + #
    +	#   Encoding UTF-16:
    +	# 
    +		#   Encoding of a single character from an ISO 10646 character value to
    +	#    UTF-16 proceeds as follows. Let U be the character number, no greater
    +	#    than 0x10FFFF.
    +	# 
    +	#    1) If U < 0x10000, encode U as a 16-bit unsigned integer and
    +	#       terminate.
    +	# 
    +	#    2) Let U' = U - 0x10000. Because U is less than or equal to 0x10FFFF,
    +	#       U' must be less than or equal to 0xFFFFF. That is, U' can be
    +	#       represented in 20 bits.
    +	# 
    +	#    3) Initialize two 16-bit unsigned integers, W1 and W2, to 0xD800 and
    +	#       0xDC00, respectively. These integers each have 10 bits free to
    +	#       encode the character value, for a total of 20 bits.
    +	# 
    +	#    4) Assign the 10 high-order bits of the 20-bit U' to the 10 low-order
    +	#       bits of W1 and the 10 low-order bits of U' to the 10 low-order
    +	#       bits of W2. Terminate.
    +	# 
    +	#    Graphically, steps 2 through 4 look like:
    +	#    U' = yyyyyyyyyyxxxxxxxxxx
    +	#    W1 = 110110yyyyyyyyyy
    +	#    W2 = 110111xxxxxxxxxx
    +	# 
    + # @param string :str string to process. + # @param boolean :setbom if true set the Byte Order Mark (BOM = 0xFEFF) + # @return string + # @access protected + # @author Nicola Asuni + # @since 1.53.0.TC005 (2005-01-05) + # @uses UTF8StringToArray + # + def UTF8ToUTF16BE(str, setbom=true) + if (!@is_unicode) + return str; # string is not in unicode + end + outstr = ""; # string to be returned + unicode = UTF8StringToArray(str); # array containing UTF-8 unicode values + numitems = unicode.length; + + if (setbom) + outstr << "\xFE\xFF"; # Byte Order Mark (BOM) + end + unicode.each do |char| + if (char == 0xFFFD) + outstr << "\xFF\xFD"; # replacement character + elsif (char < 0x10000) + outstr << (char >> 0x08).chr; + outstr << (char & 0xFF).chr; + else + char -= 0x10000; + w1 = 0xD800 | (char >> 0x10); + w2 = 0xDC00 | (char & 0x3FF); + outstr << (w1 >> 0x08).chr; + outstr << (w1 & 0xFF).chr; + outstr << (w2 >> 0x08).chr; + outstr << (w2 & 0xFF).chr; + end + end + return outstr; + end + + # ==================================================== + + # + # Set header font. + # @param array :font font + # @since 1.1 + # + def SetHeaderFont(font) + @header_font = font; + end + alias_method :set_header_font, :SetHeaderFont + + # + # Set footer font. + # @param array :font font + # @since 1.1 + # + def SetFooterFont(font) + @footer_font = font; + end + alias_method :set_footer_font, :SetFooterFont + + # + # Set language array. + # @param array :language + # @since 1.1 + # + def SetLanguageArray(language) + @l = language; + end + alias_method :set_language_array, :SetLanguageArray + # + # Set document barcode. + # @param string :bc barcode + # + def SetBarcode(bc="") + @barcode = bc; + end + + # + # Print Barcode. + # @param int :x x position in user units + # @param int :y y position in user units + # @param int :w width in user units + # @param int :h height position in user units + # @param string :type type of barcode (I25, C128A, C128B, C128C, C39) + # @param string :style barcode style + # @param string :font font for text + # @param int :xres x resolution + # @param string :code code to print + # + def writeBarcode(x, y, w, h, type, style, font, xres, code) + require(File.dirname(__FILE__) + "/barcode/barcode.rb"); + require(File.dirname(__FILE__) + "/barcode/i25object.rb"); + require(File.dirname(__FILE__) + "/barcode/c39object.rb"); + require(File.dirname(__FILE__) + "/barcode/c128aobject.rb"); + require(File.dirname(__FILE__) + "/barcode/c128bobject.rb"); + require(File.dirname(__FILE__) + "/barcode/c128cobject.rb"); + + if (code.empty?) + return; + end + + if (style.empty?) + style = BCS_ALIGN_LEFT; + style |= BCS_IMAGE_PNG; + style |= BCS_TRANSPARENT; + #:style |= BCS_BORDER; + #:style |= BCS_DRAW_TEXT; + #:style |= BCS_STRETCH_TEXT; + #:style |= BCS_REVERSE_COLOR; + end + if (font.empty?) then font = BCD_DEFAULT_FONT; end + if (xres.empty?) then xres = BCD_DEFAULT_XRES; end + + scale_factor = 1.5 * xres * @k; + bc_w = (w * scale_factor).round #width in points + bc_h = (h * scale_factor).round #height in points + + case (type.upcase) + when "I25" + obj = I25Object.new(bc_w, bc_h, style, code); + when "C128A" + obj = C128AObject.new(bc_w, bc_h, style, code); + when "C128B" + obj = C128BObject.new(bc_w, bc_h, style, code); + when "C128C" + obj = C128CObject.new(bc_w, bc_h, style, code); + when "C39" + obj = C39Object.new(bc_w, bc_h, style, code); + end + + obj.SetFont(font); + obj.DrawObject(xres); + + #use a temporary file.... + tmpName = tempnam(@@k_path_cache,'img'); + imagepng(obj.getImage(), tmpName); + Image(tmpName, x, y, w, h, 'png'); + obj.DestroyObject(); + obj = nil + unlink(tmpName); + end + + # + # Returns the PDF data. + # + def GetPDFData() + if (@state < 3) + Close(); + end + return @buffer; + end + + # --- HTML PARSER FUNCTIONS --- + + # + # Allows to preserve some HTML formatting.
    + # Supports: h1, h2, h3, h4, h5, h6, b, u, i, a, img, p, br, strong, em, ins, del, font, blockquote, li, ul, ol, hr, td, th, tr, table, sup, sub, small + # @param string :html text to display + # @param boolean :ln if true add a new line after text (default = true) + # @param int :fill Indicates if the background must be painted (1) or transparent (0). Default value: 0. + # + def writeHTML(html, ln=true, fill=0, h=0) + + @lasth = h if h > 0 + if (@lasth == 0) + #set row height + @lasth = @font_size * @@k_cell_height_ratio; + end + + @href = nil + @style = ""; + @t_cells = [[]]; + @table_id = 0; + + # pre calculate + html.split(/(<[^>]+>)/).each do |element| + if "<" == element[0,1] + #Tag + if (element[1, 1] == '/') + closedHTMLTagCalc(element[2..-2].downcase); + else + #Extract attributes + # get tag name + tag = element.scan(/([a-zA-Z0-9]*)/).flatten.delete_if {|x| x.length == 0} + tag = tag[0].to_s.downcase; + + # get attributes + attr_array = element.scan(/([^=\s]*)=["\']?([^"\']*)["\']?/) + attrs = {} + attr_array.each do |name, value| + attrs[name.downcase] = value; + end + openHTMLTagCalc(tag, attrs); + end + end + end + @table_id = 0; + + html.split(/(<[A-Za-z!?\/][^>]*?>)/).each do |element| + if "<" == element[0,1] + #Tag + if (element[1, 1] == '/') + closedHTMLTagHandler(element[2..-2].downcase); + else + #Extract attributes + # get tag name + tag = element.scan(/([a-zA-Z0-9]*)/).flatten.delete_if {|x| x.length == 0} + tag = tag[0].to_s.downcase; + + # get attributes + attr_array = element.scan(/([^=\s]*)=["\']?([^"\']*)["\']?/) + attrs = {} + attr_array.each do |name, value| + attrs[name.downcase] = value; + end + openHTMLTagHandler(tag, attrs, fill); + end + + else + #Text + if (@tdbegin) + element.gsub!(/[\t\r\n\f]/, ""); + @tdtext << element.gsub(/ /, " "); + elsif (@href) + element.gsub!(/[\t\r\n\f]/, ""); + addHtmlLink(@href, element, fill); + elsif (@pre_state == true and element.length > 0) + Write(@lasth, unhtmlentities(element), '', fill); + elsif (element.strip.length > 0) + element.gsub!(/[\t\r\n\f]/, ""); + element.gsub!(/ /, " "); + Write(@lasth, unhtmlentities(element), '', fill); + end + end + end + + if (ln) + Ln(@lasth); + end + end + alias_method :write_html, :writeHTML + + # + # Prints a cell (rectangular area) with optional borders, background color and html text string. The upper-left corner of the cell corresponds to the current position. After the call, the current position moves to the right or to the next line.
    + # If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting. + # @param float :w Cell width. If 0, the cell extends up to the right margin. + # @param float :h Cell minimum height. The cell extends automatically if needed. + # @param float :x upper-left corner X coordinate + # @param float :y upper-left corner Y coordinate + # @param string :html html text to print. Default value: empty string. + # @param mixed :border Indicates if borders must be drawn around the cell. The value can be either a number:
    • 0: no border (default)
    • 1: frame
    or a string containing some or all of the following characters (in any order):
    • L: left
    • T: top
    • R: right
    • B: bottom
    + # @param int :ln Indicates where the current position should go after the call. Possible values are:
    • 0: to the right
    • 1: to the beginning of the next line
    • 2: below
    +# Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0. + # @param int :fill Indicates if the cell background must be painted (1) or transparent (0). Default value: 0. + # @see Cell() + # + def writeHTMLCell(w, h, x, y, html='', border=0, ln=1, fill=0) + + if (@lasth == 0) + #set row height + @lasth = @font_size * @@k_cell_height_ratio; + end + + if (x == 0) + x = GetX(); + end + if (y == 0) + y = GetY(); + end + + # get current page number + pagenum = @page; + + SetX(x); + SetY(y); + + if (w == 0) + w = @fw - x - @r_margin; + end + + b=0; + if (border) + if (border==1) + border='LTRB'; + b='LRT'; + b2='LR'; + elsif border.is_a?(String) + b2=''; + if (border.include?('L')) + b2<<'L'; + end + if (border.include?('R')) + b2<<'R'; + end + b=(border.include?('T')) ? b2 + 'T' : b2; + end + end + + # store original margin values + l_margin = @l_margin; + r_margin = @r_margin; + + # set new margin values + SetLeftMargin(x); + SetRightMargin(@fw - x - w); + + # calculate remaining vertical space on page + restspace = GetPageHeight() - GetY() - GetBreakMargin(); + + writeHTML(html, true, fill); # write html text + SetX(x) + + currentY = GetY(); + @auto_page_break = false; + # check if a new page has been created + if (@page > pagenum) + # design a cell around the text on first page + currentpage = @page; + @page = pagenum; + SetY(GetPageHeight() - restspace - GetBreakMargin()); + SetX(x) + Cell(w, restspace - 1, "", b, 0, 'L', 0); + b = b2; + @page += 1; + while @page < currentpage + SetY(@t_margin); # put cursor at the beginning of text + SetX(x) + Cell(w, @page_break_trigger - @t_margin, "", b, 0, 'L', 0); + @page += 1; + end + if (border.is_a?(String) and border.include?('B')) + b<<'B'; + end + # design a cell around the text on last page + SetY(@t_margin); # put cursor at the beginning of text + SetX(x) + Cell(w, currentY - @t_margin, "", b, 0, 'L', 0); + else + SetY(y); # put cursor at the beginning of text + # design a cell around the text + SetX(x) + Cell(w, [h, (currentY - y)].max, "", border, 0, 'L', 0); + end + @auto_page_break = true; + + # restore original margin values + SetLeftMargin(l_margin); + SetRightMargin(r_margin); + + @lasth = h + + # move cursor to specified position + if (ln == 0) + # go to the top-right of the cell + @x = x + w; + @y = y; + elsif (ln == 1) + # go to the beginning of the next line + @x = @l_margin; + @y = currentY; + elsif (ln == 2) + # go to the bottom-left of the cell (below) + @x = x; + @y = currentY; + end + end + alias_method :write_html_cell, :writeHTMLCell + + # + # Check html table tag position. + # + # @param array :table potision array + # @param int :current tr tag id number + # @param int :current td tag id number + # @access private + # @return int : next td_id position. + # value 0 mean that can use position. + # + def checkTableBlockingCellPosition(table, tr_id, td_id ) + 0.upto(tr_id) do |j| + 0.upto(@t_cells[table][j].size - 1) do |i| + if @t_cells[table][j][i]['i0'] <= td_id and td_id <= @t_cells[table][j][i]['i1'] + if @t_cells[table][j][i]['j0'] <= tr_id and tr_id <= @t_cells[table][j][i]['j1'] + return @t_cells[table][j][i]['i1'] - td_id + 1; + end + end + end + end + return 0; + end + + # + # Calculate opening tags. + # + # html table cell array : @t_cells + # + # i0: table cell start position + # i1: table cell end position + # j0: table row start position + # j1: table row end position + # + # +------+ + # |i0,j0 | + # | i1,j1| + # +------+ + # + # example html: + # + # + # + # + # + #
    + # + # i: 0 1 2 + # j+----+----+----+ + # :|0,0 |1,0 |2,0 | + # 0| 0,0| 1,0| 2,0| + # +----+----+----+ + # |0,1 |2,1 | + # 1| 1,1| 2,1| + # +----+----+----+ + # |0,2 |1,2 |2,2 | + # 2| | 1,2| 2,2| + # + +----+----+ + # | |1,3 |2,3 | + # 3| 0,3| 1,3| 2,3| + # +----+----+----+ + # + # html table cell array : + # [[[i0=>0,j0=>0,i1=>0,j1=>0],[i0=>1,j0=>0,i1=>1,j1=>0],[i0=>2,j0=>0,i1=>2,j1=>0]], + # [[i0=>0,j0=>1,i1=>1,j1=>1],[i0=>2,j0=>1,i1=>2,j1=>1]], + # [[i0=>0,j0=>2,i1=>0,j1=>3],[i0=>1,j0=>2,i1=>1,j1=>2],[i0=>2,j0=>2,i1=>2,j1=>2]] + # [[i0=>1,j0=>3,i1=>1,j1=>3],[i0=>2,j0=>3,i1=>2,j1=>3]]] + # + # @param string :tag tag name (in upcase) + # @param string :attr tag attribute (in upcase) + # @access private + # + def openHTMLTagCalc(tag, attrs) + #Opening tag + case (tag) + when 'table' + @max_table_columns[@table_id] = 0; + @t_columns = 0; + @tr_id = -1; + when 'tr' + if @max_table_columns[@table_id] < @t_columns + @max_table_columns[@table_id] = @t_columns; + end + @t_columns = 0; + @tr_id += 1; + @td_id = -1; + @t_cells[@table_id].push [] + when 'td', 'th' + @td_id += 1; + if attrs['colspan'].nil? or attrs['colspan'] == '' + colspan = 1; + else + colspan = attrs['colspan'].to_i; + end + if attrs['rowspan'].nil? or attrs['rowspan'] == '' + rowspan = 1; + else + rowspan = attrs['rowspan'].to_i; + end + + i = 0; + while true + next_i_distance = checkTableBlockingCellPosition(@table_id, @tr_id, @td_id + i); + if next_i_distance == 0 + @t_cells[@table_id][@tr_id].push "i0"=>@td_id + i, "j0"=>@tr_id, "i1"=>(@td_id + i + colspan - 1), "j1"=>@tr_id + rowspan - 1 + break; + end + i += next_i_distance; + end + + @t_columns += colspan; + end + end + + # + # Calculate closing tags. + # @param string :tag tag name (in upcase) + # @access private + # + def closedHTMLTagCalc(tag) + #Closing tag + case (tag) + when 'table' + if @max_table_columns[@table_id] < @t_columns + @max_table_columns[@table_id] = @t_columns; + end + @table_id += 1; + @t_cells.push [] + end + end + + # + # Convert to accessible file path + # @param string :attrname image file name + # + def getImageFilename( attrname ) + nil + end + + # + # Process opening tags. + # @param string :tag tag name (in upcase) + # @param string :attr tag attribute (in upcase) + # @param int :fill Indicates if the cell background must be painted (1) or transparent (0). Default value: 0. + # @access private + # + def openHTMLTagHandler(tag, attrs, fill=0) + #Opening tag + case (tag) + when 'pre' + @pre_state = true; + @l_margin += 5; + @r_margin += 5; + @x += 5; + + when 'table' + Ln(); + if @default_table_columns < @max_table_columns[@table_id] + @table_columns = @max_table_columns[@table_id]; + else + @table_columns = @default_table_columns; + end + @l_margin += 5; + @r_margin += 5; + @x += 5; + + if attrs['border'].nil? or attrs['border'] == '' + @tableborder = 0; + else + @tableborder = attrs['border']; + end + @tr_id = -1; + @max_td_page[0] = @page; + @max_td_y[0] = @y; + + when 'tr', 'td', 'th' + if tag == 'th' + SetStyle('b', true); + @tdalign = "C"; + end + if ((!attrs['width'].nil?) and (attrs['width'] != '')) + @tdwidth = (attrs['width'].to_i/4); + else + @tdwidth = ((@w - @l_margin - @r_margin) / @table_columns); + end + + if tag == 'tr' + @tr_id += 1; + @td_id = -1; + else + @td_id += 1; + @x = @l_margin + @tdwidth * @t_cells[@table_id][@tr_id][@td_id]['i0']; + end + + if attrs['colspan'].nil? or attrs['border'] == '' + @colspan = 1; + else + @colspan = attrs['colspan'].to_i; + end + @tdwidth *= @colspan; + if ((!attrs['height'].nil?) and (attrs['height'] != '')) + @tdheight=(attrs['height'].to_i / @k); + else + @tdheight = @lasth; + end + if ((!attrs['align'].nil?) and (attrs['align'] != '')) + case (attrs['align']) + when 'center' + @tdalign = "C"; + when 'right' + @tdalign = "R"; + when 'left' + @tdalign = "L"; + end + end + if ((!attrs['bgcolor'].nil?) and (attrs['bgcolor'] != '')) + coul = convertColorHexToDec(attrs['bgcolor']); + SetFillColor(coul['R'], coul['G'], coul['B']); + @tdfill=1; + end + @tdbegin=true; + + when 'hr' + margin = 1; + if ((!attrs['width'].nil?) and (attrs['width'] != '')) + hrWidth = attrs['width']; + else + hrWidth = @w - @l_margin - @r_margin - margin; + end + SetLineWidth(0.2); + Line(@x + margin, @y, @x + hrWidth, @y); + Ln(); + + when 'strong' + SetStyle('b', true); + + when 'em' + SetStyle('i', true); + + when 'ins' + SetStyle('u', true); + + when 'del' + SetStyle('d', true); + + when 'b', 'i', 'u' + SetStyle(tag, true); + + when 'a' + @href = attrs['href']; + + when 'img' + if (!attrs['src'].nil?) + # Don't generates image inside table tag + if (@tdbegin) + @tdtext << attrs['src']; + return + end + # Only generates image include a pdf if RMagick is avalaible + unless Object.const_defined?(:Magick) + Write(@lasth, attrs['src'], '', fill); + return + end + file = getImageFilename(attrs['src']) + if (file.nil?) + Write(@lasth, attrs['src'], '', fill); + return + end + + if (attrs['width'].nil?) + attrs['width'] = 0; + end + if (attrs['height'].nil?) + attrs['height'] = 0; + end + + begin + Image(file, GetX(),GetY(), pixelsToMillimeters(attrs['width']), pixelsToMillimeters(attrs['height'])); + #SetX(@img_rb_x); + SetY(@img_rb_y); + rescue => err + logger.error "pdf: Image: error: #{err.message}" + Write(@lasth, attrs['src'], '', fill); + end + end + + when 'ul', 'ol' + if @li_count == 0 + Ln() if @prevquote_count == @quote_count; # insert Ln for keeping quote lines + @prevquote_count = @quote_count; + end + if @li_state == true + Ln(); + @li_state = false; + end + if tag == 'ul' + @list_ordered[@li_count] = false; + else + @list_ordered[@li_count] = true; + end + @list_count[@li_count] = 0; + @li_count += 1 + + when 'li' + Ln() if @li_state == true + if (@list_ordered[@li_count - 1]) + @list_count[@li_count - 1] += 1; + @li_spacer = " " * @li_count + (@list_count[@li_count - 1]).to_s + ". "; + else + #unordered list simbol + @li_spacer = " " * @li_count + "- "; + end + Write(@lasth, @spacer + @li_spacer, '', fill); + @li_state = true; + + when 'blockquote' + if (@quote_count == 0) + SetStyle('i', true); + @l_margin += 5; + else + @l_margin += 5 / 2; + end + @x = @l_margin; + @quote_top[@quote_count] = @y; + @quote_page[@quote_count] = @page; + @quote_count += 1 + when 'br' + if @tdbegin + @tdtext << "\n" + return + end + Ln(); + + if (@li_spacer.length > 0) + @x += GetStringWidth(@li_spacer); + end + + when 'p' + Ln(); + 0.upto(@quote_count - 1) do |i| + if @quote_page[i] == @page; + if @quote_top[i] == @y - @lasth; # fix start line + @quote_top[i] = @y; + end + else + if @quote_page[i] == @page - 1; + @quote_page[i] = @page; # fix start line + @quote_top[i] = @t_margin; + end + end + end + + when 'sup' + currentfont_size = @font_size; + @tempfontsize = @font_size_pt; + SetFontSize(@font_size_pt * @@k_small_ratio); + SetXY(GetX(), GetY() - ((currentfont_size - @font_size)*(@@k_small_ratio))); + + when 'sub' + currentfont_size = @font_size; + @tempfontsize = @font_size_pt; + SetFontSize(@font_size_pt * @@k_small_ratio); + SetXY(GetX(), GetY() + ((currentfont_size - @font_size)*(@@k_small_ratio))); + + when 'small' + currentfont_size = @font_size; + @tempfontsize = @font_size_pt; + SetFontSize(@font_size_pt * @@k_small_ratio); + SetXY(GetX(), GetY() + ((currentfont_size - @font_size)/3)); + + when 'font' + if (!attrs['color'].nil? and attrs['color']!='') + coul = convertColorHexToDec(attrs['color']); + SetTextColor(coul['R'], coul['G'], coul['B']); + @issetcolor=true; + end + if (!attrs['face'].nil? and @fontlist.include?(attrs['face'].downcase)) + SetFont(attrs['face'].downcase); + @issetfont=true; + end + if (!attrs['size'].nil?) + headsize = attrs['size'].to_i; + else + headsize = 0; + end + currentfont_size = @font_size; + @tempfontsize = @font_size_pt; + SetFontSize(@font_size_pt + headsize); + @lasth = @font_size * @@k_cell_height_ratio; + + when 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' + Ln(); + headsize = (4 - tag[1,1].to_f) * 2 + @tempfontsize = @font_size_pt; + SetFontSize(@font_size_pt + headsize); + SetStyle('b', true); + @lasth = @font_size * @@k_cell_height_ratio; + + end + end + + # + # Process closing tags. + # @param string :tag tag name (in upcase) + # @access private + # + def closedHTMLTagHandler(tag) + #Closing tag + case (tag) + when 'pre' + @pre_state = false; + @l_margin -= 5; + @r_margin -= 5; + @x = @l_margin; + Ln(); + + when 'td','th' + base_page = @page; + base_x = @x; + base_y = @y; + + MultiCell(@tdwidth, @tdheight, unhtmlentities(@tdtext.strip), @tableborder, @tdalign, @tdfill, 1); + tr_end = @t_cells[@table_id][@tr_id][@td_id]['j1'] + 1; + if @max_td_page[tr_end].nil? or (@max_td_page[tr_end] < @page) + @max_td_page[tr_end] = @page + @max_td_y[tr_end] = @y + elsif (@max_td_page[tr_end] == @page) + @max_td_y[tr_end] = @y if @max_td_y[tr_end].nil? or (@max_td_y[tr_end] < @y) + end + + @page = base_page; + @x = base_x + @tdwidth; + @y = base_y; + @tdtext = ''; + @tdbegin = false; + @tdwidth = 0; + @tdheight = 0; + @tdalign = "L"; + SetStyle('b', false); + @tdfill = 0; + SetFillColor(@prevfill_color[0], @prevfill_color[1], @prevfill_color[2]); + + when 'tr' + @y = @max_td_y[@tr_id + 1]; + @x = @l_margin; + @page = @max_td_page[@tr_id + 1]; + + when 'table' + # Write Table Line + width = (@w - @l_margin - @r_margin) / @table_columns; + 0.upto(@t_cells[@table_id].size - 1) do |j| + 0.upto(@t_cells[@table_id][j].size - 1) do |i| + @page = @max_td_page[j] + i0=@t_cells[@table_id][j][i]['i0']; + j0=@t_cells[@table_id][j][i]['j0']; + i1=@t_cells[@table_id][j][i]['i1']; + j1=@t_cells[@table_id][j][i]['j1']; + + Line(@l_margin + width * i0, @max_td_y[j0], @l_margin + width * (i1+1), @max_td_y[j0]) # top + if ( @page == @max_td_page[j1 + 1]) + Line(@l_margin + width * i0, @max_td_y[j0], @l_margin + width * i0, @max_td_y[j1+1]) # left + Line(@l_margin + width * (i1+1), @max_td_y[j0], @l_margin + width * (i1+1), @max_td_y[j1+1]) # right + else + Line(@l_margin + width * i0, @max_td_y[j0], @l_margin + width * i0, @page_break_trigger) # left + Line(@l_margin + width * (i1+1), @max_td_y[j0], @l_margin + width * (i1+1), @page_break_trigger) # right + @page += 1; + while @page < @max_td_page[j1 + 1] + Line(@l_margin + width * i0, @t_margin, @l_margin + width * i0, @page_break_trigger) # left + Line(@l_margin + width * (i1+1), @t_margin, @l_margin + width * (i1+1), @page_break_trigger) # right + @page += 1; + end + Line(@l_margin + width * i0, @t_margin, @l_margin + width * i0, @max_td_y[j1+1]) # left + Line(@l_margin + width * (i1+1), @t_margin, @l_margin + width * (i1+1), @max_td_y[j1+1]) # right + end + Line(@l_margin + width * i0, @max_td_y[j1+1], @l_margin + width * (i1+1), @max_td_y[j1+1]) # bottom + end + end + + @l_margin -= 5; + @r_margin -= 5; + @tableborder=0; + @table_id += 1; + + when 'strong' + SetStyle('b', false); + + when 'em' + SetStyle('i', false); + + when 'ins' + SetStyle('u', false); + + when 'del' + SetStyle('d', false); + + when 'b', 'i', 'u' + SetStyle(tag, false); + + when 'a' + @href = nil; + + when 'p' + Ln(); + + when 'sup' + currentfont_size = @font_size; + SetFontSize(@tempfontsize); + @tempfontsize = @font_size_pt; + SetXY(GetX(), GetY() - ((currentfont_size - @font_size)*(@@k_small_ratio))); + + when 'sub' + currentfont_size = @font_size; + SetFontSize(@tempfontsize); + @tempfontsize = @font_size_pt; + SetXY(GetX(), GetY() + ((currentfont_size - @font_size)*(@@k_small_ratio))); + + when 'small' + currentfont_size = @font_size; + SetFontSize(@tempfontsize); + @tempfontsize = @font_size_pt; + SetXY(GetX(), GetY() - ((@font_size - currentfont_size)/3)); + + when 'font' + if (@issetcolor == true) + SetTextColor(@prevtext_color[0], @prevtext_color[1], @prevtext_color[2]); + end + if (@issetfont) + @font_family = @prevfont_family; + @font_style = @prevfont_style; + SetFont(@font_family); + @issetfont = false; + end + currentfont_size = @font_size; + SetFontSize(@tempfontsize); + @tempfontsize = @font_size_pt; + #@text_color = @prevtext_color; + @lasth = @font_size * @@k_cell_height_ratio; + + when 'blockquote' + @quote_count -= 1 + if (@quote_page[@quote_count] == @page) + Line(@l_margin - 1, @quote_top[@quote_count], @l_margin - 1, @y) # quoto line + else + cur_page = @page; + cur_y = @y; + @page = @quote_page[@quote_count]; + if (@quote_top[@quote_count] < @page_break_trigger) + Line(@l_margin - 1, @quote_top[@quote_count], @l_margin - 1, @page_break_trigger) # quoto line + end + @page += 1; + while @page < cur_page + Line(@l_margin - 1, @t_margin, @l_margin - 1, @page_break_trigger) # quoto line + @page += 1; + end + @y = cur_y; + Line(@l_margin - 1, @t_margin, @l_margin - 1, @y) # quoto line + end + if (@quote_count <= 0) + SetStyle('i', false); + @l_margin -= 5; + else + @l_margin -= 5 / 2; + end + @x = @l_margin; + Ln() if @quote_count == 0 + + when 'ul', 'ol' + @li_count -= 1 + if @li_state == true + Ln(); + @li_state = false; + end + + when 'li' + @li_spacer = ""; + if @li_state == true + Ln(); + @li_state = false; + end + + when 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' + SetFontSize(@tempfontsize); + @tempfontsize = @font_size_pt; + SetStyle('b', false); + Ln(); + @lasth = @font_size * @@k_cell_height_ratio; + + if tag == 'h1' or tag == 'h2' or tag == 'h3' or tag == 'h4' + margin = 1; + hrWidth = @w - @l_margin - @r_margin - margin; + if tag == 'h1' or tag == 'h2' + SetLineWidth(0.2); + else + SetLineWidth(0.1); + end + Line(@x + margin, @y, @x + hrWidth, @y); + end + end + end + + # + # Sets font style. + # @param string :tag tag name (in lowercase) + # @param boolean :enable + # @access private + # + def SetStyle(tag, enable) + #Modify style and select corresponding font + ['b', 'i', 'u', 'd'].each do |s| + if tag.downcase == s + if enable + @style << s if ! @style.include?(s) + else + @style = @style.gsub(s,'') + end + end + end + SetFont('', @style); + end + + # + # Output anchor link. + # @param string :url link URL + # @param string :name link name + # @param int :fill Indicates if the cell background must be painted (1) or transparent (0). Default value: 0. + # @access public + # + def addHtmlLink(url, name, fill=0) + #Put a hyperlink + SetTextColor(0, 0, 255); + SetStyle('u', true); + Write(@lasth, name, url, fill); + SetStyle('u', false); + SetTextColor(0); + end + + # + # Returns an associative array (keys: R,G,B) from + # a hex html code (e.g. #3FE5AA). + # @param string :color hexadecimal html color [#rrggbb] + # @return array + # @access private + # + def convertColorHexToDec(color = "#000000") + tbl_color = {} + tbl_color['R'] = color[1,2].hex.to_i; + tbl_color['G'] = color[3,2].hex.to_i; + tbl_color['B'] = color[5,2].hex.to_i; + return tbl_color; + end + + # + # Converts pixels to millimeters in 72 dpi. + # @param int :px pixels + # @return float millimeters + # @access private + # + def pixelsToMillimeters(px) + return px.to_f * 25.4 / 72; + end + + # + # Reverse function for htmlentities. + # Convert entities in UTF-8. + # + # @param :text_to_convert Text to convert. + # @return string converted + # + def unhtmlentities(string) + CGI.unescapeHTML(string) + end + +end # END OF CLASS + +#TODO 2007-05-25 (EJM) Level=0 - +#Handle special IE contype request +# if (!_SERVER['HTTP_USER_AGENT'].nil? and (_SERVER['HTTP_USER_AGENT']=='contype')) +# header('Content-Type: application/pdf'); +# exit; +# } diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/8a/8a838241ecb1de57abec573d9ab086b1b83f3ab1.svn-base --- a/.svn/pristine/8a/8a838241ecb1de57abec573d9ab086b1b83f3ab1.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,174 +0,0 @@ -module CodeRay module Scanners - - # by Josh Goebel - class SQL < Scanner - - register_for :sql - - KEYWORDS = %w( - all and any as before begin between by case check collate - each else end exists - for foreign from full group having if in inner is join - like not of on or order outer over references - then to union using values when where - left right distinct - ) - - OBJECTS = %w( - database databases table tables column columns fields index constraint - constraints transaction function procedure row key view trigger - ) - - COMMANDS = %w( - add alter comment create delete drop grant insert into select update set - show prompt begin commit rollback replace truncate - ) - - PREDEFINED_TYPES = %w( - char varchar varchar2 enum binary text tinytext mediumtext - longtext blob tinyblob mediumblob longblob timestamp - date time datetime year double decimal float int - integer tinyint mediumint bigint smallint unsigned bit - bool boolean hex bin oct - ) - - PREDEFINED_FUNCTIONS = %w( sum cast substring abs pi count min max avg now ) - - DIRECTIVES = %w( - auto_increment unique default charset initially deferred - deferrable cascade immediate read write asc desc after - primary foreign return engine - ) - - PREDEFINED_CONSTANTS = %w( null true false ) - - IDENT_KIND = WordList::CaseIgnoring.new(:ident). - add(KEYWORDS, :keyword). - add(OBJECTS, :type). - add(COMMANDS, :class). - add(PREDEFINED_TYPES, :predefined_type). - add(PREDEFINED_CONSTANTS, :predefined_constant). - add(PREDEFINED_FUNCTIONS, :predefined). - add(DIRECTIVES, :directive) - - ESCAPE = / [rbfntv\n\\\/'"] | x[a-fA-F0-9]{1,2} | [0-7]{1,3} | . /mx - UNICODE_ESCAPE = / u[a-fA-F0-9]{4} | U[a-fA-F0-9]{8} /x - - STRING_PREFIXES = /[xnb]|_\w+/i - - def scan_tokens encoder, options - - state = :initial - string_type = nil - string_content = '' - name_expected = false - - until eos? - - if state == :initial - - if match = scan(/ \s+ | \\\n /x) - encoder.text_token match, :space - - elsif match = scan(/(?:--\s?|#).*/) - encoder.text_token match, :comment - - elsif match = scan(%r( /\* (!)? (?: .*? \*/ | .* ) )mx) - encoder.text_token match, self[1] ? :directive : :comment - - elsif match = scan(/ [*\/=<>:;,!&^|()\[\]{}~%] | [-+\.](?!\d) /x) - name_expected = true if match == '.' && check(/[A-Za-z_]/) - encoder.text_token match, :operator - - elsif match = scan(/(#{STRING_PREFIXES})?([`"'])/o) - prefix = self[1] - string_type = self[2] - encoder.begin_group :string - encoder.text_token prefix, :modifier if prefix - match = string_type - state = :string - encoder.text_token match, :delimiter - - elsif match = scan(/ @? [A-Za-z_][A-Za-z_0-9]* /x) - encoder.text_token match, name_expected ? :ident : (match[0] == ?@ ? :variable : IDENT_KIND[match]) - name_expected = false - - elsif match = scan(/0[xX][0-9A-Fa-f]+/) - encoder.text_token match, :hex - - elsif match = scan(/0[0-7]+(?![89.eEfF])/) - encoder.text_token match, :octal - - elsif match = scan(/[-+]?(?>\d+)(?![.eEfF])/) - encoder.text_token match, :integer - - elsif match = scan(/[-+]?(?:\d[fF]|\d*\.\d+(?:[eE][+-]?\d+)?|\d+[eE][+-]?\d+)/) - encoder.text_token match, :float - - elsif match = scan(/\\N/) - encoder.text_token match, :predefined_constant - - else - encoder.text_token getch, :error - - end - - elsif state == :string - if match = scan(/[^\\"'`]+/) - string_content << match - next - elsif match = scan(/["'`]/) - if string_type == match - if peek(1) == string_type # doubling means escape - string_content << string_type << getch - next - end - unless string_content.empty? - encoder.text_token string_content, :content - string_content = '' - end - encoder.text_token match, :delimiter - encoder.end_group :string - state = :initial - string_type = nil - else - string_content << match - end - elsif match = scan(/ \\ (?: #{ESCAPE} | #{UNICODE_ESCAPE} ) /mox) - unless string_content.empty? - encoder.text_token string_content, :content - string_content = '' - end - encoder.text_token match, :char - elsif match = scan(/ \\ . /mox) - string_content << match - next - elsif match = scan(/ \\ | $ /x) - unless string_content.empty? - encoder.text_token string_content, :content - string_content = '' - end - encoder.text_token match, :error - state = :initial - else - raise "else case \" reached; %p not handled." % peek(1), encoder - end - - else - raise 'else-case reached', encoder - - end - - end - - if state == :string - encoder.end_group state - end - - encoder - - end - - end - -end end \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/8a/8a9427066e2d6e798584d5fb43dde42f7e5a0ae0.svn-base --- a/.svn/pristine/8a/8a9427066e2d6e798584d5fb43dde42f7e5a0ae0.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,18 +0,0 @@ -module CodeRay -module Encoders - - # = Null Encoder - # - # Does nothing and returns an empty string. - class Null < Encoder - - register_for :null - - def text_token text, kind - # do nothing - end - - end - -end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/8a/8aaabf0921abadbc953f9bb52b798cfb728da0d6.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/8a/8aaabf0921abadbc953f9bb52b798cfb728da0d6.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,14 @@ +class ExportPdf < ActiveRecord::Migration + # model removed + class Permission < ActiveRecord::Base; end + + def self.up + Permission.create :controller => "projects", :action => "export_issues_pdf", :description => "label_export_pdf", :sort => 1002, :is_public => true, :mail_option => 0, :mail_enabled => 0 + Permission.create :controller => "issues", :action => "export_pdf", :description => "label_export_pdf", :sort => 1015, :is_public => true, :mail_option => 0, :mail_enabled => 0 + end + + def self.down + Permission.where("controller=? and action=?", 'projects', 'export_issues_pdf').first.destroy + Permission.where("controller=? and action=?", 'issues', 'export_pdf').first.destroy + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/8a/8ab4630ebd77e09c1eecc6ce7e00d173fda9baab.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/8a/8ab4630ebd77e09c1eecc6ce7e00d173fda9baab.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,54 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class Redmine::ApiTest::HttpBasicLoginTest < Redmine::ApiTest::Base + fixtures :projects, :trackers, :issue_statuses, :issues, + :enumerations, :users, :issue_categories, + :projects_trackers, + :roles, + :member_roles, + :members, + :enabled_modules + + def setup + Setting.rest_api_enabled = '1' + Setting.login_required = '1' + end + + def teardown + Setting.rest_api_enabled = '0' + Setting.login_required = '0' + end + + # Using the NewsController because it's a simple API. + context "get /news" do + setup do + project = Project.find('onlinestore') + EnabledModule.create(:project => project, :name => 'news') + end + + context "in :xml format" do + should_allow_http_basic_auth_with_username_and_password(:get, "/projects/onlinestore/news.xml") + end + + context "in :json format" do + should_allow_http_basic_auth_with_username_and_password(:get, "/projects/onlinestore/news.json") + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/8a/8ac2d7f20aa45eda756c0873b6a4368ace36d01a.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/8a/8ac2d7f20aa45eda756c0873b6a4368ace36d01a.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,9 @@ +class AddIssuesClosedOn < ActiveRecord::Migration + def up + add_column :issues, :closed_on, :datetime, :default => nil + end + + def down + remove_column :issues, :closed_on + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/8a/8adb47a9617850b441bd264fdfaa485feeb05a17.svn-base --- a/.svn/pristine/8a/8adb47a9617850b441bd264fdfaa485feeb05a17.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,26 +0,0 @@ -class ProjectEnumerationsController < ApplicationController - before_filter :find_project_by_project_id - before_filter :authorize - - def update - if request.put? && params[:enumerations] - Project.transaction do - params[:enumerations].each do |id, activity| - @project.update_or_create_time_entry_activity(id, activity) - end - end - flash[:notice] = l(:notice_successful_update) - end - - redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project - end - - def destroy - @project.time_entry_activities.each do |time_entry_activity| - time_entry_activity.destroy(time_entry_activity.parent) - end - flash[:notice] = l(:notice_successful_update) - redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project - end - -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/8b/8b0c72afd0d7787739416aa1510d0e28d12a95ed.svn-base --- a/.svn/pristine/8b/8b0c72afd0d7787739416aa1510d0e28d12a95ed.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,63 +0,0 @@ -require File.dirname(__FILE__) + '/../test_helper' -require 'rails_generator' -require 'rails_generator/scripts/generate' - -class MigrationsTest < Test::Unit::TestCase - - @@migration_dir = "#{RAILS_ROOT}/db/migrate" - - def setup - ActiveRecord::Migration.verbose = false - Engines.plugins[:test_migration].migrate(0) - end - - def teardown - FileUtils.rm_r(@@migration_dir) if File.exist?(@@migration_dir) - end - - def test_engine_migrations_can_run_down - assert !table_exists?('tests'), ActiveRecord::Base.connection.tables.inspect - assert !table_exists?('others'), ActiveRecord::Base.connection.tables.inspect - assert !table_exists?('extras'), ActiveRecord::Base.connection.tables.inspect - end - - def test_engine_migrations_can_run_up - Engines.plugins[:test_migration].migrate(3) - assert table_exists?('tests') - assert table_exists?('others') - assert table_exists?('extras') - end - - def test_engine_migrations_can_upgrade_incrementally - Engines.plugins[:test_migration].migrate(1) - assert table_exists?('tests') - assert !table_exists?('others') - assert !table_exists?('extras') - assert_equal 1, Engines::Plugin::Migrator.current_version(Engines.plugins[:test_migration]) - - - Engines.plugins[:test_migration].migrate(2) - assert table_exists?('others') - assert_equal 2, Engines::Plugin::Migrator.current_version(Engines.plugins[:test_migration]) - - - Engines.plugins[:test_migration].migrate(3) - assert table_exists?('extras') - assert_equal 3, Engines::Plugin::Migrator.current_version(Engines.plugins[:test_migration]) - end - - def test_generator_creates_plugin_migration_file - Rails::Generator::Scripts::Generate.new.run(['plugin_migration', 'test_migration'], :quiet => true) - assert migration_file, "migration file is missing" - end - - private - - def table_exists?(table) - ActiveRecord::Base.connection.tables.include?(table) - end - - def migration_file - Dir["#{@@migration_dir}/*test_migration_to_version_3.rb"][0] - end -end \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/8b/8b34b320f3a3448ad5019fb625383697dc8a3c95.svn-base --- a/.svn/pristine/8b/8b34b320f3a3448ad5019fb625383697dc8a3c95.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,395 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) - -class IssueNestedSetTest < ActiveSupport::TestCase - fixtures :projects, :users, :members, :member_roles, :roles, - :trackers, :projects_trackers, - :versions, - :issue_statuses, :issue_categories, :issue_relations, :workflows, - :enumerations, - :issues, - :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values, - :time_entries - - self.use_transactional_fixtures = false - - def test_create_root_issue - issue1 = create_issue! - issue2 = create_issue! - issue1.reload - issue2.reload - - assert_equal [issue1.id, nil, 1, 2], [issue1.root_id, issue1.parent_id, issue1.lft, issue1.rgt] - assert_equal [issue2.id, nil, 1, 2], [issue2.root_id, issue2.parent_id, issue2.lft, issue2.rgt] - end - - def test_create_child_issue - parent = create_issue! - child = create_issue!(:parent_issue_id => parent.id) - parent.reload - child.reload - - assert_equal [parent.id, nil, 1, 4], [parent.root_id, parent.parent_id, parent.lft, parent.rgt] - assert_equal [parent.id, parent.id, 2, 3], [child.root_id, child.parent_id, child.lft, child.rgt] - end - - def test_creating_a_child_in_different_project_should_not_validate - issue = create_issue! - child = Issue.new(:project_id => 2, :tracker_id => 1, :author_id => 1, - :subject => 'child', :parent_issue_id => issue.id) - assert !child.save - assert_not_nil child.errors[:parent_issue_id] - end - - def test_move_a_root_to_child - parent1 = create_issue! - parent2 = create_issue! - child = create_issue!(:parent_issue_id => parent1.id) - - parent2.parent_issue_id = parent1.id - parent2.save! - child.reload - parent1.reload - parent2.reload - - assert_equal [parent1.id, 1, 6], [parent1.root_id, parent1.lft, parent1.rgt] - assert_equal [parent1.id, 4, 5], [parent2.root_id, parent2.lft, parent2.rgt] - assert_equal [parent1.id, 2, 3], [child.root_id, child.lft, child.rgt] - end - - def test_move_a_child_to_root - parent1 = create_issue! - parent2 = create_issue! - child = create_issue!(:parent_issue_id => parent1.id) - - child.parent_issue_id = nil - child.save! - child.reload - parent1.reload - parent2.reload - - assert_equal [parent1.id, 1, 2], [parent1.root_id, parent1.lft, parent1.rgt] - assert_equal [parent2.id, 1, 2], [parent2.root_id, parent2.lft, parent2.rgt] - assert_equal [child.id, 1, 2], [child.root_id, child.lft, child.rgt] - end - - def test_move_a_child_to_another_issue - parent1 = create_issue! - parent2 = create_issue! - child = create_issue!(:parent_issue_id => parent1.id) - - child.parent_issue_id = parent2.id - child.save! - child.reload - parent1.reload - parent2.reload - - assert_equal [parent1.id, 1, 2], [parent1.root_id, parent1.lft, parent1.rgt] - assert_equal [parent2.id, 1, 4], [parent2.root_id, parent2.lft, parent2.rgt] - assert_equal [parent2.id, 2, 3], [child.root_id, child.lft, child.rgt] - end - - def test_move_a_child_with_descendants_to_another_issue - parent1 = create_issue! - parent2 = create_issue! - child = create_issue!(:parent_issue_id => parent1.id) - grandchild = create_issue!(:parent_issue_id => child.id) - - parent1.reload - parent2.reload - child.reload - grandchild.reload - - assert_equal [parent1.id, 1, 6], [parent1.root_id, parent1.lft, parent1.rgt] - assert_equal [parent2.id, 1, 2], [parent2.root_id, parent2.lft, parent2.rgt] - assert_equal [parent1.id, 2, 5], [child.root_id, child.lft, child.rgt] - assert_equal [parent1.id, 3, 4], [grandchild.root_id, grandchild.lft, grandchild.rgt] - - child.reload.parent_issue_id = parent2.id - child.save! - child.reload - grandchild.reload - parent1.reload - parent2.reload - - assert_equal [parent1.id, 1, 2], [parent1.root_id, parent1.lft, parent1.rgt] - assert_equal [parent2.id, 1, 6], [parent2.root_id, parent2.lft, parent2.rgt] - assert_equal [parent2.id, 2, 5], [child.root_id, child.lft, child.rgt] - assert_equal [parent2.id, 3, 4], [grandchild.root_id, grandchild.lft, grandchild.rgt] - end - - def test_move_a_child_with_descendants_to_another_project - parent1 = create_issue! - child = create_issue!(:parent_issue_id => parent1.id) - grandchild = create_issue!(:parent_issue_id => child.id) - - assert child.reload.move_to_project(Project.find(2)) - child.reload - grandchild.reload - parent1.reload - - assert_equal [1, parent1.id, 1, 2], [parent1.project_id, parent1.root_id, parent1.lft, parent1.rgt] - assert_equal [2, child.id, 1, 4], [child.project_id, child.root_id, child.lft, child.rgt] - assert_equal [2, child.id, 2, 3], [grandchild.project_id, grandchild.root_id, grandchild.lft, grandchild.rgt] - end - - def test_invalid_move_to_another_project - parent1 = create_issue! - child = create_issue!(:parent_issue_id => parent1.id) - grandchild = create_issue!(:parent_issue_id => child.id, :tracker_id => 2) - Project.find(2).tracker_ids = [1] - - parent1.reload - assert_equal [1, parent1.id, 1, 6], [parent1.project_id, parent1.root_id, parent1.lft, parent1.rgt] - - # child can not be moved to Project 2 because its child is on a disabled tracker - assert_equal false, Issue.find(child.id).move_to_project(Project.find(2)) - child.reload - grandchild.reload - parent1.reload - - # no change - assert_equal [1, parent1.id, 1, 6], [parent1.project_id, parent1.root_id, parent1.lft, parent1.rgt] - assert_equal [1, parent1.id, 2, 5], [child.project_id, child.root_id, child.lft, child.rgt] - assert_equal [1, parent1.id, 3, 4], [grandchild.project_id, grandchild.root_id, grandchild.lft, grandchild.rgt] - end - - def test_moving_an_issue_to_a_descendant_should_not_validate - parent1 = create_issue! - parent2 = create_issue! - child = create_issue!(:parent_issue_id => parent1.id) - grandchild = create_issue!(:parent_issue_id => child.id) - - child.reload - child.parent_issue_id = grandchild.id - assert !child.save - assert_not_nil child.errors[:parent_issue_id] - end - - def test_moving_an_issue_should_keep_valid_relations_only - issue1 = create_issue! - issue2 = create_issue! - issue3 = create_issue!(:parent_issue_id => issue2.id) - issue4 = create_issue! - r1 = IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES) - r2 = IssueRelation.create!(:issue_from => issue1, :issue_to => issue3, :relation_type => IssueRelation::TYPE_PRECEDES) - r3 = IssueRelation.create!(:issue_from => issue2, :issue_to => issue4, :relation_type => IssueRelation::TYPE_PRECEDES) - issue2.reload - issue2.parent_issue_id = issue1.id - issue2.save! - assert !IssueRelation.exists?(r1.id) - assert !IssueRelation.exists?(r2.id) - assert IssueRelation.exists?(r3.id) - end - - def test_destroy_should_destroy_children - issue1 = create_issue! - issue2 = create_issue! - issue3 = create_issue!(:parent_issue_id => issue2.id) - issue4 = create_issue!(:parent_issue_id => issue1.id) - - issue3.init_journal(User.find(2)) - issue3.subject = 'child with journal' - issue3.save! - - assert_difference 'Issue.count', -2 do - assert_difference 'Journal.count', -1 do - assert_difference 'JournalDetail.count', -1 do - Issue.find(issue2.id).destroy - end - end - end - - issue1.reload - issue4.reload - assert !Issue.exists?(issue2.id) - assert !Issue.exists?(issue3.id) - assert_equal [issue1.id, 1, 4], [issue1.root_id, issue1.lft, issue1.rgt] - assert_equal [issue1.id, 2, 3], [issue4.root_id, issue4.lft, issue4.rgt] - end - - def test_destroy_child_should_update_parent - issue = create_issue! - child1 = create_issue!(:parent_issue_id => issue.id) - child2 = create_issue!(:parent_issue_id => issue.id) - - issue.reload - assert_equal [issue.id, 1, 6], [issue.root_id, issue.lft, issue.rgt] - - child2.reload.destroy - - issue.reload - assert_equal [issue.id, 1, 4], [issue.root_id, issue.lft, issue.rgt] - end - - def test_destroy_parent_issue_updated_during_children_destroy - parent = create_issue! - create_issue!(:start_date => Date.today, :parent_issue_id => parent.id) - create_issue!(:start_date => 2.days.from_now, :parent_issue_id => parent.id) - - assert_difference 'Issue.count', -3 do - Issue.find(parent.id).destroy - end - end - - def test_destroy_child_issue_with_children - root = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'root') - child = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => root.id) - leaf = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'leaf', :parent_issue_id => child.id) - leaf.init_journal(User.find(2)) - leaf.subject = 'leaf with journal' - leaf.save! - - assert_difference 'Issue.count', -2 do - assert_difference 'Journal.count', -1 do - assert_difference 'JournalDetail.count', -1 do - Issue.find(child.id).destroy - end - end - end - - root = Issue.find(root.id) - assert root.leaf?, "Root issue is not a leaf (lft: #{root.lft}, rgt: #{root.rgt})" - end - - def test_destroy_issue_with_grand_child - parent = create_issue! - issue = create_issue!(:parent_issue_id => parent.id) - child = create_issue!(:parent_issue_id => issue.id) - grandchild1 = create_issue!(:parent_issue_id => child.id) - grandchild2 = create_issue!(:parent_issue_id => child.id) - - assert_difference 'Issue.count', -4 do - Issue.find(issue.id).destroy - parent.reload - assert_equal [1, 2], [parent.lft, parent.rgt] - end - end - - def test_parent_priority_should_be_the_highest_child_priority - parent = create_issue!(:priority => IssuePriority.find_by_name('Normal')) - # Create children - child1 = create_issue!(:priority => IssuePriority.find_by_name('High'), :parent_issue_id => parent.id) - assert_equal 'High', parent.reload.priority.name - child2 = create_issue!(:priority => IssuePriority.find_by_name('Immediate'), :parent_issue_id => child1.id) - assert_equal 'Immediate', child1.reload.priority.name - assert_equal 'Immediate', parent.reload.priority.name - child3 = create_issue!(:priority => IssuePriority.find_by_name('Low'), :parent_issue_id => parent.id) - assert_equal 'Immediate', parent.reload.priority.name - # Destroy a child - child1.destroy - assert_equal 'Low', parent.reload.priority.name - # Update a child - child3.reload.priority = IssuePriority.find_by_name('Normal') - child3.save! - assert_equal 'Normal', parent.reload.priority.name - end - - def test_parent_dates_should_be_lowest_start_and_highest_due_dates - parent = create_issue! - create_issue!(:start_date => '2010-01-25', :due_date => '2010-02-15', :parent_issue_id => parent.id) - create_issue!( :due_date => '2010-02-13', :parent_issue_id => parent.id) - create_issue!(:start_date => '2010-02-01', :due_date => '2010-02-22', :parent_issue_id => parent.id) - parent.reload - assert_equal Date.parse('2010-01-25'), parent.start_date - assert_equal Date.parse('2010-02-22'), parent.due_date - end - - def test_parent_done_ratio_should_be_average_done_ratio_of_leaves - parent = create_issue! - create_issue!(:done_ratio => 20, :parent_issue_id => parent.id) - assert_equal 20, parent.reload.done_ratio - create_issue!(:done_ratio => 70, :parent_issue_id => parent.id) - assert_equal 45, parent.reload.done_ratio - - child = create_issue!(:done_ratio => 0, :parent_issue_id => parent.id) - assert_equal 30, parent.reload.done_ratio - - create_issue!(:done_ratio => 30, :parent_issue_id => child.id) - assert_equal 30, child.reload.done_ratio - assert_equal 40, parent.reload.done_ratio - end - - def test_parent_done_ratio_should_be_weighted_by_estimated_times_if_any - parent = create_issue! - create_issue!(:estimated_hours => 10, :done_ratio => 20, :parent_issue_id => parent.id) - assert_equal 20, parent.reload.done_ratio - create_issue!(:estimated_hours => 20, :done_ratio => 50, :parent_issue_id => parent.id) - assert_equal (50 * 20 + 20 * 10) / 30, parent.reload.done_ratio - end - - def test_parent_estimate_should_be_sum_of_leaves - parent = create_issue! - create_issue!(:estimated_hours => nil, :parent_issue_id => parent.id) - assert_equal nil, parent.reload.estimated_hours - create_issue!(:estimated_hours => 5, :parent_issue_id => parent.id) - assert_equal 5, parent.reload.estimated_hours - create_issue!(:estimated_hours => 7, :parent_issue_id => parent.id) - assert_equal 12, parent.reload.estimated_hours - end - - def test_move_parent_updates_old_parent_attributes - first_parent = create_issue! - second_parent = create_issue! - child = create_issue!(:estimated_hours => 5, :parent_issue_id => first_parent.id) - assert_equal 5, first_parent.reload.estimated_hours - child.update_attributes(:estimated_hours => 7, :parent_issue_id => second_parent.id) - assert_equal 7, second_parent.reload.estimated_hours - assert_nil first_parent.reload.estimated_hours - end - - def test_reschuling_a_parent_should_reschedule_subtasks - parent = create_issue! - c1 = create_issue!(:start_date => '2010-05-12', :due_date => '2010-05-18', :parent_issue_id => parent.id) - c2 = create_issue!(:start_date => '2010-06-03', :due_date => '2010-06-10', :parent_issue_id => parent.id) - parent.reload - parent.reschedule_after(Date.parse('2010-06-02')) - c1.reload - assert_equal [Date.parse('2010-06-02'), Date.parse('2010-06-08')], [c1.start_date, c1.due_date] - c2.reload - assert_equal [Date.parse('2010-06-03'), Date.parse('2010-06-10')], [c2.start_date, c2.due_date] # no change - parent.reload - assert_equal [Date.parse('2010-06-02'), Date.parse('2010-06-10')], [parent.start_date, parent.due_date] - end - - def test_project_copy_should_copy_issue_tree - p = Project.create!(:name => 'Tree copy', :identifier => 'tree-copy', :tracker_ids => [1, 2]) - i1 = create_issue!(:project_id => p.id, :subject => 'i1') - i2 = create_issue!(:project_id => p.id, :subject => 'i2', :parent_issue_id => i1.id) - i3 = create_issue!(:project_id => p.id, :subject => 'i3', :parent_issue_id => i1.id) - i4 = create_issue!(:project_id => p.id, :subject => 'i4', :parent_issue_id => i2.id) - i5 = create_issue!(:project_id => p.id, :subject => 'i5') - c = Project.new(:name => 'Copy', :identifier => 'copy', :tracker_ids => [1, 2]) - c.copy(p, :only => 'issues') - c.reload - - assert_equal 5, c.issues.count - ic1, ic2, ic3, ic4, ic5 = c.issues.find(:all, :order => 'subject') - assert ic1.root? - assert_equal ic1, ic2.parent - assert_equal ic1, ic3.parent - assert_equal ic2, ic4.parent - assert ic5.root? - end - - # Helper that creates an issue with default attributes - def create_issue!(attributes={}) - Issue.create!({:project_id => 1, :tracker_id => 1, :author_id => 1, :subject => 'test'}.merge(attributes)) - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/8b/8b54895a280a4b6be2315b819f9e36a6caaa65f2.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/8b/8b54895a280a4b6be2315b819f9e36a6caaa65f2.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,23 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class TimeEntryCustomField < CustomField + def type_name + :label_spent_time + end +end + diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/8b/8b6b9cb4de924997fd5c8d066cf49e5fb943de93.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/8b/8b6b9cb4de924997fd5c8d066cf49e5fb943de93.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,56 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 CalendarsController < ApplicationController + menu_item :calendar + before_filter :find_optional_project + + rescue_from Query::StatementInvalid, :with => :query_statement_invalid + + helper :issues + helper :projects + helper :queries + include QueriesHelper + helper :sort + include SortHelper + + def show + if params[:year] and params[:year].to_i > 1900 + @year = params[:year].to_i + if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13 + @month = params[:month].to_i + end + end + @year ||= Date.today.year + @month ||= Date.today.month + + @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month) + retrieve_query + @query.group_by = nil + if @query.valid? + events = [] + events += @query.issues(:include => [:tracker, :assigned_to, :priority], + :conditions => ["((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?))", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt] + ) + events += @query.versions(:conditions => ["effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt]) + + @calendar.events = events + end + + render :action => 'show', :layout => false if request.xhr? + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/8b/8b8a4a4fb98bb4ad97ae1d153aef221af93fc010.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/8b/8b8a4a4fb98bb4ad97ae1d153aef221af93fc010.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,43 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +desc <<-END_DESC +Send reminders about issues due in the next days. + +Available options: + * days => number of days to remind about (defaults to 7) + * tracker => id of tracker (defaults to all trackers) + * project => id or identifier of project (defaults to all projects) + * users => comma separated list of user/group ids who should be reminded + +Example: + rake redmine:send_reminders days=7 users="1,23, 56" RAILS_ENV="production" +END_DESC + +namespace :redmine do + task :send_reminders => :environment do + options = {} + options[:days] = ENV['days'].to_i if ENV['days'] + options[:project] = ENV['project'] if ENV['project'] + options[:tracker] = ENV['tracker'].to_i if ENV['tracker'] + options[:users] = (ENV['users'] || '').split(',').each(&:strip!) + + Mailer.with_synched_deliveries do + Mailer.reminders(options) + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/8b/8b8ba498cf3cdcb76cdc45e1ecf9d424b5e9b3cc.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/8b/8b8ba498cf3cdcb76cdc45e1ecf9d424b5e9b3cc.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,71 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +module MyHelper + def calendar_items(startdt, enddt) + Issue.visible. + where(:project_id => User.current.projects.map(&:id)). + where("(start_date>=? and start_date<=?) or (due_date>=? and due_date<=?)", startdt, enddt, startdt, enddt). + includes(:project, :tracker, :priority, :assigned_to). + all + end + + def documents_items + Document.visible.order("#{Document.table_name}.created_on DESC").limit(10).all + end + + def issuesassignedtome_items + Issue.visible.open. + where(:assigned_to_id => ([User.current.id] + User.current.group_ids)). + limit(10). + includes(:status, :project, :tracker, :priority). + order("#{IssuePriority.table_name}.position DESC, #{Issue.table_name}.updated_on DESC"). + all + end + + def issuesreportedbyme_items + Issue.visible. + where(:author_id => User.current.id). + limit(10). + includes(:status, :project, :tracker). + order("#{Issue.table_name}.updated_on DESC"). + all + end + + def issueswatched_items + Issue.visible.on_active_project.watched_by(User.current.id).recently_updated.limit(10).all + end + + def news_items + News.visible. + where(:project_id => User.current.projects.map(&:id)). + limit(10). + includes(:project, :author). + order("#{News.table_name}.created_on DESC"). + all + end + + def timelog_items + TimeEntry. + where("#{TimeEntry.table_name}.user_id = ? AND #{TimeEntry.table_name}.spent_on BETWEEN ? AND ?", User.current.id, Date.today - 6, Date.today). + includes(:activity, :project, {:issue => [:tracker, :status]}). + order("#{TimeEntry.table_name}.spent_on DESC, #{Project.table_name}.name ASC, #{Tracker.table_name}.position ASC, #{Issue.table_name}.id ASC"). + all + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/8b/8bb9ae19f70bfe10707fcbca8e4a7cb4cc52174d.svn-base --- a/.svn/pristine/8b/8bb9ae19f70bfe10707fcbca8e4a7cb4cc52174d.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,44 +0,0 @@ -<%= error_messages_for 'auth_source' %> - -
    - -

    -<%= text_field 'auth_source', 'name' %>

    - -

    -<%= text_field 'auth_source', 'host' %>

    - -

    -<%= text_field 'auth_source', 'port', :size => 6 %> <%= check_box 'auth_source', 'tls' %> LDAPS

    - -

    -<%= text_field 'auth_source', 'account' %>

    - -

    -<%= password_field 'auth_source', 'account_password', :name => 'ignore', - :value => ((@auth_source.new_record? || @auth_source.account_password.blank?) ? '' : ('x'*15)), - :onfocus => "this.value=''; this.name='auth_source[account_password]';", - :onchange => "this.name='auth_source[account_password]';" %>

    - -

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

    - -

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

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

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

    - -

    -<%= text_field 'auth_source', 'attr_firstname', :size => 20 %>

    - -

    -<%= text_field 'auth_source', 'attr_lastname', :size => 20 %>

    - -

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

    -
    - - diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/8b/8bc1e7aab7b493f12083c3af8e8303e9dd4b73f1.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/8b/8bc1e7aab7b493f12083c3af8e8303e9dd4b73f1.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,269 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../../test_helper', __FILE__) + +class Redmine::I18nTest < ActiveSupport::TestCase + include Redmine::I18n + include ActionView::Helpers::NumberHelper + + def setup + User.current.language = nil + end + + def teardown + set_language_if_valid 'en' + end + + def test_date_format_default + set_language_if_valid 'en' + today = Date.today + Setting.date_format = '' + assert_equal I18n.l(today), format_date(today) + end + + def test_date_format + set_language_if_valid 'en' + today = Date.today + Setting.date_format = '%d %m %Y' + assert_equal today.strftime('%d %m %Y'), format_date(today) + end + + def test_date_format_default_with_user_locale + set_language_if_valid 'es' + today = now = Time.parse('2011-02-20 14:00:00') + Setting.date_format = '%d %B %Y' + User.current.language = 'fr' + s1 = "20 f\xc3\xa9vrier 2011" + s1.force_encoding("UTF-8") if s1.respond_to?(:force_encoding) + assert_equal s1, format_date(today) + User.current.language = nil + assert_equal '20 Febrero 2011', format_date(today) + end + + def test_date_and_time_for_each_language + Setting.date_format = '' + valid_languages.each do |lang| + set_language_if_valid lang + assert_nothing_raised "#{lang} failure" do + format_date(Date.today) + format_time(Time.now) + format_time(Time.now, false) + assert_not_equal 'default', ::I18n.l(Date.today, :format => :default), + "date.formats.default missing in #{lang}" + assert_not_equal 'time', ::I18n.l(Time.now, :format => :time), + "time.formats.time missing in #{lang}" + end + assert l('date.day_names').is_a?(Array) + assert_equal 7, l('date.day_names').size + + assert l('date.month_names').is_a?(Array) + assert_equal 13, l('date.month_names').size + end + end + + def test_time_for_each_zone + ActiveSupport::TimeZone.all.each do |zone| + User.current.stubs(:time_zone).returns(zone.name) + assert_nothing_raised "#{zone} failure" do + format_time(Time.now) + end + end + end + + def test_time_format + set_language_if_valid 'en' + now = Time.parse('2011-02-20 15:45:22') + with_settings :time_format => '%H:%M' do + with_settings :date_format => '' do + assert_equal '02/20/2011 15:45', format_time(now) + assert_equal '15:45', format_time(now, false) + end + with_settings :date_format => '%Y-%m-%d' do + assert_equal '2011-02-20 15:45', format_time(now) + assert_equal '15:45', format_time(now, false) + end + end + end + + def test_time_format_default + set_language_if_valid 'en' + now = Time.parse('2011-02-20 15:45:22') + with_settings :time_format => '' do + with_settings :date_format => '' do + assert_equal '02/20/2011 03:45 pm', format_time(now) + assert_equal '03:45 pm', format_time(now, false) + end + with_settings :date_format => '%Y-%m-%d' do + assert_equal '2011-02-20 03:45 pm', format_time(now) + assert_equal '03:45 pm', format_time(now, false) + end + end + end + + def test_time_format_default_with_user_locale + set_language_if_valid 'en' + User.current.language = 'fr' + now = Time.parse('2011-02-20 15:45:22') + with_settings :time_format => '' do + with_settings :date_format => '' do + assert_equal '20/02/2011 15:45', format_time(now) + assert_equal '15:45', format_time(now, false) + end + with_settings :date_format => '%Y-%m-%d' do + assert_equal '2011-02-20 15:45', format_time(now) + assert_equal '15:45', format_time(now, false) + end + end + end + + def test_time_format + set_language_if_valid 'en' + now = Time.now + Setting.date_format = '%d %m %Y' + Setting.time_format = '%H %M' + assert_equal now.strftime('%d %m %Y %H %M'), format_time(now) + assert_equal now.strftime('%H %M'), format_time(now, false) + end + + def test_utc_time_format + set_language_if_valid 'en' + now = Time.now + Setting.date_format = '%d %m %Y' + Setting.time_format = '%H %M' + assert_equal now.strftime('%d %m %Y %H %M'), format_time(now.utc) + assert_equal now.strftime('%H %M'), format_time(now.utc, false) + end + + def test_number_to_human_size_for_each_language + valid_languages.each do |lang| + set_language_if_valid lang + assert_nothing_raised "#{lang} failure" do + size = number_to_human_size(257024) + assert_match /251/, size + end + end + end + + def test_day_name + set_language_if_valid 'fr' + assert_equal 'dimanche', day_name(0) + assert_equal 'jeudi', day_name(4) + end + + def test_day_letter + set_language_if_valid 'fr' + assert_equal 'd', day_letter(0) + assert_equal 'j', day_letter(4) + end + + def test_number_to_currency_for_each_language + valid_languages.each do |lang| + set_language_if_valid lang + assert_nothing_raised "#{lang} failure" do + number_to_currency(-1000.2) + end + end + end + + def test_number_to_currency_default + set_language_if_valid 'bs' + assert_equal "KM -1000,20", number_to_currency(-1000.2) + set_language_if_valid 'de' + euro_sign = "\xe2\x82\xac" + euro_sign.force_encoding('UTF-8') if euro_sign.respond_to?(:force_encoding) + assert_equal "-1000,20 #{euro_sign}", number_to_currency(-1000.2) + end + + def test_valid_languages + assert valid_languages.is_a?(Array) + assert valid_languages.first.is_a?(Symbol) + end + + def test_languages_options + options = languages_options + + assert options.is_a?(Array) + assert_equal valid_languages.size, options.size + assert_nil options.detect {|option| !option.is_a?(Array)} + assert_nil options.detect {|option| option.size != 2} + assert_nil options.detect {|option| !option.first.is_a?(String) || !option.last.is_a?(String)} + assert_include ["English", "en"], options + end + + def test_locales_validness + lang_files_count = Dir["#{Rails.root}/config/locales/*.yml"].size + assert_equal lang_files_count, valid_languages.size + valid_languages.each do |lang| + assert set_language_if_valid(lang) + end + set_language_if_valid('en') + end + + def test_valid_language + to_test = {'fr' => :fr, + 'Fr' => :fr, + 'zh' => :zh, + 'zh-tw' => :"zh-TW", + 'zh-TW' => :"zh-TW", + 'zh-ZZ' => nil } + to_test.each {|lang, expected| assert_equal expected, find_language(lang)} + end + + def test_fallback + ::I18n.backend.store_translations(:en, {:untranslated => "Untranslated string"}) + ::I18n.locale = 'en' + assert_equal "Untranslated string", l(:untranslated) + ::I18n.locale = 'fr' + assert_equal "Untranslated string", l(:untranslated) + + ::I18n.backend.store_translations(:fr, {:untranslated => "Pas de traduction"}) + ::I18n.locale = 'en' + assert_equal "Untranslated string", l(:untranslated) + ::I18n.locale = 'fr' + assert_equal "Pas de traduction", l(:untranslated) + end + + def test_utf8 + set_language_if_valid 'ja' + str_ja_yes = "\xe3\x81\xaf\xe3\x81\x84" + i18n_ja_yes = l(:general_text_Yes) + if str_ja_yes.respond_to?(:force_encoding) + str_ja_yes.force_encoding('UTF-8') + assert_equal "UTF-8", i18n_ja_yes.encoding.to_s + end + assert_equal str_ja_yes, i18n_ja_yes + end + + def test_traditional_chinese_locale + set_language_if_valid 'zh-TW' + str_tw = "Traditional Chinese (\xe7\xb9\x81\xe9\xab\x94\xe4\xb8\xad\xe6\x96\x87)" + if str_tw.respond_to?(:force_encoding) + str_tw.force_encoding('UTF-8') + end + assert_equal str_tw, l(:general_lang_name) + end + + def test_french_locale + set_language_if_valid 'fr' + str_fr = "Fran\xc3\xa7ais" + if str_fr.respond_to?(:force_encoding) + str_fr.force_encoding('UTF-8') + end + assert_equal str_fr, l(:general_lang_name) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/8b/8bccd0624e4f8225c7389853d52b177216bc29f0.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/8b/8bccd0624e4f8225c7389853d52b177216bc29f0.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,89 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 ContextMenusController < ApplicationController + helper :watchers + helper :issues + + def issues + @issues = Issue.visible.all(:conditions => {:id => params[:ids]}, :include => :project) + (render_404; return) unless @issues.present? + if (@issues.size == 1) + @issue = @issues.first + end + @issue_ids = @issues.map(&:id).sort + + @allowed_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&) + @projects = @issues.collect(&:project).compact.uniq + @project = @projects.first if @projects.size == 1 + + @can = {:edit => User.current.allowed_to?(:edit_issues, @projects), + :log_time => (@project && User.current.allowed_to?(:log_time, @project)), + :update => (User.current.allowed_to?(:edit_issues, @projects) || (User.current.allowed_to?(:change_status, @projects) && !@allowed_statuses.blank?)), + :move => (@project && User.current.allowed_to?(:move_issues, @project)), + :copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)), + :delete => User.current.allowed_to?(:delete_issues, @projects) + } + if @project + if @issue + @assignables = @issue.assignable_users + else + @assignables = @project.assignable_users + end + @trackers = @project.trackers + else + #when multiple projects, we only keep the intersection of each set + @assignables = @projects.map(&:assignable_users).reduce(:&) + @trackers = @projects.map(&:trackers).reduce(:&) + end + @versions = @projects.map {|p| p.shared_versions.open}.reduce(:&) + + @priorities = IssuePriority.active.reverse + @back = back_url + + @options_by_custom_field = {} + if @can[:edit] + custom_fields = @issues.map(&:available_custom_fields).reduce(:&).select do |f| + %w(bool list user version).include?(f.field_format) && !f.multiple? + end + custom_fields.each do |field| + values = field.possible_values_options(@projects) + if values.any? + @options_by_custom_field[field] = values + end + end + end + + @safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&) + render :layout => false + end + + def time_entries + @time_entries = TimeEntry.all( + :conditions => {:id => params[:ids]}, :include => :project) + (render_404; return) unless @time_entries.present? + + @projects = @time_entries.collect(&:project).compact.uniq + @project = @projects.first if @projects.size == 1 + @activities = TimeEntryActivity.shared.active + @can = {:edit => User.current.allowed_to?(:edit_time_entries, @projects), + :delete => User.current.allowed_to?(:edit_time_entries, @projects) + } + @back = back_url + render :layout => false + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/8c/8c1d2e96303e1f62e53705f1a07477a847f9fb94.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/8c/8c1d2e96303e1f62e53705f1a07477a847f9fb94.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,135 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class TimeEntryTest < ActiveSupport::TestCase + fixtures :issues, :projects, :users, :time_entries, + :members, :roles, :member_roles, + :trackers, :issue_statuses, + :projects_trackers, + :journals, :journal_details, + :issue_categories, :enumerations, + :groups_users, + :enabled_modules + + def test_hours_format + 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, + "45m" => 0.75, + "45 m" => 0.75, + "3h15" => 3.25, + "3h 15" => 3.25, + "3 h 15" => 3.25, + "3 h 15m" => 3.25, + "3 h 15 m" => 3.25, + "3 hours" => 3.0, + "12min" => 0.2, + "12 Min" => 0.2, + } + + assertions.each do |k, v| + t = TimeEntry.new(:hours => k) + assert_equal v, t.hours, "Converting #{k} failed:" + end + end + + def test_hours_should_default_to_nil + assert_nil TimeEntry.new.hours + end + + def test_spent_on_with_blank + c = TimeEntry.new + c.spent_on = '' + assert_nil c.spent_on + end + + def test_spent_on_with_nil + c = TimeEntry.new + c.spent_on = nil + assert_nil c.spent_on + end + + def test_spent_on_with_string + c = TimeEntry.new + c.spent_on = "2011-01-14" + assert_equal Date.parse("2011-01-14"), c.spent_on + end + + def test_spent_on_with_invalid_string + c = TimeEntry.new + c.spent_on = "foo" + assert_nil c.spent_on + end + + def test_spent_on_with_date + c = TimeEntry.new + c.spent_on = Date.today + assert_equal Date.today, c.spent_on + end + + def test_spent_on_with_time + c = TimeEntry.new + c.spent_on = Time.now + assert_equal Date.today, c.spent_on + end + + def test_validate_time_entry + anon = User.anonymous + project = Project.find(1) + issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => anon.id, :status_id => 1, + :priority => IssuePriority.all.first, :subject => 'test_create', + :description => 'IssueTest#test_create', :estimated_hours => '1:30') + assert issue.save + activity = TimeEntryActivity.find_by_name('Design') + te = TimeEntry.create(:spent_on => '2010-01-01', + :hours => 100000, + :issue => issue, + :project => project, + :user => anon, + :activity => activity) + assert_equal 1, te.errors.count + end + + def test_spent_on_with_2_digits_year_should_not_be_valid + entry = TimeEntry.new(:project => Project.find(1), :user => User.find(1), :activity => TimeEntryActivity.first, :hours => 1) + entry.spent_on = "09-02-04" + assert !entry.valid? + assert_include I18n.translate('activerecord.errors.messages.not_a_date'), entry.errors[:spent_on] + end + + def test_set_project_if_nil + anon = User.anonymous + project = Project.find(1) + issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => anon.id, :status_id => 1, + :priority => IssuePriority.all.first, :subject => 'test_create', + :description => 'IssueTest#test_create', :estimated_hours => '1:30') + assert issue.save + activity = TimeEntryActivity.find_by_name('Design') + te = TimeEntry.create(:spent_on => '2010-01-01', + :hours => 10, + :issue => issue, + :user => anon, + :activity => activity) + assert_equal project.id, te.project.id + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/8c/8c336a0caf0b197036e75f77b3fda9e373c1dbe0.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/8c/8c336a0caf0b197036e75f77b3fda9e373c1dbe0.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,1125 @@ +# Spanish translations for Rails +# by Francisco Fernando García Nieto (ffgarcianieto@gmail.com) +# Redmine spanish translation: +# by J. Cayetano Delgado (Cayetano _dot_ Delgado _at_ ioko _dot_ com) + +es: + number: + # Used in number_with_delimiter() + # These are also the defaults for 'currency', 'percentage', 'precision', and 'human' + format: + # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5) + separator: "," + # Delimets thousands (e.g. 1,000,000 is a million) (always in groups of three) + delimiter: "." + # Number of decimals, behind the separator (1 with a precision of 2 gives: 1.00) + precision: 3 + + # Used in number_to_currency() + currency: + format: + # Where is the currency sign? %u is the currency unit, %n the number (default: $5.00) + format: "%n %u" + unit: "€" + # These three are to override number.format and are optional + separator: "," + delimiter: "." + precision: 2 + + # Used in number_to_percentage() + percentage: + format: + # These three are to override number.format and are optional + # separator: + delimiter: "" + # precision: + + # Used in number_to_precision() + precision: + format: + # These three are to override number.format and are optional + # separator: + delimiter: "" + # precision: + + # Used in number_to_human_size() + human: + format: + # These three are to override number.format and are optional + # separator: + delimiter: "" + precision: 3 + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + + # Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words() + datetime: + distance_in_words: + half_a_minute: "medio minuto" + less_than_x_seconds: + one: "menos de 1 segundo" + other: "menos de %{count} segundos" + x_seconds: + one: "1 segundo" + other: "%{count} segundos" + less_than_x_minutes: + one: "menos de 1 minuto" + other: "menos de %{count} minutos" + x_minutes: + one: "1 minuto" + other: "%{count} minutos" + about_x_hours: + one: "alrededor de 1 hora" + other: "alrededor de %{count} horas" + x_hours: + one: "1 hora" + other: "%{count} horas" + x_days: + one: "1 día" + other: "%{count} días" + about_x_months: + one: "alrededor de 1 mes" + other: "alrededor de %{count} meses" + x_months: + one: "1 mes" + other: "%{count} meses" + about_x_years: + one: "alrededor de 1 año" + other: "alrededor de %{count} años" + over_x_years: + one: "más de 1 año" + other: "más de %{count} años" + almost_x_years: + one: "casi 1 año" + other: "casi %{count} años" + + activerecord: + errors: + template: + header: + one: "no se pudo guardar este %{model} porque se encontró 1 error" + other: "no se pudo guardar este %{model} porque se encontraron %{count} errores" + # The variable :count is also available + body: "Se encontraron problemas con los siguientes campos:" + + # The values :model, :attribute and :value are always available for interpolation + # The value :count is available when applicable. Can be used for pluralization. + messages: + inclusion: "no está incluido en la lista" + exclusion: "está reservado" + invalid: "no es válido" + confirmation: "no coincide con la confirmación" + accepted: "debe ser aceptado" + empty: "no puede estar vacío" + blank: "no puede estar en blanco" + too_long: "es demasiado largo (%{count} caracteres máximo)" + too_short: "es demasiado corto (%{count} caracteres mínimo)" + wrong_length: "no tiene la longitud correcta (%{count} caracteres exactos)" + taken: "ya está en uso" + not_a_number: "no es un número" + greater_than: "debe ser mayor que %{count}" + greater_than_or_equal_to: "debe ser mayor que o igual a %{count}" + equal_to: "debe ser igual a %{count}" + less_than: "debe ser menor que %{count}" + less_than_or_equal_to: "debe ser menor que o igual a %{count}" + odd: "debe ser impar" + even: "debe ser par" + greater_than_start_date: "debe ser posterior a la fecha de comienzo" + not_same_project: "no pertenece al mismo proyecto" + circular_dependency: "Esta relación podría crear una dependencia circular" + cant_link_an_issue_with_a_descendant: "Esta petición no puede ser ligada a una de estas tareas" + + # Append your own errors here or at the model/attributes scope. + + models: + # Overrides default messages + + attributes: + # Overrides model and default messages. + + direction: ltr + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%Y-%m-%d" + short: "%d de %b" + long: "%d de %B de %Y" + + day_names: [Domingo, Lunes, Martes, Miércoles, Jueves, Viernes, Sábado] + abbr_day_names: [Dom, Lun, Mar, Mie, Jue, Vie, Sab] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, Enero, Febrero, Marzo, Abril, Mayo, Junio, Julio, Agosto, Septiembre, Octubre, Noviembre, Diciembre] + abbr_month_names: [~, Ene, Feb, Mar, Abr, May, Jun, Jul, Ago, Sep, Oct, Nov, Dic] + # Used in date_select and datime_select. + order: + - :year + - :month + - :day + + time: + formats: + default: "%A, %d de %B de %Y %H:%M:%S %z" + time: "%H:%M" + short: "%d de %b %H:%M" + long: "%d de %B de %Y %H:%M" + am: "am" + pm: "pm" + +# Used in array.to_sentence. + support: + array: + sentence_connector: "y" + + actionview_instancetag_blank_option: Por favor seleccione + + 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: Acceder + 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_in_progress: En curso + 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_developer: 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: %{value}" + 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_annotate_big_text_file: "La entrada no puede anotarse, al superar el tamaño máximo para ficheros de texto." + error_scm_command_failed: "Se produjo un error al acceder al repositorio: %{value}" + error_scm_not_found: "La entrada y/o la revisión no existe en el repositorio." + 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_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_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_is_filter: Usado como filtro + field_is_for_all: Para todos los proyectos + 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: 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_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_port: Puerto + 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: Asunto + field_subproject: Proyecto secundario + 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_first_day_of_week: '1' + general_lang_name: 'Español' + general_pdf_encoding: UTF-8 + general_text_No: 'No' + general_text_Yes: 'Sí' + general_text_no: 'no' + general_text_yes: 'sí' + 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 %{author} hace %{age}" + 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: "%{value} 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_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_x_open_issues_abbr_on_total: + zero: 0 abiertas / %{total} + one: 1 abierta / %{total} + other: "%{count} abiertas / %{total}" + label_x_open_issues_abbr: + zero: 0 abiertas + one: 1 abierta + other: "%{count} abiertas" + label_x_closed_issues_abbr: + zero: 0 cerradas + one: 1 cerrada + other: "%{count} 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_x_comments: + zero: sin comentarios + one: 1 comentario + other: "%{count} 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: %{value}" + label_document: Documento + label_document_added: Documento añadido + label_document_new: Nuevo documento + label_document_plural: Documentos + 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: "%{value} hora" + label_f_hour_plural: "%{value} horas" + label_feed_plural: Feeds + label_feeds_access_key_created_on: "Clave de acceso por RSS creada hace %{value}" + 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_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 %{value}" + label_jump_to_a_project: Ir al proyecto... + label_language_based: Basado en el idioma + label_last_changes: "últimos %{count} cambios" + label_last_login: Última conexión + label_last_month: último mes + label_last_n_days: "últimos %{count} días" + 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_login: Iniciar sesión + label_logout: Terminar sesió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_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_view_all: Ver todas las noticias + label_next: Siguiente + label_no_change_option: (Sin cambios) + label_no_data: Ningún dato disponible + label_nobody: nadie + label_none: ninguno + label_not_contains: no contiene + label_not_equals: no igual + label_open_issues: abierta + label_open_issues_plural: abiertas + 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_permissions_report: Informe de permisos + label_personalize_page: Personalizar esta página + label_planning: Planificación + label_please_login: Por favor, inicie sesió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_x_projects: + zero: sin proyectos + one: 1 proyecto + other: "%{count} proyectos" + label_public_projects: Proyectos públicos + label_query: Consulta personalizada + label_query_new: Nueva consulta + 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_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_roadmap: Planificación + label_roadmap_due_in: "Finaliza en %{value}" + label_roadmap_no_issues: No hay peticiones para esta versión + label_roadmap_overdue: "%{value} 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_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 %{value}" + 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: Mantener la sesión abierta + 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 %{value}" + label_updated_time_by: "Actualizado por %{author} hace %{age}" + label_used_by: Utilizado por + label_user: Usuario + label_user_activity: "Actividad de %{value}" + 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_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: Modificación Wiki + label_wiki_edit_plural: Modificaciones Wiki + label_wiki_page: Página Wiki + label_wiki_page_plural: Páginas Wiki + label_workflow: Flujo de trabajo + label_year: Año + label_yesterday: ayer + mail_body_account_activation_request: "Se ha inscrito un nuevo usuario (%{value}). La cuenta está pendiende de aprobación:" + mail_body_account_information: Información sobre su cuenta + mail_body_account_information_external: "Puede usar su cuenta %{value} 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: "%{count} peticion(es) asignadas a tí finalizan en los próximos %{days} días:" + mail_subject_account_activation_request: "Petición de activación de cuenta %{value}" + mail_subject_lost_password: "Tu contraseña del %{value}" + mail_subject_register: "Activación de la cuenta del %{value}" + mail_subject_reminder: "%{count} peticion(es) finalizan en los próximos %{days} 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. + notice_email_error: "Ha ocurrido un error mientras enviando el correo (%{value})" + notice_email_sent: "Se ha enviado un correo a %{value}" + notice_failed_to_save_issues: "Imposible grabar %{count} peticion(es) de %{total} seleccionada(s): %{ids}." + 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_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_documents: Documentos + project_module_files: Ficheros + project_module_issue_tracking: Peticiones + project_module_news: Noticias + project_module_repository: Repositorio + project_module_time_tracking: Control de tiempo + project_module_wiki: Wiki + setting_activity_days_default: Días a mostrar en la actividad de proyecto + 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: Inicio de sesión automático + setting_bcc_recipients: Ocultar las copias de carbón (bcc) + setting_commit_fix_keywords: Palabras clave para la corrección + 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 + 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_per_page_options: Objetos por página + setting_plain_text_mail: sólo texto plano (no HTML) + setting_protocol: Protocolo + 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: "%{count} caracteres como máximo." + text_caracters_minimum: "%{count} 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 %{hours} 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: "Las notificaciones están desactivadas porque el servidor de correo no está configurado.\nConfigure el servidor de SMTP en config/configuration.yml y reinicie la aplicación para activar los cambios." + text_enumeration_category_reassign_to: 'Reasignar al siguiente valor:' + text_enumeration_destroy_question: "%{count} objetos con este valor asignado." + text_file_repository_writable: Se puede escribir en el repositorio + text_issue_added: "Petición %{id} añadida por %{author}." + text_issue_category_destroy_assignments: Dejar las peticiones sin categoría + text_issue_category_destroy_question: "Algunas peticiones (%{count}) 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 %{id} ha sido actualizada por %{author}." + 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_length_between: "Longitud entre %{min} y %{max} 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_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 %{value}" + text_subprojects_destroy_warning: "Los proyectos secundarios: %{value} también se eliminarán" + text_tip_issue_begin_day: tarea que comienza este día + text_tip_issue_begin_end_day: tarea que comienza y termina este día + text_tip_issue_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: "%{value} 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: Se puede escribir en el directorio público de las extensiones + warning_attachments_not_saved: "No se han podido grabar %{count} ficheros." + button_create_and_continue: Crear y continuar + text_custom_field_possible_values_info: 'Un valor en cada línea' + label_display: Mostrar + field_editable: Modificable + setting_repository_log_display_limit: Número máximo de revisiones mostradas en el fichero de trazas + setting_file_max_size_displayed: Tamaño máximo de los ficheros de texto mostrados + field_watcher: Seguidor + setting_openid: Permitir identificación y registro por OpenID + field_identity_url: URL de OpenID + label_login_with_open_id_option: o identifíquese con OpenID + field_content: Contenido + label_descending: Descendente + label_sort: Ordenar + label_ascending: Ascendente + label_date_from_to: Desde %{start} hasta %{end} + label_greater_or_equal: ">=" + label_less_or_equal: <= + text_wiki_page_destroy_question: Esta página tiene %{descendants} página(s) hija(s) y descendiente(s). ¿Qué desea hacer? + text_wiki_page_reassign_children: Reasignar páginas hijas a esta página + text_wiki_page_nullify_children: Dejar páginas hijas como páginas raíz + text_wiki_page_destroy_children: Eliminar páginas hijas y todos sus descendientes + setting_password_min_length: Longitud mínima de la contraseña + field_group_by: Agrupar resultados por + mail_subject_wiki_content_updated: "La página wiki '%{id}' ha sido actualizada" + label_wiki_content_added: Página wiki añadida + mail_subject_wiki_content_added: "Se ha añadido la página wiki '%{id}'." + mail_body_wiki_content_added: "%{author} ha añadido la página wiki '%{id}'." + label_wiki_content_updated: Página wiki actualizada + mail_body_wiki_content_updated: La página wiki '%{id}' ha sido actualizada por %{author}. + permission_add_project: Crear proyecto + setting_new_project_user_role_id: Permiso asignado a un usuario no-administrador para crear proyectos + label_view_all_revisions: Ver todas las revisiones + label_tag: Etiqueta + label_branch: Rama + error_no_tracker_in_project: Este proyecto no tiene asociados tipos de peticiones. Por favor, revise la configuración. + error_no_default_issue_status: No se ha definido un estado de petición por defecto. Por favor, revise la configuración (en "Administración" -> "Estados de las peticiones"). + text_journal_changed: "%{label} cambiado %{old} por %{new}" + text_journal_set_to: "%{label} establecido a %{value}" + text_journal_deleted: "%{label} eliminado (%{old})" + label_group_plural: Grupos + label_group: Grupo + label_group_new: Nuevo grupo + label_time_entry_plural: Tiempo dedicado + text_journal_added: "Añadido %{label} %{value}" + field_active: Activo + enumeration_system_activity: Actividad del sistema + permission_delete_issue_watchers: Borrar seguidores + version_status_closed: cerrado + version_status_locked: bloqueado + version_status_open: abierto + error_can_not_reopen_issue_on_closed_version: No se puede reabrir una petición asignada a una versión cerrada + + label_user_anonymous: Anónimo + button_move_and_follow: Mover y seguir + setting_default_projects_modules: Módulos activados por defecto en proyectos nuevos + setting_gravatar_default: Imagen Gravatar por defecto + field_sharing: Compartir + button_copy_and_follow: Copiar y seguir + label_version_sharing_hierarchy: Con la jerarquía del proyecto + label_version_sharing_tree: Con el árbol del proyecto + label_version_sharing_descendants: Con proyectos hijo + label_version_sharing_system: Con todos los proyectos + label_version_sharing_none: No compartir + button_duplicate: Duplicar + error_can_not_archive_project: Este proyecto no puede ser archivado + label_copy_source: Fuente + setting_issue_done_ratio: Calcular el ratio de tareas realizadas con + setting_issue_done_ratio_issue_status: Usar el estado de tareas + error_issue_done_ratios_not_updated: Ratios de tareas realizadas no actualizado. + error_workflow_copy_target: Por favor, elija categoría(s) y perfil(es) destino + setting_issue_done_ratio_issue_field: Utilizar el campo de petición + label_copy_same_as_target: El mismo que el destino + label_copy_target: Destino + notice_issue_done_ratios_updated: Ratios de tareas realizadas actualizados. + error_workflow_copy_source: Por favor, elija una categoría o rol de origen + label_update_issue_done_ratios: Actualizar ratios de tareas realizadas + setting_start_of_week: Comenzar las semanas en + permission_view_issues: Ver peticiones + label_display_used_statuses_only: Sólo mostrar los estados usados por este tipo de petición + label_revision_id: Revisión %{value} + label_api_access_key: Clave de acceso de la API + label_api_access_key_created_on: Clave de acceso de la API creada hace %{value} + label_feeds_access_key: Clave de acceso RSS + notice_api_access_key_reseted: Clave de acceso a la API regenerada. + setting_rest_api_enabled: Activar servicio web REST + label_missing_api_access_key: Clave de acceso a la API ausente + label_missing_feeds_access_key: Clave de accesso RSS ausente + button_show: Mostrar + text_line_separated: Múltiples valores permitidos (un valor en cada línea). + setting_mail_handler_body_delimiters: Truncar correos tras una de estas líneas + permission_add_subprojects: Crear subproyectos + label_subproject_new: Nuevo subproyecto + text_own_membership_delete_confirmation: |- + Está a punto de eliminar algún o todos sus permisos y podría perder la posibilidad de modificar este proyecto tras hacerlo. + ¿Está seguro de querer continuar? + label_close_versions: Cerrar versiones completadas + label_board_sticky: Pegajoso + label_board_locked: Bloqueado + permission_export_wiki_pages: Exportar páginas wiki + setting_cache_formatted_text: Cachear texto formateado + permission_manage_project_activities: Gestionar actividades del proyecto + error_unable_delete_issue_status: Fue imposible eliminar el estado de la petición + label_profile: Perfil + permission_manage_subtasks: Gestionar subtareas + field_parent_issue: Tarea padre + label_subtask_plural: Subtareas + label_project_copy_notifications: Enviar notificaciones por correo electrónico durante la copia del proyecto + error_can_not_delete_custom_field: Fue imposible eliminar el campo personalizado + error_unable_to_connect: Fue imposible conectar con (%{value}) + error_can_not_remove_role: Este rol está en uso y no puede ser eliminado. + error_can_not_delete_tracker: Este tipo contiene peticiones y no puede ser eliminado. + field_principal: Principal + label_my_page_block: Bloque Mi página + notice_failed_to_save_members: "Fallo al guardar miembro(s): %{errors}." + text_zoom_out: Alejar + text_zoom_in: Acercar + notice_unable_delete_time_entry: Fue imposible eliminar la entrada de tiempo dedicado. + label_overall_spent_time: Tiempo total dedicado + field_time_entries: Log time + project_module_gantt: Gantt + project_module_calendar: Calendario + button_edit_associated_wikipage: "Editar paginas Wiki asociadas: %{page_title}" + field_text: Campo de texto + label_user_mail_option_only_owner: Solo para objetos que soy propietario + setting_default_notification_option: Opcion de notificacion por defecto + label_user_mail_option_only_my_events: Solo para objetos que soy seguidor o estoy involucrado + label_user_mail_option_only_assigned: Solo para objetos que estoy asignado + label_user_mail_option_none: Sin eventos + field_member_of_group: Asignado al grupo + field_assigned_to_role: Asignado al perfil + notice_not_authorized_archived_project: El proyecto al que intenta acceder ha sido archivado. + label_principal_search: "Buscar por usuario o grupo:" + label_user_search: "Buscar por usuario:" + field_visible: Visible + setting_emails_header: Encabezado de Correos + + setting_commit_logtime_activity_id: Actividad de los tiempos registrados + text_time_logged_by_changeset: Aplicado en los cambios %{value}. + setting_commit_logtime_enabled: Habilitar registro de horas + notice_gantt_chart_truncated: Se recortó el diagrama porque excede el número máximo de elementos que pueden ser mostrados (%{max}) + setting_gantt_items_limit: Número máximo de elementos mostrados en el diagrama de Gantt + field_warn_on_leaving_unsaved: Avisarme cuando vaya a abandonar una página con texto no guardado + text_warn_on_leaving_unsaved: Esta página contiene texto no guardado y si la abandona sus cambios se perderán + label_my_queries: Mis consultas personalizadas + text_journal_changed_no_detail: "Se actualizó %{label}" + label_news_comment_added: Comentario añadido a noticia + button_expand_all: Expandir todo + button_collapse_all: Contraer todo + label_additional_workflow_transitions_for_assignee: Transiciones adicionales permitidas cuando la petición está asignada al usuario + label_additional_workflow_transitions_for_author: Transiciones adicionales permitidas cuando el usuario es autor de la petición + label_bulk_edit_selected_time_entries: Editar en bloque las horas seleccionadas + text_time_entries_destroy_confirmation: ¿Está seguro de querer eliminar (la hora seleccionada/las horas seleccionadas)? + label_role_anonymous: Anónimo + label_role_non_member: No miembro + label_issue_note_added: Nota añadida + label_issue_status_updated: Estado actualizado + label_issue_priority_updated: Prioridad actualizada + label_issues_visibility_own: Peticiones creadas por el usuario o asignadas a él + field_issues_visibility: Visibilidad de las peticiones + label_issues_visibility_all: Todas las peticiones + permission_set_own_issues_private: Poner las peticiones propias como públicas o privadas + field_is_private: Privada + permission_set_issues_private: Poner peticiones como públicas o privadas + label_issues_visibility_public: Todas las peticiones no privadas + text_issues_destroy_descendants_confirmation: Se procederá a borrar también %{count} subtarea(s). + field_commit_logs_encoding: Codificación de los mensajes de commit + field_scm_path_encoding: Codificación de las rutas + text_scm_path_encoding_note: "Por defecto: UTF-8" + field_path_to_repository: Ruta al repositorio + field_root_directory: Directorio raíz + field_cvs_module: Módulo + field_cvsroot: CVSROOT + text_mercurial_repository_note: Repositorio local (e.g. /hgrepo, c:\hgrepo) + text_scm_command: Orden + text_scm_command_version: Versión + label_git_report_last_commit: Informar del último commit para ficheros y directorios + text_scm_config: Puede configurar las órdenes de cada scm en configuration/configuration.yml. Por favor, reinicie la aplicación después de editarlo + text_scm_command_not_available: La orden para el Scm no está disponible. Por favor, compruebe la configuración en el panel de administración. + notice_issue_successful_create: Petición %{id} creada. + label_between: entre + setting_issue_group_assignment: Permitir asignar peticiones a grupos + label_diff: diferencias + text_git_repository_note: El repositorio es básico y local (p.e. /gitrepo, c:\gitrepo) + description_query_sort_criteria_direction: Dirección de ordenación + description_project_scope: Ãmbito de búsqueda + description_filter: Filtro + description_user_mail_notification: Configuración de notificaciones por correo + description_date_from: Introduzca la fecha de inicio + description_message_content: Contenido del mensaje + description_available_columns: Columnas disponibles + description_date_range_interval: Elija el rango seleccionando la fecha de inicio y fin + description_issue_category_reassign: Elija la categoría de la petición + description_search: Campo de búsqueda + description_notes: Notas + description_date_range_list: Elija el rango en la lista + description_choose_project: Proyectos + description_date_to: Introduzca la fecha fin + description_query_sort_criteria_attribute: Atributo de ordenación + description_wiki_subpages_reassign: Elija la nueva página padre + description_selected_columns: Columnas seleccionadas + label_parent_revision: Padre + label_child_revision: Hijo + setting_default_issue_start_date_to_creation_date: Utilizar la fecha actual como fecha de inicio para nuevas peticiones + button_edit_section: Editar esta sección + setting_repositories_encodings: Codificación de adjuntos y repositorios + description_all_columns: Todas las columnas + button_export: Exportar + label_export_options: "%{export_format} opciones de exportación" + error_attachment_too_big: Este fichero no se puede adjuntar porque excede el tamaño máximo de fichero (%{max_size}) + notice_failed_to_save_time_entries: "Error al guardar %{count} entradas de tiempo de las %{total} seleccionadas: %{ids}." + label_x_issues: + zero: 0 petición + one: 1 petición + other: "%{count} peticiones" + label_repository_new: Nuevo repositorio + field_repository_is_default: Repositorio principal + label_copy_attachments: Copiar adjuntos + label_item_position: "%{position}/%{count}" + label_completed_versions: Versiones completadas + text_project_identifier_info: Solo se permiten letras en minúscula (a-z), números, guiones y barras bajas.
    Una vez guardado, el identificador no se puede cambiar. + field_multiple: Valores múltiples + setting_commit_cross_project_ref: Permitir referenciar y resolver peticiones de todos los demás proyectos + text_issue_conflict_resolution_add_notes: Añadir mis notas y descartar mis otros cambios + text_issue_conflict_resolution_overwrite: Aplicar mis campos de todas formas (las notas anteriores se mantendrán pero algunos cambios podrían ser sobreescritos) + notice_issue_update_conflict: La petición ha sido actualizada por otro usuario mientras la editaba. + text_issue_conflict_resolution_cancel: Descartar todos mis cambios y mostrar de nuevo %{link} + permission_manage_related_issues: Gestionar peticiones relacionadas + field_auth_source_ldap_filter: Filtro LDAP + label_search_for_watchers: Buscar seguidores para añadirlos + notice_account_deleted: Su cuenta ha sido eliminada + setting_unsubscribe: Permitir a los usuarios borrar sus propias cuentas + button_delete_my_account: Borrar mi cuenta + text_account_destroy_confirmation: |- + ¿Está seguro de querer proceder? + Su cuenta quedará borrada permanentemente, sin la posibilidad de reactivarla. + error_session_expired: Su sesión ha expirado. Por favor, vuelva a identificarse. + text_session_expiration_settings: "Advertencia: el cambio de estas opciones podría hacer expirar las sesiones activas, incluyendo la suya." + setting_session_lifetime: Tiempo de vida máximo de las sesiones + setting_session_timeout: Tiempo máximo de inactividad de las sesiones + label_session_expiration: Expiración de sesiones + permission_close_project: Cerrar / reabrir el proyecto + label_show_closed_projects: Ver proyectos cerrados + button_close: Cerrar + button_reopen: Reabrir + project_status_active: activo + project_status_closed: cerrado + project_status_archived: archivado + text_project_closed: Este proyecto está cerrado y es de sólo lectura + notice_user_successful_create: Usuario %{id} creado. + field_core_fields: Campos básicos + field_timeout: Tiempo de inactividad (en segundos) + setting_thumbnails_enabled: Mostrar miniaturas de los adjuntos + setting_thumbnails_size: Tamaño de las miniaturas (en píxeles) + label_status_transitions: Transiciones de estado + label_fields_permissions: Permisos sobre los campos + label_readonly: Sólo lectura + label_required: Requerido + text_repository_identifier_info: Solo se permiten letras en minúscula (a-z), números, guiones y barras bajas.
    Una vez guardado, el identificador no se puede cambiar. + field_board_parent: Foro padre + label_attribute_of_project: "%{name} del proyecto" + label_attribute_of_author: "%{name} del autor" + label_attribute_of_assigned_to: "%{name} de la persona asignada" + label_attribute_of_fixed_version: "%{name} de la versión indicada" + label_copy_subtasks: Copiar subtareas + label_copied_to: copiada a + label_copied_from: copiada desde + label_any_issues_in_project: cualquier petición del proyecto + label_any_issues_not_in_project: cualquier petición que no sea del proyecto + field_private_notes: Notas privadas + permission_view_private_notes: Ver notas privadas + permission_set_notes_private: Poner notas como privadas + label_no_issues_in_project: no hay peticiones en el proyecto + label_any: todos + label_last_n_weeks: en las últimas %{count} semanas + setting_cross_project_subtasks: Permitir subtareas cruzadas entre proyectos + label_cross_project_descendants: Con proyectos hijo + label_cross_project_tree: Con el árbol del proyecto + label_cross_project_hierarchy: Con la jerarquía del proyecto + label_cross_project_system: Con todos los proyectos + button_hide: Ocultar + setting_non_working_week_days: Días no laborables + label_in_the_next_days: en los próximos + label_in_the_past_days: en los anteriores + label_attribute_of_user: "%{name} del usuario" + text_turning_multiple_off: Si desactiva los valores múltiples, éstos serán eliminados para dejar un único valor por elemento. + label_attribute_of_issue: "%{name} de la petición" + permission_add_documents: Añadir documentos + permission_edit_documents: Editar documentos + permission_delete_documents: Borrar documentos + label_gantt_progress_line: Línea de progreso + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Total diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/8c/8c4fab1287b72e3f74cffb8a1dd8ba6c360dd802.svn-base --- a/.svn/pristine/8c/8c4fab1287b72e3f74cffb8a1dd8ba6c360dd802.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,110 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class QueriesController < ApplicationController - menu_item :issues - before_filter :find_query, :except => [:new, :create, :index] - before_filter :find_optional_project, :only => [:new, :create] - - accept_api_auth :index - - include QueriesHelper - - def index - case params[:format] - when 'xml', 'json' - @offset, @limit = api_offset_and_limit - else - @limit = per_page_option - end - - @query_count = Query.visible.count - @query_pages = Paginator.new self, @query_count, @limit, params['page'] - @queries = Query.visible.all(:limit => @limit, :offset => @offset, :order => "#{Query.table_name}.name") - - respond_to do |format| - format.html { render :nothing => true } - format.api - end - end - - def new - @query = Query.new - @query.user = User.current - @query.project = @project - @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin? - build_query_from_params - end - - verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed } - def create - @query = Query.new(params[:query]) - @query.user = User.current - @query.project = params[:query_is_for_all] ? nil : @project - @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin? - build_query_from_params - @query.column_names = nil if params[:default_columns] - - if @query.save - flash[:notice] = l(:notice_successful_create) - redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query - else - render :action => 'new', :layout => !request.xhr? - end - end - - def edit - end - - verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed } - def update - @query.attributes = params[:query] - @query.project = nil if params[:query_is_for_all] - @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin? - build_query_from_params - @query.column_names = nil if params[:default_columns] - - if @query.save - flash[:notice] = l(:notice_successful_update) - redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query - else - render :action => 'edit' - end - end - - verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed } - def destroy - @query.destroy - redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1 - end - -private - def find_query - @query = Query.find(params[:id]) - @project = @query.project - render_403 unless @query.editable_by?(User.current) - rescue ActiveRecord::RecordNotFound - render_404 - end - - def find_optional_project - @project = Project.find(params[:project_id]) if params[:project_id] - render_403 unless User.current.allowed_to?(:save_queries, @project, :global => true) - rescue ActiveRecord::RecordNotFound - render_404 - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/8c/8c8f1143f138ce617f21f89790a7e1f8a60e4e92.svn-base --- a/.svn/pristine/8c/8c8f1143f138ce617f21f89790a7e1f8a60e4e92.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,21 +0,0 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -module AuthSourcesHelper -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/8c/8c9985bb9b23719d27561e0988a8665ae9ec3fc9.svn-base --- a/.svn/pristine/8c/8c9985bb9b23719d27561e0988a8665ae9ec3fc9.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,6 +0,0 @@ -

    <%= link_to l(@enumeration.option_name), :controller => 'enumerations', :action => 'index' %> » <%=h @enumeration %>

    - -<% form_tag({:action => 'update', :id => @enumeration}, :class => "tabular") do %> - <%= render :partial => 'form' %> - <%= submit_tag l(:button_save) %> -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/8c/8cc3b49fb336589b0c77ad88bf1cb1e4d4cc2b4b.svn-base --- a/.svn/pristine/8c/8cc3b49fb336589b0c77ad88bf1cb1e4d4cc2b4b.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,215 +0,0 @@ -#!/usr/bin/env ruby -require 'coderay' - -$options, args = ARGV.partition { |arg| arg[/^-[hv]$|--\w+/] } -subcommand = args.first if /^\w/ === args.first -subcommand = nil if subcommand && File.exist?(subcommand) -args.delete subcommand - -def option? *options - !($options & options).empty? -end - -def tty? - $stdout.tty? || option?('--tty') -end - -def version - puts <<-USAGE -CodeRay #{CodeRay::VERSION} - USAGE -end - -def help - puts <<-HELP -This is CodeRay #{CodeRay::VERSION}, a syntax highlighting tool for selected languages. - -usage: - coderay [-language] [input] [-format] [output] - -defaults: - language detect from input file name or shebang; fall back to plain text - input STDIN - format detect from output file name or use terminal; fall back to HTML - output STDOUT - -common: - coderay file.rb # highlight file to terminal - coderay file.rb > file.html # highlight file to HTML page - coderay file.rb -div > file.html # highlight file to HTML snippet - -configure output: - coderay file.py output.json # output tokens as JSON - coderay file.py -loc # count lines of code in Python file - -configure input: - coderay -python file # specify the input language - coderay -ruby # take input from STDIN - -more: - coderay stylesheet [style] # print CSS stylesheet - HELP -end - -def commands - puts <<-COMMANDS - general: - highlight code highlighting (default command, optional) - stylesheet print the CSS stylesheet with the given name (aliases: style, css) - - about: - list [of] list all available plugins (or just the scanners|encoders|styles|filetypes) - commands print this list - help show some help - version print CodeRay version - COMMANDS -end - -def print_list_of plugin_host - plugins = plugin_host.all_plugins.map do |plugin| - info = " #{plugin.plugin_id}: #{plugin.title}" - - aliases = (plugin.aliases - [:default]).map { |key| "-#{key}" }.sort_by { |key| key.size } - if plugin.respond_to?(:file_extension) || !aliases.empty? - additional_info = [] - additional_info << aliases.join(', ') unless aliases.empty? - info << " (#{additional_info.join('; ')})" - end - - info << ' <-- default' if plugin.aliases.include? :default - - info - end - puts plugins.sort -end - -if option? '-v', '--version' - version -end - -if option? '-h', '--help' - help -end - -case subcommand -when 'highlight', nil - if ARGV.empty? - version - help - else - signature = args.map { |arg| arg[/^-/] ? '-' : 'f' }.join - names = args.map { |arg| arg.sub(/^-/, '') } - case signature - when /^$/ - exit - when /^ff?$/ - input_file, output_file, = *names - when /^f-f?$/ - input_file, output_format, output_file, = *names - when /^-ff?$/ - input_lang, input_file, output_file, = *names - when /^-f-f?$/ - input_lang, input_file, output_format, output_file, = *names - when /^--?f?$/ - input_lang, output_format, output_file, = *names - else - $stdout = $stderr - help - puts - puts "Unknown parameter order: #{args.join ' '}, expected: [-language] [input] [-format] [output]" - exit 1 - end - - if input_file - input_lang ||= CodeRay::FileType.fetch input_file, :text, true - end - - if output_file - output_format ||= CodeRay::FileType[output_file] - else - output_format ||= :terminal - end - - output_format = :page if output_format.to_s == 'html' - - if input_file - input = File.read input_file - else - input = $stdin.read - end - - begin - file = - if output_file - File.open output_file, 'w' - else - $stdout.sync = true - $stdout - end - CodeRay.encode(input, input_lang, output_format, :out => file) - file.puts - rescue CodeRay::PluginHost::PluginNotFound => boom - $stdout = $stderr - if boom.message[/CodeRay::(\w+)s could not load plugin :?(.*?): /] - puts "I don't know the #$1 \"#$2\"." - else - puts boom.message - end - # puts "I don't know this plugin: #{boom.message[/Could not load plugin (.*?): /, 1]}." - rescue CodeRay::Scanners::Scanner::ScanError # FIXME: rescue Errno::EPIPE - # this is sometimes raised by pagers; ignore [TODO: wtf?] - ensure - file.close if output_file - end - end -when 'li', 'list' - arg = args.first && args.first.downcase - if [nil, 's', 'sc', 'scanner', 'scanners'].include? arg - puts 'input languages (Scanners):' - print_list_of CodeRay::Scanners - end - - if [nil, 'e', 'en', 'enc', 'encoder', 'encoders'].include? arg - puts 'output formats (Encoders):' - print_list_of CodeRay::Encoders - end - - if [nil, 'st', 'style', 'styles'].include? arg - puts 'CSS themes for HTML output (Styles):' - print_list_of CodeRay::Styles - end - - if [nil, 'f', 'ft', 'file', 'filetype', 'filetypes'].include? arg - puts 'recognized file types:' - - filetypes = Hash.new { |h, k| h[k] = [] } - CodeRay::FileType::TypeFromExt.inject filetypes do |types, (ext, type)| - types[type.to_s] << ".#{ext}" - types - end - CodeRay::FileType::TypeFromName.inject filetypes do |types, (name, type)| - types[type.to_s] << name - types - end - - filetypes.sort.each do |type, exts| - puts " #{type}: #{exts.sort_by { |ext| ext.size }.join(', ')}" - end - end -when 'stylesheet', 'style', 'css' - puts CodeRay::Encoders[:html]::CSS.new(args.first).stylesheet -when 'commands' - commands -when 'help' - help -else - $stdout = $stderr - help - puts - if subcommand[/\A\w+\z/] - puts "Unknown command: #{subcommand}" - else - puts "File not found: #{subcommand}" - end - exit 1 -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/8d/8d030bdcda347746d48417b7db44c0048d96bb66.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/8d/8d030bdcda347746d48417b7db44c0048d96bb66.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,1138 @@ +# Korean translations for Ruby on Rails +ko: + direction: ltr + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%Y/%m/%d" + short: "%m/%d" + long: "%Yë…„ %mì›” %dì¼ (%a)" + + day_names: [ì¼ìš”ì¼, 월요ì¼, 화요ì¼, 수요ì¼, 목요ì¼, 금요ì¼, 토요ì¼] + abbr_day_names: [ì¼, ì›”, í™”, 수, 목, 금, 토] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, 1ì›”, 2ì›”, 3ì›”, 4ì›”, 5ì›”, 6ì›”, 7ì›”, 8ì›”, 9ì›”, 10ì›”, 11ì›”, 12ì›”] + abbr_month_names: [~, 1ì›”, 2ì›”, 3ì›”, 4ì›”, 5ì›”, 6ì›”, 7ì›”, 8ì›”, 9ì›”, 10ì›”, 11ì›”, 12ì›”] + # Used in date_select and datime_select. + order: + - :year + - :month + - :day + + time: + formats: + default: "%Y/%m/%d %H:%M:%S" + time: "%H:%M" + short: "%y/%m/%d %H:%M" + long: "%Yë…„ %Bì›” %dì¼, %H시 %Më¶„ %Sì´ˆ %Z" + am: "오전" + pm: "오후" + + datetime: + distance_in_words: + half_a_minute: "30ì´ˆ" + less_than_x_seconds: + one: "ì¼ì´ˆ ì´í•˜" + other: "%{count}ì´ˆ ì´í•˜" + x_seconds: + one: "ì¼ì´ˆ" + other: "%{count}ì´ˆ" + less_than_x_minutes: + one: "ì¼ë¶„ ì´í•˜" + other: "%{count}ë¶„ ì´í•˜" + x_minutes: + one: "ì¼ë¶„" + other: "%{count}ë¶„" + about_x_hours: + one: "약 한시간" + other: "약 %{count}시간" + x_hours: + one: "1 시간" + other: "%{count} 시간" + x_days: + one: "하루" + other: "%{count}ì¼" + about_x_months: + one: "약 한달" + other: "약 %{count}달" + x_months: + one: "한달" + other: "%{count}달" + about_x_years: + one: "약 ì¼ë…„" + other: "약 %{count}ë…„" + over_x_years: + one: "ì¼ë…„ ì´ìƒ" + other: "%{count}ë…„ ì´ìƒ" + almost_x_years: + one: "약 1ë…„" + other: "약 %{count}ë…„" + prompts: + year: "ë…„" + month: "ì›”" + day: "ì¼" + hour: "시" + minute: "ë¶„" + second: "ì´ˆ" + + number: + # Used in number_with_delimiter() + # These are also the defaults for 'currency', 'percentage', 'precision', and 'human' + format: + # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5) + separator: "." + # Delimets thousands (e.g. 1,000,000 is a million) (always in groups of three) + delimiter: "," + # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00) + precision: 3 + + # Used in number_to_currency() + currency: + format: + # Where is the currency sign? %u is the currency unit, %n the number (default: $5.00) + format: "%u%n" + unit: "â‚©" + # These three are to override number.format and are optional + separator: "." + delimiter: "," + precision: 0 + + # Used in number_to_percentage() + percentage: + format: + # These three are to override number.format and are optional + # separator: + delimiter: "" + # precision: + + # Used in number_to_precision() + precision: + format: + # These three are to override number.format and are optional + # separator: + delimiter: "" + # precision: + + # Used in number_to_human_size() + human: + format: + # These three are to override number.format and are optional + # separator: + delimiter: "" + precision: 3 + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + +# Used in array.to_sentence. + support: + array: + words_connector: ", " + two_words_connector: "ê³¼ " + last_word_connector: ", " + sentence_connector: "그리고" + skip_last_comma: false + + activerecord: + errors: + template: + header: + one: "í•œê°œì˜ ì˜¤ë¥˜ê°€ ë°œìƒí•´ %{model}ì„(를) 저장하지 않았습니다." + other: "%{count}ê°œì˜ ì˜¤ë¥˜ê°€ ë°œìƒí•´ %{model}ì„(를) 저장하지 않았습니다." + # The variable :count is also available + body: "ë‹¤ìŒ í•­ëª©ì— ë¬¸ì œê°€ 발견했습니다:" + + messages: + inclusion: "ì€ ëª©ë¡ì— í¬í•¨ë˜ì–´ 있지 않습니다" + exclusion: "ì€ ì˜ˆì•½ë˜ì–´ 있습니다" + invalid: "ì€ ìœ íš¨í•˜ì§€ 않습니다." + confirmation: "ì€ í™•ì¸ì´ ë˜ì§€ 않았습니다" + accepted: "ì€ ì¸ì •ë˜ì–´ì•¼ 합니다" + empty: "ì€ ê¸¸ì´ê°€ 0ì´ì–´ì„œëŠ” 안ë©ë‹ˆë‹¤." + blank: "ì€ ë¹ˆ ê°’ì´ì–´ì„œëŠ” 안 ë©ë‹ˆë‹¤" + too_long: "ì€ ë„ˆë¬´ ê¹ë‹ˆë‹¤ (최대 %{count}ìž ê¹Œì§€)" + too_short: "ì€ ë„ˆë¬´ 짧습니다 (최소 %{count}ìž ê¹Œì§€)" + wrong_length: "ì€ ê¸¸ì´ê°€ 틀렸습니다 (%{count}ìžì´ì–´ì•¼ 합니다.)" + taken: "ì€ ì´ë¯¸ ì„ íƒëœ ê²ë‹ˆë‹¤" + not_a_number: "ì€ ìˆ«ìžê°€ 아닙니다" + greater_than: "ì€ %{count}보다 커야 합니다." + greater_than_or_equal_to: "ì€ %{count}보다 í¬ê±°ë‚˜ 같아야 합니다" + equal_to: "ì€ %{count}(와)ê³¼ 같아야 합니다" + less_than: "ì€ %{count}보다 작어야 합니다" + less_than_or_equal_to: "ì€ %{count}ê³¼ 같거나 ì´í•˜ì„ 요구합니다" + odd: "ì€ í™€ìˆ˜ì—¬ì•¼ 합니다" + even: "ì€ ì§ìˆ˜ì—¬ì•¼ 합니다" + greater_than_start_date: "는 시작날짜보다 커야 합니다" + not_same_project: "는 ê°™ì€ í”„ë¡œì íŠ¸ì— ì†í•´ 있지 않습니다" + circular_dependency: "ì´ ê´€ê³„ëŠ” 순환 ì˜ì¡´ê´€ê³„를 만들 수 있습니다" + cant_link_an_issue_with_a_descendant: "ì¼ê°ì€ 하위 ì¼ê°ê³¼ ì—°ê²°í•  수 없습니다." + + actionview_instancetag_blank_option: ì„ íƒí•˜ì„¸ìš” + + general_text_No: '아니오' + general_text_Yes: '예' + general_text_no: '아니오' + general_text_yes: '예' + general_lang_name: 'Korean (한국어)' + general_csv_separator: ',' + general_csv_decimal_separator: '.' + general_csv_encoding: CP949 + general_pdf_encoding: CP949 + general_first_day_of_week: '7' + + notice_account_updated: ê³„ì •ì´ ì„±ê³µì ìœ¼ë¡œ 변경ë˜ì—ˆìŠµë‹ˆë‹¤. + notice_account_invalid_creditentials: ìž˜ëª»ëœ ê³„ì • ë˜ëŠ” 비밀번호 + notice_account_password_updated: 비밀번호가 잘 변경ë˜ì—ˆìŠµë‹ˆë‹¤. + notice_account_wrong_password: ìž˜ëª»ëœ ë¹„ë°€ë²ˆí˜¸ + notice_account_register_done: ê³„ì •ì´ ìž˜ 만들어졌습니다. ê³„ì •ì„ í™œì„±í™”í•˜ì‹œë ¤ë©´ ë°›ì€ ë©”ì¼ì˜ ë§í¬ë¥¼ í´ë¦­í•´ì£¼ì„¸ìš”. + notice_account_unknown_email: 알려지지 ì•Šì€ ì‚¬ìš©ìž. + notice_can_t_change_password: ì´ ê³„ì •ì€ ì™¸ë¶€ ì¸ì¦ì„ ì´ìš©í•©ë‹ˆë‹¤. 비밀번호를 변경할 수 없습니다. + notice_account_lost_email_sent: 새로운 비밀번호를 위한 ë©”ì¼ì´ 발송ë˜ì—ˆìŠµë‹ˆë‹¤. + notice_account_activated: ê³„ì •ì´ í™œì„±í™”ë˜ì—ˆìŠµë‹ˆë‹¤. ì´ì œ ë¡œê·¸ì¸ í•˜ì‹¤ìˆ˜ 있습니다. + notice_successful_create: ìƒì„± 성공. + notice_successful_update: 변경 성공. + notice_successful_delete: ì‚­ì œ 성공. + notice_successful_connection: ì—°ê²° 성공. + notice_file_not_found: 요청하신 페ì´ì§€ëŠ” ì‚­ì œë˜ì—ˆê±°ë‚˜ 옮겨졌습니다. + notice_locking_conflict: 다른 사용ìžì— ì˜í•´ì„œ ë°ì´í„°ê°€ 변경ë˜ì—ˆìŠµë‹ˆë‹¤. + notice_not_authorized: ì´ íŽ˜ì´ì§€ì— 접근할 ê¶Œí•œì´ ì—†ìŠµë‹ˆë‹¤. + notice_email_sent: "%{value}님ì—게 ë©”ì¼ì´ 발송ë˜ì—ˆìŠµë‹ˆë‹¤." + notice_email_error: "ë©”ì¼ì„ 전송하는 ê³¼ì •ì— ì˜¤ë¥˜ê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤. (%{value})" + notice_feeds_access_key_reseted: RSSì— ì ‘ê·¼ê°€ëŠ¥í•œ 열쇠(key)ê°€ ìƒì„±ë˜ì—ˆìŠµë‹ˆë‹¤. + notice_failed_to_save_issues: "ì €ìž¥ì— ì‹¤íŒ¨í•˜ì˜€ìŠµë‹ˆë‹¤: 실패 %{count}(ì„ íƒ %{total}): %{ids}." + notice_no_issue_selected: "ì¼ê°ì´ ì„ íƒë˜ì§€ 않았습니다. 수정하기 ì›í•˜ëŠ” ì¼ê°ì„ ì„ íƒí•˜ì„¸ìš”" + notice_account_pending: "ê³„ì •ì´ ë§Œë“¤ì–´ì¡Œìœ¼ë©° ê´€ë¦¬ìž ìŠ¹ì¸ ëŒ€ê¸°ì¤‘ìž…ë‹ˆë‹¤." + notice_default_data_loaded: ê¸°ë³¸ê°’ì„ ì„±ê³µì ìœ¼ë¡œ ì½ì–´ë“¤ì˜€ìŠµë‹ˆë‹¤. + notice_unable_delete_version: 삭제할 수 없는 버전입니다. + + error_can_t_load_default_data: "ê¸°ë³¸ê°’ì„ ì½ì–´ë“¤ì¼ 수 없습니다.: %{value}" + error_scm_not_found: 항목ì´ë‚˜ ë¦¬ë¹„ì ¼ì´ ì €ìž¥ì†Œì— ì¡´ìž¬í•˜ì§€ 않습니다. + error_scm_command_failed: "ì €ìž¥ì†Œì— ì ‘ê·¼í•˜ëŠ” ë„ì¤‘ì— ì˜¤ë¥˜ê°€ ë°œìƒí•˜ì˜€ìŠµë‹ˆë‹¤.: %{value}" + error_scm_annotate: "í•­ëª©ì´ ì—†ê±°ë‚˜ 행별 ì´ë ¥ì„ ë³¼ 수 없습니다." + error_issue_not_found_in_project: 'ì¼ê°ì´ 없거나 ì´ í”„ë¡œì íŠ¸ì˜ ê²ƒì´ ì•„ë‹™ë‹ˆë‹¤.' + + warning_attachments_not_saved: "%{count}ê°œ 파ì¼ì„ 저장할 수 없습니다." + + mail_subject_lost_password: "%{value} 비밀번호" + mail_body_lost_password: '비밀번호를 변경하려면 ë‹¤ìŒ ë§í¬ë¥¼ í´ë¦­í•˜ì„¸ìš”.' + mail_subject_register: "%{value} 계정 활성화" + mail_body_register: 'ê³„ì •ì„ í™œì„±í™”í•˜ë ¤ë©´ ë§í¬ë¥¼ í´ë¦­í•˜ì„¸ìš”.:' + mail_body_account_information_external: "로그ì¸í•  때 %{value} ê³„ì •ì„ ì‚¬ìš©í•˜ì‹¤ 수 있습니다." + mail_body_account_information: 계정 ì •ë³´ + mail_subject_account_activation_request: "%{value} 계정 활성화 요청" + mail_body_account_activation_request: "새 사용ìž(%{value})ê°€ 등ë¡ë˜ì—ˆìŠµë‹ˆë‹¤. 관리ìžë‹˜ì˜ 승ì¸ì„ 기다리고 있습니다.:" + mail_body_reminder: "ë‹¹ì‹ ì´ ë§¡ê³  있는 ì¼ê° %{count}ê°œì˜ ì™„ë£Œê¸°í•œì´ %{days}ì¼ í›„ 입니다." + mail_subject_reminder: "ë‚´ì¼ì´ ë§Œê¸°ì¸ ì¼ê° %{count}ê°œ (%{days})" + mail_subject_wiki_content_added: "위키페ì´ì§€ '%{id}'ì´(ê°€) 추가ë˜ì—ˆìŠµë‹ˆë‹¤." + mail_subject_wiki_content_updated: "'위키페ì´ì§€ %{id}'ì´(ê°€) 수정ë˜ì—ˆìŠµë‹ˆë‹¤." + mail_body_wiki_content_added: "%{author}ì´(ê°€) 위키페ì´ì§€ '%{id}'ì„(를) 추가하였습니다." + mail_body_wiki_content_updated: "%{author}ì´(ê°€) 위키페ì´ì§€ '%{id}'ì„(를) 수정하였습니다." + + + 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: ì •ê·œì‹ + field_min_length: 최소 ê¸¸ì´ + field_max_length: 최대 ê¸¸ì´ + field_value: ê°’ + field_category: 범주 + field_title: 제목 + field_project: 프로ì íЏ + field_issue: ì¼ê° + field_status: ìƒíƒœ + field_notes: ë§ê¸€ + field_is_closed: 완료 ìƒíƒœ + field_is_default: 기본값 + field_tracker: 유형 + field_subject: 제목 + field_due_date: 완료기한 + field_assigned_to: ë‹´ë‹¹ìž + field_priority: 우선순위 + field_fixed_version: 목표버전 + field_user: ì‚¬ìš©ìž + field_role: ì—­í•  + field_homepage: 홈페ì´ì§€ + field_is_public: 공개 + field_parent: ìƒìœ„ 프로ì íЏ + field_is_in_roadmap: ë¡œë“œë§µì— í‘œì‹œ + field_login: ë¡œê·¸ì¸ + field_mail_notification: ë©”ì¼ ì•Œë¦¼ + field_admin: ê´€ë¦¬ìž + field_last_login_on: 마지막 ë¡œê·¸ì¸ + field_language: 언어 + field_effective_date: ë‚ ì§œ + field_password: 비밀번호 + field_new_password: 새 비밀번호 + field_password_confirmation: 비밀번호 í™•ì¸ + field_version: 버전 + field_type: ë°©ì‹ + field_host: 호스트 + field_port: í¬íЏ + field_account: 계정 + field_base_dn: 기본 DN + field_attr_login: ë¡œê·¸ì¸ ì†ì„± + field_attr_firstname: ì´ë¦„ ì†ì„± + field_attr_lastname: 성 ì†ì„± + field_attr_mail: ë©”ì¼ ì†ì„± + field_onthefly: ë™ì  ì‚¬ìš©ìž ìƒì„± + field_start_date: 시작시간 + field_done_ratio: ì§„ì²™ë„ + field_auth_source: ì¸ì¦ ê³µê¸‰ìž + field_hide_mail: ë©”ì¼ ì£¼ì†Œ 숨기기 + field_comments: 설명 + field_url: URL + field_start_page: 첫 페ì´ì§€ + field_subproject: 하위 프로ì íЏ + field_hours: 시간 + field_activity: 작업종류 + field_spent_on: 작업시간 + field_identifier: ì‹ë³„ìž + field_is_filter: 검색조건으로 ì‚¬ìš©ë¨ + field_issue_to_id: ì—°ê´€ëœ ì¼ê° + field_delay: 지연 + field_assignable: ì´ ì—­í• ì—게 ì¼ê°ì„ 맡길 수 ìžˆìŒ + field_redirect_existing_links: ê¸°ì¡´ì˜ ë§í¬ë¡œ ëŒë ¤ë³´ëƒ„(redirect) + field_estimated_hours: 추정시간 + field_column_names: 컬럼 + field_default_value: 기본값 + field_time_zone: 시간대 + field_searchable: 검색가능 + field_comments_sorting: 댓글 ì •ë ¬ + field_parent_title: ìƒìœ„ 제목 + field_editable: 편집가능 + field_watcher: ì¼ê°ê´€ëžŒìž + field_identity_url: OpenID URL + field_content: ë‚´ìš© + field_group_by: 결과를 묶어 보여줄 기준 + + setting_app_title: ë ˆë“œë§ˆì¸ ì œëª© + setting_app_subtitle: ë ˆë“œë§ˆì¸ ë¶€ì œëª© + setting_welcome_text: í™˜ì˜ ë©”ì‹œì§€ + setting_default_language: 기본 언어 + setting_login_required: ì¸ì¦ì´ 필요함 + setting_self_registration: ì‚¬ìš©ìž ì§ì ‘ë“±ë¡ + setting_attachment_max_size: 최대 ì²¨ë¶€íŒŒì¼ í¬ê¸° + setting_issues_export_limit: ì¼ê° 내보내기 제한 + setting_mail_from: 발신 ë©”ì¼ ì£¼ì†Œ + setting_bcc_recipients: 참조ìžë“¤ì„ bcc로 숨기기 + setting_plain_text_mail: í…스트만 (HTML ì—†ì´) + setting_host_name: 호스트 ì´ë¦„ê³¼ 경로 + setting_text_formatting: 본문 í˜•ì‹ + setting_wiki_compression: 위키 ì´ë ¥ ì••ì¶• + setting_feeds_limit: í”¼ë“œì— í¬í•¨í•  í•­ëª©ì˜ ìˆ˜ + setting_default_projects_public: 새 프로ì íŠ¸ë¥¼ 공개로 설정 + setting_autofetch_changesets: 커밋(commit)ëœ ë³€ê²½ë¬¶ìŒì„ ìžë™ìœ¼ë¡œ 가져오기 + setting_sys_api_enabled: 저장소 ê´€ë¦¬ì— WS를 사용 + setting_commit_ref_keywords: ì¼ê° ì°¸ì¡°ì— ì‚¬ìš©í•  키워드들 + setting_commit_fix_keywords: ì¼ê° í•´ê²°ì— ì‚¬ìš©í•  키워드들 + setting_autologin: ìžë™ ë¡œê·¸ì¸ + setting_date_format: ë‚ ì§œ í˜•ì‹ + setting_time_format: 시간 í˜•ì‹ + setting_cross_project_issue_relations: 다른 프로ì íŠ¸ì˜ ì¼ê°ê³¼ 연결하는 ê²ƒì„ í—ˆìš© + setting_issue_list_default_columns: ì¼ê° 목ë¡ì— 표시할 항목 + setting_emails_footer: ë©”ì¼ ê¼¬ë¦¬ + setting_protocol: 프로토콜 + setting_per_page_options: 목ë¡ì—서, 한 페ì´ì§€ì— 표시할 í–‰ + setting_user_format: ì‚¬ìš©ìž í‘œì‹œ í˜•ì‹ + setting_activity_days_default: 프로ì íЏ ìž‘ì—…ë‚´ì—­ì— í‘œì‹œí•  기간 + setting_display_subprojects_issues: 하위 프로ì íŠ¸ì˜ ì¼ê°ì„ 함께 표시 + setting_enabled_scm: "ì§€ì›í•  SCM(Source Control Management)" + setting_mail_handler_api_enabled: 수신 ë©”ì¼ì— WS를 허용 + setting_mail_handler_api_key: API 키 + setting_sequential_project_identifiers: 프로ì íЏ ì‹ë³„ìžë¥¼ 순차ì ìœ¼ë¡œ ìƒì„± + setting_gravatar_enabled: ê·¸ë¼ë°”타 ì‚¬ìš©ìž ì•„ì´ì½˜ 사용 + setting_diff_max_lines_displayed: ì°¨ì´ì (diff) ë³´ê¸°ì— í‘œì‹œí•  최대 줄수 + setting_repository_log_display_limit: 저장소 ë³´ê¸°ì— í‘œì‹œí•  ê°œì •íŒ ì´ë ¥ì˜ 최대 갯수 + setting_file_max_size_displayed: 바로 보여줄 í…스트파ì¼ì˜ 최대 í¬ê¸° + setting_openid: OpenID 로그ì¸ê³¼ ë“±ë¡ í—ˆìš© + setting_password_min_length: 최소 암호 ê¸¸ì´ + setting_new_project_user_role_id: 프로ì íŠ¸ë¥¼ 만든 사용ìžì—게 주어질 ì—­í•  + + permission_add_project: 프로ì íЏ ìƒì„± + 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: 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_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: 프로ì íЏ 위키 페ì´ì§€ + 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: ì‹œê°„ì¶”ì  + project_module_news: 뉴스 + project_module_documents: 문서 + project_module_files: íŒŒì¼ + project_module_wiki: 위키 + project_module_repository: 저장소 + project_module_boards: ê²Œì‹œíŒ + + label_user: ì‚¬ìš©ìž + label_user_plural: ì‚¬ìš©ìž + label_user_new: 새 ì‚¬ìš©ìž + label_project: 프로ì íЏ + label_project_new: 새 프로ì íЏ + label_project_plural: 프로ì íЏ + label_x_projects: + zero: ì—†ìŒ + one: "한 프로ì íЏ" + other: "%{count}ê°œ 프로ì íЏ" + label_project_all: 모든 프로ì íЏ + label_project_latest: 최근 프로ì íЏ + label_issue: ì¼ê° + label_issue_new: 새 ì¼ê°ë§Œë“¤ê¸° + label_issue_plural: ì¼ê° + label_issue_view_all: 모든 ì¼ê° 보기 + label_issues_by: "%{value}별 ì¼ê°" + label_issue_added: ì¼ê° 추가 + label_issue_updated: ì¼ê° 수정 + label_document: 문서 + label_document_new: 새 문서 + label_document_plural: 문서 + label_document_added: 문서 추가 + label_role: ì—­í•  + label_role_plural: ì—­í•  + label_role_new: 새 ì—­í•  + label_role_and_permissions: ì—­í•  ë° ê¶Œí•œ + label_member: êµ¬ì„±ì› + label_member_new: 새 êµ¬ì„±ì› + label_member_plural: êµ¬ì„±ì› + label_tracker: ì¼ê° 유형 + label_tracker_plural: ì¼ê° 유형 + label_tracker_new: 새 ì¼ê° 유형 + label_workflow: 업무í름 + label_issue_status: ì¼ê° ìƒíƒœ + label_issue_status_plural: ì¼ê° ìƒíƒœ + label_issue_status_new: 새 ì¼ê° ìƒíƒœ + label_issue_category: ì¼ê° 범주 + label_issue_category_plural: ì¼ê° 범주 + label_issue_category_new: 새 ì¼ê° 범주 + label_custom_field: ì‚¬ìš©ìž ì •ì˜ í•­ëª© + label_custom_field_plural: ì‚¬ìš©ìž ì •ì˜ í•­ëª© + label_custom_field_new: 새 ì‚¬ìš©ìž ì •ì˜ í•­ëª© + label_enumerations: 코드값 + label_enumeration_new: 새 코드값 + label_information: ì •ë³´ + label_information_plural: ì •ë³´ + label_please_login: 로그ì¸í•˜ì„¸ìš”. + label_register: ë“±ë¡ + label_login_with_open_id_option: ë˜ëŠ” OpenID로 ë¡œê·¸ì¸ + 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_registered_on: 등ë¡ì‹œê° + label_activity: 작업내역 + label_overall_activity: ì „ì²´ 작업내역 + label_user_activity: "%{value}ì˜ ìž‘ì—…ë‚´ì—­" + label_new: 새로 만들기 + label_logged_as: '로그ì¸ê³„ì •:' + label_environment: 환경 + label_authentication: ì¸ì¦ + label_auth_source: ì¸ì¦ ê³µê¸‰ìž + label_auth_source_new: 새 ì¸ì¦ ê³µê¸‰ìž + label_auth_source_plural: ì¸ì¦ ê³µê¸‰ìž + label_subproject_plural: 하위 프로ì íЏ + label_and_its_subprojects: "%{value}와 하위 프로ì íŠ¸ë“¤" + label_min_max_length: 최소 - 최대 ê¸¸ì´ + label_list: ëª©ë¡ + label_date: ë‚ ì§œ + label_integer: 정수 + label_float: ë¶€ë™ì†Œìˆ˜ + label_boolean: 부울린 + label_string: 문ìžì—´ + label_text: í…스트 + label_attribute: ì†ì„± + label_attribute_plural: ì†ì„± + 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_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_x_open_issues_abbr_on_total: + zero: "ì´ %{total} ê±´ ëª¨ë‘ ì™„ë£Œ" + one: "한 ê±´ ì§„í–‰ 중 / ì´ %{total} ê±´ 중 " + other: "%{count} ê±´ ì§„í–‰ 중 / ì´ %{total} ê±´" + label_x_open_issues_abbr: + zero: ëª¨ë‘ ì™„ë£Œ + one: 한 ê±´ ì§„í–‰ 중 + other: "%{count} ê±´ ì§„í–‰ 중" + label_x_closed_issues_abbr: + zero: ëª¨ë‘ ë¯¸ì™„ë£Œ + one: 한 ê±´ 완료 + other: "%{count} ê±´ 완료" + 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: "최근 %{count}ê°œì˜ ë³€ê²½ì‚¬í•­" + label_change_view_all: 모든 변경 ë‚´ì—­ 보기 + label_personalize_page: 입맛대로 구성하기 + label_comment: 댓글 + label_comment_plural: 댓글 + label_x_comments: + zero: 댓글 ì—†ìŒ + one: 한 ê°œì˜ ëŒ“ê¸€ + other: "%{count} ê°œì˜ ëŒ“ê¸€" + label_comment_add: 댓글 추가 + label_comment_added: ëŒ“ê¸€ì´ ì¶”ê°€ë˜ì—ˆìŠµë‹ˆë‹¤. + label_comment_delete: 댓글 ì‚­ì œ + label_query: ê²€ìƒ‰ì–‘ì‹ + label_query_plural: ê²€ìƒ‰ì–‘ì‹ + label_query_new: 새 ê²€ìƒ‰ì–‘ì‹ + label_filter_add: 검색조건 추가 + label_filter_plural: 검색조건 + label_equals: ì´ë‹¤ + label_not_equals: 아니다 + label_in_less_than: ì´ë‚´ + label_in_more_than: ì´í›„ + label_greater_or_equal: ">=" + label_less_or_equal: "<=" + label_in: ì´ë‚´ + label_today: 오늘 + label_all_time: 모든 시간 + label_yesterday: ì–´ì œ + label_this_week: ì´ë²ˆì£¼ + label_last_week: 지난 주 + label_last_n_days: "지난 %{count} ì¼" + label_this_month: ì´ë²ˆ 달 + label_last_month: 지난 달 + label_this_year: 올해 + label_date_range: ë‚ ì§œ 범위 + label_less_than_ago: ì´ì „ + label_more_than_ago: ì´í›„ + label_ago: ì¼ ì „ + label_contains: í¬í•¨ë˜ëŠ” 키워드 + label_not_contains: í¬í•¨í•˜ì§€ 않는 키워드 + label_day_plural: ì¼ + label_repository: 저장소 + label_repository_plural: 저장소 + label_browse: 저장소 둘러보기 + label_revision: ê°œì •íŒ + label_revision_plural: ê°œì •íŒ + label_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_sort_highest: 맨 위로 + label_sort_higher: 위로 + label_sort_lower: 아래로 + label_sort_lowest: 맨 아래로 + label_roadmap: 로드맵 + label_roadmap_due_in: "기한 %{value}" + label_roadmap_overdue: "%{value} 지연" + 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: 제목별 ìƒ‰ì¸ + label_index_by_date: 날짜별 ìƒ‰ì¸ + label_current_version: 현재 버전 + label_preview: 미리보기 + label_feed_plural: 피드(Feeds) + label_changes_details: 모든 ìƒì„¸ 변경 ë‚´ì—­ + label_issue_tracking: ì¼ê° ì¶”ì  + label_spent_time: 소요 시간 + label_f_hour: "%{value} 시간" + label_f_hour_plural: "%{value} 시간" + label_time_tracking: ì‹œê°„ì¶”ì  + label_change_plural: 변경사항들 + label_statistics: 통계 + label_commits_per_month: 월별 커밋 ë‚´ì—­ + label_commits_per_author: ì €ìžë³„ 커밋 ë‚´ì—­ + label_view_diff: ì°¨ì´ì  보기 + label_diff_inline: ë‘줄로 + label_diff_side_by_side: 한줄로 + label_options: 옵션 + label_copy_workflow_from: 업무í름 복사하기 + label_permissions_report: 권한 보고서 + label_watched_issues: 지켜보고 있는 ì¼ê° + label_related_issues: ì—°ê²°ëœ ì¼ê° + label_applied_status: ì ìš©ëœ ìƒíƒœ + label_loading: ì½ëŠ” 중... + label_relation_new: 새 관계 + label_relation_delete: 관계 지우기 + label_relates_to: "ë‹¤ìŒ ì¼ê°ê³¼ 관련ë¨:" + label_duplicates: "ë‹¤ìŒ ì¼ê°ì— 중복ë¨:" + label_duplicated_by: "ì¤‘ë³µëœ ì¼ê°:" + label_blocks: "ë‹¤ìŒ ì¼ê°ì˜ í•´ê²°ì„ ë§‰ê³  있ìŒ:" + label_blocked_by: "ë‹¤ìŒ ì¼ê°ì—게 막혀 있ìŒ:" + label_precedes: "다ìŒì— 진행할 ì¼ê°:" + label_follows: "ë‹¤ìŒ ì¼ê°ì„ ìš°ì„  ì§„í–‰:" + label_end_to_start: "ëì—서 시작" + label_end_to_end: "ëì—서 ë" + label_start_to_start: "시작ì—서 시작" + label_start_to_end: "시작ì—서 ë" + label_stay_logged_in: ë¡œê·¸ì¸ ìœ ì§€ + label_disabled: 비활성화 + label_show_completed_versions: ì™„ë£Œëœ ë²„ì „ 보기 + label_me: 나 + label_board: ê²Œì‹œíŒ + label_board_new: 새 ê²Œì‹œíŒ + label_board_plural: ê²Œì‹œíŒ + label_topic_plural: 주제 + label_message_plural: 글 + label_message_last: 마지막 글 + label_message_new: 새글쓰기 + label_message_posted: 글 추가 + label_reply_plural: 답글 + label_send_information: 사용ìžì—게 계정정보를 보내기 + label_year: ë…„ + label_month: ì›” + label_week: 주 + label_date_from: '기간:' + label_date_to: ' ~ ' + label_language_based: ì–¸ì–´ì„¤ì •ì— ë”°ë¦„ + label_sort_by: "%{value}(으)로 ì •ë ¬" + label_send_test_email: 테스트 ë©”ì¼ ë³´ë‚´ê¸° + label_feeds_access_key_created_on: "피드 ì ‘ê·¼ 키가 %{value} ì´ì „ì— ìƒì„±ë˜ì—ˆìŠµë‹ˆë‹¤." + label_module_plural: 모듈 + label_added_time_by: "%{author}ì´(ê°€) %{age} ì „ì— ì¶”ê°€í•¨" + label_updated_time_by: "%{author}ì´(ê°€) %{age} ì „ì— ë³€ê²½" + label_updated_time: "%{value} ì „ì— ìˆ˜ì •ë¨" + label_jump_to_a_project: 프로ì íЏ 바로가기 + label_file_plural: íŒŒì¼ + label_changeset_plural: ë³€ê²½ë¬¶ìŒ + label_default_columns: 기본 컬럼 + label_no_change_option: (수정 안함) + label_bulk_edit_selected_issues: ì„ íƒí•œ ì¼ê°ë“¤ì„ í•œêº¼ë²ˆì— ìˆ˜ì •í•˜ê¸° + label_theme: 테마 + label_default: 기본 + label_search_titles_only: 제목ì—서만 찾기 + label_user_mail_option_all: "ë‚´ê°€ ì†í•œ 프로ì íŠ¸ë¡œë“¤ë¶€í„° 모든 ë©”ì¼ ë°›ê¸°" + label_user_mail_option_selected: "ì„ íƒí•œ 프로ì íŠ¸ë“¤ë¡œë¶€í„° 모든 ë©”ì¼ ë°›ê¸°.." + label_user_mail_no_self_notified: "ë‚´ê°€ 만든 ë³€ê²½ì‚¬í•­ë“¤ì— ëŒ€í•´ì„œëŠ” 알림메ì¼ì„ 받지 않습니다." + label_registration_activation_by_email: ë©”ì¼ë¡œ ê³„ì •ì„ í™œì„±í™”í•˜ê¸° + label_registration_automatic_activation: ìžë™ 계정 활성화 + label_registration_manual_activation: ìˆ˜ë™ ê³„ì • 활성화 + label_display_per_page: "페ì´ì§€ë‹¹ 줄수: %{value}" + label_age: 마지막 ìˆ˜ì •ì¼ + label_change_properties: ì†ì„± 변경 + label_general: ì¼ë°˜ + label_more: 제목 ë° ì„¤ëª… 수정 + label_scm: 형ìƒê´€ë¦¬ì‹œìŠ¤í…œ + label_plugins: í”ŒëŸ¬ê·¸ì¸ + label_ldap_authentication: LDAP ì¸ì¦ + label_downloads_abbr: D/L + label_optional_description: 부가ì ì¸ 설명 + label_add_another_file: 다른 íŒŒì¼ ì¶”ê°€ + label_preferences: 설정 + label_chronological_order: 시간 순으로 ì •ë ¬ + label_reverse_chronological_order: 시간 역순으로 ì •ë ¬ + label_planning: 프로ì íŠ¸ê³„íš + label_incoming_emails: 수신 ë©”ì¼ + label_generate_key: 키 ìƒì„± + label_issue_watchers: ì¼ê°ê´€ëžŒìž + label_example: 예 + label_display: í‘œì‹œë°©ì‹ + label_sort: ì •ë ¬ + label_ascending: 오름차순 + label_descending: 내림차순 + label_date_from_to: "%{start}부터 %{end}까지" + label_wiki_content_added: 위키페ì´ì§€ 추가 + label_wiki_content_updated: 위키페ì´ì§€ 수정 + + button_login: ë¡œê·¸ì¸ + button_submit: í™•ì¸ + button_save: 저장 + button_check_all: 모ë‘ì„ íƒ + button_uncheck_all: ì„ íƒí•´ì œ + button_delete: ì‚­ì œ + button_create: 만들기 + button_create_and_continue: 만들고 계ì†í•˜ê¸° + button_test: 테스트 + button_edit: 편집 + button_add: 추가 + button_change: 변경 + button_apply: ì ìš© + button_clear: 지우기 + button_lock: 잠금 + button_unlock: 잠금해제 + button_download: 다운로드 + button_list: ëª©ë¡ + button_view: 보기 + button_move: ì´ë™ + button_back: 뒤로 + button_cancel: 취소 + button_activate: 활성화 + button_sort: ì •ë ¬ + button_log_time: 작업시간 ê¸°ë¡ + button_rollback: ì´ ë²„ì „ìœ¼ë¡œ ë˜ëŒë¦¬ê¸° + button_watch: 지켜보기 + button_unwatch: 관심ë„기 + button_reply: 답글 + button_archive: 잠금보관 + button_unarchive: 잠금보관해제 + button_reset: 초기화 + button_rename: ì´ë¦„바꾸기 + button_change_password: 비밀번호 바꾸기 + button_copy: 복사 + button_annotate: ì´ë ¥í•´ì„¤ + button_update: ì—…ë°ì´íЏ + button_configure: 설정 + button_quote: 댓글달기 + + status_active: 사용중 + status_registered: 등ë¡ëŒ€ê¸° + status_locked: ìž ê¹€ + + text_select_mail_notifications: 알림메ì¼ì´ 필요한 ìž‘ì—…ì„ ì„ íƒí•˜ì„¸ìš”. + text_regexp_info: 예) ^[A-Z0-9]+$ + text_min_max_length_info: 0 는 ì œí•œì´ ì—†ìŒì„ ì˜ë¯¸í•¨ + text_project_destroy_confirmation: ì´ í”„ë¡œì íŠ¸ë¥¼ 삭제하고 모든 ë°ì´í„°ë¥¼ 지우시겠습니까? + text_subprojects_destroy_warning: "하위 프로ì íЏ(%{value})ì´(ê°€) ìžë™ìœ¼ë¡œ 지워질 것입니다." + text_workflow_edit: 업무íë¦„ì„ ìˆ˜ì •í•˜ë ¤ë©´ ì—­í• ê³¼ ì¼ê° ìœ í˜•ì„ ì„ íƒí•˜ì„¸ìš”. + text_are_you_sure: ê³„ì† ì§„í–‰ 하시겠습니까? + text_tip_issue_begin_day: 오늘 시작하는 업무(task) + text_tip_issue_end_day: 오늘 종료하는 업무(task) + text_tip_issue_begin_end_day: 오늘 시작하고 종료하는 업무(task) + text_caracters_maximum: "최대 %{count} ê¸€ìž ê°€ëŠ¥" + text_caracters_minimum: "최소한 %{count} ê¸€ìž ì´ìƒì´ì–´ì•¼ 합니다." + text_length_between: "%{min} ì—서 %{max} 글ìž" + text_tracker_no_workflow: ì´ ì¼ê° 유형ì—는 업무íë¦„ì´ ì •ì˜ë˜ì§€ 않았습니다. + text_unallowed_characters: 허용ë˜ì§€ 않는 문ìžì—´ + text_comma_separated: "구분ìž','를 ì´ìš©í•´ì„œ 여러 ê°œì˜ ê°’ì„ ìž…ë ¥í•  수 있습니다." + text_issues_ref_in_commit_messages: 커밋 메시지ì—서 ì¼ê°ì„ 참조하거나 해결하기 + text_issue_added: "%{author}ì´(ê°€) ì¼ê° %{id}ì„(를) 보고하였습니다." + text_issue_updated: "%{author}ì´(ê°€) ì¼ê° %{id}ì„(를) 수정하였습니다." + text_wiki_destroy_confirmation: ì´ ìœ„í‚¤ì™€ 모든 ë‚´ìš©ì„ ì§€ìš°ì‹œê² ìŠµë‹ˆê¹Œ? + text_issue_category_destroy_question: "ì¼ë¶€ ì¼ê°ë“¤(%{count}ê°œ)ì´ ì´ ë²”ì£¼ì— ì§€ì •ë˜ì–´ 있습니다. 어떻게 하시겠습니까?" + text_issue_category_destroy_assignments: 범주 지정 지우기 + text_issue_category_reassign_to: ì¼ê°ì„ ì´ ë²”ì£¼ì— ë‹¤ì‹œ 지정하기 + text_user_mail_option: "ì„ íƒí•˜ì§€ ì•Šì€ í”„ë¡œì íЏì—서ë„, 지켜보는 중ì´ê±°ë‚˜ ì†í•´ìžˆëŠ” 사항(ì¼ê°ë¥¼ 발행했거나 í• ë‹¹ëœ ê²½ìš°)ì´ ìžˆìœ¼ë©´ 알림메ì¼ì„ 받게 ë©ë‹ˆë‹¤." + text_no_configuration_data: "ì—­í• , ì¼ê° 유형, ì¼ê° ìƒíƒœë“¤ê³¼ 업무íë¦„ì´ ì•„ì§ ì„¤ì •ë˜ì§€ 않았습니다.\n기본 ì„¤ì •ì„ ì½ì–´ë“¤ì´ëŠ” ê²ƒì„ ê¶Œìž¥í•©ë‹ˆë‹¤. ì½ì–´ë“¤ì¸ í›„ì— ìˆ˜ì •í•  수 있습니다." + text_load_default_configuration: 기본 ì„¤ì •ì„ ì½ì–´ë“¤ì´ê¸° + text_status_changed_by_changeset: "ë³€ê²½ë¬¶ìŒ %{value}ì— ì˜í•˜ì—¬ 변경ë¨" + text_issues_destroy_confirmation: 'ì„ íƒí•œ ì¼ê°ë¥¼ ì •ë§ë¡œ 삭제하시겠습니까?' + text_select_project_modules: 'ì´ í”„ë¡œì íЏì—서 활성화시킬 ëª¨ë“ˆì„ ì„ íƒí•˜ì„¸ìš”:' + text_default_administrator_account_changed: 기본 ê´€ë¦¬ìž ê³„ì •ì´ ë³€ê²½ + text_file_repository_writable: íŒŒì¼ ì €ìž¥ì†Œ 쓰기 가능 + text_plugin_assets_writable: í”ŒëŸ¬ê·¸ì¸ ì „ìš© 디렉토리가 쓰기 가능 + text_rmagick_available: RMagick 사용 가능 (ì„ íƒì ) + text_destroy_time_entries_question: 삭제하려는 ì¼ê°ì— %{hours} ì‹œê°„ì´ ë³´ê³ ë˜ì–´ 있습니다. 어떻게 하시겠습니까? + text_destroy_time_entries: ë³´ê³ ëœ ì‹œê°„ì„ ì‚­ì œí•˜ê¸° + text_assign_time_entries_to_project: ë³´ê³ ëœ ì‹œê°„ì„ í”„ë¡œì íŠ¸ì— í• ë‹¹í•˜ê¸° + text_reassign_time_entries: 'ì´ ì•Œë¦¼ì— ë³´ê³ ëœ ì‹œê°„ì„ ìž¬í• ë‹¹í•˜ê¸°:' + text_user_wrote: "%{value}ì˜ ë§ê¸€:" + text_enumeration_category_reassign_to: '새로운 ê°’ì„ ì„¤ì •:' + text_enumeration_destroy_question: "%{count} ê°œì˜ ì¼ê°ì´ ì´ ê°’ì„ ì‚¬ìš©í•˜ê³  있습니다." + text_email_delivery_not_configured: "ì´ë©”ì¼ ì „ë‹¬ì´ ì„¤ì •ë˜ì§€ 않았습니다. 그래서 ì•Œë¦¼ì´ ë¹„í™œì„±í™”ë˜ì—ˆìŠµë‹ˆë‹¤.\n SMTP서버를 config/configuration.ymlì—서 설정하고 어플리케ì´ì…˜ì„ 다시 시작하십시오. 그러면 ë™ìž‘합니다." + text_repository_usernames_mapping: "저장소 로그ì—서 ë°œê²¬ëœ ê° ì‚¬ìš©ìžì— ë ˆë“œë§ˆì¸ ì‚¬ìš©ìžë¥¼ ì—…ë°ì´íŠ¸í• ë•Œ ì„ íƒí•©ë‹ˆë‹¤.\n레드마ì¸ê³¼ ì €ìž¥ì†Œì˜ ì´ë¦„ì´ë‚˜ ì´ë©”ì¼ì´ ê°™ì€ ì‚¬ìš©ìžê°€ ìžë™ìœ¼ë¡œ ì—°ê²°ë©ë‹ˆë‹¤." + text_diff_truncated: '... ì´ ì°¨ì´ì ì€ 표시할 수 있는 최대 줄수를 초과해서 ì´ ì°¨ì´ì ì€ 잘렸습니다.' + text_custom_field_possible_values_info: 'ê° ê°’ 당 한 줄' + text_wiki_page_destroy_question: ì´ íŽ˜ì´ì§€ëŠ” %{descendants} ê°œì˜ í•˜ìœ„ 페ì´ì§€ì™€ 관련 ë‚´ìš©ì´ ìžˆìŠµë‹ˆë‹¤. ì´ ë‚´ìš©ì„ ì–´ë–»ê²Œ 하시겠습니까? + text_wiki_page_nullify_children: 하위 페ì´ì§€ë¥¼ 최ìƒìœ„ 페ì´ì§€ 아래로 지정 + text_wiki_page_destroy_children: 모든 하위 페ì´ì§€ì™€ 관련 ë‚´ìš©ì„ ì‚­ì œ + text_wiki_page_reassign_children: 하위 페ì´ì§€ë¥¼ ì´ íŽ˜ì´ì§€ 아래로 지정 + + default_role_manager: ê´€ë¦¬ìž + default_role_developer: ê°œë°œìž + default_role_reporter: ë³´ê³ ìž + default_tracker_bug: 결함 + default_tracker_feature: 새기능 + default_tracker_support: ì§€ì› + default_issue_status_new: ì‹ ê·œ + default_issue_status_in_progress: ì§„í–‰ + default_issue_status_resolved: í•´ê²° + default_issue_status_feedback: ì˜ê²¬ + default_issue_status_closed: 완료 + default_issue_status_rejected: ê±°ì ˆ + default_doc_category_user: ì‚¬ìš©ìž ë¬¸ì„œ + default_doc_category_tech: 기술 문서 + default_priority_low: ë‚®ìŒ + default_priority_normal: 보통 + default_priority_high: ë†’ìŒ + default_priority_urgent: 긴급 + default_priority_immediate: 즉시 + default_activity_design: 설계 + default_activity_development: 개발 + + enumeration_issue_priorities: ì¼ê° 우선순위 + enumeration_doc_categories: 문서 범주 + enumeration_activities: 작업분류(시간추ì ) + + field_issue_to: 관련 ì¼ê° + label_view_all_revisions: 모든 ê°œì •íŒ í‘œì‹œ + label_tag: 태그(Tag) + label_branch: 브랜치(Branch) + error_no_tracker_in_project: 사용할 수 있ë„ë¡ ì„¤ì •ëœ ì¼ê° ìœ í˜•ì´ ì—†ìŠµë‹ˆë‹¤. 프로ì íЏ ì„¤ì •ì„ í™•ì¸í•˜ì‹­ì‹œì˜¤. + error_no_default_issue_status: '기본 ìƒíƒœê°€ ì •í•´ì ¸ 있지 않습니다. ì„¤ì •ì„ í™•ì¸í•˜ì‹­ì‹œì˜¤. (주 ë©”ë‰´ì˜ "관리" -> "ì¼ê° ìƒíƒœ")' + text_journal_changed: "%{label}ì„(를) %{old}ì—서 %{new}(으)로 변경ë˜ì—ˆìŠµë‹ˆë‹¤." + text_journal_set_to: "%{label}ì„(를) %{value}(으)로 지정ë˜ì—ˆìŠµë‹ˆë‹¤." + text_journal_deleted: "%{label} ê°’ì´ ì§€ì›Œì¡ŒìŠµë‹ˆë‹¤. (%{old})" + label_group_plural: 그룹 + label_group: 그룹 + label_group_new: 새 그룹 + label_time_entry_plural: 작업시간 + text_journal_added: "%{label}ì— %{value}ì´(ê°€) 추가ë˜ì—ˆìŠµë‹ˆë‹¤." + field_active: 사용중 + enumeration_system_activity: 시스템 작업 + permission_delete_issue_watchers: ì¼ê°ê´€ëžŒìž 지우기 + version_status_closed: 닫힘 + version_status_locked: ìž ê¹€ + version_status_open: ì§„í–‰ + error_can_not_reopen_issue_on_closed_version: 닫힌 ë²„ì „ì— í• ë‹¹ëœ ì¼ê°ì€ 다시 재발ìƒì‹œí‚¬ 수 없습니다. + label_user_anonymous: ì´ë¦„ì—†ìŒ + button_move_and_follow: ì´ë™í•˜ê³  ë”°ë¼ê°€ê¸° + setting_default_projects_modules: 새 프로ì íŠ¸ì— ê¸°ë³¸ì ìœ¼ë¡œ í™œì„±í™”ë  ëª¨ë“ˆ + setting_gravatar_default: 기본 ê·¸ë¼ë°”타 ì´ë¯¸ì§€ + field_sharing: 공유 + label_version_sharing_hierarchy: ìƒìœ„ ë° í•˜ìœ„ 프로ì íЏ + label_version_sharing_system: 모든 프로ì íЏ + label_version_sharing_descendants: 하위 프로ì íЏ + label_version_sharing_tree: 최ìƒìœ„ ë° ëª¨ë“  하위 프로ì íЏ + label_version_sharing_none: ê³µìœ ì—†ìŒ + error_can_not_archive_project: ì´ í”„ë¡œì íŠ¸ë¥¼ 잠금보관할 수 없습니다. + button_duplicate: 복제 + button_copy_and_follow: 복사하고 ë”°ë¼ê°€ê¸° + label_copy_source: ì›ë³¸ + setting_issue_done_ratio: ì¼ê°ì˜ ì§„ì²™ë„ ê³„ì‚°ë°©ë²• + setting_issue_done_ratio_issue_status: ì¼ê° ìƒíƒœë¥¼ 사용하기 + error_issue_done_ratios_not_updated: ì¼ê° ì§„ì²™ë„ê°€ 수정ë˜ì§€ 않았습니다. + error_workflow_copy_target: ëŒ€ìƒ ì¼ê°ì˜ 유형과 ì—­í• ì„ ì„ íƒí•˜ì„¸ìš”. + setting_issue_done_ratio_issue_field: ì¼ê° 수정ì—서 ì§„ì²™ë„ ìž…ë ¥í•˜ê¸° + label_copy_same_as_target: 대ìƒê³¼ ê°™ìŒ. + label_copy_target: ëŒ€ìƒ + notice_issue_done_ratios_updated: ì¼ê° ì§„ì²™ë„ê°€ 수정ë˜ì—ˆìŠµë‹ˆë‹¤. + error_workflow_copy_source: ì›ë³¸ ì¼ê°ì˜ 유형ì´ë‚˜ ì—­í• ì„ ì„ íƒí•˜ì„¸ìš”. + label_update_issue_done_ratios: 모든 ì¼ê° ì§„ì²™ë„ ê°±ì‹ í•˜ê¸° + setting_start_of_week: 달력 시작 ìš”ì¼ + permission_view_issues: ì¼ê° 보기 + label_display_used_statuses_only: ì´ ì¼ê° 유형ì—서 사용ë˜ëŠ” ìƒíƒœë§Œ 보여주기 + label_revision_id: ê°œì •íŒ %{value} + label_api_access_key: API 접근키 + label_api_access_key_created_on: API 접근키가 %{value} ì „ì— ìƒì„±ë˜ì—ˆìŠµë‹ˆë‹¤. + label_feeds_access_key: RSS 접근키 + notice_api_access_key_reseted: API 접근키가 초기화ë˜ì—ˆìŠµë‹ˆë‹¤. + setting_rest_api_enabled: REST 웹서비스 활성화 + label_missing_api_access_key: API 접근키가 없습니다. + label_missing_feeds_access_key: RSS 접근키가 없습니다. + button_show: 보기 + text_line_separated: 여러 ê°’ì´ í—ˆìš©ë¨(ê°’ 마다 한 줄씩) + setting_mail_handler_body_delimiters: ë©”ì¼ ë³¸ë¬¸ êµ¬ë¶„ìž + permission_add_subprojects: 하위 프로ì íЏ 만들기 + label_subproject_new: 새 하위 프로ì íЏ + text_own_membership_delete_confirmation: |- + 권한들 ì¼ë¶€ ë˜ëŠ” 전부를 막 삭제하려고 하고 있습니다. 그렇게 ë˜ë©´ ì´ í”„ë¡œì íŠ¸ë¥¼ ë”ì´ìƒ 수정할 수 없게 ë©ë‹ˆë‹¤. + 계ì†í•˜ì‹œê² ìŠµë‹ˆê¹Œ? + label_close_versions: ì™„ë£Œëœ ë²„ì „ 닫기 + label_board_sticky: ë¶™ë°•ì´ + label_board_locked: 잠금 + permission_export_wiki_pages: 위키 페ì´ì§€ 내보내기 + setting_cache_formatted_text: 형ì‹ì„ 가진 í…스트 빠른 임시 기억 + permission_manage_project_activities: 프로ì íЏ 작업내역 관리 + error_unable_delete_issue_status: ì¼ê° ìƒíƒœë¥¼ 지울 수 없습니다. + label_profile: 사용ìžì •ë³´ + permission_manage_subtasks: 하위 ì¼ê° 관리 + field_parent_issue: ìƒìœ„ ì¼ê° + label_subtask_plural: 하위 ì¼ê° + label_project_copy_notifications: 프로ì íЏ 복사 ì¤‘ì— ì´ë©”ì¼ ì•Œë¦¼ 보내기 + error_can_not_delete_custom_field: ì‚¬ìš©ìž ì •ì˜ í•„ë“œë¥¼ 삭제할 수 없습니다. + error_unable_to_connect: ì—°ê²°í•  수 없습니다((%{value}) + error_can_not_remove_role: ì´ ì—­í• ì€ í˜„ìž¬ 사용 중ì´ì´ì„œ 삭제할 수 없습니다. + error_can_not_delete_tracker: ì´ ìœ í˜•ì˜ ì¼ê°ë“¤ì´ 있어서 삭제할 수 없습니다. + field_principal: ì‹ ì› + label_my_page_block: ë‚´ 페ì´ì§€ 출력화면 + notice_failed_to_save_members: "%{errors}:구성ì›ì„ 저장 중 실패하였습니다" + text_zoom_out: ë” ìž‘ê²Œ + text_zoom_in: ë” í¬ê²Œ + notice_unable_delete_time_entry: 시간 ê¸°ë¡ í•­ëª©ì„ ì‚­ì œí•  수 없습니다. + label_overall_spent_time: ì´ ì†Œìš”ì‹œê°„ + field_time_entries: 기ë¡ëœ 시간 + project_module_gantt: Gantt 챠트 + project_module_calendar: 달력 + button_edit_associated_wikipage: "ì—°ê´€ëœ ìœ„í‚¤ 페ì´ì§€ %{page_title} 수정" + field_text: í…스트 ì˜ì—­ + label_user_mail_option_only_owner: ë‚´ê°€ ì €ìžì¸ 사항만 + setting_default_notification_option: 기본 알림 옵션 + label_user_mail_option_only_my_events: ë‚´ê°€ 지켜보거나 ì†í•´ìžˆëŠ” 사항만 + label_user_mail_option_only_assigned: ë‚´ì—게 í• ë‹¹ëœ ì‚¬í•­ë§Œ + label_user_mail_option_none: 알림 ì—†ìŒ + field_member_of_group: í• ë‹¹ëœ ì‚¬ëžŒì˜ ê·¸ë£¹ + field_assigned_to_role: í• ë‹¹ëœ ì‚¬ëžŒì˜ ì—­í•  + notice_not_authorized_archived_project: 접근하려는 프로ì íŠ¸ëŠ” ì´ë¯¸ 잠금보관ë˜ì–´ 있습니다. + label_principal_search: "ì‚¬ìš©ìž ë° ê·¸ë£¹ 찾기:" + label_user_search: "ì‚¬ìš©ìž ì°¾ê¸°::" + field_visible: ë³´ì´ê¸° + setting_emails_header: ì´ë©”ì¼ í—¤ë” + setting_commit_logtime_activity_id: 기ë¡ëœ ì‹œê°„ì— ì ìš©í•  작업분류 + text_time_logged_by_changeset: "ë³€ê²½ë¬¶ìŒ %{value}ì—서 ì ìš©ë˜ì—ˆìŠµë‹ˆë‹¤." + setting_commit_logtime_enabled: 커밋 시ì ì— 작업 시간 ê¸°ë¡ í™œì„±í™” + notice_gantt_chart_truncated: "표시할 수 있는 최대 항목수(%{max})를 초과하여 차트가 잘렸습니다." + setting_gantt_items_limit: "Gantt ì°¨íŠ¸ì— í‘œì‹œë˜ëŠ” 최대 항목수" + field_warn_on_leaving_unsaved: "저장하지 ì•Šì€ íŽ˜ì´ì§€ë¥¼ 빠져나갈 때 나ì—게 알림" + text_warn_on_leaving_unsaved: "현재 페ì´ì§€ëŠ” 저장ë˜ì§€ ì•Šì€ ë¬¸ìžê°€ 있습니다. ì´ íŽ˜ì´ì§€ë¥¼ 빠져나가면 ë‚´ìš©ì„ ìžƒì„것입니다." + label_my_queries: "ë‚´ 검색 ì–‘ì‹" + text_journal_changed_no_detail: "%{label}ì´ ë³€ê²½ë˜ì—ˆìŠµë‹ˆë‹¤." + label_news_comment_added: "ë‰´ìŠ¤ì— ì„¤ëª…ì´ ì¶”ê°€ë˜ì—ˆìŠµë‹ˆë‹¤." + button_expand_all: "ëª¨ë‘ í™•ìž¥" + button_collapse_all: "ëª¨ë‘ ì¶•ì†Œ" + label_additional_workflow_transitions_for_assignee: "사용ìžê°€ 작업ìžì¼ 때 허용ë˜ëŠ” 추가 ìƒíƒœ" + label_additional_workflow_transitions_for_author: "사용ìžê°€ ì €ìžì¼ 때 허용ë˜ëŠ” 추가 ìƒíƒœ" + label_bulk_edit_selected_time_entries: "ì„ íƒëœ 소요 시간 대량 편집" + text_time_entries_destroy_confirmation: "ì„ íƒí•œ 소요 시간 í•­ëª©ì„ ì‚­ì œí•˜ì‹œê² ìŠµë‹ˆê¹Œ?" + label_role_anonymous: Anonymous + label_role_non_member: Non member + + label_issue_note_added: "ë§ê¸€ì´ 추가ë˜ì—ˆìŠµë‹ˆë‹¤." + label_issue_status_updated: "ìƒíƒœê°€ 변경ë˜ì—ˆìŠµë‹ˆë‹¤." + label_issue_priority_updated: "ìš°ì„  순위가 변경ë˜ì—ˆìŠµë‹ˆë‹¤." + label_issues_visibility_own: "ì¼ê°ì„ ìƒì„±í•˜ê±°ë‚˜ ë§¡ì€ ì‚¬ìš©ìž" + field_issues_visibility: "ì¼ê° ë³´ìž„" + label_issues_visibility_all: "모든 ì¼ê°" + permission_set_own_issues_private: "ìžì‹ ì˜ ì¼ê°ì„ 공개나 비공개로 설정" + field_is_private: "비공개" + permission_set_issues_private: "ì¼ê°ì„ 공개나 비공개로 설정" + label_issues_visibility_public: "비공개 ì¼ê° 제외" + text_issues_destroy_descendants_confirmation: "%{count} ê°œì˜ í•˜ìœ„ ì¼ê°ì„ 삭제할 것입니다." + field_commit_logs_encoding: "커밋(commit) ê¸°ë¡ ì¸ì½”딩" + field_scm_path_encoding: "경로 ì¸ì½”딩" + text_scm_path_encoding_note: "기본: UTF-8" + field_path_to_repository: "저장소 경로" + field_root_directory: "루트 경로" + field_cvs_module: "모듈" + field_cvsroot: "CVS 루트" + text_mercurial_repository_note: "로컬 저장소 (예: /hgrepo, c:\\hgrepo)" + text_scm_command: "명령" + text_scm_command_version: "버전" + label_git_report_last_commit: "파ì¼ì´ë‚˜ í´ë”ì˜ ë§ˆì§€ë§‰ 커밋(commit)ì„ ë³´ê³ " + text_scm_config: "SCM ëª…ë ¹ì„ config/configuration.ymlì—서 수정할 수 있습니다. 수정후ì—는 재시작하십시오." + text_scm_command_not_available: "SCM ëª…ë ¹ì„ ì‚¬ìš©í•  수 없습니다. 관리 페ì´ì§€ì˜ ì„¤ì •ì„ ê²€ì‚¬í•˜ì‹­ì‹œì˜¤." + notice_issue_successful_create: "%{id} ì¼ê°ì´ ìƒì„±ë˜ì—ˆìŠµë‹ˆë‹¤." + label_between: "사ì´" + setting_issue_group_assignment: "ê·¸ë£¹ì— ì¼ê° 할당 허용" + label_diff: "비êµ(diff)" + text_git_repository_note: "ë¡œì»¬ì˜ bare 저장소 (예: /gitrepo, c:\\gitrepo)" + description_query_sort_criteria_direction: "ì •ë ¬ ë°©í–¥" + description_project_scope: "검색 범위" + description_filter: "검색 ì¡°ê±´" + description_user_mail_notification: "ë©”ì¼ ì•Œë¦¼ 설정" + description_date_from: "시작 ë‚ ì§œ ìž…ë ¥" + description_message_content: "메세지 ë‚´ìš©" + description_available_columns: "가능한 컬럼" + description_date_range_interval: "시작과 ë 날짜로 범위를 ì„ íƒí•˜ì‹­ì‹œì˜¤." + description_issue_category_reassign: "ì¼ê° 범주를 ì„ íƒí•˜ì‹­ì‹œì˜¤." + description_search: "검색항목" + description_notes: "ë§ê¸€" + description_date_range_list: "목ë¡ì—서 범위를 ì„ íƒ í•˜ì‹­ì‹œì˜¤." + description_choose_project: "프로ì íЏ" + description_date_to: "종료 ë‚ ì§œ ìž…ë ¥" + description_query_sort_criteria_attribute: "ì •ë ¬ ì†ì„±" + description_wiki_subpages_reassign: "새로운 ìƒìœ„ 페ì´ì§€ë¥¼ ì„ íƒí•˜ì‹­ì‹œì˜¤." + description_selected_columns: "ì„ íƒëœ 컬럼" + label_parent_revision: "ìƒìœ„" + label_child_revision: "하위" + error_scm_annotate_big_text_file: "최대 í…스트 íŒŒì¼ í¬ê¸°ë¥¼ 초과 하면 í•­ëª©ì€ ì´ë ¥í™” ë  ìˆ˜ 없습니다." + setting_default_issue_start_date_to_creation_date: "새로운 ì¼ê°ì˜ 시작 날짜로 오늘 ë‚ ì§œ 사용" + button_edit_section: "ì´ ë¶€ë¶„ 수정" + setting_repositories_encodings: "첨부파ì¼ì´ë‚˜ 저장소 ì¸ì½”딩" + description_all_columns: "모든 컬럼" + button_export: "내보내기" + label_export_options: "내보내기 옵션: %{export_format}" + error_attachment_too_big: "ì´ íŒŒì¼ì€ ì œí•œëœ í¬ê¸°(%{max_size})를 초과하였기 ë•Œë¬¸ì— ì—…ë¡œë“œ í•  수 없습니다." + + notice_failed_to_save_time_entries: "%{total} ê°œì˜ ì‹œê°„ìž…ë ¥ì¤‘ ë‹¤ìŒ %{count} ê°œì˜ ì €ìž¥ì— ì‹¤íŒ¨í–ˆìŠµë‹ˆë‹¤:: %{ids}." + label_x_issues: + zero: 0 ì¼ê° + one: 1 ì¼ê° + other: "%{count} ì¼ê°" + label_repository_new: 저장소 추가 + field_repository_is_default: 주 저장소 + label_copy_attachments: ì²¨ë¶€íŒŒì¼ ë³µì‚¬ + label_item_position: "%{position}/%{count}" + label_completed_versions: 완료 버전 + text_project_identifier_info: "소문ìž(a-z),숫ìž,대쉬(-)와 밑줄(_)ë§Œ 가능합니다.
    ì‹ë³„ìžëŠ” 저장후ì—는 수정할 수 없습니다." + field_multiple: 복수선íƒê°€ëŠ¥ + setting_commit_cross_project_ref: 다른 프로ì íŠ¸ì˜ ì¼ê° 참조 ë° ìˆ˜ì • 허용 + text_issue_conflict_resolution_add_notes: ë³€ê²½ë‚´ìš©ì€ ì·¨ì†Œí•˜ê³  ë§ê¸€ë§Œ 추가 + text_issue_conflict_resolution_overwrite: 변경내용 ê°•ì œì ìš© (ì´ì „ ë§ê¸€ì„ 제외하고 ë®ì–´ ì”니다) + notice_issue_update_conflict: ì¼ê°ì´ 수정ë˜ëŠ” ë™ì•ˆ 다른 사용ìžì— ì˜í•´ì„œ 변경ë˜ì—ˆìŠµë‹ˆë‹¤. + text_issue_conflict_resolution_cancel: "ë³€ê²½ë‚´ìš©ì„ ë˜ëŒë¦¬ê³  다시 표시 %{link}" + permission_manage_related_issues: ì—°ê²°ëœ ì¼ê° 관리 + field_auth_source_ldap_filter: LDAP í•„í„° + label_search_for_watchers: 추가할 ì¼ê°ê´€ëžŒìž 검색 + notice_account_deleted: ë‹¹ì‹ ì˜ ê³„ì •ì´ ì™„ì „ížˆ ì‚­ì œë˜ì—ˆìŠµë‹ˆë‹¤. + setting_unsubscribe: 사용ìžë“¤ì´ ìžì‹ ì˜ ê³„ì •ì„ ì‚­ì œí† ë¡ í—ˆìš© + button_delete_my_account: ë‚˜ì˜ ê³„ì • ì‚­ì œ + text_account_destroy_confirmation: |- + 계ì†í•˜ì‹œê² ìŠµë‹ˆê¹Œ? + ê³„ì •ì´ ì‚­ì œë˜ë©´ 복구할 수 없습니다. + error_session_expired: ë‹¹ì‹ ì˜ ì„¸ì…˜ì´ ë§Œë£Œë˜ì—ˆìŠµë‹ˆë‹¤. 다시 로그ì¸í•˜ì„¸ìš”. + text_session_expiration_settings: "경고: ì´ ì„¤ì •ì„ ë°”ê¾¸ë©´ ë‹¹ì‹ ì„ í¬í•¨í•˜ì—¬ í˜„ìž¬ì˜ ì„¸ì…˜ë“¤ì„ ë§Œë£Œì‹œí‚¬ 수 있습니다." + setting_session_lifetime: 세션 최대 시간 + setting_session_timeout: 세션 비활성화 타임아웃 + label_session_expiration: 세션 만료 + permission_close_project: 프로ì íŠ¸ë¥¼ 닫거나 다시 열기 + label_show_closed_projects: 닫힌 프로ì íЏ 보기 + button_close: 닫기 + button_reopen: 다시 열기 + project_status_active: 사용중 + project_status_closed: 닫힘 + project_status_archived: 잠금보관 + text_project_closed: ì´ í”„ë¡œì íŠ¸ëŠ” 닫혀 있으며 ì½ê¸° 전용입니다. + notice_user_successful_create: ì‚¬ìš©ìž %{id} ì´(ê°€) ìƒì„±ë˜ì—ˆìŠµë‹ˆë‹¤. + field_core_fields: 표준 항목들 + field_timeout: 타임아웃 (ì´ˆ) + setting_thumbnails_enabled: 첨부파ì¼ì˜ ì¸ë„¤ì¼ì„ 보여줌 + setting_thumbnails_size: ì¸ë„¤ì¼ í¬ê¸° (픽셀) + label_status_transitions: ì¼ê° ìƒíƒœ 변경 + label_fields_permissions: 항목 편집 권한 + label_readonly: ì½ê¸° ì „ìš© + label_required: 필수 + text_repository_identifier_info: "소문ìž(a-z),숫ìž,대쉬(-)와 밑줄(_)ë§Œ 가능합니다.
    ì‹ë³„ìžëŠ” 저장후ì—는 수정할 수 없습니다." + field_board_parent: Parent forum + label_attribute_of_project: "프로ì íŠ¸ì˜ %{name}" + label_attribute_of_author: "ì €ìžì˜ %{name}" + label_attribute_of_assigned_to: "담당ìžì˜ %{name}" + label_attribute_of_fixed_version: "ëª©í‘œë²„ì „ì˜ %{name}" + label_copy_subtasks: 하위 ì¼ê°ë“¤ì„ 복사 + label_copied_to: "ë‹¤ìŒ ì¼ê°ìœ¼ë¡œ 복사ë¨:" + label_copied_from: "ë‹¤ìŒ ì¼ê°ìœ¼ë¡œë¶€í„° 복사ë¨:" + label_any_issues_in_project: ë‹¤ìŒ í”„ë¡œì íŠ¸ì— ì†í•œ 아무 ì¼ê° + label_any_issues_not_in_project: ë‹¤ìŒ í”„ë¡œì íŠ¸ì— ì†í•˜ì§€ ì•Šì€ ì•„ë¬´ ì¼ê° + field_private_notes: 비공개 ë§ê¸€ + permission_view_private_notes: 비공개 ë§ê¸€ 보기 + permission_set_notes_private: ë§ê¸€ì„ 비공개로 설정 + label_no_issues_in_project: ë‹¤ìŒ í”„ë¡œì íŠ¸ì˜ ì¼ê° 제외 + label_any: ëª¨ë‘ + label_last_n_weeks: 최근 %{count} 주 + setting_cross_project_subtasks: 다른 프로ì íŠ¸ì˜ ì¼ê°ì„ ìƒìœ„ ì¼ê°ìœ¼ë¡œ 지정하는 ê²ƒì„ í—ˆìš© + label_cross_project_descendants: 하위 프로ì íЏ + label_cross_project_tree: 최ìƒìœ„ ë° ëª¨ë“  하위 프로ì íЏ + label_cross_project_hierarchy: ìƒìœ„ ë° í•˜ìœ„ 프로ì íЏ + label_cross_project_system: 모든 프로ì íЏ + button_hide: 숨기기 + setting_non_working_week_days: ë¹„ê·¼ë¬´ì¼ (non-working days) + label_in_the_next_days: ë‹¤ìŒ + label_in_the_past_days: 지난 + label_attribute_of_user: "사용ìžì˜ %{name}" + text_turning_multiple_off: 복수선íƒì„ 비활성화하면, í•˜ë‚˜ì˜ ê°’ì„ ì œì™¸í•œ 나머지 ê°’ë“¤ì´ ì§€ì›Œì§‘ë‹ˆë‹¤. + label_attribute_of_issue: "ì¼ê°ì˜ %{name}" + permission_add_documents: 문서 추가 + permission_edit_documents: 문서 편집 + permission_delete_documents: 문서 ì‚­ì œ + label_gantt_progress_line: Progress line + setting_jsonp_enabled: JSONP 허용 + field_inherit_members: ìƒìœ„ 프로ì íŠ¸ë¡œë¶€í„° 구성ì›ì„ ìƒì† + field_closed_on: ì™„ë£Œì¼ + setting_default_projects_tracker_ids: 새 프로ì íŠ¸ì— ê¸°ë³¸ì ìœ¼ë¡œ 추가할 ì¼ê° 유형 + label_total_time: 합계 diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/8d/8d137ddbd4cf6f8f24fe80f23c9c81f3f5e0f6a8.svn-base --- a/.svn/pristine/8d/8d137ddbd4cf6f8f24fe80f23c9c81f3f5e0f6a8.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,127 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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 Themes - - # Return an array of installed themes - def self.themes - @@installed_themes ||= scan_themes - end - - # Rescan themes directory - def self.rescan - @@installed_themes = scan_themes - end - - # Return theme for given id, or nil if it's not found - def self.theme(id, options={}) - return nil if id.blank? - - found = themes.find {|t| t.id == id} - if found.nil? && options[:rescan] != false - rescan - found = theme(id, :rescan => false) - end - found - end - - # Class used to represent a theme - class Theme - attr_reader :path, :name, :dir - - def initialize(path) - @path = path - @dir = File.basename(path) - @name = @dir.humanize - @stylesheets = nil - @javascripts = nil - end - - # Directory name used as the theme id - def id; dir end - - def ==(theme) - theme.is_a?(Theme) && theme.dir == dir - end - - def <=>(theme) - name <=> theme.name - end - - def stylesheets - @stylesheets ||= assets("stylesheets", "css") - end - - def javascripts - @javascripts ||= assets("javascripts", "js") - end - - def stylesheet_path(source) - "/themes/#{dir}/stylesheets/#{source}" - end - - def javascript_path(source) - "/themes/#{dir}/javascripts/#{source}" - end - - private - - def assets(dir, ext) - Dir.glob("#{path}/#{dir}/*.#{ext}").collect {|f| File.basename(f).gsub(/\.#{ext}$/, '')} - end - end - - private - - def self.scan_themes - dirs = Dir.glob("#{Rails.public_path}/themes/*").select do |f| - # A theme should at least override application.css - File.directory?(f) && File.exist?("#{f}/stylesheets/application.css") - end - dirs.collect {|dir| Theme.new(dir)}.sort - end - end -end - -module ApplicationHelper - def current_theme - unless instance_variable_defined?(:@current_theme) - @current_theme = Redmine::Themes.theme(Setting.ui_theme) - end - @current_theme - end - - def stylesheet_path(source) - if current_theme && current_theme.stylesheets.include?(source) - super current_theme.stylesheet_path(source) - else - super - end - end - - def path_to_stylesheet(source) - stylesheet_path source - end - - # Returns the header tags for the current theme - def heads_for_theme - if current_theme && current_theme.javascripts.include?('theme') - javascript_include_tag current_theme.javascript_path('theme') - end - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/8d/8d16ae55d2f1c008a010086c27548d8733c4f49f.svn-base --- a/.svn/pristine/8d/8d16ae55d2f1c008a010086c27548d8733c4f49f.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,82 +0,0 @@ -var NS4 = (navigator.appName == "Netscape" && parseInt(navigator.appVersion) < 5); - -function addOption(theSel, theText, theValue) -{ - var newOpt = new Option(theText, theValue); - var selLength = theSel.length; - theSel.options[selLength] = newOpt; -} - -function swapOptions(theSel, index1, index2) -{ - var text, value; - text = theSel.options[index1].text; - value = theSel.options[index1].value; - theSel.options[index1].text = theSel.options[index2].text; - theSel.options[index1].value = theSel.options[index2].value; - theSel.options[index2].text = text; - theSel.options[index2].value = value; -} - -function deleteOption(theSel, theIndex) -{ - var selLength = theSel.length; - if(selLength>0) - { - theSel.options[theIndex] = null; - } -} - -function moveOptions(theSelFrom, theSelTo) -{ - - var selLength = theSelFrom.length; - var selectedText = new Array(); - var selectedValues = new Array(); - var selectedCount = 0; - - var i; - - for(i=selLength-1; i>=0; i--) - { - if(theSelFrom.options[i].selected) - { - selectedText[selectedCount] = theSelFrom.options[i].text; - selectedValues[selectedCount] = theSelFrom.options[i].value; - deleteOption(theSelFrom, i); - selectedCount++; - } - } - - for(i=selectedCount-1; i>=0; i--) - { - addOption(theSelTo, selectedText[i], selectedValues[i]); - } - - if(NS4) history.go(0); -} - -function moveOptionUp(theSel) { - var index = theSel.selectedIndex; - if (index > 0) { - swapOptions(theSel, index-1, index); - theSel.selectedIndex = index-1; - } -} - -function moveOptionDown(theSel) { - var index = theSel.selectedIndex; - if (index < theSel.length - 1) { - swapOptions(theSel, index, index+1); - theSel.selectedIndex = index+1; - } -} - -function selectAllOptions(id) -{ - var select = $(id); - for (var i=0; iIdentifikatoren kan ikke endres etter den er lagret.' - text_caracters_maximum: "%{count} tegn maksimum." - text_caracters_minimum: "MÃ¥ være minst %{count} tegn langt." - text_length_between: "Lengde mellom %{min} og %{max} tegn." - text_tracker_no_workflow: Ingen arbeidsflyt definert for denne sakstypen - text_unallowed_characters: Ugyldige tegn - text_comma_separated: Flere verdier tillat (kommaseparert). - text_issues_ref_in_commit_messages: Referering og retting av saker i innsendingsmelding - text_issue_added: "Sak %{id} er innrapportert av %{author}." - text_issue_updated: "Sak %{id} er oppdatert av %{author}." - text_wiki_destroy_confirmation: Er du sikker pÃ¥ at du vil slette denne wikien og alt innholdet ? - text_issue_category_destroy_question: "Noen saker (%{count}) er lagt til i denne kategorien. Hva vil du gjøre ?" - text_issue_category_destroy_assignments: Fjern bruk av kategorier - text_issue_category_reassign_to: Overfør sakene til denne kategorien - text_user_mail_option: "For ikke-valgte prosjekter vil du bare motta varsling om ting du overvÃ¥ker eller er involveret i (eks. saker du er forfatter av eller er tildelt)." - text_no_configuration_data: "Roller, arbeidsflyt, sakstyper og -statuser er ikke konfigurert enda.\nDet anbefales sterkt Ã¥ laste inn standardkonfigurasjonen. Du vil kunne endre denne etter den er innlastet." - text_load_default_configuration: Last inn standardkonfigurasjonen - text_status_changed_by_changeset: "Brukt i endringssett %{value}." - text_issues_destroy_confirmation: 'Er du sikker pÃ¥ at du vil slette valgte sak(er) ?' - text_select_project_modules: 'Velg moduler du vil aktivere for dette prosjektet:' - text_default_administrator_account_changed: Standard administrator-konto er endret - text_file_repository_writable: Fil-arkivet er skrivbart - text_rmagick_available: RMagick er tilgjengelig (valgfritt) - text_destroy_time_entries_question: "%{hours} timer er ført pÃ¥ sakene du er i ferd med Ã¥ slette. Hva vil du gjøre ?" - text_destroy_time_entries: Slett førte timer - text_assign_time_entries_to_project: Overfør førte timer til prosjektet - text_reassign_time_entries: 'Overfør førte timer til denne saken:' - text_user_wrote: "%{value} skrev:" - - default_role_manager: Leder - default_role_developer: Utvikler - default_role_reporter: Rapportør - default_tracker_bug: Feil - default_tracker_feature: Funksjon - default_tracker_support: Support - default_issue_status_new: Ny - default_issue_status_in_progress: PÃ¥gÃ¥r - default_issue_status_resolved: Avklart - default_issue_status_feedback: Tilbakemelding - default_issue_status_closed: Lukket - default_issue_status_rejected: Avvist - default_doc_category_user: Brukerdokumentasjon - default_doc_category_tech: Teknisk dokumentasjon - default_priority_low: Lav - default_priority_normal: Normal - default_priority_high: Høy - default_priority_urgent: Haster - default_priority_immediate: OmgÃ¥ende - default_activity_design: Design - default_activity_development: Utvikling - - enumeration_issue_priorities: Sakssprioriteringer - enumeration_doc_categories: Dokumentkategorier - enumeration_activities: Aktiviteter (tidsregistrering) - text_enumeration_category_reassign_to: 'Endre dem til denne verdien:' - text_enumeration_destroy_question: "%{count} objekter er endret til denne verdien." - label_incoming_emails: Innkommende e-post - label_generate_key: Generer en nøkkel - setting_mail_handler_api_enabled: Skru pÃ¥ WS for innkommende epost - setting_mail_handler_api_key: API-nøkkel - text_email_delivery_not_configured: "Levering av epost er ikke satt opp, og varsler er skrudd av.\nStill inn din SMTP-tjener i config/configuration.yml og start programmet pÃ¥ nytt for Ã¥ skru det pÃ¥." - field_parent_title: Overordnet side - label_issue_watchers: OvervÃ¥kere - button_quote: Sitat - setting_sequential_project_identifiers: Generer sekvensielle prosjekt-IDer - notice_unable_delete_version: Kan ikke slette versjonen - label_renamed: gitt nytt navn - label_copied: kopiert - setting_plain_text_mail: kun ren tekst (ikke HTML) - permission_view_files: Vise filer - permission_edit_issues: Redigere saker - permission_edit_own_time_entries: Redigere egne timelister - permission_manage_public_queries: Administrere delte søk - permission_add_issues: Legge inn saker - permission_log_time: Loggføre timer - permission_view_changesets: Vise endringssett - permission_view_time_entries: Vise brukte timer - permission_manage_versions: Administrere versjoner - permission_manage_wiki: Administrere wiki - permission_manage_categories: Administrere kategorier for saker - permission_protect_wiki_pages: Beskytte wiki-sider - permission_comment_news: Kommentere nyheter - permission_delete_messages: Slette meldinger - permission_select_project_modules: Velge prosjektmoduler - permission_manage_documents: Administrere dokumenter - permission_edit_wiki_pages: Redigere wiki-sider - permission_add_issue_watchers: Legge til overvÃ¥kere - permission_view_gantt: Vise gantt-diagram - permission_move_issues: Flytte saker - permission_manage_issue_relations: Administrere saksrelasjoner - permission_delete_wiki_pages: Slette wiki-sider - permission_manage_boards: Administrere forum - permission_delete_wiki_pages_attachments: Slette vedlegg - permission_view_wiki_edits: Vise wiki-historie - permission_add_messages: Sende meldinger - permission_view_messages: Vise meldinger - permission_manage_files: Administrere filer - permission_edit_issue_notes: Redigere notater - permission_manage_news: Administrere nyheter - permission_view_calendar: Vise kalender - permission_manage_members: Administrere medlemmer - permission_edit_messages: Redigere meldinger - permission_delete_issues: Slette saker - permission_view_issue_watchers: Vise liste over overvÃ¥kere - permission_manage_repository: Administrere depot - permission_commit_access: Tilgang til innsending - permission_browse_repository: Bla gjennom depot - permission_view_documents: Vise dokumenter - permission_edit_project: Redigere prosjekt - permission_add_issue_notes: Legge til notater - permission_save_queries: Lagre søk - permission_view_wiki_pages: Vise wiki - permission_rename_wiki_pages: Gi wiki-sider nytt navn - permission_edit_time_entries: Redigere timelister - permission_edit_own_issue_notes: Redigere egne notater - setting_gravatar_enabled: Bruk Gravatar-brukerikoner - label_example: Eksempel - 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: Rediger egne meldinger - permission_delete_own_messages: Slett egne meldinger - label_user_activity: "%{value}s aktivitet" - label_updated_time_by: "Oppdatert av %{author} for %{age} siden" - 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: "%{count} fil(er) kunne ikke lagres." - button_create_and_continue: Opprett og fortsett - text_custom_field_possible_values_info: 'En linje for hver verdi' - label_display: Visning - field_editable: Redigerbar - setting_repository_log_display_limit: Maks antall revisjoner vist i fil-loggen - setting_file_max_size_displayed: Max size of text files displayed inline - field_watcher: OvervÃ¥ker - setting_openid: Tillat OpenID innlogging og registrering - field_identity_url: OpenID URL - label_login_with_open_id_option: eller logg inn med OpenID - field_content: Innhold - label_descending: Synkende - label_sort: Sorter - label_ascending: Stigende - label_date_from_to: Fra %{start} til %{end} - label_greater_or_equal: ">=" - label_less_or_equal: <= - text_wiki_page_destroy_question: Denne siden har %{descendants} underside(r). Hva ønsker du Ã¥ gjøre? - text_wiki_page_reassign_children: Tilknytt undersider til denne overordnede siden - text_wiki_page_nullify_children: Behold undersider som rotsider - text_wiki_page_destroy_children: Slett undersider og alle deres underliggende sider - setting_password_min_length: Minimum passordlengde - field_group_by: Grupper resultater etter - mail_subject_wiki_content_updated: "Wiki-side '%{id}' er oppdatert" - label_wiki_content_added: Wiki-side opprettet - mail_subject_wiki_content_added: "Wiki-side '%{id}' er opprettet" - mail_body_wiki_content_added: Wiki-siden '%{id}' ble opprettet av %{author}. - label_wiki_content_updated: Wiki-side oppdatert - mail_body_wiki_content_updated: Wiki-siden '%{id}' ble oppdatert av %{author}. - permission_add_project: Opprett prosjekt - setting_new_project_user_role_id: Rolle gitt en ikke-administratorbruker som oppretter et prosjekt - label_view_all_revisions: Se alle revisjoner - label_tag: Tag - label_branch: Gren - error_no_tracker_in_project: Ingen sakstyper er tilknyttet dette prosjektet. Vennligst kontroller prosjektets innstillinger. - error_no_default_issue_status: Ingen standard saksstatus er angitt. Vennligst kontroller konfigurasjonen (GÃ¥ til "Administrasjon -> Saksstatuser"). - text_journal_changed: "%{label} endret fra %{old} til %{new}" - text_journal_set_to: "%{label} satt til %{value}" - text_journal_deleted: "%{label} slettet (%{old})" - label_group_plural: Grupper - label_group: Gruppe - label_group_new: Ny gruppe - label_time_entry_plural: Brukt tid - text_journal_added: "%{label} %{value} lagt til" - field_active: Aktiv - enumeration_system_activity: Systemaktivitet - permission_delete_issue_watchers: Slett overvÃ¥kere - version_status_closed: stengt - version_status_locked: lÃ¥st - version_status_open: Ã¥pen - error_can_not_reopen_issue_on_closed_version: En sak tilknyttet en stengt versjon kan ikke gjenÃ¥pnes. - label_user_anonymous: Anonym - button_move_and_follow: Flytt og følg etter - setting_default_projects_modules: Standard aktiverte moduler for nye prosjekter - setting_gravatar_default: Standard Gravatar-bilde - field_sharing: Deling - label_version_sharing_hierarchy: Med prosjekt-hierarki - label_version_sharing_system: Med alle prosjekter - label_version_sharing_descendants: Med underprosjekter - label_version_sharing_tree: Med prosjekt-tre - label_version_sharing_none: Ikke delt - error_can_not_archive_project: Dette prosjektet kan ikke arkiveres - button_duplicate: Duplikat - button_copy_and_follow: Kopier og følg etter - label_copy_source: Kilde - setting_issue_done_ratio: Kalkuler ferdigstillingsprosent ut i fra - setting_issue_done_ratio_issue_status: Bruk saksstatuser - error_issue_done_ratios_not_updated: Ferdigstillingsprosent oppdateres ikke. - error_workflow_copy_target: Vennligst velg sakstype(r) og rolle(r) - setting_issue_done_ratio_issue_field: Bruk felt fra saker - label_copy_same_as_target: Samme som mÃ¥l - label_copy_target: MÃ¥l - notice_issue_done_ratios_updated: Ferdigstillingsprosent oppdatert. - error_workflow_copy_source: Vennligst velg en kilde-sakstype eller rolle. - label_update_issue_done_ratios: Oppdatert ferdigstillingsprosent - setting_start_of_week: Start kalender pÃ¥ - permission_view_issues: Se pÃ¥ saker - label_display_used_statuses_only: Vis kun statuser som brukes av denne sakstypen - label_revision_id: Revision %{value} - label_api_access_key: API tilgangsnøkkel - label_api_access_key_created_on: API tilgangsnøkkel opprettet for %{value} siden - label_feeds_access_key: RSS tilgangsnøkkel - notice_api_access_key_reseted: Din API tilgangsnøkkel ble resatt. - setting_rest_api_enabled: Aktiver REST webservice - label_missing_api_access_key: Mangler en API tilgangsnøkkel - label_missing_feeds_access_key: Mangler en RSS tilgangsnøkkel - button_show: Vis - text_line_separated: Flere verdier er tillatt (en linje per verdi). - setting_mail_handler_body_delimiters: Avkort epost etter en av disse linjene - permission_add_subprojects: Opprett underprosjekt - label_subproject_new: Nytt underprosjekt - text_own_membership_delete_confirmation: |- - Du er i ferd med Ã¥ fjerne noen eller alle rettigheter og vil kanskje ikke være i stand til Ã¥ redigere dette prosjektet etterpÃ¥. - Er du sikker pÃ¥ at du vil fortsette? - label_close_versions: Steng fullførte versjoner - label_board_sticky: Fast - label_board_locked: LÃ¥st - permission_export_wiki_pages: Eksporter wiki-sider - setting_cache_formatted_text: Mellomlagre formattert tekst - permission_manage_project_activities: Administrere prosjektaktiviteter - error_unable_delete_issue_status: Kan ikke slette saksstatus - label_profile: Profil - permission_manage_subtasks: Administrere undersaker - field_parent_issue: Overordnet sak - label_subtask_plural: Undersaker - label_project_copy_notifications: Send epost-varslinger under prosjektkopiering - error_can_not_delete_custom_field: Kan ikke slette eget felt - error_unable_to_connect: Kunne ikke koble til (%{value}) - error_can_not_remove_role: Denne rollen er i bruk og kan ikke slettes. - error_can_not_delete_tracker: Denne sakstypen inneholder saker og kan ikke slettes. - field_principal: Principal - label_my_page_block: Min side felt - notice_failed_to_save_members: "Feil ved lagring av medlem(mer): %{errors}." - text_zoom_out: Zoom ut - text_zoom_in: Zoom inn - notice_unable_delete_time_entry: Kan ikke slette oppføring fra timeliste. - label_overall_spent_time: All tidsbruk - field_time_entries: Loggfør tid - project_module_gantt: Gantt - project_module_calendar: Kalender - button_edit_associated_wikipage: "Rediger tilhørende Wiki-side: %{page_title}" - text_are_you_sure_with_children: Slett sak og alle undersaker? - field_text: Tekstfelt - label_user_mail_option_only_owner: Kun for ting jeg eier - setting_default_notification_option: Standardvalg for varslinger - label_user_mail_option_only_my_events: Kun for ting jeg overvÃ¥ker eller er involvert i - label_user_mail_option_only_assigned: Kun for ting jeg er tildelt - label_user_mail_option_none: Ingen hendelser - field_member_of_group: Den tildeltes gruppe - field_assigned_to_role: Den tildeltes rolle - notice_not_authorized_archived_project: Prosjektet du forsøker Ã¥ Ã¥pne er blitt arkivert. - label_principal_search: "Søk etter bruker eller gruppe:" - label_user_search: "Søk etter bruker:" - field_visible: Synlig - setting_emails_header: Eposthode - setting_commit_logtime_activity_id: Aktivitet for logget tid. - text_time_logged_by_changeset: Lagt til i endringssett %{value}. - setting_commit_logtime_enabled: Muliggjør loggføring av tid - notice_gantt_chart_truncated: Diagrammet ble avkortet fordi det overstiger det maksimale antall elementer som kan vises (%{max}) - setting_gantt_items_limit: Maksimalt antall elementer vist pÃ¥ gantt-diagrammet - field_warn_on_leaving_unsaved: Vis meg en advarsel nÃ¥r jeg forlater en side med ikke lagret tekst - text_warn_on_leaving_unsaved: Den gjeldende siden inneholder tekst som ikke er lagret, som vil bli tapt hvis du forlater denne siden. - label_my_queries: Mine egne spørringer - text_journal_changed_no_detail: "%{label} oppdatert" - label_news_comment_added: Kommentar lagt til en nyhet - button_expand_all: Utvid alle - button_collapse_all: Kollaps alle - label_additional_workflow_transitions_for_assignee: Ytterligere overganger tillatt nÃ¥r brukeren er sakens tildelte - label_additional_workflow_transitions_for_author: Ytterligere overganger tillatt nÃ¥r brukeren er den som har opprettet saken - label_bulk_edit_selected_time_entries: Masserediger valgte timeliste-oppføringer - text_time_entries_destroy_confirmation: Er du sikker pÃ¥ du vil slette de(n) valgte timeliste-oppføringen(e)? - label_role_anonymous: Anonym - label_role_non_member: Ikke medlem - label_issue_note_added: Notat lagt til - label_issue_status_updated: Status oppdatert - label_issue_priority_updated: Prioritet oppdatert - label_issues_visibility_own: Saker opprettet av eller tildelt brukeren - field_issues_visibility: Synlighet pÃ¥ saker - label_issues_visibility_all: Alle saker - permission_set_own_issues_private: Gjør egne saker offentlige eller private - field_is_private: Privat - permission_set_issues_private: Gjør saker offentlige eller private - label_issues_visibility_public: Alle ikke-private saker - text_issues_destroy_descendants_confirmation: Dette vil ogsÃ¥ slette %{count} undersak(er). - field_commit_logs_encoding: Tegnkoding for innsendingsmeldinger - field_scm_path_encoding: Koding av sti - text_scm_path_encoding_note: "Standard: UTF-8" - field_path_to_repository: Sti til depot - field_root_directory: Rotkatalog - field_cvs_module: Modul - field_cvsroot: CVSROOT - text_mercurial_repository_note: Lokalt depot (f.eks. /hgrepo, c:\hgrepo) - text_scm_command: Kommando - text_scm_command_version: Versjon - label_git_report_last_commit: Rapporter siste innsending for filer og kataloger - text_scm_config: Du kan konfigurere scm kommandoer i config/configuration.yml. Vennligst restart applikasjonen etter Ã¥ ha redigert filen. - text_scm_command_not_available: Scm kommando er ikke tilgjengelig. Vennligst kontroller innstillingene i administrasjonspanelet. - - text_git_repository_note: Depot er bart og lokalt (f.eks. /gitrepo, c:\gitrepo) - - notice_issue_successful_create: Sak %{id} opprettet. - label_between: mellom - setting_issue_group_assignment: Tillat tildeling av saker til grupper - label_diff: diff - - description_query_sort_criteria_direction: Sort direction - description_project_scope: Search scope - description_filter: Filter - description_user_mail_notification: Mail notification settings - description_date_from: Enter start date - description_message_content: Message content - description_available_columns: Available Columns - description_date_range_interval: Choose range by selecting start and end date - description_issue_category_reassign: Choose issue category - description_search: Searchfield - description_notes: Notes - description_date_range_list: Choose range from list - description_choose_project: Projects - description_date_to: Enter end date - description_query_sort_criteria_attribute: Sort attribute - description_wiki_subpages_reassign: Choose new parent page - description_selected_columns: Selected Columns - label_parent_revision: Parent - label_child_revision: Child - error_scm_annotate_big_text_file: The entry cannot be annotated, as it exceeds the maximum text file size. - setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues - button_edit_section: Edit this section - setting_repositories_encodings: Attachments and repositories encodings - description_all_columns: All Columns - button_export: Export - label_export_options: "%{export_format} export options" - error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size}) diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/8d/8d49dae3dcbe3c33e8ba68468bf87f82d0d0b226.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/8d/8d49dae3dcbe3c33e8ba68468bf87f82d0d0b226.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,111 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class MembersControllerTest < ActionController::TestCase + fixtures :projects, :members, :member_roles, :roles, :users + + def setup + User.current = nil + @request.session[:user_id] = 2 + end + + def test_create + assert_difference 'Member.count' do + post :create, :project_id => 1, :membership => {:role_ids => [1], :user_id => 7} + end + assert_redirected_to '/projects/ecookbook/settings/members' + assert User.find(7).member_of?(Project.find(1)) + end + + def test_create_multiple + assert_difference 'Member.count', 3 do + post :create, :project_id => 1, :membership => {:role_ids => [1], :user_ids => [7, 8, 9]} + end + assert_redirected_to '/projects/ecookbook/settings/members' + assert User.find(7).member_of?(Project.find(1)) + end + + def test_xhr_create + assert_difference 'Member.count', 3 do + xhr :post, :create, :project_id => 1, :membership => {:role_ids => [1], :user_ids => [7, 8, 9]} + assert_response :success + assert_template 'create' + assert_equal 'text/javascript', response.content_type + end + assert User.find(7).member_of?(Project.find(1)) + assert User.find(8).member_of?(Project.find(1)) + assert User.find(9).member_of?(Project.find(1)) + assert_include 'tab-content-members', response.body + end + + def test_xhr_create_with_failure + assert_no_difference 'Member.count' do + xhr :post, :create, :project_id => 1, :membership => {:role_ids => [], :user_ids => [7, 8, 9]} + assert_response :success + assert_template 'create' + assert_equal 'text/javascript', response.content_type + end + assert_match /alert/, response.body, "Alert message not sent" + end + + def test_edit + assert_no_difference 'Member.count' do + put :update, :id => 2, :membership => {:role_ids => [1], :user_id => 3} + end + assert_redirected_to '/projects/ecookbook/settings/members' + end + + def test_xhr_edit + assert_no_difference 'Member.count' do + xhr :put, :update, :id => 2, :membership => {:role_ids => [1], :user_id => 3} + assert_response :success + assert_template 'update' + assert_equal 'text/javascript', response.content_type + end + member = Member.find(2) + assert_equal [1], member.role_ids + assert_equal 3, member.user_id + assert_include 'tab-content-members', response.body + end + + def test_destroy + assert_difference 'Member.count', -1 do + delete :destroy, :id => 2 + end + assert_redirected_to '/projects/ecookbook/settings/members' + assert !User.find(3).member_of?(Project.find(1)) + end + + def test_xhr_destroy + assert_difference 'Member.count', -1 do + xhr :delete, :destroy, :id => 2 + assert_response :success + assert_template 'destroy' + assert_equal 'text/javascript', response.content_type + end + assert_nil Member.find_by_id(2) + assert_include 'tab-content-members', response.body + end + + def test_autocomplete + get :autocomplete, :project_id => 1, :q => 'mis', :format => 'js' + assert_response :success + assert_include 'User Misc', response.body + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/8d/8d646867ca13f3417cfc72ec4e7ed07e69398fdb.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/8d/8d646867ca13f3417cfc72ec4e7ed07e69398fdb.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,19 @@ +class RemoveIssuesDefaultFkValues < ActiveRecord::Migration + def up + change_column_default :issues, :tracker_id, nil + change_column_default :issues, :project_id, nil + change_column_default :issues, :status_id, nil + change_column_default :issues, :assigned_to_id, nil + change_column_default :issues, :priority_id, nil + change_column_default :issues, :author_id, nil + end + + def down + change_column_default :issues, :tracker_id, 0 + change_column_default :issues, :project_id, 0 + change_column_default :issues, :status_id, 0 + change_column_default :issues, :assigned_to_id, 0 + change_column_default :issues, :priority_id, 0 + change_column_default :issues, :author_id, 0 + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/8e/8e3a1cce1761039cfaa74854eab700c58025e356.svn-base --- a/.svn/pristine/8e/8e3a1cce1761039cfaa74854eab700c58025e356.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,8 +0,0 @@ -<%= f.error_messages %> - -
    -

    <%= f.text_field :lastname, :label => :field_name %>

    - <% @group.custom_field_values.each do |value| %> -

    <%= custom_field_tag_with_label :group, value %>

    - <% end %> -
    diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/8e/8e4c7afc7109386dd42e0a86c04a6247a9bd537e.svn-base --- a/.svn/pristine/8e/8e4c7afc7109386dd42e0a86c04a6247a9bd537e.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,343 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../../../../test_helper', __FILE__) -require 'digest/md5' - -class Redmine::WikiFormatting::TextileFormatterTest < ActionView::TestCase - - def setup - @formatter = Redmine::WikiFormatting::Textile::Formatter - end - - MODIFIERS = { - "*" => 'strong', # bold - "_" => 'em', # italic - "+" => 'ins', # underline - "-" => 'del', # deleted - "^" => 'sup', # superscript - "~" => 'sub' # subscript - } - - def test_modifiers - assert_html_output( - '*bold*' => 'bold', - 'before *bold*' => 'before bold', - '*bold* after' => 'bold after', - '*two words*' => 'two words', - '*two*words*' => 'two*words', - '*two * words*' => 'two * words', - '*two* *words*' => 'two words', - '*(two)* *(words)*' => '(two) (words)', - # with class - '*(foo)two words*' => 'two words' - ) - end - - def test_modifiers_combination - MODIFIERS.each do |m1, tag1| - MODIFIERS.each do |m2, tag2| - next if m1 == m2 - text = "#{m2}#{m1}Phrase modifiers#{m1}#{m2}" - html = "<#{tag2}><#{tag1}>Phrase modifiers" - assert_html_output text => html - end - end - end - - def test_inline_code - assert_html_output( - 'this is @some code@' => 'this is some code', - '@@' => '<Location /redmine>' - ) - end - - def test_escaping - assert_html_output( - 'this is a " => "

    <script>some script;</script>

    ", + # do not escape pre/code tags + "
    \nline 1\nline2
    " => "
    \nline 1\nline2
    ", + "
    \nline 1\nline2
    " => "
    \nline 1\nline2
    ", + "
    content
    " => "
    <div>content</div>
    ", + "HTML comment: " => "

    HTML comment: <!-- no comments -->

    ", + " -# <%= image_path "my_face", :plugin => "my_plugin" %> -# -# Where the default helpers allow the specification of more than one file (i.e. the -# javascript and stylesheet helpers), you can do similarly for multiple assets from -# within a single plugin. -# -# --- -# -# This module enhances four of the methods from ActionView::Helpers::AssetTagHelper: -# -# * stylesheet_link_tag -# * javascript_include_tag -# * image_path -# * image_tag -# -# Each one of these methods now accepts the key/value pair :plugin => "plugin_name", -# which can be used to specify the originating plugin for any assets. -# -module Engines::RailsExtensions::AssetHelpers - def self.included(base) #:nodoc: - base.class_eval do - [:stylesheet_link_tag, :javascript_include_tag, :image_path, :image_tag].each do |m| - alias_method_chain m, :engine_additions - end - end - end - - # Adds plugin functionality to Rails' default stylesheet_link_tag method. - def stylesheet_link_tag_with_engine_additions(*sources) - stylesheet_link_tag_without_engine_additions(*Engines::RailsExtensions::AssetHelpers.pluginify_sources("stylesheets", *sources)) - end - - # Adds plugin functionality to Rails' default javascript_include_tag method. - def javascript_include_tag_with_engine_additions(*sources) - javascript_include_tag_without_engine_additions(*Engines::RailsExtensions::AssetHelpers.pluginify_sources("javascripts", *sources)) - end - - #-- - # Our modified image_path now takes a 'plugin' option, though it doesn't require it - #++ - - # Adds plugin functionality to Rails' default image_path method. - def image_path_with_engine_additions(source, options={}) - options.stringify_keys! - source = Engines::RailsExtensions::AssetHelpers.plugin_asset_path(options["plugin"], "images", source) if options["plugin"] - image_path_without_engine_additions(source) - end - - # Adds plugin functionality to Rails' default image_tag method. - def image_tag_with_engine_additions(source, options={}) - options.stringify_keys! - if options["plugin"] - source = Engines::RailsExtensions::AssetHelpers.plugin_asset_path(options["plugin"], "images", source) - options.delete("plugin") - end - image_tag_without_engine_additions(source, options) - end - - #-- - # The following are methods on this module directly because of the weird-freaky way - # Rails creates the helper instance that views actually get - #++ - - # Convert sources to the paths for the given plugin, if any plugin option is given - def self.pluginify_sources(type, *sources) - options = sources.last.is_a?(Hash) ? sources.pop.stringify_keys : { } - sources.map! { |s| plugin_asset_path(options["plugin"], type, s) } if options["plugin"] - options.delete("plugin") # we don't want it appearing in the HTML - sources << options # re-add options - end - - # Returns the publicly-addressable relative URI for the given asset, type and plugin - def self.plugin_asset_path(plugin_name, type, asset) - raise "No plugin called '#{plugin_name}' - please use the full name of a loaded plugin." if Engines.plugins[plugin_name].nil? - "#{ActionController::Base.relative_url_root}/#{Engines.plugins[plugin_name].public_asset_directory}/#{type}/#{asset}" - end - -end - -module ::ActionView::Helpers::AssetTagHelper #:nodoc: - include Engines::RailsExtensions::AssetHelpers -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/fd/fdc1344d7f646f61fa7506b2b2b25effb91b6273.svn-base --- a/.svn/pristine/fd/fdc1344d7f646f61fa7506b2b2b25effb91b6273.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 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 PreviewsController < ApplicationController - before_filter :find_project - - def issue - @issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank? - if @issue - @attachements = @issue.attachments - @description = params[:issue] && params[:issue][:description] - if @description && @description.gsub(/(\r?\n|\n\r?)/, "\n") == @issue.description.to_s.gsub(/(\r?\n|\n\r?)/, "\n") - @description = nil - end - @notes = params[:notes] - else - @description = (params[:issue] ? params[:issue][:description] : nil) - end - render :layout => false - end - - def news - @text = (params[:news] ? params[:news][:description] : nil) - render :partial => 'common/preview' - end - - private - - def find_project - project_id = (params[:issue] && params[:issue][:project_id]) || params[:project_id] - @project = Project.find(project_id) - rescue ActiveRecord::RecordNotFound - render_404 - end - -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/fd/fdfe5659f8fae8662194ea302cbaaa172aeab193.svn-base --- a/.svn/pristine/fd/fdfe5659f8fae8662194ea302cbaaa172aeab193.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,168 +0,0 @@ -# encoding: utf-8 -# -# Redmine - project management software -# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) - -class AttachmentTest < ActiveSupport::TestCase - fixtures :users, :projects, :roles, :members, :member_roles, - :enabled_modules, :issues, :trackers, :attachments - - class MockFile - attr_reader :original_filename, :content_type, :content, :size - - def initialize(attributes) - @original_filename = attributes[:original_filename] - @content_type = attributes[:content_type] - @content = attributes[:content] || "Content" - @size = content.size - end - end - - def setup - set_tmp_attachments_directory - end - - def test_create - a = Attachment.new(:container => Issue.find(1), - :file => uploaded_test_file("testfile.txt", "text/plain"), - :author => User.find(1)) - assert a.save - assert_equal 'testfile.txt', a.filename - assert_equal 59, a.filesize - assert_equal 'text/plain', a.content_type - assert_equal 0, a.downloads - assert_equal '1478adae0d4eb06d35897518540e25d6', a.digest - assert File.exist?(a.diskfile) - assert_equal 59, File.size(a.diskfile) - end - - def test_destroy - a = Attachment.new(:container => Issue.find(1), - :file => uploaded_test_file("testfile.txt", "text/plain"), - :author => User.find(1)) - assert a.save - assert_equal 'testfile.txt', a.filename - assert_equal 59, a.filesize - assert_equal 'text/plain', a.content_type - assert_equal 0, a.downloads - assert_equal '1478adae0d4eb06d35897518540e25d6', a.digest - diskfile = a.diskfile - assert File.exist?(diskfile) - assert_equal 59, File.size(a.diskfile) - assert a.destroy - assert !File.exist?(diskfile) - end - - def test_create_should_auto_assign_content_type - a = Attachment.new(:container => Issue.find(1), - :file => uploaded_test_file("testfile.txt", ""), - :author => User.find(1)) - assert a.save - assert_equal 'text/plain', a.content_type - end - - def test_identical_attachments_at_the_same_time_should_not_overwrite - a1 = Attachment.create!(:container => Issue.find(1), - :file => uploaded_test_file("testfile.txt", ""), - :author => User.find(1)) - a2 = Attachment.create!(:container => Issue.find(1), - :file => uploaded_test_file("testfile.txt", ""), - :author => User.find(1)) - assert a1.disk_filename != a2.disk_filename - end - - def test_filename_should_be_basenamed - a = Attachment.new(:file => MockFile.new(:original_filename => "path/to/the/file")) - assert_equal 'file', a.filename - end - - def test_filename_should_be_sanitized - a = Attachment.new(:file => MockFile.new(:original_filename => "valid:[] invalid:?%*|\"'<>chars")) - assert_equal 'valid_[] invalid_chars', a.filename - end - - def test_diskfilename - assert Attachment.disk_filename("test_file.txt") =~ /^\d{12}_test_file.txt$/ - assert_equal 'test_file.txt', Attachment.disk_filename("test_file.txt")[13..-1] - assert_equal '770c509475505f37c2b8fb6030434d6b.txt', Attachment.disk_filename("test_accentué.txt")[13..-1] - assert_equal 'f8139524ebb8f32e51976982cd20a85d', Attachment.disk_filename("test_accentué")[13..-1] - assert_equal 'cbb5b0f30978ba03731d61f9f6d10011', Attachment.disk_filename("test_accentué.ça")[13..-1] - end - - context "Attachmnet.attach_files" do - should "attach the file" do - issue = Issue.first - assert_difference 'Attachment.count' do - Attachment.attach_files(issue, - '1' => { - 'file' => uploaded_test_file('testfile.txt', 'text/plain'), - 'description' => 'test' - }) - end - - attachment = Attachment.first(:order => 'id DESC') - assert_equal issue, attachment.container - assert_equal 'testfile.txt', attachment.filename - assert_equal 59, attachment.filesize - assert_equal 'test', attachment.description - assert_equal 'text/plain', attachment.content_type - assert File.exists?(attachment.diskfile) - assert_equal 59, File.size(attachment.diskfile) - end - - should "add unsaved files to the object as unsaved attachments" do - # Max size of 0 to force Attachment creation failures - with_settings(:attachment_max_size => 0) do - @project = Project.generate! - response = Attachment.attach_files(@project, { - '1' => {'file' => mock_file, 'description' => 'test'}, - '2' => {'file' => mock_file, 'description' => 'test'} - }) - - assert response[:unsaved].present? - assert_equal 2, response[:unsaved].length - assert response[:unsaved].first.new_record? - assert response[:unsaved].second.new_record? - assert_equal response[:unsaved], @project.unsaved_attachments - end - end - end - - def test_latest_attach - Attachment.storage_path = "#{Rails.root}/test/fixtures/files" - a1 = Attachment.find(16) - assert_equal "testfile.png", a1.filename - assert a1.readable? - assert (! a1.visible?(User.anonymous)) - assert a1.visible?(User.find(2)) - a2 = Attachment.find(17) - assert_equal "testfile.PNG", a2.filename - assert a2.readable? - assert (! a2.visible?(User.anonymous)) - assert a2.visible?(User.find(2)) - assert a1.created_on < a2.created_on - - la1 = Attachment.latest_attach([a1, a2], "testfile.png") - assert_equal 17, la1.id - la2 = Attachment.latest_attach([a1, a2], "Testfile.PNG") - assert_equal 17, la2.id - - set_tmp_attachments_directory - end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/fe/fe0465ebb05e92c484a2edd16cfdf129d7d41205.svn-base --- a/.svn/pristine/fe/fe0465ebb05e92c484a2edd16cfdf129d7d41205.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,81 +0,0 @@ -module CodeRay -module Scanners - - load :html - load :ruby - - # Scanner for HTML ERB templates. - class ERB < Scanner - - register_for :erb - title 'HTML ERB Template' - - KINDS_NOT_LOC = HTML::KINDS_NOT_LOC - - ERB_RUBY_BLOCK = / - (<%(?!%)[-=\#]?) - ((?> - [^\-%]* # normal* - (?> # special - (?: %(?!>) | -(?!%>) ) - [^\-%]* # normal* - )* - )) - ((?: -?%> )?) - /x # :nodoc: - - START_OF_ERB = / - <%(?!%) - /x # :nodoc: - - protected - - def setup - @ruby_scanner = CodeRay.scanner :ruby, :tokens => @tokens, :keep_tokens => true - @html_scanner = CodeRay.scanner :html, :tokens => @tokens, :keep_tokens => true, :keep_state => true - end - - def reset_instance - super - @html_scanner.reset - end - - def scan_tokens encoder, options - - until eos? - - if (match = scan_until(/(?=#{START_OF_ERB})/o) || scan_rest) and not match.empty? - @html_scanner.tokenize match, :tokens => encoder - - elsif match = scan(/#{ERB_RUBY_BLOCK}/o) - start_tag = self[1] - code = self[2] - end_tag = self[3] - - encoder.begin_group :inline - encoder.text_token start_tag, :inline_delimiter - - if start_tag == '<%#' - encoder.text_token code, :comment - else - @ruby_scanner.tokenize code, :tokens => encoder - end unless code.empty? - - encoder.text_token end_tag, :inline_delimiter unless end_tag.empty? - encoder.end_group :inline - - else - raise_inspect 'else-case reached!', encoder - - end - - end - - encoder - - end - - end - -end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/fe/fe2e55a5ec8942da65be048ad7fee177c1286f80.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/fe/fe2e55a5ec8942da65be048ad7fee177c1286f80.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,9 @@ +class AddProjectsInheritMembers < ActiveRecord::Migration + def up + add_column :projects, :inherit_members, :boolean, :default => false, :null => false + end + + def down + remove_column :projects, :inherit_members + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/fe/fe4ff1d0091ede8f5bbe79d77084f595941f18d8.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/fe/fe4ff1d0091ede8f5bbe79d77084f595941f18d8.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,1093 @@ +# Chinese (China) translations for Ruby on Rails +# by tsechingho (http://github.com/tsechingho) +zh: + # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl) + direction: ltr + jquery: + locale: "zh-CN" + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%Y-%m-%d" + short: "%b%dæ—¥" + long: "%Yå¹´%b%dæ—¥" + + day_names: [星期天, 星期一, 星期二, 星期三, 星期四, 星期五, 星期六] + abbr_day_names: [æ—¥, 一, 二, 三, å››, 五, å…­] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, 一月, 二月, 三月, 四月, 五月, 六月, 七月, 八月, 乿œˆ, åæœˆ, å一月, å二月] + abbr_month_names: [~, 1月, 2月, 3月, 4月, 5月, 6月, 7月, 8月, 9月, 10月, 11月, 12月] + # Used in date_select and datime_select. + order: + - :year + - :month + - :day + + time: + formats: + default: "%Yå¹´%b%dæ—¥ %A %H:%M:%S" + time: "%H:%M" + short: "%b%dæ—¥ %H:%M" + long: "%Yå¹´%b%dæ—¥ %H:%M" + am: "上åˆ" + pm: "下åˆ" + + datetime: + distance_in_words: + half_a_minute: "åŠåˆ†é’Ÿ" + less_than_x_seconds: + one: "一秒内" + other: "少于 %{count} ç§’" + x_seconds: + one: "一秒" + other: "%{count} ç§’" + less_than_x_minutes: + one: "一分钟内" + other: "少于 %{count} 分钟" + x_minutes: + one: "一分钟" + other: "%{count} 分钟" + about_x_hours: + one: "å¤§çº¦ä¸€å°æ—¶" + other: "大约 %{count} å°æ—¶" + x_hours: + one: "1 å°æ—¶" + other: "%{count} å°æ—¶" + x_days: + one: "一天" + other: "%{count} 天" + about_x_months: + one: "大约一个月" + other: "大约 %{count} 个月" + x_months: + one: "一个月" + other: "%{count} 个月" + about_x_years: + one: "大约一年" + other: "大约 %{count} å¹´" + over_x_years: + one: "超过一年" + other: "超过 %{count} å¹´" + almost_x_years: + one: "将近 1 å¹´" + other: "将近 %{count} å¹´" + + number: + # Default format for numbers + format: + separator: "." + delimiter: "" + precision: 3 + human: + format: + delimiter: "" + precision: 3 + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + +# Used in array.to_sentence. + support: + array: + sentence_connector: "å’Œ" + skip_last_comma: false + + activerecord: + errors: + template: + header: + one: "由于å‘生了一个错误 %{model} 无法ä¿å­˜" + other: "%{count} 个错误使得 %{model} 无法ä¿å­˜" + messages: + inclusion: "ä¸åŒ…å«äºŽåˆ—表中" + exclusion: "是ä¿ç•™å…³é”®å­—" + invalid: "是无效的" + confirmation: "与确认值ä¸åŒ¹é…" + accepted: "必须是å¯è¢«æŽ¥å—çš„" + empty: "ä¸èƒ½ç•™ç©º" + blank: "ä¸èƒ½ä¸ºç©ºå­—符" + too_long: "过长(最长为 %{count} 个字符)" + too_short: "过短(最短为 %{count} 个字符)" + wrong_length: "é•¿åº¦éžæ³•(必须为 %{count} 个字符)" + taken: "å·²ç»è¢«ä½¿ç”¨" + not_a_number: "䏿˜¯æ•°å­—" + not_a_date: "䏿˜¯åˆæ³•日期" + greater_than: "必须大于 %{count}" + greater_than_or_equal_to: "必须大于或等于 %{count}" + equal_to: "必须等于 %{count}" + less_than: "å¿…é¡»å°äºŽ %{count}" + less_than_or_equal_to: "å¿…é¡»å°äºŽæˆ–等于 %{count}" + odd: "å¿…é¡»ä¸ºå•æ•°" + even: "å¿…é¡»ä¸ºåŒæ•°" + greater_than_start_date: "必须在起始日期之åŽ" + not_same_project: "ä¸å±žäºŽåŒä¸€ä¸ªé¡¹ç›®" + circular_dependency: "此关è”将导致循环ä¾èµ–" + cant_link_an_issue_with_a_descendant: "问题ä¸èƒ½å…³è”到它的å­ä»»åŠ¡" + + actionview_instancetag_blank_option: 请选择 + + general_text_No: 'å¦' + general_text_Yes: '是' + general_text_no: 'å¦' + general_text_yes: '是' + general_lang_name: 'Simplified Chinese (简体中文)' + general_csv_separator: ',' + general_csv_decimal_separator: '.' + general_csv_encoding: gb18030 + general_pdf_encoding: gb18030 + general_first_day_of_week: '7' + + notice_account_updated: å¸å·æ›´æ–°æˆåŠŸ + notice_account_invalid_creditentials: æ— æ•ˆçš„ç”¨æˆ·åæˆ–å¯†ç  + notice_account_password_updated: å¯†ç æ›´æ–°æˆåŠŸ + notice_account_wrong_password: 密ç é”™è¯¯ + notice_account_register_done: å¸å·åˆ›å»ºæˆåŠŸï¼Œè¯·ä½¿ç”¨æ³¨å†Œç¡®è®¤é‚®ä»¶ä¸­çš„é“¾æŽ¥æ¥æ¿€æ´»æ‚¨çš„å¸å·ã€‚ + notice_account_unknown_email: 未知用户 + notice_can_t_change_password: 该å¸å·ä½¿ç”¨äº†å¤–部认è¯ï¼Œå› æ­¤æ— æ³•更改密ç ã€‚ + notice_account_lost_email_sent: 系统已将引导您设置新密ç çš„邮件å‘é€ç»™æ‚¨ã€‚ + notice_account_activated: 您的å¸å·å·²è¢«æ¿€æ´»ã€‚您现在å¯ä»¥ç™»å½•了。 + notice_successful_create: 创建æˆåŠŸ + notice_successful_update: æ›´æ–°æˆåŠŸ + notice_successful_delete: 删除æˆåŠŸ + notice_successful_connection: 连接æˆåŠŸ + notice_file_not_found: 您访问的页é¢ä¸å­˜åœ¨æˆ–已被删除。 + notice_locking_conflict: æ•°æ®å·²è¢«å¦ä¸€ä½ç”¨æˆ·æ›´æ–° + notice_not_authorized: 对ä¸èµ·ï¼Œæ‚¨æ— æƒè®¿é—®æ­¤é¡µé¢ã€‚ + notice_not_authorized_archived_project: è¦è®¿é—®çš„项目已ç»å½’档。 + notice_email_sent: "邮件已å‘é€è‡³ %{value}" + notice_email_error: "å‘é€é‚®ä»¶æ—¶å‘生错误 (%{value})" + notice_feeds_access_key_reseted: 您的RSSå­˜å–键已被é‡ç½®ã€‚ + notice_api_access_key_reseted: 您的API访问键已被é‡ç½®ã€‚ + notice_failed_to_save_issues: "%{count} 个问题ä¿å­˜å¤±è´¥ï¼ˆå…±é€‰æ‹© %{total} 个问题):%{ids}." + notice_failed_to_save_members: "æˆå‘˜ä¿å­˜å¤±è´¥: %{errors}." + notice_no_issue_selected: "未选择任何问题ï¼è¯·é€‰æ‹©æ‚¨è¦ç¼–辑的问题。" + notice_account_pending: "您的å¸å·å·²è¢«æˆåŠŸåˆ›å»ºï¼Œæ­£åœ¨ç­‰å¾…ç®¡ç†å‘˜çš„审核。" + notice_default_data_loaded: æˆåŠŸè½½å…¥é»˜è®¤è®¾ç½®ã€‚ + notice_unable_delete_version: 无法删除版本 + notice_unable_delete_time_entry: 无法删除工时 + notice_issue_done_ratios_updated: 问题完æˆåº¦å·²æ›´æ–°ã€‚ + notice_gantt_chart_truncated: "The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max})" + + error_can_t_load_default_data: "无法载入默认设置:%{value}" + error_scm_not_found: "版本库中ä¸å­˜åœ¨è¯¥æ¡ç›®å’Œï¼ˆæˆ–)其修订版本。" + error_scm_command_failed: "访问版本库时å‘生错误:%{value}" + error_scm_annotate: "该æ¡ç›®ä¸å­˜åœ¨æˆ–无法追溯。" + error_issue_not_found_in_project: '问题ä¸å­˜åœ¨æˆ–ä¸å±žäºŽæ­¤é¡¹ç›®' + error_no_tracker_in_project: 该项目未设定跟踪标签,请检查项目é…置。 + error_no_default_issue_status: 未设置默认的问题状æ€ã€‚请检查系统设置("管ç†" -> "问题状æ€")。 + error_can_not_delete_custom_field: 无法删除自定义属性 + error_can_not_delete_tracker: "该跟踪标签已包å«é—®é¢˜,无法删除" + error_can_not_remove_role: "该角色正在使用中,无法删除" + error_can_not_reopen_issue_on_closed_version: 该问题被关è”到一个已ç»å…³é—­çš„ç‰ˆæœ¬ï¼Œå› æ­¤æ— æ³•é‡æ–°æ‰“开。 + error_can_not_archive_project: 该项目无法被存档 + error_issue_done_ratios_not_updated: 问题完æˆåº¦æœªèƒ½è¢«æ›´æ–°ã€‚ + error_workflow_copy_source: 请选择一个æºè·Ÿè¸ªæ ‡ç­¾æˆ–者角色 + error_workflow_copy_target: 请选择目标跟踪标签和角色 + error_unable_delete_issue_status: '无法删除问题状æ€' + error_unable_to_connect: "无法连接 (%{value})" + warning_attachments_not_saved: "%{count} 个文件ä¿å­˜å¤±è´¥" + + mail_subject_lost_password: "您的 %{value} 密ç " + mail_body_lost_password: '请点击以下链接æ¥ä¿®æ”¹æ‚¨çš„密ç ï¼š' + mail_subject_register: "%{value}å¸å·æ¿€æ´»" + mail_body_register: 'è¯·ç‚¹å‡»ä»¥ä¸‹é“¾æŽ¥æ¥æ¿€æ´»æ‚¨çš„å¸å·ï¼š' + mail_body_account_information_external: "您å¯ä»¥ä½¿ç”¨æ‚¨çš„ %{value} å¸å·æ¥ç™»å½•。" + mail_body_account_information: 您的å¸å·ä¿¡æ¯ + mail_subject_account_activation_request: "%{value}å¸å·æ¿€æ´»è¯·æ±‚" + mail_body_account_activation_request: "新用户(%{value}ï¼‰å·²å®Œæˆæ³¨å†Œï¼Œæ­£åœ¨ç­‰å€™æ‚¨çš„审核:" + mail_subject_reminder: "%{count} 个问题需è¦å°½å¿«è§£å†³ (%{days})" + mail_body_reminder: "指派给您的 %{count} 个问题需è¦åœ¨ %{days} 天内完æˆï¼š" + mail_subject_wiki_content_added: "'%{id}' wiki页é¢å·²æ·»åŠ " + mail_body_wiki_content_added: "'%{id}' wiki页é¢å·²ç”± %{author} 添加。" + mail_subject_wiki_content_updated: "'%{id}' wiki页é¢å·²æ›´æ–°ã€‚" + mail_body_wiki_content_updated: "'%{id}' wiki页é¢å·²ç”± %{author} 更新。" + + + 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: æ­£åˆ™è¡¨è¾¾å¼ + field_min_length: 最å°é•¿åº¦ + field_max_length: 最大长度 + field_value: 值 + field_category: 类别 + field_title: 标题 + field_project: 项目 + field_issue: 问题 + field_status: çŠ¶æ€ + field_notes: 说明 + field_is_closed: 已关闭的问题 + field_is_default: 默认值 + field_tracker: 跟踪 + field_subject: 主题 + field_due_date: è®¡åˆ’å®Œæˆæ—¥æœŸ + field_assigned_to: 指派给 + field_priority: 优先级 + field_fixed_version: 目标版本 + field_user: 用户 + field_principal: 用户/用户组 + field_role: 角色 + field_homepage: 主页 + field_is_public: 公开 + field_parent: 上级项目 + field_is_in_roadmap: 在路线图中显示 + field_login: 登录å + field_mail_notification: 邮件通知 + field_admin: 管ç†å‘˜ + field_last_login_on: 最åŽç™»å½• + field_language: 语言 + field_effective_date: 日期 + field_password: å¯†ç  + field_new_password: æ–°å¯†ç  + field_password_confirmation: 确认 + field_version: 版本 + field_type: 类型 + field_host: 主机 + field_port: ç«¯å£ + field_account: å¸å· + field_base_dn: Base DN + field_attr_login: 登录å属性 + field_attr_firstname: å字属性 + field_attr_lastname: å§“æ°å±žæ€§ + field_attr_mail: 邮件属性 + field_onthefly: 峿—¶ç”¨æˆ·ç”Ÿæˆ + field_start_date: 开始日期 + field_done_ratio: "% 完æˆ" + field_auth_source: è®¤è¯æ¨¡å¼ + field_hide_mail: éšè—æˆ‘çš„é‚®ä»¶åœ°å€ + field_comments: 注释 + field_url: URL + field_start_page: 起始页 + field_subproject: å­é¡¹ç›® + field_hours: å°æ—¶ + field_activity: 活动 + field_spent_on: 日期 + field_identifier: 标识 + field_is_filter: 作为过滤æ¡ä»¶ + field_issue_to: 相关问题 + field_delay: 延期 + field_assignable: é—®é¢˜å¯æŒ‡æ´¾ç»™æ­¤è§’色 + field_redirect_existing_links: é‡å®šå‘到现有链接 + field_estimated_hours: 预期时间 + field_column_names: 列 + field_time_entries: 工时 + field_time_zone: 时区 + field_searchable: å¯ç”¨ä½œæœç´¢æ¡ä»¶ + field_default_value: 默认值 + field_comments_sorting: 显示注释 + field_parent_title: ä¸Šçº§é¡µé¢ + field_editable: å¯ç¼–辑 + field_watcher: 跟踪者 + field_identity_url: OpenID URL + field_content: 内容 + field_group_by: æ ¹æ®æ­¤æ¡ä»¶åˆ†ç»„ + field_sharing: 共享 + field_parent_issue: 父任务 + field_member_of_group: 用户组的æˆå‘˜ + field_assigned_to_role: 角色的æˆå‘˜ + field_text: 文本字段 + field_visible: å¯è§çš„ + + setting_app_title: åº”ç”¨ç¨‹åºæ ‡é¢˜ + setting_app_subtitle: 应用程åºå­æ ‡é¢˜ + setting_welcome_text: 欢迎文字 + setting_default_language: 默认语言 + setting_login_required: è¦æ±‚è®¤è¯ + setting_self_registration: å…许自注册 + setting_attachment_max_size: 附件大å°é™åˆ¶ + setting_issues_export_limit: 问题导出æ¡ç›®çš„é™åˆ¶ + setting_mail_from: 邮件å‘ä»¶äººåœ°å€ + setting_bcc_recipients: ä½¿ç”¨å¯†ä»¶æŠ„é€ (bcc) + setting_plain_text_mail: 纯文本(无HTML) + setting_host_name: 主机åç§° + setting_text_formatting: æ–‡æœ¬æ ¼å¼ + setting_wiki_compression: 压缩WikiåŽ†å²æ–‡æ¡£ + setting_feeds_limit: RSS Feedå†…å®¹æ¡æ•°é™åˆ¶ + setting_default_projects_public: 新建项目默认为公开项目 + setting_autofetch_changesets: 自动获å–程åºå˜æ›´ + setting_sys_api_enabled: å¯ç”¨ç”¨äºŽç‰ˆæœ¬åº“管ç†çš„Web Service + setting_commit_ref_keywords: 用于引用问题的关键字 + setting_commit_fix_keywords: 用于解决问题的关键字 + setting_autologin: 自动登录 + setting_date_format: æ—¥æœŸæ ¼å¼ + setting_time_format: æ—¶é—´æ ¼å¼ + setting_cross_project_issue_relations: å…许ä¸åŒé¡¹ç›®ä¹‹é—´çš„é—®é¢˜å…³è” + setting_issue_list_default_columns: 问题列表中显示的默认列 + setting_emails_header: 邮件头 + setting_emails_footer: 邮件签å + setting_protocol: åè®® + setting_per_page_options: æ¯é¡µæ˜¾ç¤ºæ¡ç›®ä¸ªæ•°çš„设置 + setting_user_format: ç”¨æˆ·æ˜¾ç¤ºæ ¼å¼ + setting_activity_days_default: 在项目活动中显示的天数 + setting_display_subprojects_issues: 在项目页é¢ä¸Šé»˜è®¤æ˜¾ç¤ºå­é¡¹ç›®çš„问题 + setting_enabled_scm: å¯ç”¨ SCM + setting_mail_handler_body_delimiters: åœ¨è¿™äº›è¡Œä¹‹åŽæˆªæ–­é‚®ä»¶ + setting_mail_handler_api_enabled: å¯ç”¨ç”¨äºŽæŽ¥æ”¶é‚®ä»¶çš„æœåŠ¡ + setting_mail_handler_api_key: API key + setting_sequential_project_identifiers: 顺åºäº§ç”Ÿé¡¹ç›®æ ‡è¯† + setting_gravatar_enabled: 使用Gravatarç”¨æˆ·å¤´åƒ + setting_gravatar_default: 默认的Gravatarå¤´åƒ + setting_diff_max_lines_displayed: 查看差别页é¢ä¸Šæ˜¾ç¤ºçš„æœ€å¤§è¡Œæ•° + setting_file_max_size_displayed: å…许直接显示的最大文本文件 + setting_repository_log_display_limit: åœ¨æ–‡ä»¶å˜æ›´è®°å½•页é¢ä¸Šæ˜¾ç¤ºçš„æœ€å¤§ä¿®è®¢ç‰ˆæœ¬æ•°é‡ + setting_openid: å…许使用OpenID登录和注册 + setting_password_min_length: 最短密ç é•¿åº¦ + setting_new_project_user_role_id: éžç®¡ç†å‘˜ç”¨æˆ·æ–°å»ºé¡¹ç›®æ—¶å°†è¢«èµ‹äºˆçš„(在该项目中的)角色 + setting_default_projects_modules: 新建项目默认å¯ç”¨çš„æ¨¡å— + setting_issue_done_ratio: 计算问题完æˆåº¦ï¼š + setting_issue_done_ratio_issue_field: 使用问题(的完æˆåº¦ï¼‰å±žæ€§ + setting_issue_done_ratio_issue_status: ä½¿ç”¨é—®é¢˜çŠ¶æ€ + setting_start_of_week: 日历开始于 + setting_rest_api_enabled: å¯ç”¨REST web service + setting_cache_formatted_text: 缓存格å¼åŒ–文字 + setting_default_notification_option: 默认æé†’选项 + setting_commit_logtime_enabled: 激活时间日志 + setting_commit_logtime_activity_id: 记录的活动 + setting_gantt_items_limit: 在甘特图上显示的最大记录数 + + permission_add_project: 新建项目 + permission_add_subprojects: 新建å­é¡¹ç›® + permission_edit_project: 编辑项目 + permission_select_project_modules: é€‰æ‹©é¡¹ç›®æ¨¡å— + permission_manage_members: ç®¡ç†æˆå‘˜ + permission_manage_project_activities: 管ç†é¡¹ç›®æ´»åЍ + permission_manage_versions: 管ç†ç‰ˆæœ¬ + permission_manage_categories: 管ç†é—®é¢˜ç±»åˆ« + permission_view_issues: 查看问题 + 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_delete_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_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: åˆ é™¤è‡ªå·±çš„å¸–å­ + permission_export_wiki_pages: 导出 wiki é¡µé¢ + permission_manage_subtasks: 管ç†å­ä»»åŠ¡ + + project_module_issue_tracking: 问题跟踪 + project_module_time_tracking: 时间跟踪 + project_module_news: æ–°é—» + project_module_documents: 文档 + project_module_files: 文件 + project_module_wiki: Wiki + project_module_repository: 版本库 + project_module_boards: 讨论区 + project_module_calendar: 日历 + project_module_gantt: 甘特图 + + label_user: 用户 + label_user_plural: 用户 + label_user_new: 新建用户 + label_user_anonymous: 匿å用户 + label_project: 项目 + label_project_new: 新建项目 + label_project_plural: 项目 + label_x_projects: + zero: 无项目 + one: 1 个项目 + other: "%{count} 个项目" + label_project_all: 所有的项目 + label_project_latest: 最近的项目 + label_issue: 问题 + label_issue_new: 新建问题 + label_issue_plural: 问题 + label_issue_view_all: 查看所有问题 + label_issues_by: "按 %{value} 分组显示问题" + label_issue_added: 问题已添加 + label_issue_updated: 问题已更新 + label_document: 文档 + label_document_new: 新建文档 + label_document_plural: 文档 + label_document_added: 文档已添加 + label_role: 角色 + label_role_plural: 角色 + label_role_new: 新建角色 + label_role_and_permissions: 角色和æƒé™ + label_member: æˆå‘˜ + label_member_new: 新建æˆå‘˜ + label_member_plural: æˆå‘˜ + label_tracker: 跟踪标签 + label_tracker_plural: 跟踪标签 + label_tracker_new: 新建跟踪标签 + label_workflow: 工作æµç¨‹ + label_issue_status: é—®é¢˜çŠ¶æ€ + label_issue_status_plural: é—®é¢˜çŠ¶æ€ + label_issue_status_new: æ–°å»ºé—®é¢˜çŠ¶æ€ + label_issue_category: 问题类别 + label_issue_category_plural: 问题类别 + label_issue_category_new: 新建问题类别 + label_custom_field: 自定义属性 + label_custom_field_plural: 自定义属性 + label_custom_field_new: 新建自定义属性 + label_enumerations: 枚举值 + label_enumeration_new: 新建枚举值 + label_information: ä¿¡æ¯ + label_information_plural: ä¿¡æ¯ + label_please_login: 请登录 + label_register: 注册 + label_login_with_open_id_option: 或使用OpenID登录 + label_password_lost: å¿˜è®°å¯†ç  + label_home: 主页 + label_my_page: æˆ‘çš„å·¥ä½œå° + label_my_account: 我的å¸å· + label_my_projects: 我的项目 + label_my_page_block: æˆ‘çš„å·¥ä½œå°æ¨¡å— + label_administration: ç®¡ç† + label_login: 登录 + label_logout: 退出 + label_help: 帮助 + label_reported_issues: 已报告的问题 + label_assigned_to_me_issues: 指派给我的问题 + label_last_login: 最åŽç™»å½• + label_registered_on: 注册于 + label_activity: 活动 + label_overall_activity: 活动概览 + label_user_activity: "%{value} 的活动" + label_new: 新建 + label_logged_as: 登录为 + label_environment: 环境 + label_authentication: è®¤è¯ + label_auth_source: è®¤è¯æ¨¡å¼ + label_auth_source_new: æ–°å»ºè®¤è¯æ¨¡å¼ + label_auth_source_plural: è®¤è¯æ¨¡å¼ + label_subproject_plural: å­é¡¹ç›® + label_subproject_new: 新建å­é¡¹ç›® + label_and_its_subprojects: "%{value} åŠå…¶å­é¡¹ç›®" + label_min_max_length: æœ€å° - 最大 长度 + label_list: 列表 + label_date: 日期 + label_integer: æ•´æ•° + label_float: 浮点数 + label_boolean: 布尔值 + label_string: 字符串 + label_text: 文本 + label_attribute: 属性 + label_attribute_plural: 属性 + 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_settings: é…ç½® + label_overview: 概述 + label_version: 版本 + label_version_new: 新建版本 + label_version_plural: 版本 + label_close_versions: 关闭已完æˆçš„版本 + 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_x_open_issues_abbr_on_total: + zero: 0 打开 / %{total} + one: 1 打开 / %{total} + other: "%{count} 打开 / %{total}" + label_x_open_issues_abbr: + zero: 0 打开 + one: 1 打开 + other: "%{count} 打开" + label_x_closed_issues_abbr: + zero: 0 已关闭 + one: 1 已关闭 + other: "%{count} 已关闭" + 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: 甘特图 + label_internal: 内部 + label_last_changes: "最近的 %{count} æ¬¡å˜æ›´" + label_change_view_all: æŸ¥çœ‹æ‰€æœ‰å˜æ›´ + label_personalize_page: 个性化定制本页 + label_comment: 评论 + label_comment_plural: 评论 + label_x_comments: + zero: 无评论 + one: 1 æ¡è¯„论 + other: "%{count} æ¡è¯„论" + label_comment_add: 添加评论 + label_comment_added: 评论已添加 + label_comment_delete: 删除评论 + label_query: 自定义查询 + label_query_plural: 自定义查询 + label_query_new: 新建查询 + label_filter_add: 增加过滤器 + label_filter_plural: 过滤器 + label_equals: 等于 + label_not_equals: ä¸ç­‰äºŽ + label_in_less_than: 剩余天数å°äºŽ + label_in_more_than: 剩余天数大于 + label_greater_or_equal: '>=' + label_less_or_equal: '<=' + label_in: 剩余天数 + label_today: 今天 + label_all_time: 全部时间 + label_yesterday: 昨天 + label_this_week: 本周 + label_last_week: 上周 + label_last_n_days: "æœ€åŽ %{count} 天" + label_this_month: 本月 + label_last_month: 上月 + label_this_year: 今年 + label_date_range: 日期范围 + label_less_than_ago: 之å‰å¤©æ•°å°‘于 + label_more_than_ago: 之å‰å¤©æ•°å¤§äºŽ + label_ago: 之å‰å¤©æ•° + label_contains: åŒ…å« + label_not_contains: ä¸åŒ…å« + label_day_plural: 天 + label_repository: 版本库 + label_repository_plural: 版本库 + label_browse: æµè§ˆ + label_branch: 分支 + label_tag: 标签 + label_revision: 修订 + label_revision_plural: 修订 + label_revision_id: 修订 %{value} + label_associated_revisions: 相关修订版本 + label_added: 已添加 + label_modified: 已修改 + label_copied: å·²å¤åˆ¶ + label_renamed: å·²é‡å‘½å + label_deleted: 已删除 + label_latest_revision: 最近的修订版本 + label_latest_revision_plural: 最近的修订版本 + label_view_revisions: 查看修订 + label_view_all_revisions: 查看所有修订 + label_max_size: 最大尺寸 + label_sort_highest: 置顶 + label_sort_higher: 上移 + label_sort_lower: 下移 + label_sort_lowest: 置底 + label_roadmap: 路线图 + label_roadmap_due_in: "截止日期到 %{value}" + label_roadmap_overdue: "%{value} 延期" + label_roadmap_no_issues: 该版本没有问题 + label_search: æœç´¢ + label_result_plural: 结果 + label_all_words: 所有å•è¯ + label_wiki: Wiki + label_wiki_edit: Wiki 编辑 + label_wiki_edit_plural: Wiki 编辑记录 + label_wiki_page: Wiki é¡µé¢ + label_wiki_page_plural: Wiki é¡µé¢ + label_index_by_title: 按标题索引 + label_index_by_date: 按日期索引 + label_current_version: 当å‰ç‰ˆæœ¬ + label_preview: 预览 + label_feed_plural: Feeds + label_changes_details: æ‰€æœ‰å˜æ›´çš„详情 + label_issue_tracking: 问题跟踪 + label_spent_time: 耗时 + label_overall_spent_time: 总体耗时 + label_f_hour: "%{value} å°æ—¶" + label_f_hour_plural: "%{value} å°æ—¶" + label_time_tracking: 时间跟踪 + label_change_plural: å˜æ›´ + label_statistics: 统计 + label_commits_per_month: æ¯æœˆæäº¤æ¬¡æ•° + label_commits_per_author: æ¯ç”¨æˆ·æäº¤æ¬¡æ•° + label_view_diff: 查看差别 + label_diff_inline: 直列 + label_diff_side_by_side: 并排 + label_options: 选项 + label_copy_workflow_from: 从以下选项å¤åˆ¶å·¥ä½œæµç¨‹ + label_permissions_report: æƒé™æŠ¥è¡¨ + label_watched_issues: 跟踪的问题 + label_related_issues: 相关的问题 + label_applied_status: 应用åŽçš„çŠ¶æ€ + label_loading: 载入中... + label_relation_new: æ–°å»ºå…³è” + label_relation_delete: åˆ é™¤å…³è” + label_relates_to: å…³è”到 + label_duplicates: é‡å¤ + label_duplicated_by: 与其é‡å¤ + label_blocks: 阻挡 + label_blocked_by: 被阻挡 + label_precedes: 优先于 + label_follows: è·ŸéšäºŽ + label_end_to_start: 结æŸ-开始 + label_end_to_end: 结æŸ-ç»“æŸ + label_start_to_start: 开始-开始 + label_start_to_end: 开始-ç»“æŸ + label_stay_logged_in: ä¿æŒç™»å½•çŠ¶æ€ + label_disabled: ç¦ç”¨ + label_show_completed_versions: 显示已完æˆçš„版本 + label_me: 我 + label_board: 讨论区 + label_board_new: 新建讨论区 + label_board_plural: 讨论区 + label_board_locked: é”定 + label_board_sticky: 置顶 + label_topic_plural: 主题 + label_message_plural: å¸–å­ + label_message_last: æœ€æ–°çš„å¸–å­ + label_message_new: æ–°è´´ + label_message_posted: å‘帖æˆåŠŸ + label_reply_plural: å›žå¤ + label_send_information: 给用户å‘é€å¸å·ä¿¡æ¯ + label_year: å¹´ + label_month: 月 + label_week: 周 + label_date_from: 从 + label_date_to: 到 + label_language_based: æ ¹æ®ç”¨æˆ·çš„语言 + label_sort_by: "æ ¹æ® %{value} 排åº" + label_send_test_email: å‘逿µ‹è¯•邮件 + label_feeds_access_key: RSSå­˜å–é”® + label_missing_feeds_access_key: 缺少RSSå­˜å–é”® + label_feeds_access_key_created_on: "RSSå­˜å–键是在 %{value} 之å‰å»ºç«‹çš„" + label_module_plural: æ¨¡å— + label_added_time_by: "ç”± %{author} 在 %{age} 之剿·»åŠ " + label_updated_time: " 更新于 %{value} 之å‰" + label_updated_time_by: "ç”± %{author} 更新于 %{age} 之å‰" + label_jump_to_a_project: 选择一个项目... + label_file_plural: 文件 + label_changeset_plural: å˜æ›´ + label_default_columns: 默认列 + label_no_change_option: (ä¸å˜) + label_bulk_edit_selected_issues: 批é‡ä¿®æ”¹é€‰ä¸­çš„问题 + label_theme: 主题 + label_default: 默认 + label_search_titles_only: 仅在标题中æœç´¢ + label_user_mail_option_all: "æ”¶å–æˆ‘的项目的所有通知" + label_user_mail_option_selected: "æ”¶å–选中项目的所有通知..." + label_user_mail_option_none: "䏿”¶å–任何通知" + label_user_mail_option_only_my_events: "åªæ”¶å–我跟踪或å‚与的项目的通知" + label_user_mail_option_only_assigned: "åªæ”¶å–分é…给我的" + label_user_mail_option_only_owner: åªæ”¶å–由我创建的 + label_user_mail_no_self_notified: "ä¸è¦å‘é€å¯¹æˆ‘自己æäº¤çš„修改的通知" + label_registration_activation_by_email: é€šè¿‡é‚®ä»¶è®¤è¯æ¿€æ´»å¸å· + label_registration_manual_activation: 手动激活å¸å· + label_registration_automatic_activation: 自动激活å¸å· + label_display_per_page: "æ¯é¡µæ˜¾ç¤ºï¼š%{value}" + label_age: æäº¤æ—¶é—´ + label_change_properties: 修改属性 + label_general: 一般 + label_more: 更多 + label_scm: SCM + label_plugins: æ’ä»¶ + label_ldap_authentication: LDAP è®¤è¯ + label_downloads_abbr: D/L + label_optional_description: å¯é€‰çš„æè¿° + label_add_another_file: 添加其它文件 + label_preferences: 首选项 + label_chronological_order: æŒ‰æ—¶é—´é¡ºåº + label_reverse_chronological_order: 按时间顺åºï¼ˆå€’åºï¼‰ + label_planning: 计划 + label_incoming_emails: 接收邮件 + label_generate_key: 生æˆä¸€ä¸ªkey + label_issue_watchers: 跟踪者 + label_example: 示例 + label_display: 显示 + label_sort: æŽ’åº + label_ascending: å‡åº + label_descending: é™åº + label_date_from_to: 从 %{start} 到 %{end} + label_wiki_content_added: Wiki 页é¢å·²æ·»åŠ  + label_wiki_content_updated: Wiki 页é¢å·²æ›´æ–° + label_group: 组 + label_group_plural: 组 + label_group_new: 新建组 + label_time_entry_plural: 耗时 + label_version_sharing_none: ä¸å…±äº« + label_version_sharing_descendants: 与å­é¡¹ç›®å…±äº« + label_version_sharing_hierarchy: 与项目继承层次共享 + label_version_sharing_tree: 与项目树共享 + label_version_sharing_system: 与所有项目共享 + label_update_issue_done_ratios: 更新问题的完æˆåº¦ + label_copy_source: æº + label_copy_target: 目标 + label_copy_same_as_target: 与目标一致 + label_display_used_statuses_only: åªæ˜¾ç¤ºè¢«æ­¤è·Ÿè¸ªæ ‡ç­¾ä½¿ç”¨çš„çŠ¶æ€ + label_api_access_key: API访问键 + label_missing_api_access_key: 缺少API访问键 + label_api_access_key_created_on: API访问键是在 %{value} 之å‰å»ºç«‹çš„ + label_profile: 简介 + label_subtask_plural: å­ä»»åŠ¡ + label_project_copy_notifications: å¤åˆ¶é¡¹ç›®æ—¶å‘é€é‚®ä»¶é€šçŸ¥ + label_principal_search: "æœç´¢ç”¨æˆ·æˆ–组:" + label_user_search: "æœç´¢ç”¨æˆ·ï¼š" + + button_login: 登录 + button_submit: æäº¤ + button_save: ä¿å­˜ + button_check_all: 全选 + button_uncheck_all: 清除 + button_delete: 删除 + button_create: 创建 + button_create_and_continue: 创建并继续 + button_test: 测试 + button_edit: 编辑 + button_edit_associated_wikipage: "编辑相关wiki页é¢: %{page_title}" + button_add: 新增 + button_change: 修改 + button_apply: 应用 + button_clear: 清除 + button_lock: é”定 + button_unlock: è§£é” + button_download: 下载 + button_list: 列表 + button_view: 查看 + button_move: 移动 + button_move_and_follow: 移动并转到新问题 + button_back: 返回 + button_cancel: å–æ¶ˆ + button_activate: 激活 + button_sort: æŽ’åº + button_log_time: 登记工时 + button_rollback: æ¢å¤åˆ°è¿™ä¸ªç‰ˆæœ¬ + button_watch: 跟踪 + button_unwatch: å–æ¶ˆè·Ÿè¸ª + button_reply: å›žå¤ + button_archive: 存档 + button_unarchive: å–æ¶ˆå­˜æ¡£ + button_reset: é‡ç½® + button_rename: é‡å‘½å/é‡å®šå‘ + button_change_password: ä¿®æ”¹å¯†ç  + button_copy: å¤åˆ¶ + button_copy_and_follow: å¤åˆ¶å¹¶è½¬åˆ°æ–°é—®é¢˜ + button_annotate: 追溯 + button_update: æ›´æ–° + button_configure: é…ç½® + button_quote: 引用 + button_duplicate: 副本 + button_show: 显示 + + status_active: 活动的 + status_registered: 已注册 + status_locked: å·²é”定 + + version_status_open: 打开 + version_status_locked: é”定 + version_status_closed: 关闭 + + field_active: 活动 + + text_select_mail_notifications: 选择需è¦å‘é€é‚®ä»¶é€šçŸ¥çš„动作 + text_regexp_info: 例如:^[A-Z0-9]+$ + text_min_max_length_info: 0 表示没有é™åˆ¶ + text_project_destroy_confirmation: 您确信è¦åˆ é™¤è¿™ä¸ªé¡¹ç›®ä»¥åŠæ‰€æœ‰ç›¸å…³çš„æ•°æ®å—? + text_subprojects_destroy_warning: "以下å­é¡¹ç›®ä¹Ÿå°†è¢«åŒæ—¶åˆ é™¤ï¼š%{value}" + text_workflow_edit: 选择角色和跟踪标签æ¥ç¼–辑工作æµç¨‹ + text_are_you_sure: 您确定? + text_journal_changed: "%{label} 从 %{old} å˜æ›´ä¸º %{new}" + text_journal_set_to: "%{label} 被设置为 %{value}" + text_journal_deleted: "%{label} 已删除 (%{old})" + text_journal_added: "%{label} %{value} 已添加" + text_tip_issue_begin_day: 今天开始的任务 + text_tip_issue_end_day: 今天结æŸçš„任务 + text_tip_issue_begin_end_day: 今天开始并结æŸçš„任务 + text_caracters_maximum: "最多 %{count} 个字符。" + text_caracters_minimum: "è‡³å°‘éœ€è¦ %{count} 个字符。" + text_length_between: "长度必须在 %{min} 到 %{max} 个字符之间。" + text_tracker_no_workflow: 此跟踪标签未定义工作æµç¨‹ + text_unallowed_characters: éžæ³•字符 + text_comma_separated: å¯ä»¥ä½¿ç”¨å¤šä¸ªå€¼ï¼ˆç”¨é€—å·,分开)。 + text_line_separated: å¯ä»¥ä½¿ç”¨å¤šä¸ªå€¼ï¼ˆæ¯è¡Œä¸€ä¸ªå€¼ï¼‰ã€‚ + text_issues_ref_in_commit_messages: 在æäº¤ä¿¡æ¯ä¸­å¼•用和解决问题 + text_issue_added: "问题 %{id} 已由 %{author} æäº¤ã€‚" + text_issue_updated: "问题 %{id} 已由 %{author} 更新。" + text_wiki_destroy_confirmation: 您确定è¦åˆ é™¤è¿™ä¸ª wiki åŠå…¶æ‰€æœ‰å†…容å—? + text_issue_category_destroy_question: "有一些问题(%{count} ä¸ªï¼‰å±žäºŽæ­¤ç±»åˆ«ã€‚æ‚¨æƒ³è¿›è¡Œå“ªç§æ“作?" + text_issue_category_destroy_assignments: 删除问题的所属类别(问题å˜ä¸ºæ— ç±»åˆ«ï¼‰ + text_issue_category_reassign_to: 为问题选择其它类别 + text_user_mail_option: "对于没有选中的项目,您将åªä¼šæ”¶åˆ°æ‚¨è·Ÿè¸ªæˆ–å‚与的项目的通知(比如说,您是问题的报告者, 或被指派解决此问题)。" + text_no_configuration_data: "角色ã€è·Ÿè¸ªæ ‡ç­¾ã€é—®é¢˜çжæ€å’Œå·¥ä½œæµç¨‹è¿˜æ²¡æœ‰è®¾ç½®ã€‚\n强烈建议您先载入默认设置,然åŽåœ¨æ­¤åŸºç¡€ä¸Šè¿›è¡Œä¿®æ”¹ã€‚" + text_load_default_configuration: 载入默认设置 + text_status_changed_by_changeset: "å·²åº”ç”¨åˆ°å˜æ›´åˆ—表 %{value}." + text_time_logged_by_changeset: "已应用到修订版本 %{value}." + text_issues_destroy_confirmation: '您确定è¦åˆ é™¤é€‰ä¸­çš„问题å—?' + text_select_project_modules: '请选择此项目å¯ä»¥ä½¿ç”¨çš„æ¨¡å—:' + text_default_administrator_account_changed: 默认的管ç†å‘˜å¸å·å·²æ”¹å˜ + text_file_repository_writable: 附件路径å¯å†™ + text_plugin_assets_writable: æ’件的附件路径å¯å†™ + text_rmagick_available: RMagick å¯ç”¨ï¼ˆå¯é€‰çš„) + text_destroy_time_entries_question: 您è¦åˆ é™¤çš„问题已ç»ä¸ŠæŠ¥äº† %{hours} å°æ—¶çš„工作é‡ã€‚æ‚¨æƒ³è¿›è¡Œé‚£ç§æ“作? + text_destroy_time_entries: åˆ é™¤ä¸ŠæŠ¥çš„å·¥ä½œé‡ + text_assign_time_entries_to_project: å°†å·²ä¸ŠæŠ¥çš„å·¥ä½œé‡æäº¤åˆ°é¡¹ç›®ä¸­ + text_reassign_time_entries: 'å°†å·²ä¸ŠæŠ¥çš„å·¥ä½œé‡æŒ‡å®šåˆ°æ­¤é—®é¢˜ï¼š' + text_user_wrote: "%{value} 写到:" + text_enumeration_destroy_question: "%{count} 个对象被关è”到了这个枚举值。" + text_enumeration_category_reassign_to: '将它们关è”到新的枚举值:' + text_email_delivery_not_configured: "邮件傿•°å°šæœªé…置,因此邮件通知功能已被ç¦ç”¨ã€‚\n请在config/configuration.yml中é…置您的SMTPæœåŠ¡å™¨ä¿¡æ¯å¹¶é‡æ–°å¯åŠ¨ä»¥ä½¿å…¶ç”Ÿæ•ˆã€‚" + text_repository_usernames_mapping: "选择或更新与版本库中的用户å对应的Redmine用户。\n版本库中与Redmine中的åŒå用户将被自动对应。" + text_diff_truncated: '... å·®åˆ«å†…å®¹è¶…è¿‡äº†å¯æ˜¾ç¤ºçš„æœ€å¤§è¡Œæ•°å¹¶å·²è¢«æˆªæ–­' + text_custom_field_possible_values_info: 'æ¯é¡¹æ•°å€¼ä¸€è¡Œ' + text_wiki_page_destroy_question: æ­¤é¡µé¢æœ‰ %{descendants} 个å­é¡µé¢å’Œä¸‹çº§é¡µé¢ã€‚æ‚¨æƒ³è¿›è¡Œé‚£ç§æ“作? + text_wiki_page_nullify_children: å°†å­é¡µé¢ä¿ç•™ä¸ºæ ¹é¡µé¢ + text_wiki_page_destroy_children: 删除å­é¡µé¢åŠå…¶æ‰€æœ‰ä¸‹çº§é¡µé¢ + text_wiki_page_reassign_children: å°†å­é¡µé¢çš„上级页é¢è®¾ç½®ä¸º + text_own_membership_delete_confirmation: 你正在删除你现有的æŸäº›æˆ–全部æƒé™ï¼Œå¦‚果这样åšäº†ä½ å¯èƒ½å°†ä¼šå†ä¹Ÿæ— æ³•编辑该项目了。你确定è¦ç»§ç»­å—? + text_zoom_in: 放大 + text_zoom_out: ç¼©å° + + default_role_manager: 管ç†äººå‘˜ + default_role_developer: å¼€å‘人员 + default_role_reporter: 报告人员 + default_tracker_bug: 错误 + default_tracker_feature: 功能 + default_tracker_support: æ”¯æŒ + default_issue_status_new: 新建 + default_issue_status_in_progress: 进行中 + default_issue_status_resolved: 已解决 + default_issue_status_feedback: å馈 + default_issue_status_closed: 已关闭 + default_issue_status_rejected: å·²æ‹’ç» + default_doc_category_user: 用户文档 + default_doc_category_tech: 技术文档 + default_priority_low: 低 + default_priority_normal: 普通 + default_priority_high: 高 + default_priority_urgent: 紧急 + default_priority_immediate: 立刻 + default_activity_design: 设计 + default_activity_development: å¼€å‘ + + enumeration_issue_priorities: 问题优先级 + enumeration_doc_categories: 文档类别 + enumeration_activities: 活动(时间跟踪) + enumeration_system_activity: 系统活动 + + field_warn_on_leaving_unsaved: 当离开未ä¿å­˜å†…å®¹çš„é¡µé¢æ—¶ï¼Œæç¤ºæˆ‘ + text_warn_on_leaving_unsaved: 若离开当å‰é¡µé¢ï¼Œåˆ™è¯¥é¡µé¢å†…未ä¿å­˜çš„内容将丢失。 + label_my_queries: 我的自定义查询 + text_journal_changed_no_detail: "%{label} 已更新。" + label_news_comment_added: 添加到新闻的评论 + button_expand_all: 展开所有 + button_collapse_all: åˆæ‹¢æ‰€æœ‰ + label_additional_workflow_transitions_for_assignee: 当用户是问题的分é…对象时所å…许的问题状æ€è½¬æ¢ + label_additional_workflow_transitions_for_author: 当用户是问题作者时所å…许的问题状æ€è½¬æ¢ + label_bulk_edit_selected_time_entries: 批é‡ä¿®æ”¹é€‰å®šçš„æ—¶é—´æ¡ç›® + text_time_entries_destroy_confirmation: 是å¦ç¡®å®šè¦åˆ é™¤é€‰å®šçš„æ—¶é—´æ¡ç›®ï¼Ÿ + label_role_anonymous: Anonymous + label_role_non_member: Non member + label_issue_note_added: 问题备注已添加 + label_issue_status_updated: é—®é¢˜çŠ¶æ€æ›´æ–° + label_issue_priority_updated: 问题优先级更新 + label_issues_visibility_own: 创建或分é…给用户的问题 + field_issues_visibility: 问题å¯è§ + label_issues_visibility_all: 全部问题 + permission_set_own_issues_private: è®¾ç½®è‡ªå·±çš„é—®é¢˜ä¸ºå…¬å¼€æˆ–ç§æœ‰ + field_is_private: ç§æœ‰ + permission_set_issues_private: è®¾ç½®é—®é¢˜ä¸ºå…¬å¼€æˆ–ç§æœ‰ + label_issues_visibility_public: 全部éžç§æœ‰é—®é¢˜ + text_issues_destroy_descendants_confirmation: æ­¤æ“ä½œåŒæ—¶ä¼šåˆ é™¤ %{count} 个å­ä»»åŠ¡ã€‚ + + field_commit_logs_encoding: æäº¤æ³¨é‡Šçš„ç¼–ç  + field_scm_path_encoding: è·¯å¾„ç¼–ç  + text_scm_path_encoding_note: "默认: UTF-8" + field_path_to_repository: 库路径 + field_root_directory: 根目录 + field_cvs_module: CVS Module + field_cvsroot: CVSROOT + text_mercurial_repository_note: 本地库 (e.g. /hgrepo, c:\hgrepo) + text_scm_command: 命令 + text_scm_command_version: 版本 + label_git_report_last_commit: 报告最åŽä¸€æ¬¡æ–‡ä»¶/目录æäº¤ + text_scm_config: 您å¯ä»¥åœ¨config/configuration.yml中é…置您的SCM命令。 请在编辑åŽï¼Œé‡å¯Redmine应用。 + text_scm_command_not_available: Scm命令ä¸å¯ç”¨ã€‚ 请检查管ç†é¢æ¿çš„é…置。 + text_git_repository_note: 库中无内容。(e.g. /gitrepo, c:\gitrepo) + notice_issue_successful_create: 问题 %{id} 已创建。 + label_between: 介于 + setting_issue_group_assignment: å…许问题被分é…给组 + label_diff: diff + description_query_sort_criteria_direction: æŽ’åºæ–¹å¼ + description_project_scope: æœç´¢èŒƒå›´ + description_filter: 过滤器 + description_user_mail_notification: 邮件通知设置 + description_date_from: 输入开始日期 + description_message_content: ä¿¡æ¯å†…容 + description_available_columns: 备选列 + description_date_range_interval: æŒ‰å¼€å§‹æ—¥æœŸå’Œç»“æŸæ—¥æœŸé€‰æ‹©èŒƒå›´ + description_issue_category_reassign: 选择问题类别 + description_search: æœç´¢å­—段 + description_notes: 批注 + description_date_range_list: 从列表中选择范围 + description_choose_project: 项目 + description_date_to: è¾“å…¥ç»“æŸæ—¥æœŸ + description_query_sort_criteria_attribute: æŽ’åºæ–¹å¼ + description_wiki_subpages_reassign: é€‰æ‹©çˆ¶é¡µé¢ + description_selected_columns: 已选列 + label_parent_revision: 父修订 + label_child_revision: å­ä¿®è®¢ + error_scm_annotate_big_text_file: 输入文本内容超长,无法输入。 + setting_default_issue_start_date_to_creation_date: ä½¿ç”¨å½“å‰æ—¥æœŸä½œä¸ºæ–°é—®é¢˜çš„开始日期 + button_edit_section: 编辑此区域 + setting_repositories_encodings: é™„ä»¶å’Œç‰ˆæœ¬åº“ç¼–ç  + description_all_columns: 所有列 + button_export: 导出 + label_export_options: "%{export_format} 导出选项" + error_attachment_too_big: 该文件无法上传。超过文件大å°é™åˆ¶ (%{max_size}) + notice_failed_to_save_time_entries: "无法ä¿å­˜ä¸‹åˆ—所选å–çš„ %{total} 个项目中的 %{count} 工时: %{ids}。" + label_x_issues: + zero: 0 问题 + one: 1 问题 + other: "%{count} 问题" + label_repository_new: 新建版本库 + field_repository_is_default: 主版本库 + label_copy_attachments: å¤åˆ¶é™„ä»¶ + label_item_position: "%{position}/%{count}" + label_completed_versions: 已完æˆçš„版本 + text_project_identifier_info: ä»…å°å†™å­—æ¯ï¼ˆa-zï¼‰ã€æ•°å­—ã€ç ´æŠ˜å·ï¼ˆ-)和下划线(_)å¯ä»¥ä½¿ç”¨ã€‚
    一旦ä¿å­˜ï¼Œæ ‡è¯†æ— æ³•修改。 + field_multiple: 多é‡å–值 + setting_commit_cross_project_ref: å…许引用/ä¿®å¤æ‰€æœ‰å…¶ä»–项目的问题 + text_issue_conflict_resolution_add_notes: æ·»åŠ è¯´æ˜Žå¹¶å–æ¶ˆæˆ‘çš„å…¶ä»–å˜æ›´å¤„ç†ã€‚ + text_issue_conflict_resolution_overwrite: ç›´æŽ¥å¥—ç”¨æˆ‘çš„å˜æ›´ (先å‰çš„说明将被ä¿ç•™ï¼Œä½†æ˜¯æŸäº›å˜æ›´å†…容å¯èƒ½ä¼šè¢«è¦†ç›–) + notice_issue_update_conflict: 当您正在编辑这个问题的时候,它已ç»è¢«å…¶ä»–人抢先一步更新过了。 + text_issue_conflict_resolution_cancel: å–æ¶ˆæˆ‘æ‰€æœ‰çš„å˜æ›´å¹¶é‡æ–°åˆ·æ–°æ˜¾ç¤º %{link} 。 + permission_manage_related_issues: ç›¸å…³é—®é¢˜ç®¡ç† + field_auth_source_ldap_filter: LDAP 过滤器 + label_search_for_watchers: é€šè¿‡æŸ¥æ‰¾æ–¹å¼æ·»åŠ è·Ÿè¸ªè€… + notice_account_deleted: 您的账å·å·²è¢«æ°¸ä¹…删除(账å·å·²æ— æ³•æ¢å¤ï¼‰ã€‚ + setting_unsubscribe: å…许用户退订 + button_delete_my_account: åˆ é™¤æˆ‘çš„è´¦å· + text_account_destroy_confirmation: |- + 确定继续处ç†ï¼Ÿ + 您的账å·ä¸€æ—¦åˆ é™¤ï¼Œå°†æ— æ³•冿¬¡æ¿€æ´»ä½¿ç”¨ã€‚ + error_session_expired: 您的会è¯å·²è¿‡æœŸã€‚è¯·é‡æ–°ç™»é™†ã€‚ + text_session_expiration_settings: "警告: 更改这些设置将会使包括你在内的当å‰ä¼šè¯å¤±æ•ˆã€‚" + setting_session_lifetime: ä¼šè¯æœ€å¤§æœ‰æ•ˆæ—¶é—´ + setting_session_timeout: 会è¯é—²ç½®è¶…æ—¶ + label_session_expiration: 会è¯è¿‡æœŸ + permission_close_project: 关闭/é‡å¼€é¡¹ç›® + label_show_closed_projects: 查看已关闭的项目 + button_close: 关闭 + button_reopen: é‡å¼€ + project_status_active: 已激活 + project_status_closed: 已关闭 + project_status_archived: 已存档 + text_project_closed: 当å‰é¡¹ç›®å·²è¢«å…³é—­ã€‚当å‰é¡¹ç›®åªè¯»ã€‚ + notice_user_successful_create: 用户 %{id} 已创建。 + field_core_fields: 标准字段 + field_timeout: è¶…æ—¶ (ç§’) + setting_thumbnails_enabled: 显示附件略缩图 + setting_thumbnails_size: 略缩图尺寸 (åƒç´ ) + label_status_transitions: 状æ€è½¬æ¢ + label_fields_permissions: 字段æƒé™ + label_readonly: åªè¯» + label_required: å¿…å¡« + text_repository_identifier_info: ä»…å°å†™å­—æ¯ï¼ˆa-zï¼‰ã€æ•°å­—ã€ç ´æŠ˜å·ï¼ˆ-)和下划线(_)å¯ä»¥ä½¿ç”¨ã€‚
    一旦ä¿å­˜ï¼Œæ ‡è¯†æ— æ³•修改。 + field_board_parent: çˆ¶è®ºå› + label_attribute_of_project: 项目 %{name} + label_attribute_of_author: 作者 %{name} + label_attribute_of_assigned_to: 分é…ç»™ %{name} + label_attribute_of_fixed_version: 目标版本 %{name} + label_copy_subtasks: å¤åˆ¶å­ä»»åŠ¡ + label_copied_to: å¤åˆ¶åˆ° + label_copied_from: å¤åˆ¶äºŽ + label_any_issues_in_project: 项目内任æ„问题 + label_any_issues_not_in_project: 项目外任æ„问题 + field_private_notes: ç§æœ‰æ³¨è§£ + permission_view_private_notes: æŸ¥çœ‹ç§æœ‰æ³¨è§£ + permission_set_notes_private: è®¾ç½®ä¸ºç§æœ‰æ³¨è§£ + label_no_issues_in_project: 项目内无相关问题 + label_any: 全部 + label_last_n_weeks: 上 %{count} å‘¨å‰ + setting_cross_project_subtasks: 支æŒè·¨é¡¹ç›®å­ä»»åŠ¡ + label_cross_project_descendants: 与å­é¡¹ç›®å…±äº« + label_cross_project_tree: 与项目树共享 + label_cross_project_hierarchy: 与项目继承层次共享 + label_cross_project_system: 与所有项目共享 + button_hide: éšè— + setting_non_working_week_days: Non-working days + label_in_the_next_days: in the next + label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: åˆè®¡ diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/fe/fe616c1a4677a1989f6035ef3d43e4954fce98ea.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/fe/fe616c1a4677a1989f6035ef3d43e4954fce98ea.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,51 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class RoutingEnumerationsTest < ActionController::IntegrationTest + def test_enumerations + assert_routing( + { :method => 'get', :path => "/enumerations" }, + { :controller => 'enumerations', :action => 'index' } + ) + assert_routing( + { :method => 'get', :path => "/enumerations/new" }, + { :controller => 'enumerations', :action => 'new' } + ) + assert_routing( + { :method => 'post', :path => "/enumerations" }, + { :controller => 'enumerations', :action => 'create' } + ) + assert_routing( + { :method => 'get', :path => "/enumerations/2/edit" }, + { :controller => 'enumerations', :action => 'edit', :id => '2' } + ) + assert_routing( + { :method => 'put', :path => "/enumerations/2" }, + { :controller => 'enumerations', :action => 'update', :id => '2' } + ) + assert_routing( + { :method => 'delete', :path => "/enumerations/2" }, + { :controller => 'enumerations', :action => 'destroy', :id => '2' } + ) + assert_routing( + { :method => 'get', :path => "/enumerations/issue_priorities.xml" }, + { :controller => 'enumerations', :action => 'index', :type => 'issue_priorities', :format => 'xml' } + ) + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/ff/ff068acc0413b56f4b540c721950411f34fdcce5.svn-base --- a/.svn/pristine/ff/ff068acc0413b56f4b540c721950411f34fdcce5.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ -

    <%=l(:label_issue_new)%>

    - -<% labelled_tabular_form_for :issue, @issue, :url => {:controller => 'issues', :action => 'create', :project_id => @project}, - :html => {:multipart => true, :id => 'issue-form', :class => 'tabular new-issue-form'} do |f| %> - <%= error_messages_for 'issue' %> -
    - <%= render :partial => 'issues/form', :locals => {:f => f} %> -
    - <%= submit_tag l(:button_create) %> - <%= submit_tag l(:button_create_and_continue), :name => 'continue' %> - <%= link_to_remote l(:label_preview), - { :url => preview_issue_path(:project_id => @project), - :method => 'post', - :update => 'preview', - :with => "Form.serialize('issue-form')", - :complete => "Element.scrollTo('preview')" - }, :accesskey => accesskey(:preview) %> - - <%= javascript_tag "Form.Element.focus('issue_subject');" %> -<% end %> - -
    - -<% content_for :header_tags do %> - <%= stylesheet_link_tag 'scm' %> - <%= robot_exclusion_tag %> -<% end %> diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/ff/ff1c47ab3a4a85cdc878d408e530d4ba3db0f578.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ff/ff1c47ab3a4a85cdc878d408e530d4ba3db0f578.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,149 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class Redmine::ApiTest::AttachmentsTest < Redmine::ApiTest::Base + fixtures :projects, :trackers, :issue_statuses, :issues, + :enumerations, :users, :issue_categories, + :projects_trackers, + :roles, + :member_roles, + :members, + :enabled_modules, + :attachments + + def setup + Setting.rest_api_enabled = '1' + set_fixtures_attachments_directory + end + + def teardown + set_tmp_attachments_directory + end + + test "GET /attachments/:id.xml should return the attachment" do + get '/attachments/7.xml', {}, credentials('jsmith') + assert_response :success + assert_equal 'application/xml', @response.content_type + assert_tag :tag => 'attachment', + :child => { + :tag => 'id', + :content => '7', + :sibling => { + :tag => 'filename', + :content => 'archive.zip', + :sibling => { + :tag => 'content_url', + :content => 'http://www.example.com/attachments/download/7/archive.zip' + } + } + } + end + + test "GET /attachments/:id.xml should deny access without credentials" do + get '/attachments/7.xml' + assert_response 401 + set_tmp_attachments_directory + end + + test "GET /attachments/download/:id/:filename should return the attachment content" do + get '/attachments/download/7/archive.zip', {}, credentials('jsmith') + assert_response :success + assert_equal 'application/octet-stream', @response.content_type + set_tmp_attachments_directory + end + + test "GET /attachments/download/:id/:filename should deny access without credentials" do + get '/attachments/download/7/archive.zip' + assert_response 302 + set_tmp_attachments_directory + end + + test "POST /uploads.xml should return the token" do + set_tmp_attachments_directory + assert_difference 'Attachment.count' do + post '/uploads.xml', 'File content', {"CONTENT_TYPE" => 'application/octet-stream'}.merge(credentials('jsmith')) + assert_response :created + assert_equal 'application/xml', response.content_type + end + + xml = Hash.from_xml(response.body) + assert_kind_of Hash, xml['upload'] + token = xml['upload']['token'] + assert_not_nil token + + attachment = Attachment.first(:order => 'id DESC') + assert_equal token, attachment.token + assert_nil attachment.container + assert_equal 2, attachment.author_id + assert_equal 'File content'.size, attachment.filesize + assert attachment.content_type.blank? + assert attachment.filename.present? + assert_match /\d+_[0-9a-z]+/, attachment.diskfile + assert File.exist?(attachment.diskfile) + assert_equal 'File content', File.read(attachment.diskfile) + end + + test "POST /uploads.json should return the token" do + set_tmp_attachments_directory + assert_difference 'Attachment.count' do + post '/uploads.json', 'File content', {"CONTENT_TYPE" => 'application/octet-stream'}.merge(credentials('jsmith')) + assert_response :created + assert_equal 'application/json', response.content_type + end + + json = ActiveSupport::JSON.decode(response.body) + assert_kind_of Hash, json['upload'] + token = json['upload']['token'] + assert_not_nil token + + attachment = Attachment.first(:order => 'id DESC') + assert_equal token, attachment.token + end + + test "POST /uploads.xml should accept :filename param as the attachment filename" do + set_tmp_attachments_directory + assert_difference 'Attachment.count' do + post '/uploads.xml?filename=test.txt', 'File content', {"CONTENT_TYPE" => 'application/octet-stream'}.merge(credentials('jsmith')) + assert_response :created + end + + attachment = Attachment.order('id DESC').first + assert_equal 'test.txt', attachment.filename + assert_match /_test\.txt$/, attachment.diskfile + end + + test "POST /uploads.xml should not accept other content types" do + set_tmp_attachments_directory + assert_no_difference 'Attachment.count' do + post '/uploads.xml', 'PNG DATA', {"CONTENT_TYPE" => 'image/png'}.merge(credentials('jsmith')) + assert_response 406 + end + end + + test "POST /uploads.xml should return errors if file is too big" do + set_tmp_attachments_directory + with_settings :attachment_max_size => 1 do + assert_no_difference 'Attachment.count' do + post '/uploads.xml', ('x' * 2048), {"CONTENT_TYPE" => 'application/octet-stream'}.merge(credentials('jsmith')) + assert_response 422 + assert_tag 'error', :content => /exceeds the maximum allowed file size/ + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/ff/ff62b4bdd41363f9cdc9bf0c3cbe24d3f90f2713.svn-base --- a/.svn/pristine/ff/ff62b4bdd41363f9cdc9bf0c3cbe24d3f90f2713.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,18 +0,0 @@ -
    - - -<% line_num = 1 %> -<% syntax_highlight(filename, Redmine::CodesetUtil.to_utf8_by_setting(content)).each_line do |line| %> - - - - - <% line_num += 1 %> -<% end %> - -
    - <%= line_num %> - -
    <%= line %>
    -
    -
    diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/ff/ff7c7a78022ec834d13d1e81c3b7d935dfcd48bf.svn-base --- a/.svn/pristine/ff/ff7c7a78022ec834d13d1e81c3b7d935dfcd48bf.svn-base Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,25 +0,0 @@ -module CodeRay -module Encoders - - load :token_kind_filter - - # A simple Filter that removes all tokens of the :comment kind. - # - # Alias: +remove_comments+ - # - # Usage: - # CodeRay.scan('print # foo', :ruby).comment_filter.text - # #-> "print " - # - # See also: TokenKindFilter, LinesOfCode - class CommentFilter < TokenKindFilter - - register_for :comment_filter - - DEFAULT_OPTIONS = superclass::DEFAULT_OPTIONS.merge \ - :exclude => [:comment, :docstring] - - end - -end -end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/ff/ffca1425f1aa4e1091d7e6036ce643ffe45832ab.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ff/ffca1425f1aa4e1091d7e6036ce643ffe45832ab.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,19 @@ +class CreateProjectsTrackers < ActiveRecord::Migration + def self.up + create_table :projects_trackers, :id => false do |t| + t.column :project_id, :integer, :default => 0, :null => false + t.column :tracker_id, :integer, :default => 0, :null => false + end + add_index :projects_trackers, :project_id, :name => :projects_trackers_project_id + + # Associates all trackers to all projects (as it was before) + tracker_ids = Tracker.all.collect(&:id) + Project.all.each do |project| + project.tracker_ids = tracker_ids + end + end + + def self.down + drop_table :projects_trackers + end +end diff -r 0a574315af3e -r 4f746d8966dd .svn/pristine/ff/ffce98a27e5a6dc82e77f6a0e106be536bad8666.svn-base --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.svn/pristine/ff/ffce98a27e5a6dc82e77f6a0e106be536bad8666.svn-base Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,568 @@ +# Copyright (c) 2005 Rick Olson +# +# 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. + +module ActiveRecord #:nodoc: + module Acts #:nodoc: + # Specify this act if you want to save a copy of the row in a versioned table. This assumes there is a + # versioned table ready and that your model has a version field. This works with optimistic locking if the lock_version + # column is present as well. + # + # The class for the versioned model is derived the first time it is seen. Therefore, if you change your database schema you have to restart + # your container for the changes to be reflected. In development mode this usually means restarting WEBrick. + # + # class Page < ActiveRecord::Base + # # assumes pages_versions table + # acts_as_versioned + # end + # + # Example: + # + # page = Page.create(:title => 'hello world!') + # page.version # => 1 + # + # page.title = 'hello world' + # page.save + # page.version # => 2 + # page.versions.size # => 2 + # + # page.revert_to(1) # using version number + # page.title # => 'hello world!' + # + # page.revert_to(page.versions.last) # using versioned instance + # page.title # => 'hello world' + # + # page.versions.earliest # efficient query to find the first version + # page.versions.latest # efficient query to find the most recently created version + # + # + # Simple Queries to page between versions + # + # page.versions.before(version) + # page.versions.after(version) + # + # Access the previous/next versions from the versioned model itself + # + # version = page.versions.latest + # version.previous # go back one version + # version.next # go forward one version + # + # See ActiveRecord::Acts::Versioned::ClassMethods#acts_as_versioned for configuration options + module Versioned + CALLBACKS = [:set_new_version, :save_version_on_create, :save_version?, :clear_altered_attributes] + def self.included(base) # :nodoc: + base.extend ClassMethods + end + + module ClassMethods + # == Configuration options + # + # * class_name - versioned model class name (default: PageVersion in the above example) + # * table_name - versioned model table name (default: page_versions in the above example) + # * foreign_key - foreign key used to relate the versioned model to the original model (default: page_id in the above example) + # * inheritance_column - name of the column to save the model's inheritance_column value for STI. (default: versioned_type) + # * version_column - name of the column in the model that keeps the version number (default: version) + # * sequence_name - name of the custom sequence to be used by the versioned model. + # * limit - number of revisions to keep, defaults to unlimited + # * if - symbol of method to check before saving a new version. If this method returns false, a new version is not saved. + # For finer control, pass either a Proc or modify Model#version_condition_met? + # + # acts_as_versioned :if => Proc.new { |auction| !auction.expired? } + # + # or... + # + # class Auction + # def version_condition_met? # totally bypasses the :if option + # !expired? + # end + # end + # + # * if_changed - Simple way of specifying attributes that are required to be changed before saving a model. This takes + # either a symbol or array of symbols. WARNING - This will attempt to overwrite any attribute setters you may have. + # Use this instead if you want to write your own attribute setters (and ignore if_changed): + # + # def name=(new_name) + # write_changed_attribute :name, new_name + # end + # + # * extend - Lets you specify a module to be mixed in both the original and versioned models. You can also just pass a block + # to create an anonymous mixin: + # + # class Auction + # acts_as_versioned do + # def started? + # !started_at.nil? + # end + # end + # end + # + # or... + # + # module AuctionExtension + # def started? + # !started_at.nil? + # end + # end + # class Auction + # acts_as_versioned :extend => AuctionExtension + # end + # + # Example code: + # + # @auction = Auction.find(1) + # @auction.started? + # @auction.versions.first.started? + # + # == Database Schema + # + # The model that you're versioning needs to have a 'version' attribute. The model is versioned + # into a table called #{model}_versions where the model name is singlular. The _versions table should + # contain all the fields you want versioned, the same version column, and a #{model}_id foreign key field. + # + # A lock_version field is also accepted if your model uses Optimistic Locking. If your table uses Single Table inheritance, + # then that field is reflected in the versioned model as 'versioned_type' by default. + # + # Acts_as_versioned comes prepared with the ActiveRecord::Acts::Versioned::ActMethods::ClassMethods#create_versioned_table + # method, perfect for a migration. It will also create the version column if the main model does not already have it. + # + # class AddVersions < ActiveRecord::Migration + # def self.up + # # create_versioned_table takes the same options hash + # # that create_table does + # Post.create_versioned_table + # end + # + # def self.down + # Post.drop_versioned_table + # end + # end + # + # == Changing What Fields Are Versioned + # + # By default, acts_as_versioned will version all but these fields: + # + # [self.primary_key, inheritance_column, 'version', 'lock_version', versioned_inheritance_column] + # + # You can add or change those by modifying #non_versioned_columns. Note that this takes strings and not symbols. + # + # class Post < ActiveRecord::Base + # acts_as_versioned + # self.non_versioned_columns << 'comments_count' + # end + # + def acts_as_versioned(options = {}, &extension) + # don't allow multiple calls + return if self.included_modules.include?(ActiveRecord::Acts::Versioned::ActMethods) + + send :include, ActiveRecord::Acts::Versioned::ActMethods + + cattr_accessor :versioned_class_name, :versioned_foreign_key, :versioned_table_name, :versioned_inheritance_column, + :version_column, :max_version_limit, :track_altered_attributes, :version_condition, :version_sequence_name, :non_versioned_columns, + :version_association_options + + # legacy + alias_method :non_versioned_fields, :non_versioned_columns + alias_method :non_versioned_fields=, :non_versioned_columns= + + class << self + alias_method :non_versioned_fields, :non_versioned_columns + alias_method :non_versioned_fields=, :non_versioned_columns= + end + + send :attr_accessor, :altered_attributes + + self.versioned_class_name = options[:class_name] || "Version" + self.versioned_foreign_key = options[:foreign_key] || self.to_s.foreign_key + self.versioned_table_name = options[:table_name] || "#{table_name_prefix}#{base_class.name.demodulize.underscore}_versions#{table_name_suffix}" + self.versioned_inheritance_column = options[:inheritance_column] || "versioned_#{inheritance_column}" + self.version_column = options[:version_column] || 'version' + self.version_sequence_name = options[:sequence_name] + self.max_version_limit = options[:limit].to_i + self.version_condition = options[:if] || true + self.non_versioned_columns = [self.primary_key, inheritance_column, 'version', 'lock_version', versioned_inheritance_column] + self.version_association_options = { + :class_name => "#{self.to_s}::#{versioned_class_name}", + :foreign_key => versioned_foreign_key, + :dependent => :delete_all + }.merge(options[:association_options] || {}) + + if block_given? + extension_module_name = "#{versioned_class_name}Extension" + silence_warnings do + self.const_set(extension_module_name, Module.new(&extension)) + end + + options[:extend] = self.const_get(extension_module_name) + end + + class_eval do + has_many :versions, version_association_options do + # finds earliest version of this record + def earliest + @earliest ||= order('version').first + end + + # find latest version of this record + def latest + @latest ||= order('version desc').first + end + end + before_save :set_new_version + after_create :save_version_on_create + after_update :save_version + after_save :clear_old_versions + after_save :clear_altered_attributes + + unless options[:if_changed].nil? + self.track_altered_attributes = true + options[:if_changed] = [options[:if_changed]] unless options[:if_changed].is_a?(Array) + options[:if_changed].each do |attr_name| + define_method("#{attr_name}=") do |value| + write_changed_attribute attr_name, value + end + end + end + + include options[:extend] if options[:extend].is_a?(Module) + end + + # create the dynamic versioned model + const_set(versioned_class_name, Class.new(ActiveRecord::Base)).class_eval do + def self.reloadable? ; false ; end + # find first version before the given version + def self.before(version) + order('version desc'). + where("#{original_class.versioned_foreign_key} = ? and version < ?", version.send(original_class.versioned_foreign_key), version.version). + first + end + + # find first version after the given version. + def self.after(version) + order('version'). + where("#{original_class.versioned_foreign_key} = ? and version > ?", version.send(original_class.versioned_foreign_key), version.version). + first + end + + def previous + self.class.before(self) + end + + def next + self.class.after(self) + end + + def versions_count + page.version + end + end + + versioned_class.cattr_accessor :original_class + versioned_class.original_class = self + versioned_class.table_name = versioned_table_name + versioned_class.belongs_to self.to_s.demodulize.underscore.to_sym, + :class_name => "::#{self.to_s}", + :foreign_key => versioned_foreign_key + versioned_class.send :include, options[:extend] if options[:extend].is_a?(Module) + versioned_class.set_sequence_name version_sequence_name if version_sequence_name + end + end + + module ActMethods + def self.included(base) # :nodoc: + base.extend ClassMethods + end + + # Finds a specific version of this record + def find_version(version = nil) + self.class.find_version(id, version) + end + + # Saves a version of the model if applicable + def save_version + save_version_on_create if save_version? + end + + # Saves a version of the model in the versioned table. This is called in the after_save callback by default + def save_version_on_create + rev = self.class.versioned_class.new + self.clone_versioned_model(self, rev) + rev.version = send(self.class.version_column) + rev.send("#{self.class.versioned_foreign_key}=", self.id) + rev.save + end + + # Clears old revisions if a limit is set with the :limit option in acts_as_versioned. + # Override this method to set your own criteria for clearing old versions. + def clear_old_versions + return if self.class.max_version_limit == 0 + excess_baggage = send(self.class.version_column).to_i - self.class.max_version_limit + if excess_baggage > 0 + sql = "DELETE FROM #{self.class.versioned_table_name} WHERE version <= #{excess_baggage} AND #{self.class.versioned_foreign_key} = #{self.id}" + self.class.versioned_class.connection.execute sql + end + end + + def versions_count + version + end + + # Reverts a model to a given version. Takes either a version number or an instance of the versioned model + def revert_to(version) + if version.is_a?(self.class.versioned_class) + return false unless version.send(self.class.versioned_foreign_key) == self.id and !version.new_record? + else + return false unless version = versions.find_by_version(version) + end + self.clone_versioned_model(version, self) + self.send("#{self.class.version_column}=", version.version) + true + end + + # Reverts a model to a given version and saves the model. + # Takes either a version number or an instance of the versioned model + def revert_to!(version) + revert_to(version) ? save_without_revision : false + end + + # Temporarily turns off Optimistic Locking while saving. Used when reverting so that a new version is not created. + def save_without_revision + save_without_revision! + true + rescue + false + end + + def save_without_revision! + without_locking do + without_revision do + save! + end + end + end + + # Returns an array of attribute keys that are versioned. See non_versioned_columns + def versioned_attributes + self.attributes.keys.select { |k| !self.class.non_versioned_columns.include?(k) } + end + + # If called with no parameters, gets whether the current model has changed and needs to be versioned. + # If called with a single parameter, gets whether the parameter has changed. + def changed?(attr_name = nil) + attr_name.nil? ? + (!self.class.track_altered_attributes || (altered_attributes && altered_attributes.length > 0)) : + (altered_attributes && altered_attributes.include?(attr_name.to_s)) + end + + # keep old dirty? method + alias_method :dirty?, :changed? + + # Clones a model. Used when saving a new version or reverting a model's version. + def clone_versioned_model(orig_model, new_model) + self.versioned_attributes.each do |key| + new_model.send("#{key}=", orig_model.send(key)) if orig_model.respond_to?(key) + end + + if self.class.columns_hash.include?(self.class.inheritance_column) + if orig_model.is_a?(self.class.versioned_class) + new_model[new_model.class.inheritance_column] = orig_model[self.class.versioned_inheritance_column] + elsif new_model.is_a?(self.class.versioned_class) + new_model[self.class.versioned_inheritance_column] = orig_model[orig_model.class.inheritance_column] + end + end + end + + # Checks whether a new version shall be saved or not. Calls version_condition_met? and changed?. + def save_version? + version_condition_met? && changed? + end + + # Checks condition set in the :if option to check whether a revision should be created or not. Override this for + # custom version condition checking. + def version_condition_met? + case + when version_condition.is_a?(Symbol) + send(version_condition) + when version_condition.respond_to?(:call) && (version_condition.arity == 1 || version_condition.arity == -1) + version_condition.call(self) + else + version_condition + end + end + + # Executes the block with the versioning callbacks disabled. + # + # @foo.without_revision do + # @foo.save + # end + # + def without_revision(&block) + self.class.without_revision(&block) + end + + # Turns off optimistic locking for the duration of the block + # + # @foo.without_locking do + # @foo.save + # end + # + def without_locking(&block) + self.class.without_locking(&block) + end + + def empty_callback() end #:nodoc: + + protected + # sets the new version before saving, unless you're using optimistic locking. In that case, let it take care of the version. + def set_new_version + self.send("#{self.class.version_column}=", self.next_version) if new_record? || (!locking_enabled? && save_version?) + end + + # Gets the next available version for the current record, or 1 for a new record + def next_version + return 1 if new_record? + (versions.calculate(:max, :version) || 0) + 1 + end + + # clears current changed attributes. Called after save. + def clear_altered_attributes + self.altered_attributes = [] + end + + def write_changed_attribute(attr_name, attr_value) + # Convert to db type for comparison. Avoids failing Float<=>String comparisons. + attr_value_for_db = self.class.columns_hash[attr_name.to_s].type_cast(attr_value) + (self.altered_attributes ||= []) << attr_name.to_s unless self.changed?(attr_name) || self.send(attr_name) == attr_value_for_db + write_attribute(attr_name, attr_value_for_db) + end + + module ClassMethods + # Finds a specific version of a specific row of this model + def find_version(id, version = nil) + return find(id) unless version + + conditions = ["#{versioned_foreign_key} = ? AND version = ?", id, version] + options = { :conditions => conditions, :limit => 1 } + + if result = find_versions(id, options).first + result + else + raise RecordNotFound, "Couldn't find #{name} with ID=#{id} and VERSION=#{version}" + end + end + + # Finds versions of a specific model. Takes an options hash like find + def find_versions(id, options = {}) + versioned_class.all({ + :conditions => ["#{versioned_foreign_key} = ?", id], + :order => 'version' }.merge(options)) + end + + # Returns an array of columns that are versioned. See non_versioned_columns + def versioned_columns + self.columns.select { |c| !non_versioned_columns.include?(c.name) } + end + + # Returns an instance of the dynamic versioned model + def versioned_class + const_get versioned_class_name + end + + # Rake migration task to create the versioned table using options passed to acts_as_versioned + def create_versioned_table(create_table_options = {}) + # create version column in main table if it does not exist + if !self.content_columns.find { |c| %w(version lock_version).include? c.name } + self.connection.add_column table_name, :version, :integer + end + + self.connection.create_table(versioned_table_name, create_table_options) do |t| + t.column versioned_foreign_key, :integer + t.column :version, :integer + end + + updated_col = nil + self.versioned_columns.each do |col| + updated_col = col if !updated_col && %(updated_at updated_on).include?(col.name) + self.connection.add_column versioned_table_name, col.name, col.type, + :limit => col.limit, + :default => col.default, + :scale => col.scale, + :precision => col.precision + end + + if type_col = self.columns_hash[inheritance_column] + self.connection.add_column versioned_table_name, versioned_inheritance_column, type_col.type, + :limit => type_col.limit, + :default => type_col.default, + :scale => type_col.scale, + :precision => type_col.precision + end + + if updated_col.nil? + self.connection.add_column versioned_table_name, :updated_at, :timestamp + end + end + + # Rake migration task to drop the versioned table + def drop_versioned_table + self.connection.drop_table versioned_table_name + end + + # Executes the block with the versioning callbacks disabled. + # + # Foo.without_revision do + # @foo.save + # end + # + def without_revision(&block) + class_eval do + CALLBACKS.each do |attr_name| + alias_method "orig_#{attr_name}".to_sym, attr_name + alias_method attr_name, :empty_callback + end + end + block.call + ensure + class_eval do + CALLBACKS.each do |attr_name| + alias_method attr_name, "orig_#{attr_name}".to_sym + end + end + end + + # Turns off optimistic locking for the duration of the block + # + # Foo.without_locking do + # @foo.save + # end + # + def without_locking(&block) + current = ActiveRecord::Base.lock_optimistically + ActiveRecord::Base.lock_optimistically = false if current + result = block.call + ActiveRecord::Base.lock_optimistically = true if current + result + end + end + end + end + end +end + +ActiveRecord::Base.send :include, ActiveRecord::Acts::Versioned \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd .svn/wc.db Binary file .svn/wc.db has changed diff -r 0a574315af3e -r 4f746d8966dd Gemfile --- a/Gemfile Fri Jun 14 09:07:32 2013 +0100 +++ b/Gemfile Fri Jun 14 09:28:30 2013 +0100 @@ -1,9 +1,9 @@ -source 'http://rubygems.org' +source 'https://rubygems.org' gem "rails", "3.2.13" gem "jquery-rails", "~> 2.0.2" gem "i18n", "~> 0.6.0" -gem "coderay", "~> 1.0.6" +gem "coderay", "~> 1.0.9" gem "fastercsv", "~> 1.5.0", :platforms => [:mri_18, :mingw_18, :jruby] gem "builder", "3.0.0" @@ -28,43 +28,47 @@ end end -# Database gems -platforms :mri, :mingw do - group :postgresql do - gem "pg", ">= 0.11.0" - end - - group :sqlite do - gem "sqlite3" - end +platforms :jruby do + # jruby-openssl is bundled with JRuby 1.7.0 + gem "jruby-openssl" if Object.const_defined?(:JRUBY_VERSION) && JRUBY_VERSION < '1.7.0' + gem "activerecord-jdbc-adapter", "1.2.5" end -platforms :mri_18, :mingw_18 do - group :mysql do - gem "mysql", "~> 2.8.1" +# Include database gems for the adapters found in the database +# configuration file +require 'erb' +require 'yaml' +database_file = File.join(File.dirname(__FILE__), "config/database.yml") +if File.exist?(database_file) + database_config = YAML::load(ERB.new(IO.read(database_file)).result) + adapters = database_config.values.map {|c| c['adapter']}.compact.uniq + if adapters.any? + adapters.each do |adapter| + case adapter + when 'mysql2' + gem "mysql2", "~> 0.3.11", :platforms => [:mri, :mingw] + gem "activerecord-jdbcmysql-adapter", :platforms => :jruby + when 'mysql' + gem "mysql", "~> 2.8.1", :platforms => [:mri, :mingw] + gem "activerecord-jdbcmysql-adapter", :platforms => :jruby + when /postgresql/ + gem "pg", ">= 0.11.0", :platforms => [:mri, :mingw] + gem "activerecord-jdbcpostgresql-adapter", :platforms => :jruby + when /sqlite3/ + gem "sqlite3", :platforms => [:mri, :mingw] + gem "activerecord-jdbcsqlite3-adapter", :platforms => :jruby + when /sqlserver/ + gem "tiny_tds", "~> 0.5.1", :platforms => [:mri, :mingw] + gem "activerecord-sqlserver-adapter", :platforms => [:mri, :mingw] + else + warn("Unknown database adapter `#{adapter}` found in config/database.yml, use Gemfile.local to load your own database gems") + end + end + else + warn("No adapter found in config/database.yml, please configure it first") end -end - -platforms :mri_19, :mingw_19 do - group :mysql do - gem "mysql2", "~> 0.3.11" - end -end - -platforms :jruby do - gem "jruby-openssl" - - group :mysql do - gem "activerecord-jdbcmysql-adapter" - end - - group :postgresql do - gem "activerecord-jdbcpostgresql-adapter" - end - - group :sqlite do - gem "activerecord-jdbcsqlite3-adapter" - end +else + warn("Please configure your config/database.yml first") end group :development do @@ -73,13 +77,10 @@ end group :test do - gem "shoulda", "~> 2.11" - # Shoulda does not work nice on Ruby 1.9.3 and JRuby 1.7. - # It seems to need test-unit explicitely. - platforms = [:mri_19] - platforms << :jruby if defined?(JRUBY_VERSION) && JRUBY_VERSION >= "1.7" - gem "test-unit", :platforms => platforms + gem "shoulda", "~> 3.3.2" gem "mocha", "~> 0.13.3" + gem 'capybara', '~> 2.0.0' + gem 'nokogiri', '< 1.6.0' end local_gemfile = File.join(File.dirname(__FILE__), "Gemfile.local") diff -r 0a574315af3e -r 4f746d8966dd app/controllers/account_controller.rb --- a/app/controllers/account_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/account_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -25,7 +25,9 @@ # Login request and validation def login if request.get? - logout_user + if User.current.logged? + redirect_to home_url + end else authenticate_user end @@ -36,15 +38,20 @@ # Log out current user and redirect to welcome page def logout - logout_user - redirect_to home_url + if User.current.anonymous? + redirect_to home_url + elsif request.post? + logout_user + redirect_to home_url + end + # display the logout form end # Lets user choose a new password def lost_password - redirect_to(home_url) && return unless Setting.lost_password? + (redirect_to(home_url); return) unless Setting.lost_password? if params[:token] - @token = Token.find_by_action_and_value("recovery", params[:token].to_s) + @token = Token.find_token("recovery", params[:token].to_s) if @token.nil? || @token.expired? redirect_to home_url return @@ -92,11 +99,11 @@ # User self-registration def register - redirect_to(home_url) && return unless Setting.self_registration? || session[:auth_source_registration] + (redirect_to(home_url); return) unless Setting.self_registration? || session[:auth_source_registration] if request.get? session[:auth_source_registration] = nil - @user = User.new(:language => Setting.default_language) + @user = User.new(:language => current_language.to_s) @ssamr_user_details = SsamrUserDetail.new @@ -116,7 +123,7 @@ session[:auth_source_registration] = nil self.logged_user = @user flash[:notice] = l(:notice_account_activated) - redirect_to :controller => 'my', :action => 'account' + redirect_to my_account_path end else @user.login = params[:user][:login] @@ -145,11 +152,11 @@ # Token based account activation def activate - redirect_to(home_url) && return unless Setting.self_registration? && params[:token] - token = Token.find_by_action_and_value('register', params[:token]) - redirect_to(home_url) && return unless token and !token.expired? + (redirect_to(home_url); return) unless Setting.self_registration? && params[:token].present? + token = Token.find_token('register', params[:token].to_s) + (redirect_to(home_url); return) unless token and !token.expired? user = token.user - redirect_to(home_url) && return unless user.registered? + (redirect_to(home_url); return) unless user.registered? user.activate if user.save token.destroy @@ -182,12 +189,14 @@ end def open_id_authenticate(openid_url) - authenticate_with_open_id(openid_url, :required => [:nickname, :fullname, :email], :return_to => signin_url, :method => :post) do |result, identity_url, registration| + back_url = signin_url(:autologin => params[:autologin]) + + authenticate_with_open_id(openid_url, :required => [:nickname, :fullname, :email], :return_to => back_url, :method => :post) 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? + (redirect_to(home_url); return) unless Setting.self_registration? # Create on the fly user.login = registration['nickname'] unless registration['nickname'].nil? @@ -231,12 +240,11 @@ set_autologin_cookie(user) end call_hook(:controller_account_success_authentication_after, {:user => user }) - redirect_back_or_default :controller => 'my', :action => 'page' + redirect_back_or_default my_page_path end def set_autologin_cookie(user) token = Token.create(:user => user, :action => 'autologin') - cookie_name = Redmine::Configuration['autologin_cookie_name'] || 'autologin' cookie_options = { :value => token.value, :expires => 1.year.from_now, @@ -244,7 +252,7 @@ :secure => (Redmine::Configuration['autologin_cookie_secure'] ? true : false), :httponly => true } - cookies[cookie_name] = cookie_options + cookies[autologin_cookie_name] = cookie_options end # Onthefly creation failed, display the registration form to fill/fix attributes @@ -283,7 +291,7 @@ if user.save self.logged_user = user flash[:notice] = l(:notice_account_activated) - redirect_to :controller => 'my', :action => 'account' + redirect_to my_account_path else yield if block_given? end diff -r 0a574315af3e -r 4f746d8966dd app/controllers/activities_controller.rb --- a/app/controllers/activities_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/activities_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/controllers/admin_controller.rb --- a/app/controllers/admin_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/admin_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -23,19 +23,18 @@ before_filter :require_admin helper :sort - include SortHelper + include SortHelper def index @no_configuration_data = Redmine::DefaultData::Loader::no_data? end - + def projects @status = params[:status] || 1 - scope = Project.status(@status) + scope = Project.status(@status).order('lft') scope = scope.like(params[:name]) if params[:name].present? - - @projects = scope.all(:order => 'lft') + @projects = scope.all render :action => "projects", :layout => false if request.xhr? end @@ -55,7 +54,7 @@ flash[:error] = l(:error_can_t_load_default_data, e.message) end end - redirect_to :action => 'index' + redirect_to admin_path end def test_email @@ -69,7 +68,7 @@ flash[:error] = l(:notice_email_error, e.message) end ActionMailer::Base.raise_delivery_errors = raise_delivery_errors - redirect_to :controller => 'settings', :action => 'edit', :tab => 'notifications' + redirect_to settings_path(:tab => 'notifications') end def info diff -r 0a574315af3e -r 4f746d8966dd app/controllers/application_controller.rb --- a/app/controllers/application_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/application_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -22,6 +22,9 @@ class ApplicationController < ActionController::Base include Redmine::I18n + include Redmine::Pagination + include RoutesHelper + helper :routes class_attribute :accept_api_auth_actions class_attribute :accept_rss_auth_actions @@ -32,7 +35,7 @@ protect_from_forgery def handle_unverified_request super - cookies.delete(:autologin) + cookies.delete(autologin_cookie_name) end before_filter :session_expiration, :user_setup, :check_if_login_required, :set_localization @@ -124,10 +127,14 @@ user end + def autologin_cookie_name + Redmine::Configuration['autologin_cookie_name'].presence || 'autologin' + end + def try_to_autologin - if cookies[:autologin] && Setting.autologin? + if cookies[autologin_cookie_name] && Setting.autologin? # auto-login feature starts a new session - user = User.try_to_autologin(cookies[:autologin]) + user = User.try_to_autologin(cookies[autologin_cookie_name]) if user reset_session start_user_session(user) @@ -150,7 +157,7 @@ # Logs out current user def logout_user if User.current.logged? - cookies.delete :autologin + cookies.delete(autologin_cookie_name) Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin']) self.logged_user = nil end @@ -300,6 +307,16 @@ render_404 end + def find_attachments + if (attachments = params[:attachments]).present? + att = attachments.values.collect do |attachment| + Attachment.find_by_token( attachment[:token] ) if attachment[:token].present? + end + att.compact! + end + @attachments = att || [] + end + # make sure that the user is a member of the project (or admin) if project is private # used as a before_filter for actions that do not require any particular permission on the project def check_project_privacy diff -r 0a574315af3e -r 4f746d8966dd app/controllers/attachments_controller.rb --- a/app/controllers/attachments_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/attachments_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -91,15 +91,17 @@ @attachment = Attachment.new(:file => request.raw_post) @attachment.author = User.current @attachment.filename = params[:filename].presence || Redmine::Utils.random_hex(16) + saved = @attachment.save - if @attachment.save - respond_to do |format| - format.api { render :action => 'upload', :status => :created } - end - else - respond_to do |format| - format.api { render_validation_errors(@attachment) } - end + respond_to do |format| + format.js + format.api { + if saved + render :action => 'upload', :status => :created + else + render_validation_errors(@attachment) + end + } end end @@ -107,9 +109,17 @@ if @attachment.container.respond_to?(:init_journal) @attachment.container.init_journal(User.current) end - # Make sure association callbacks are called - @attachment.container.attachments.delete(@attachment) - redirect_to_referer_or project_path(@project) + if @attachment.container + # Make sure association callbacks are called + @attachment.container.attachments.delete(@attachment) + else + @attachment.destroy + end + + respond_to do |format| + format.html { redirect_to_referer_or project_path(@project) } + format.js + end end def toggle_active @@ -132,7 +142,12 @@ # Checks that the file exists and is readable def file_readable - @attachment.readable? ? true : render_404 + if @attachment.readable? + true + else + logger.error "Cannot send attachment, #{@attachment.diskfile} does not exist or is unreadable." + render_404 + end end def read_authorize diff -r 0a574315af3e -r 4f746d8966dd app/controllers/auth_sources_controller.rb --- a/app/controllers/auth_sources_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/auth_sources_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -20,57 +20,77 @@ menu_item :ldap_authentication before_filter :require_admin + before_filter :find_auth_source, :only => [:edit, :update, :test_connection, :destroy] def index - @auth_source_pages, @auth_sources = paginate AuthSource, :per_page => 10 + @auth_source_pages, @auth_sources = paginate AuthSource, :per_page => 25 end def new klass_name = params[:type] || 'AuthSourceLdap' @auth_source = AuthSource.new_subclass_instance(klass_name, params[:auth_source]) + render_404 unless @auth_source end def create @auth_source = AuthSource.new_subclass_instance(params[:type], params[:auth_source]) if @auth_source.save flash[:notice] = l(:notice_successful_create) - redirect_to :action => 'index' + redirect_to auth_sources_path else render :action => 'new' end end def edit - @auth_source = AuthSource.find(params[:id]) end def update - @auth_source = AuthSource.find(params[:id]) if @auth_source.update_attributes(params[:auth_source]) flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'index' + redirect_to auth_sources_path else render :action => 'edit' end end def test_connection - @auth_source = AuthSource.find(params[:id]) begin @auth_source.test_connection flash[:notice] = l(:notice_successful_connection) rescue Exception => e flash[:error] = l(:error_unable_to_connect, e.message) end - redirect_to :action => 'index' + redirect_to auth_sources_path end def destroy - @auth_source = AuthSource.find(params[:id]) - unless @auth_source.users.find(:first) + unless @auth_source.users.exists? @auth_source.destroy flash[:notice] = l(:notice_successful_delete) end - redirect_to :action => 'index' + redirect_to auth_sources_path + end + + def autocomplete_for_new_user + results = AuthSource.search(params[:term]) + + render :json => results.map {|result| { + 'value' => result[:login], + 'label' => "#{result[:login]} (#{result[:firstname]} #{result[:lastname]})", + 'login' => result[:login].to_s, + 'firstname' => result[:firstname].to_s, + 'lastname' => result[:lastname].to_s, + 'mail' => result[:mail].to_s, + 'auth_source_id' => result[:auth_source_id].to_s + }} + end + + private + + def find_auth_source + @auth_source = AuthSource.find(params[:id]) + rescue ActiveRecord::RecordNotFound + render_404 end end diff -r 0a574315af3e -r 4f746d8966dd app/controllers/auto_completes_controller.rb --- a/app/controllers/auto_completes_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/auto_completes_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -23,10 +23,10 @@ q = (params[:q] || params[:term]).to_s.strip if q.present? scope = (params[:scope] == "all" || @project.nil? ? Issue : @project.issues).visible - if q.match(/^\d+$/) - @issues << scope.find_by_id(q.to_i) + if q.match(/\A#?(\d+)\z/) + @issues << scope.find_by_id($1.to_i) end - @issues += scope.where("LOWER(#{Issue.table_name}.subject) LIKE ?", "%#{q.downcase}%").order("#{Issue.table_name}.id DESC").limit(10).all + @issues += scope.where("LOWER(#{Issue.table_name}.subject) LIKE LOWER(?)", "%#{q}%").order("#{Issue.table_name}.id DESC").limit(10).all @issues.compact! end render :layout => false diff -r 0a574315af3e -r 4f746d8966dd app/controllers/boards_controller.rb --- a/app/controllers/boards_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/boards_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -37,17 +37,17 @@ respond_to do |format| format.html { sort_init 'updated_on', 'desc' - sort_update 'created_on' => "#{Message.table_name}.created_on", + sort_update 'created_on' => "#{Message.table_name}.created_on", 'replies' => "#{Message.table_name}.replies_count", 'updated_on' => "COALESCE(last_replies_messages.created_on, #{Message.table_name}.created_on)" @topic_count = @board.topics.count - @topic_pages = Paginator.new self, @topic_count, per_page_option, params['page'] + @topic_pages = Paginator.new @topic_count, per_page_option, params['page'] @topics = @board.topics. reorder("#{Message.table_name}.sticky DESC"). includes(:last_reply). - limit(@topic_pages.items_per_page). - offset(@topic_pages.current.offset). + limit(@topic_pages.per_page). + offset(@topic_pages.offset). order(sort_clause). preload(:author, {:last_reply => :author}). all @@ -55,9 +55,11 @@ render :action => 'show', :layout => !request.xhr? } format.atom { - @messages = @board.messages.find :all, :order => 'created_on DESC', - :include => [:author, :board], - :limit => Setting.feeds_limit.to_i + @messages = @board.messages. + reorder('created_on DESC'). + includes(:author, :board). + limit(Setting.feeds_limit.to_i). + all render_feed(@messages, :title => "#{@project}: #{@board}") } end @@ -98,7 +100,7 @@ private def redirect_to_settings_in_projects - redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'boards' + redirect_to settings_project_path(@project, :tab => 'boards') end def find_board_if_available diff -r 0a574315af3e -r 4f746d8966dd app/controllers/calendars_controller.rb --- a/app/controllers/calendars_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/calendars_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/controllers/comments_controller.rb --- a/app/controllers/comments_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/comments_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -32,12 +32,12 @@ flash[:notice] = l(:label_comment_added) end - redirect_to :controller => 'news', :action => 'show', :id => @news + redirect_to news_path(@news) end def destroy @news.comments.find(params[:comment_id]).destroy - redirect_to :controller => 'news', :action => 'show', :id => @news + redirect_to news_path(@news) end private diff -r 0a574315af3e -r 4f746d8966dd app/controllers/context_menus_controller.rb --- a/app/controllers/context_menus_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/context_menus_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -21,6 +21,7 @@ def issues @issues = Issue.visible.all(:conditions => {:id => params[:ids]}, :include => :project) + (render_404; return) unless @issues.present? if (@issues.size == 1) @issue = @issues.first end @@ -74,6 +75,8 @@ def time_entries @time_entries = TimeEntry.all( :conditions => {:id => params[:ids]}, :include => :project) + (render_404; return) unless @time_entries.present? + @projects = @time_entries.collect(&:project).compact.uniq @project = @projects.first if @projects.size == 1 @activities = TimeEntryActivity.shared.active diff -r 0a574315af3e -r 4f746d8966dd app/controllers/custom_fields_controller.rb --- a/app/controllers/custom_fields_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/custom_fields_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -23,7 +23,7 @@ before_filter :find_custom_field, :only => [:edit, :update, :destroy] def index - @custom_fields_by_type = CustomField.find(:all).group_by {|f| f.class.name } + @custom_fields_by_type = CustomField.all.group_by {|f| f.class.name } @tab = params[:tab] || 'IssueCustomField' end @@ -31,10 +31,10 @@ end def create - if request.post? and @custom_field.save + if @custom_field.save flash[:notice] = l(:notice_successful_create) call_hook(:controller_custom_fields_new_after_save, :params => params, :custom_field => @custom_field) - redirect_to :action => 'index', :tab => @custom_field.class.name + redirect_to custom_fields_path(:tab => @custom_field.class.name) else render :action => 'new' end @@ -44,21 +44,22 @@ end def update - if request.put? and @custom_field.update_attributes(params[:custom_field]) + if @custom_field.update_attributes(params[:custom_field]) flash[:notice] = l(:notice_successful_update) call_hook(:controller_custom_fields_edit_after_save, :params => params, :custom_field => @custom_field) - redirect_to :action => 'index', :tab => @custom_field.class.name + redirect_to custom_fields_path(:tab => @custom_field.class.name) else render :action => 'edit' end end def destroy - @custom_field.destroy - redirect_to :action => 'index', :tab => @custom_field.class.name - rescue - flash[:error] = l(:error_can_not_delete_custom_field) - redirect_to :action => 'index' + begin + @custom_field.destroy + rescue + flash[:error] = l(:error_can_not_delete_custom_field) + end + redirect_to custom_fields_path(:tab => @custom_field.class.name) end private @@ -67,6 +68,8 @@ @custom_field = CustomField.new_subclass_instance(params[:type], params[:custom_field]) if @custom_field.nil? render_404 + else + @custom_field.default_value = nil end end diff -r 0a574315af3e -r 4f746d8966dd app/controllers/documents_controller.rb --- a/app/controllers/documents_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/documents_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -27,7 +27,7 @@ def index @sort_by = %w(category date title author).include?(params[:sort_by]) ? params[:sort_by] : 'category' - documents = @project.documents.find :all, :include => [:attachments, :category] + documents = @project.documents.includes(:attachments, :category).all case @sort_by when 'date' @grouped = documents.group_by {|d| d.updated_on.to_date } @@ -43,7 +43,7 @@ end def show - @attachments = @document.attachments.find(:all, :order => "created_on DESC") + @attachments = @document.attachments.all end def new @@ -58,7 +58,7 @@ if @document.save render_attachment_warning_if_needed(@document) flash[:notice] = l(:notice_successful_create) - redirect_to :action => 'index', :project_id => @project + redirect_to project_documents_path(@project) else render :action => 'new' end @@ -71,7 +71,7 @@ @document.safe_attributes = params[:document] if request.put? and @document.save flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'show', :id => @document + redirect_to document_path(@document) else render :action => 'edit' end @@ -79,7 +79,7 @@ def destroy @document.destroy if request.delete? - redirect_to :controller => 'documents', :action => 'index', :project_id => @project + redirect_to project_documents_path(@project) end def add_attachment @@ -89,6 +89,6 @@ if attachments.present? && attachments[:files].present? && Setting.notified_events.include?('document_added') Mailer.attachments_added(attachments[:files]).deliver end - redirect_to :action => 'show', :id => @document + redirect_to document_path(@document) end end diff -r 0a574315af3e -r 4f746d8966dd app/controllers/enumerations_controller.rb --- a/app/controllers/enumerations_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/enumerations_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -46,7 +46,7 @@ def create if request.post? && @enumeration.save flash[:notice] = l(:notice_successful_create) - redirect_to :action => 'index' + redirect_to enumerations_path else render :action => 'new' end @@ -58,7 +58,7 @@ def update if request.put? && @enumeration.update_attributes(params[:enumeration]) flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'index' + redirect_to enumerations_path else render :action => 'edit' end @@ -68,16 +68,16 @@ if !@enumeration.in_use? # No associated objects @enumeration.destroy - redirect_to :action => 'index' + redirect_to enumerations_path return elsif params[:reassign_to_id] if reassign_to = @enumeration.class.find_by_id(params[:reassign_to_id]) @enumeration.destroy(reassign_to) - redirect_to :action => 'index' + redirect_to enumerations_path return end end - @enumerations = @enumeration.class.all - [@enumeration] + @enumerations = @enumeration.class.system.all - [@enumeration] end private diff -r 0a574315af3e -r 4f746d8966dd app/controllers/files_controller.rb --- a/app/controllers/files_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/files_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -32,8 +32,8 @@ '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 + @containers = [ Project.includes(:attachments).reorder(sort_clause).find(@project.id)] + @containers += @project.versions.includes(:attachments).reorder(sort_clause).all.sort.reverse render :layout => !request.xhr? end diff -r 0a574315af3e -r 4f746d8966dd app/controllers/gantts_controller.rb --- a/app/controllers/gantts_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/gantts_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/controllers/groups_controller.rb --- a/app/controllers/groups_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/groups_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -84,7 +84,7 @@ @group.destroy respond_to do |format| - format.html { redirect_to(groups_url) } + format.html { redirect_to(groups_path) } format.api { render_api_ok } end end @@ -93,7 +93,7 @@ @users = User.find_all_by_id(params[:user_id] || params[:user_ids]) @group.users << @users if request.post? respond_to do |format| - format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'users' } + format.html { redirect_to edit_group_path(@group, :tab => 'users') } format.js format.api { render_api_ok } end @@ -102,22 +102,23 @@ def remove_user @group.users.delete(User.find(params[:user_id])) if request.delete? respond_to do |format| - format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'users' } + format.html { redirect_to edit_group_path(@group, :tab => 'users') } format.js format.api { render_api_ok } end end def autocomplete_for_user - @users = User.active.not_in_group(@group).like(params[:q]).all(:limit => 100) - render :layout => false + respond_to do |format| + format.js + end end def edit_membership @membership = Member.edit_membership(params[:membership_id], params[:membership], @group) @membership.save if request.post? respond_to do |format| - format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'memberships' } + format.html { redirect_to edit_group_path(@group, :tab => 'memberships') } format.js end end @@ -125,7 +126,7 @@ def destroy_membership Member.find(params[:membership_id]).destroy if request.post? respond_to do |format| - format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'memberships' } + format.html { redirect_to edit_group_path(@group, :tab => 'memberships') } format.js end end diff -r 0a574315af3e -r 4f746d8966dd app/controllers/issue_categories_controller.rb --- a/app/controllers/issue_categories_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/issue_categories_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,14 +26,14 @@ def index respond_to do |format| - format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project } + format.html { redirect_to_settings_in_projects } format.api { @categories = @project.issue_categories.all } end end def show respond_to do |format| - format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project } + format.html { redirect_to_settings_in_projects } format.api end end @@ -55,7 +55,7 @@ respond_to do |format| format.html do flash[:notice] = l(:notice_successful_create) - redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project + redirect_to_settings_in_projects end format.js format.api { render :action => 'show', :status => :created, :location => issue_category_path(@category) } @@ -78,7 +78,7 @@ respond_to do |format| format.html { flash[:notice] = l(:notice_successful_update) - redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project + redirect_to_settings_in_projects } format.api { render_api_ok } end @@ -99,7 +99,7 @@ end @category.destroy(reassign_to) respond_to do |format| - format.html { redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'categories' } + format.html { redirect_to_settings_in_projects } format.api { render_api_ok } end return @@ -107,7 +107,12 @@ @categories = @project.issue_categories - [@category] end -private + private + + def redirect_to_settings_in_projects + redirect_to settings_project_path(@project, :tab => 'categories') + end + # Wrap ApplicationController's find_model_object method to set # @category instead of just @issue_category def find_model_object diff -r 0a574315af3e -r 4f746d8966dd app/controllers/issue_relations_controller.rb --- a/app/controllers/issue_relations_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/issue_relations_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -48,9 +48,9 @@ saved = @relation.save respond_to do |format| - format.html { redirect_to :controller => 'issues', :action => 'show', :id => @issue } + format.html { redirect_to issue_path(@issue) } format.js { - @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? } + @relations = @issue.reload.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? } } format.api { if saved @@ -67,7 +67,7 @@ @relation.destroy respond_to do |format| - format.html { redirect_to issue_path } # TODO : does this really work since @issue is always nil? What is it useful to? + format.html { redirect_to issue_path(@relation.issue_from) } format.js format.api { render_api_ok } end diff -r 0a574315af3e -r 4f746d8966dd app/controllers/issue_statuses_controller.rb --- a/app/controllers/issue_statuses_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/issue_statuses_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -25,7 +25,7 @@ def index respond_to do |format| format.html { - @issue_status_pages, @issue_statuses = paginate :issue_statuses, :per_page => 25, :order => "position" + @issue_status_pages, @issue_statuses = paginate IssueStatus.sorted, :per_page => 25 render :action => "index", :layout => false if request.xhr? } format.api { @@ -42,7 +42,7 @@ @issue_status = IssueStatus.new(params[:issue_status]) if request.post? && @issue_status.save flash[:notice] = l(:notice_successful_create) - redirect_to :action => 'index' + redirect_to issue_statuses_path else render :action => 'new' end @@ -56,7 +56,7 @@ @issue_status = IssueStatus.find(params[:id]) if request.put? && @issue_status.update_attributes(params[:issue_status]) flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'index' + redirect_to issue_statuses_path else render :action => 'edit' end @@ -64,11 +64,11 @@ def destroy IssueStatus.find(params[:id]).destroy - redirect_to :action => 'index' + redirect_to issue_statuses_path rescue flash[:error] = l(:error_unable_delete_issue_status) - redirect_to :action => 'index' - end + redirect_to issue_statuses_path + end def update_issue_done_ratio if request.post? && IssueStatus.update_issue_done_ratios @@ -76,6 +76,6 @@ else flash[:error] = l(:error_issue_done_ratios_not_updated) end - redirect_to :action => 'index' + redirect_to issue_statuses_path end end diff -r 0a574315af3e -r 4f746d8966dd app/controllers/issues_controller.rb --- a/app/controllers/issues_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/issues_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -21,11 +21,11 @@ before_filter :find_issue, :only => [:show, :edit, :update] before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :destroy] - before_filter :find_project, :only => [:new, :create] + before_filter :find_project, :only => [:new, :create, :update_form] before_filter :authorize, :except => [:index] before_filter :find_optional_project, :only => [:index] before_filter :check_for_default_issue_status, :only => [:new, :create] - before_filter :build_new_issue_from_params, :only => [:new, :create] + before_filter :build_new_issue_from_params, :only => [:new, :create, :update_form] accept_rss_auth :index, :show accept_api_auth :index, :show, :create, :update, :destroy @@ -71,8 +71,8 @@ end @issue_count = @query.issue_count - @issue_pages = Paginator.new self, @issue_count, @limit, params['page'] - @offset ||= @issue_pages.current.offset + @issue_pages = Paginator.new @issue_count, @limit, params['page'] + @offset ||= @issue_pages.offset @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version], :order => sort_clause, :offset => @offset, @@ -85,8 +85,8 @@ Issue.load_visible_relations(@issues) if include_in_api_response?('relations') } format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") } - format.csv { send_data(issues_to_csv(@issues, @project, @query, params), :type => 'text/csv; header=present', :filename => 'export.csv') } - format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') } + format.csv { send_data(query_to_csv(@issues, @query, params), :type => 'text/csv; header=present', :filename => 'issues.csv') } + format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'issues.pdf') } end else respond_to do |format| @@ -132,7 +132,6 @@ def new respond_to do |format| format.html { render :action => 'new', :layout => !request.xhr? } - format.js { render :partial => 'update_form' } end end @@ -154,8 +153,12 @@ format.html { render_attachment_warning_if_needed(@issue) flash[:notice] = l(:notice_issue_successful_create, :id => view_context.link_to("##{@issue.id}", issue_path(@issue), :title => @issue.subject)) - redirect_to(params[:continue] ? { :action => 'new', :project_id => @issue.project, :issue => {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} } : - { :action => 'show', :id => @issue }) + if params[:continue] + attrs = {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} + redirect_to new_project_issue_path(@issue.project, :issue => attrs) + else + redirect_to issue_path(@issue) + end } format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) } end @@ -196,7 +199,7 @@ flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record? respond_to do |format| - format.html { redirect_back_or_default({:action => 'show', :id => @issue}) } + format.html { redirect_back_or_default issue_path(@issue) } format.api { render_api_ok } end else @@ -207,6 +210,11 @@ end end + # Updates the issue form when changing the project, status or tracker + # on issue creation/update + def update_form + end + # Bulk edit/copy a set of issues def bulk_edit @issues.sort! @@ -279,12 +287,12 @@ if params[:follow] if @issues.size == 1 && moved_issues.size == 1 - redirect_to :controller => 'issues', :action => 'show', :id => moved_issues.first + redirect_to issue_path(moved_issues.first) elsif moved_issues.map(&:project).uniq.size == 1 - redirect_to :controller => 'issues', :action => 'index', :project_id => moved_issues.map(&:project).first + redirect_to project_issues_path(moved_issues.map(&:project).first) end else - redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project}) + redirect_back_or_default _project_issues_path(@project) end end @@ -317,7 +325,7 @@ end end respond_to do |format| - format.html { redirect_back_or_default(:action => 'index', :project_id => @project) } + format.html { redirect_back_or_default _project_issues_path(@project) } format.api { render_api_ok } end end diff -r 0a574315af3e -r 4f746d8966dd app/controllers/journals_controller.rb --- a/app/controllers/journals_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/journals_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -80,7 +80,7 @@ @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.html { redirect_to issue_path(@journal.journalized) } format.js { render :action => 'update' } end else diff -r 0a574315af3e -r 4f746d8966dd app/controllers/mail_handler_controller.rb --- a/app/controllers/mail_handler_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/mail_handler_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/controllers/members_controller.rb --- a/app/controllers/members_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/members_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -32,8 +32,8 @@ format.api { @offset, @limit = api_offset_and_limit @member_count = @project.member_principals.count - @member_pages = Paginator.new self, @member_count, @limit, params['page'] - @offset ||= @member_pages.current.offset + @member_pages = Paginator.new @member_count, @limit, params['page'] + @offset ||= @member_pages.offset @members = @project.member_principals.all( :order => "#{Member.table_name}.id", :limit => @limit, diff -r 0a574315af3e -r 4f746d8966dd app/controllers/messages_controller.rb --- a/app/controllers/messages_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/messages_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,6 +19,7 @@ menu_item :boards default_search_scope :messages before_filter :find_board, :only => [:new, :preview] + before_filter :find_attachments, :only => [:preview] before_filter :find_message, :except => [:new, :preview] before_filter :authorize, :except => [:preview, :edit, :destroy] @@ -39,11 +40,13 @@ end @reply_count = @topic.children.count - @reply_pages = Paginator.new self, @reply_count, REPLIES_PER_PAGE, page - @replies = @topic.children.find(:all, :include => [:author, :attachments, {:board => :project}], - :order => "#{Message.table_name}.created_on ASC", - :limit => @reply_pages.items_per_page, - :offset => @reply_pages.current.offset) + @reply_pages = Paginator.new @reply_count, REPLIES_PER_PAGE, page + @replies = @topic.children. + includes(:author, :attachments, {:board => :project}). + reorder("#{Message.table_name}.created_on ASC"). + limit(@reply_pages.per_page). + offset(@reply_pages.offset). + all @reply = Message.new(:subject => "RE: #{@message.subject}") render :action => "show", :layout => false if request.xhr? @@ -115,7 +118,6 @@ def preview message = @board.messages.find_by_id(params[:id]) - @attachements = message.attachments if message @text = (params[:message] || params[:reply])[:content] @previewed = message render :partial => 'common/preview' diff -r 0a574315af3e -r 4f746d8966dd app/controllers/my_controller.rb --- a/app/controllers/my_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/my_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -94,7 +94,7 @@ @user.notified_project_ids = (@user.mail_notification == 'selected' ? params[:notified_project_ids] : []) set_language_if_valid @user.language flash[:notice] = l(:notice_account_updated) - redirect_to :action => 'account' + redirect_to my_account_path return end end @@ -104,7 +104,7 @@ def destroy @user = User.current unless @user.own_account_deletable? - redirect_to :action => 'account' + redirect_to my_account_path return end @@ -123,7 +123,7 @@ @user = User.current unless @user.change_password_allowed? flash[:error] = l(:notice_can_t_change_password) - redirect_to :action => 'account' + redirect_to my_account_path return end if request.post? @@ -131,7 +131,7 @@ @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation] if @user.save flash[:notice] = l(:notice_account_password_updated) - redirect_to :action => 'account' + redirect_to my_account_path end else flash[:error] = l(:notice_account_wrong_password) @@ -149,7 +149,7 @@ User.current.rss_key flash[:notice] = l(:notice_feeds_access_key_reseted) end - redirect_to :action => 'account' + redirect_to my_account_path end # Create a new API key @@ -162,7 +162,7 @@ User.current.api_key flash[:notice] = l(:notice_api_access_key_reseted) end - redirect_to :action => 'account' + redirect_to my_account_path end # User's page layout configuration @@ -192,7 +192,7 @@ @user.pref[:my_page_layout] = layout @user.pref.save end - redirect_to :action => 'page_layout' + redirect_to my_page_layout_path end # Remove a block to user's page @@ -205,7 +205,7 @@ %w(top left right).each {|f| (layout[f] ||= []).delete block } @user.pref[:my_page_layout] = layout @user.pref.save - redirect_to :action => 'page_layout' + redirect_to my_page_layout_path end # Change blocks order on user's page diff -r 0a574315af3e -r 4f746d8966dd app/controllers/news_controller.rb --- a/app/controllers/news_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/news_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -40,8 +40,8 @@ scope = @project ? @project.news.visible : News.visible @news_count = scope.count - @news_pages = Paginator.new self, @news_count, @limit, params['page'] - @offset ||= @news_pages.current.offset + @news_pages = Paginator.new @news_count, @limit, params['page'] + @offset ||= @news_pages.offset @newss = scope.all(:include => [:author, :project], :order => "#{News.table_name}.created_on DESC", :offset => @offset, @@ -73,7 +73,7 @@ if @news.save render_attachment_warning_if_needed(@news) flash[:notice] = l(:notice_successful_create) - redirect_to :controller => 'news', :action => 'index', :project_id => @project + redirect_to project_news_index_path(@project) else render :action => 'new' end @@ -88,7 +88,7 @@ if @news.save render_attachment_warning_if_needed(@news) flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'show', :id => @news + redirect_to news_path(@news) else render :action => 'edit' end @@ -96,7 +96,7 @@ def destroy @news.destroy - redirect_to :action => 'index', :project_id => @project + redirect_to project_news_index_path(@project) end private diff -r 0a574315af3e -r 4f746d8966dd app/controllers/previews_controller.rb --- a/app/controllers/previews_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/previews_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,12 +16,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class PreviewsController < ApplicationController - before_filter :find_project + before_filter :find_project, :find_attachments def issue @issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank? if @issue - @attachements = @issue.attachments @description = params[:issue] && params[:issue][:description] if @description && @description.gsub(/(\r?\n|\n\r?)/, "\n") == @issue.description.to_s.gsub(/(\r?\n|\n\r?)/, "\n") @description = nil @@ -37,7 +36,6 @@ def news if params[:id].present? && news = News.visible.find_by_id(params[:id]) @previewed = news - @attachments = news.attachments end @text = (params[:news] ? params[:news][:description] : nil) render :partial => 'common/preview' diff -r 0a574315af3e -r 4f746d8966dd app/controllers/project_enumerations_controller.rb --- a/app/controllers/project_enumerations_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/project_enumerations_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -29,7 +29,7 @@ flash[:notice] = l(:notice_successful_update) end - redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project + redirect_to settings_project_path(@project, :tab => 'activities') end def destroy @@ -37,7 +37,6 @@ time_entry_activity.destroy(time_entry_activity.parent) end flash[:notice] = l(:notice_successful_update) - redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project + redirect_to settings_project_path(@project, :tab => 'activities') end - end diff -r 0a574315af3e -r 4f746d8966dd app/controllers/projects_controller.rb --- a/app/controllers/projects_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/projects_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -43,6 +43,7 @@ helper :repositories include RepositoriesHelper include ProjectsHelper + helper :members include ActivitiesHelper helper :activities @@ -70,11 +71,10 @@ format.api { @offset, @limit = api_offset_and_limit @project_count = Project.visible.count - @projects = Project.visible.all(:offset => @offset, :limit => @limit, :order => 'lft') + @projects = Project.visible.offset(@offset).limit(@limit).order('lft').all } format.atom { - projects = Project.visible.find(:all, :order => 'created_on DESC', - :limit => Setting.feeds_limit.to_i) + projects = Project.visible.order('created_on DESC').limit(Setting.feeds_limit.to_i).all render_feed(projects, :title => "#{Setting.app_title}: #{l(:label_project_latest)}") } end @@ -91,14 +91,14 @@ end def new - @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position") + @issue_custom_fields = IssueCustomField.sorted.all @trackers = Tracker.sorted.all @project = Project.new @project.safe_attributes = params[:project] end def create - @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position") + @issue_custom_fields = IssueCustomField.sorted.all @trackers = Tracker.sorted.all @project = Project.new @project.safe_attributes = params[:project] @@ -114,10 +114,12 @@ respond_to do |format| format.html { flash[:notice] = l(:notice_successful_create) - redirect_to(params[:continue] ? - {:controller => 'projects', :action => 'new', :project => {:parent_id => @project.parent_id}.reject {|k,v| v.nil?}} : - {:controller => 'projects', :action => 'settings', :id => @project} - ) + if params[:continue] + attrs = {:parent_id => @project.parent_id}.reject {|k,v| v.nil?} + redirect_to new_project_path(attrs) + else + redirect_to settings_project_path(@project) + end } format.api { render :action => 'show', :status => :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) } end @@ -127,15 +129,11 @@ format.api { render_validation_errors(@project) } end end - end def copy - @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position") + @issue_custom_fields = IssueCustomField.sorted.all @trackers = Tracker.sorted.all - @root_projects = Project.find(:all, - :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}", - :order => 'name') @source_project = Project.find(params[:id]) if request.get? @project = Project.copy_from(@source_project) @@ -147,13 +145,13 @@ if validate_parent_id && @project.copy(@source_project, :only => params[:only]) @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id') flash[:notice] = l(:notice_successful_create) - redirect_to :controller => 'projects', :action => 'settings', :id => @project + redirect_to settings_project_path(@project) elsif !@project.new_record? # Project was created # But some objects were not copied due to validation failures # (eg. issues from disabled trackers) # TODO: inform about that - redirect_to :controller => 'projects', :action => 'settings', :id => @project + redirect_to settings_project_path(@project) end end end @@ -164,14 +162,14 @@ # Show @project def show - if params[:jump] - # try to redirect to the requested menu item - redirect_to_project_menu_item(@project, params[:jump]) && return + # try to redirect to the requested menu item + if params[:jump] && redirect_to_project_menu_item(@project, params[:jump]) + return end @users_by_role = @project.users_by_role @subprojects = @project.children.visible.all - @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC") + @news = @project.news.limit(5).includes(:author, :project).reorder("#{News.table_name}.created_on DESC").all @trackers = @project.rolled_up_trackers cond = @project.project_condition(Setting.display_subprojects_issues?) @@ -192,7 +190,7 @@ end def settings - @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position") + @issue_custom_fields = IssueCustomField.sorted.all @issue_category ||= IssueCategory.new @member ||= @project.members.new @trackers = Tracker.sorted.all @@ -209,7 +207,7 @@ respond_to do |format| format.html { flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'settings', :id => @project + redirect_to settings_project_path(@project) } format.api { render_api_ok } end @@ -235,7 +233,7 @@ def modules @project.enabled_module_names = params[:enabled_module_names] flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'settings', :id => @project, :tab => 'modules' + redirect_to settings_project_path(@project, :tab => 'modules') end def archive @@ -244,12 +242,12 @@ flash[:error] = l(:error_can_not_archive_project) end end - redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status])) + redirect_to admin_projects_path(:status => params[:status]) end def unarchive @project.unarchive if request.post? && !@project.active? - redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status])) + redirect_to admin_projects_path(:status => params[:status]) end def close @@ -268,7 +266,7 @@ if api_request? || params[:confirm] @project_to_destroy.destroy respond_to do |format| - format.html { redirect_to :controller => 'admin', :action => 'projects' } + format.html { redirect_to admin_projects_path } format.api { render_api_ok } end end diff -r 0a574315af3e -r 4f746d8966dd app/controllers/queries_controller.rb --- a/app/controllers/queries_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/queries_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -32,35 +32,34 @@ @limit = per_page_option end - @query_count = Query.visible.count - @query_pages = Paginator.new self, @query_count, @limit, params['page'] - @queries = Query.visible.all(:limit => @limit, :offset => @offset, :order => "#{Query.table_name}.name") + @query_count = IssueQuery.visible.count + @query_pages = Paginator.new @query_count, @limit, params['page'] + @queries = IssueQuery.visible.all(:limit => @limit, :offset => @offset, :order => "#{Query.table_name}.name") respond_to do |format| - format.html { render :nothing => true } format.api end end def new - @query = Query.new + @query = IssueQuery.new @query.user = User.current @query.project = @project @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin? - build_query_from_params + @query.build_from_params(params) end def create - @query = Query.new(params[:query]) + @query = IssueQuery.new(params[:query]) @query.user = User.current @query.project = params[:query_is_for_all] ? nil : @project @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin? - build_query_from_params + @query.build_from_params(params) @query.column_names = nil if params[:default_columns] if @query.save flash[:notice] = l(:notice_successful_create) - redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query + redirect_to _project_issues_path(@project, :query_id => @query) else render :action => 'new', :layout => !request.xhr? end @@ -73,12 +72,12 @@ @query.attributes = params[:query] @query.project = nil if params[:query_is_for_all] @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin? - build_query_from_params + @query.build_from_params(params) @query.column_names = nil if params[:default_columns] if @query.save flash[:notice] = l(:notice_successful_update) - redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query + redirect_to _project_issues_path(@project, :query_id => @query) else render :action => 'edit' end @@ -86,12 +85,12 @@ def destroy @query.destroy - redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1 + redirect_to _project_issues_path(@project, :set_filter => 1) end private def find_query - @query = Query.find(params[:id]) + @query = IssueQuery.find(params[:id]) @project = @query.project render_403 unless @query.editable_by?(User.current) rescue ActiveRecord::RecordNotFound diff -r 0a574315af3e -r 4f746d8966dd app/controllers/reports_controller.rb --- a/app/controllers/reports_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/reports_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -90,6 +90,6 @@ private def find_issue_statuses - @statuses = IssueStatus.find(:all, :order => 'position') + @statuses = IssueStatus.sorted.all end end diff -r 0a574315af3e -r 4f746d8966dd app/controllers/repositories_controller.rb --- a/app/controllers/repositories_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/repositories_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -139,13 +139,14 @@ def revisions @changeset_count = @repository.changesets.count - @changeset_pages = Paginator.new self, @changeset_count, + @changeset_pages = Paginator.new @changeset_count, per_page_option, params['page'] - @changesets = @repository.changesets.find(:all, - :limit => @changeset_pages.items_per_page, - :offset => @changeset_pages.current.offset, - :include => [:user, :repository, :parents]) + @changesets = @repository.changesets. + limit(@changeset_pages.per_page). + offset(@changeset_pages.offset). + includes(:user, :repository, :parents). + all respond_to do |format| format.html { render :layout => false if request.xhr? } diff -r 0a574315af3e -r 4f746d8966dd app/controllers/roles_controller.rb --- a/app/controllers/roles_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/roles_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,7 +26,7 @@ def index respond_to do |format| format.html { - @role_pages, @roles = paginate :roles, :per_page => 25, :order => 'builtin, position' + @role_pages, @roles = paginate Role.sorted, :per_page => 25 render :action => "index", :layout => false if request.xhr? } format.api { @@ -58,7 +58,7 @@ @role.workflow_rules.copy(copy_from) end flash[:notice] = l(:notice_successful_create) - redirect_to :action => 'index' + redirect_to roles_path else @roles = Role.sorted.all render :action => 'new' @@ -71,7 +71,7 @@ def update if request.put? and @role.update_attributes(params[:role]) flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'index' + redirect_to roles_path else render :action => 'edit' end @@ -79,10 +79,10 @@ def destroy @role.destroy - redirect_to :action => 'index' + redirect_to roles_path rescue flash[:error] = l(:error_can_not_remove_role) - redirect_to :action => 'index' + redirect_to roles_path end def permissions @@ -94,7 +94,7 @@ role.save end flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'index' + redirect_to roles_path end end diff -r 0a574315af3e -r 4f746d8966dd app/controllers/search_controller.rb --- a/app/controllers/search_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/search_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -18,9 +18,6 @@ class SearchController < ApplicationController before_filter :find_optional_project - helper :messages - include MessagesHelper - def index @question = params[:q] || "" @question.strip! @@ -43,8 +40,8 @@ begin; offset = params[:offset].to_time if params[:offset]; rescue; end # quick jump to an issue - if @question.match(/^#?(\d+)$/) && Issue.visible.find_by_id($1.to_i) - redirect_to :controller => "issues", :action => "show", :id => $1 + if (m = @question.match(/^#?(\d+)$/)) && (issue = Issue.visible.find_by_id(m[1].to_i)) + redirect_to issue_path(issue) return end diff -r 0a574315af3e -r 4f746d8966dd app/controllers/settings_controller.rb --- a/app/controllers/settings_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/settings_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,6 +19,8 @@ layout 'admin' menu_item :plugins, :only => :plugin + helper :queries + before_filter :require_admin def index @@ -36,7 +38,7 @@ Setting[name] = value end flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'edit', :tab => params[:tab] + redirect_to settings_path(:tab => params[:tab]) else @options = {} user_format = User::USER_FORMATS.collect{|key, value| [key, value[:setting_order]]}.sort{|a, b| a[1] <=> b[1]} @@ -52,10 +54,15 @@ def plugin @plugin = Redmine::Plugin.find(params[:id]) + unless @plugin.configurable? + render_404 + return + end + if request.post? Setting.send "plugin_#{@plugin.id}=", params[:settings] flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'plugin', :id => @plugin.id + redirect_to plugin_settings_path(@plugin) else @partial = @plugin.settings[:partial] @settings = Setting.send "plugin_#{@plugin.id}" diff -r 0a574315af3e -r 4f746d8966dd app/controllers/sys_controller.rb --- a/app/controllers/sys_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/sys_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/controllers/timelog_controller.rb --- a/app/controllers/timelog_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/timelog_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -30,41 +30,34 @@ accept_rss_auth :index accept_api_auth :index, :show, :create, :update, :destroy + rescue_from Query::StatementInvalid, :with => :query_statement_invalid + helper :sort include SortHelper helper :issues include TimelogHelper helper :custom_fields include CustomFieldsHelper + helper :queries + include QueriesHelper def index - sort_init 'spent_on', 'desc' - sort_update 'spent_on' => ['spent_on', "#{TimeEntry.table_name}.created_on"], - 'user' => 'user_id', - 'activity' => 'activity_id', - 'project' => "#{Project.table_name}.name", - 'issue' => 'issue_id', - 'hours' => 'hours' + @query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_') + scope = time_entry_scope - retrieve_date_range - - scope = TimeEntry.visible.spent_between(@from, @to) - if @issue - scope = scope.on_issue(@issue) - elsif @project - scope = scope.on_project(@project, Setting.display_subprojects_issues?) - end + sort_init(@query.sort_criteria.empty? ? [['spent_on', 'desc']] : @query.sort_criteria) + sort_update(@query.sortable_columns) respond_to do |format| format.html { # Paginate results @entry_count = scope.count - @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page'] + @entry_pages = Paginator.new @entry_count, per_page_option, params['page'] @entries = scope.all( :include => [:project, :activity, :user, {:issue => :tracker}], :order => sort_clause, - :limit => @entry_pages.items_per_page, - :offset => @entry_pages.current.offset + :limit => @entry_pages.per_page, + :offset => @entry_pages.offset ) @total_hours = scope.sum(:hours).to_f @@ -94,14 +87,16 @@ :include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}], :order => sort_clause ) - send_data(entries_to_csv(@entries), :type => 'text/csv; header=present', :filename => 'timelog.csv') + send_data(query_to_csv(@entries, @query, params), :type => 'text/csv; header=present', :filename => 'timelog.csv') } end end def report - retrieve_date_range - @report = Redmine::Helpers::TimeReport.new(@project, @issue, params[:criteria], params[:columns], @from, @to) + @query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_') + scope = time_entry_scope + + @report = Redmine::Helpers::TimeReport.new(@project, @issue, params[:criteria], params[:columns], scope) respond_to do |format| format.html { render :layout => !request.xhr? } @@ -134,16 +129,24 @@ flash[:notice] = l(:notice_successful_create) if params[:continue] if params[:project_id] - redirect_to :action => 'new', :project_id => @time_entry.project, :issue_id => @time_entry.issue, + options = { :time_entry => {:issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id}, :back_url => params[:back_url] + } + if @time_entry.issue + redirect_to new_project_issue_time_entry_path(@time_entry.project, @time_entry.issue, options) + else + redirect_to new_project_time_entry_path(@time_entry.project, options) + end else - redirect_to :action => 'new', + options = { :time_entry => {:project_id => @time_entry.project_id, :issue_id => @time_entry.issue_id, :activity_id => @time_entry.activity_id}, :back_url => params[:back_url] + } + redirect_to new_time_entry_path(options) end else - redirect_back_or_default :action => 'index', :project_id => @time_entry.project + redirect_back_or_default project_time_entries_path(@time_entry.project) end } format.api { render :action => 'show', :status => :created, :location => time_entry_url(@time_entry) } @@ -169,7 +172,7 @@ respond_to do |format| format.html { flash[:notice] = l(:notice_successful_update) - redirect_back_or_default :action => 'index', :project_id => @time_entry.project + redirect_back_or_default project_time_entries_path(@time_entry.project) } format.api { render_api_ok } end @@ -200,7 +203,7 @@ end end set_flash_from_bulk_time_entry_save(@time_entries, unsaved_time_entry_ids) - redirect_back_or_default({:controller => 'timelog', :action => 'index', :project_id => @projects.first}) + redirect_back_or_default project_time_entries_path(@projects.first) end def destroy @@ -219,7 +222,7 @@ else flash[:error] = l(:notice_unable_delete_time_entry) end - redirect_back_or_default(:action => 'index', :project_id => @projects.first) + redirect_back_or_default project_time_entries_path(@projects.first) } format.api { if destroyed @@ -291,51 +294,15 @@ end end - # Retrieves the date range based on predefined ranges or specific from/to param dates - def retrieve_date_range - @free_period = false - @from, @to = nil, nil - - if params[:period_type] == '1' || (params[:period_type].nil? && !params[:period].nil?) - case params[:period].to_s - when 'today' - @from = @to = Date.today - when 'yesterday' - @from = @to = Date.today - 1 - when 'current_week' - @from = Date.today - (Date.today.cwday - 1)%7 - @to = @from + 6 - when 'last_week' - @from = Date.today - 7 - (Date.today.cwday - 1)%7 - @to = @from + 6 - when 'last_2_weeks' - @from = Date.today - 14 - (Date.today.cwday - 1)%7 - @to = @from + 13 - when '7_days' - @from = Date.today - 7 - @to = Date.today - when 'current_month' - @from = Date.civil(Date.today.year, Date.today.month, 1) - @to = (@from >> 1) - 1 - when 'last_month' - @from = Date.civil(Date.today.year, Date.today.month, 1) << 1 - @to = (@from >> 1) - 1 - when '30_days' - @from = Date.today - 30 - @to = Date.today - when 'current_year' - @from = Date.civil(Date.today.year, 1, 1) - @to = Date.civil(Date.today.year, 12, 31) - end - elsif params[:period_type] == '2' || (params[:period_type].nil? && (!params[:from].nil? || !params[:to].nil?)) - begin; @from = params[:from].to_s.to_date unless params[:from].blank?; rescue; end - begin; @to = params[:to].to_s.to_date unless params[:to].blank?; rescue; end - @free_period = true - else - # default + # Returns the TimeEntry scope for index and report actions + def time_entry_scope + scope = TimeEntry.visible.where(@query.statement) + if @issue + scope = scope.on_issue(@issue) + elsif @project + scope = scope.on_project(@project, Setting.display_subprojects_issues?) end - - @from, @to = @to, @from if @from && @to && @from > @to + scope end def parse_params_for_bulk_time_entry_attributes(params) diff -r 0a574315af3e -r 4f746d8966dd app/controllers/trackers_controller.rb --- a/app/controllers/trackers_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/trackers_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -25,7 +25,7 @@ def index respond_to do |format| format.html { - @tracker_pages, @trackers = paginate :trackers, :per_page => 10, :order => 'position' + @tracker_pages, @trackers = paginate Tracker.sorted, :per_page => 25 render :action => "index", :layout => false if request.xhr? } format.api { @@ -36,19 +36,19 @@ def new @tracker ||= Tracker.new(params[:tracker]) - @trackers = Tracker.find :all, :order => 'position' - @projects = Project.find(:all) + @trackers = Tracker.sorted.all + @projects = Project.all end def create @tracker = Tracker.new(params[:tracker]) - if request.post? and @tracker.save + if @tracker.save # workflow copy if !params[:copy_workflow_from].blank? && (copy_from = Tracker.find_by_id(params[:copy_workflow_from])) @tracker.workflow_rules.copy(copy_from) end flash[:notice] = l(:notice_successful_create) - redirect_to :action => 'index' + redirect_to trackers_path return end new @@ -57,14 +57,14 @@ def edit @tracker ||= Tracker.find(params[:id]) - @projects = Project.find(:all) + @projects = Project.all end def update @tracker = Tracker.find(params[:id]) - if request.put? and @tracker.update_attributes(params[:tracker]) + if @tracker.update_attributes(params[:tracker]) flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'index' + redirect_to trackers_path return end edit @@ -78,7 +78,7 @@ else @tracker.destroy end - redirect_to :action => 'index' + redirect_to trackers_path end def fields @@ -92,7 +92,7 @@ end end flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'fields' + redirect_to fields_trackers_path return end @trackers = Tracker.sorted.all diff -r 0a574315af3e -r 4f746d8966dd app/controllers/users_controller.rb --- a/app/controllers/users_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/users_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -45,12 +45,9 @@ scope = scope.in_group(params[:group_id]) if params[:group_id].present? @user_count = scope.count - @user_pages = Paginator.new self, @user_count, @limit, params['page'] - @offset ||= @user_pages.current.offset - @users = scope.find :all, - :order => sort_clause, - :limit => @limit, - :offset => @offset + @user_pages = Paginator.new @user_count, @limit, params['page'] + @offset ||= @user_pages.offset + @users = scope.order(sort_clause).limit(@limit).offset(@offset).all respond_to do |format| format.html { @@ -58,7 +55,7 @@ render :layout => !request.xhr? } format.api - end + end end def show @@ -89,7 +86,7 @@ def new @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option) - @auth_sources = AuthSource.find(:all) + @auth_sources = AuthSource.all @ssamr_user_details = SsamrUserDetail.new end @@ -124,15 +121,16 @@ respond_to do |format| format.html { flash[:notice] = l(:notice_user_successful_create, :id => view_context.link_to(@user.login, user_path(@user))) - redirect_to(params[:continue] ? - {:controller => 'users', :action => 'new'} : - {:controller => 'users', :action => 'edit', :id => @user} - ) + if params[:continue] + redirect_to new_user_path + else + redirect_to edit_user_path(@user) + end } format.api { render :action => 'show', :status => :created, :location => user_url(@user) } end else - @auth_sources = AuthSource.find(:all) + @auth_sources = AuthSource.all # Clear password input @user.password = @user.password_confirmation = nil @@ -144,7 +142,7 @@ end def edit - + @auth_sources = AuthSource.all @ssamr_user_details = @user.ssamr_user_detail if @user.ssamr_user_detail == nil @@ -153,7 +151,6 @@ @selected_institution_id = @user.ssamr_user_detail.institution_id.to_i end - @auth_sources = AuthSource.find(:all) @membership ||= Member.new end @@ -208,7 +205,7 @@ format.api { render_api_ok } end else - @auth_sources = AuthSource.find(:all) + @auth_sources = AuthSource.all @membership ||= Member.new # Clear password input @user.password = @user.password_confirmation = nil @@ -223,7 +220,7 @@ def destroy @user.destroy respond_to do |format| - format.html { redirect_back_or_default(users_url) } + format.html { redirect_back_or_default(users_path) } format.api { render_api_ok } end end @@ -232,7 +229,7 @@ @membership = Member.edit_membership(params[:membership_id], params[:membership], @user) @membership.save respond_to do |format| - format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' } + format.html { redirect_to edit_user_path(@user, :tab => 'memberships') } format.js end end @@ -243,7 +240,7 @@ @membership.destroy end respond_to do |format| - format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' } + format.html { redirect_to edit_user_path(@user, :tab => 'memberships') } format.js end end diff -r 0a574315af3e -r 4f746d8966dd app/controllers/versions_controller.rb --- a/app/controllers/versions_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/versions_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -31,7 +31,7 @@ def index respond_to do |format| format.html { - @trackers = @project.trackers.find(:all, :order => 'position') + @trackers = @project.trackers.sorted.all retrieve_selected_tracker_ids(@trackers, @trackers.select {|t| t.is_in_roadmap?}) @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1') project_ids = @with_subprojects ? @project.self_and_descendants.collect(&:id) : [@project.id] @@ -64,9 +64,10 @@ def show respond_to do |format| format.html { - @issues = @version.fixed_issues.visible.find(:all, - :include => [:status, :tracker, :priority], - :order => "#{Tracker.table_name}.position, #{Issue.table_name}.id") + @issues = @version.fixed_issues.visible. + includes(:status, :tracker, :priority). + reorder("#{Tracker.table_name}.position, #{Issue.table_name}.id"). + all } format.api end @@ -95,7 +96,7 @@ respond_to do |format| format.html do flash[:notice] = l(:notice_successful_create) - redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project + redirect_back_or_default settings_project_path(@project, :tab => 'versions') end format.js format.api do @@ -124,7 +125,7 @@ respond_to do |format| format.html { flash[:notice] = l(:notice_successful_update) - redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project + redirect_back_or_default settings_project_path(@project, :tab => 'versions') } format.api { render_api_ok } end @@ -141,21 +142,21 @@ if request.put? @project.close_completed_versions end - redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project + redirect_to settings_project_path(@project, :tab => 'versions') end def destroy if @version.fixed_issues.empty? @version.destroy respond_to do |format| - format.html { redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project } + format.html { redirect_back_or_default settings_project_path(@project, :tab => 'versions') } format.api { render_api_ok } end else respond_to do |format| format.html { flash[:error] = l(:notice_unable_delete_version) - redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project + redirect_to settings_project_path(@project, :tab => 'versions') } format.api { head :unprocessable_entity } end diff -r 0a574315af3e -r 4f746d8966dd app/controllers/watchers_controller.rb --- a/app/controllers/watchers_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/watchers_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,35 +16,36 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class WatchersController < ApplicationController - before_filter :find_project - before_filter :require_login, :check_project_privacy, :only => [:watch, :unwatch] - before_filter :authorize, :only => [:new, :destroy] + before_filter :require_login, :find_watchables, :only => [:watch, :unwatch] def watch - if @watched.respond_to?(:visible?) && !@watched.visible?(User.current) - render_403 - else - set_watcher(User.current, true) - end + set_watcher(@watchables, User.current, true) end def unwatch - set_watcher(User.current, false) + set_watcher(@watchables, User.current, false) end + before_filter :find_project, :authorize, :only => [:new, :create, :append, :destroy, :autocomplete_for_user] + accept_api_auth :create, :destroy + def new end def create - if params[:watcher].is_a?(Hash) && request.post? - user_ids = params[:watcher][:user_ids] || [params[:watcher][:user_id]] - user_ids.each do |user_id| - Watcher.create(:watchable => @watched, :user_id => user_id) - end + user_ids = [] + if params[:watcher].is_a?(Hash) + user_ids << (params[:watcher][:user_ids] || params[:watcher][:user_id]) + else + user_ids << params[:user_id] + end + user_ids.flatten.compact.uniq.each do |user_id| + Watcher.create(:watchable => @watched, :user_id => user_id) end respond_to do |format| format.html { redirect_to_referer_or {render :text => 'Watcher added.', :layout => true}} format.js + format.api { render_api_ok } end end @@ -56,22 +57,24 @@ end def destroy - @watched.set_watcher(User.find(params[:user_id]), false) if request.post? + @watched.set_watcher(User.find(params[:user_id]), false) respond_to do |format| format.html { redirect_to :back } format.js + format.api { render_api_ok } end end def autocomplete_for_user - @users = User.active.like(params[:q]).find(:all, :limit => 100) + @users = User.active.sorted.like(params[:q]).limit(100).all if @watched @users -= @watched.watcher_users end render :layout => false end -private + private + def find_project if params[:object_type] && params[:object_id] klass = Object.const_get(params[:object_type].camelcase) @@ -85,11 +88,22 @@ render_404 end - def set_watcher(user, watching) - @watched.set_watcher(user, watching) + def find_watchables + klass = Object.const_get(params[:object_type].camelcase) rescue nil + if klass && klass.respond_to?('watched_by') + @watchables = klass.find_all_by_id(Array.wrap(params[:object_id])) + raise Unauthorized if @watchables.any? {|w| w.respond_to?(:visible?) && !w.visible?} + end + render_404 unless @watchables.present? + end + + def set_watcher(watchables, user, watching) + watchables.each do |watchable| + watchable.set_watcher(user, watching) + end respond_to do |format| format.html { redirect_to_referer_or {render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true}} - format.js { render :partial => 'set_watcher', :locals => {:user => user, :watched => @watched} } + format.js { render :partial => 'set_watcher', :locals => {:user => user, :watched => watchables} } end end end diff -r 0a574315af3e -r 4f746d8966dd app/controllers/welcome_controller.rb --- a/app/controllers/welcome_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/welcome_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/controllers/wiki_controller.rb --- a/app/controllers/wiki_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/wiki_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -37,6 +37,7 @@ before_filter :find_existing_or_new_page, :only => [:show, :edit, :update] before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy, :destroy_version] accept_api_auth :index, :show, :update, :destroy + before_filter :find_attachments, :only => [:preview] helper :attachments include AttachmentsHelper @@ -159,10 +160,10 @@ call_hook(:controller_wiki_edit_after_save, { :params => params, :page => @page}) respond_to do |format| - format.html { redirect_to :action => 'show', :project_id => @project, :id => @page.title } + format.html { redirect_to project_wiki_page_path(@project, @page.title) } format.api { if was_new_page - render :action => 'show', :status => :created, :location => url_for(:controller => 'wiki', :action => 'show', :project_id => @project, :id => @page.title) + render :action => 'show', :status => :created, :location => project_wiki_page_path(@project, @page.title) else render_api_ok end @@ -199,25 +200,26 @@ @original_title = @page.pretty_title if request.post? && @page.update_attributes(params[:wiki_page]) flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'show', :project_id => @project, :id => @page.title + redirect_to project_wiki_page_path(@project, @page.title) end end def protect @page.update_attribute :protected, params[:protected] - redirect_to :action => 'show', :project_id => @project, :id => @page.title + redirect_to project_wiki_page_path(@project, @page.title) end # show page history def history @version_count = @page.content.versions.count - @version_pages = Paginator.new self, @version_count, per_page_option, params['page'] + @version_pages = Paginator.new @version_count, per_page_option, params['page'] # don't load text - @versions = @page.content.versions.find :all, - :select => "id, author_id, comments, updated_on, version", - :order => 'version DESC', - :limit => @version_pages.items_per_page + 1, - :offset => @version_pages.current.offset + @versions = @page.content.versions. + select("id, author_id, comments, updated_on, version"). + reorder('version DESC'). + limit(@version_pages.per_page + 1). + offset(@version_pages.offset). + all render :layout => false if request.xhr? end @@ -260,7 +262,7 @@ end @page.destroy respond_to do |format| - format.html { redirect_to :action => 'index', :project_id => @project } + format.html { redirect_to project_wiki_index_path(@project) } format.api { render_api_ok } end end @@ -270,7 +272,7 @@ @content = @page.content_for_version(params[:version]) @content.destroy - redirect_to_referer_or :action => 'history', :id => @page.title, :project_id => @project + redirect_to_referer_or history_project_wiki_page_path(@project, @page.title) end # Export wiki to a single pdf or html file @@ -292,7 +294,7 @@ # page is nil when previewing a new page return render_403 unless page.nil? || editable?(page) if page - @attachements = page.attachments + @attachments += page.attachments @previewed = page.content end @text = params[:content][:text] @@ -349,6 +351,6 @@ end def load_pages_for_index - @pages = @wiki.pages.with_updated_on.order("#{WikiPage.table_name}.title").includes(:wiki => :project).includes(:parent).all + @pages = @wiki.pages.with_updated_on.reorder("#{WikiPage.table_name}.title").includes(:wiki => :project).includes(:parent).all end end diff -r 0a574315af3e -r 4f746d8966dd app/controllers/wikis_controller.rb --- a/app/controllers/wikis_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/wikis_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -30,7 +30,7 @@ def destroy if request.post? && params[:confirm] && @project.wiki @project.wiki.destroy - redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'wiki' + redirect_to settings_project_path(@project, :tab => 'wiki') end end end diff -r 0a574315af3e -r 4f746d8966dd app/controllers/workflows_controller.rb --- a/app/controllers/workflows_controller.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/controllers/workflows_controller.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -38,7 +38,7 @@ } } if @role.save - redirect_to :action => 'edit', :role_id => @role, :tracker_id => @tracker, :used_statuses_only => params[:used_statuses_only] + redirect_to workflows_edit_path(:role_id => @role, :tracker_id => @tracker, :used_statuses_only => params[:used_statuses_only]) return end end @@ -64,7 +64,7 @@ if request.post? && @role && @tracker WorkflowPermission.replace_permissions(@tracker, @role, params[:permissions] || {}) - redirect_to :action => 'permissions', :role_id => @role, :tracker_id => @tracker, :used_statuses_only => params[:used_statuses_only] + redirect_to workflows_permissions_path(:role_id => @role, :tracker_id => @tracker, :used_statuses_only => params[:used_statuses_only]) return end @@ -106,12 +106,12 @@ if request.post? if params[:source_tracker_id].blank? || params[:source_role_id].blank? || (@source_tracker.nil? && @source_role.nil?) flash.now[:error] = l(:error_workflow_copy_source) - elsif @target_trackers.nil? || @target_roles.nil? + elsif @target_trackers.blank? || @target_roles.blank? flash.now[:error] = l(:error_workflow_copy_target) else WorkflowRule.copy(@source_tracker, @source_role, @target_trackers, @target_roles) flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'copy', :source_tracker_id => @source_tracker, :source_role_id => @source_role + redirect_to workflows_copy_path(:source_tracker_id => @source_tracker, :source_role_id => @source_role) end end end diff -r 0a574315af3e -r 4f746d8966dd app/helpers/account_helper.rb --- a/app/helpers/account_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/account_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/helpers/activities_helper.rb --- a/app/helpers/activities_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/activities_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,3 +1,21 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2013 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 ActivitiesHelper @@ -147,5 +165,18 @@ threshold = insthash.values.sort.last(count).first insthash.keys.select { |k| insthash[k] >= threshold }.sample(count) end + + def sort_activity_events(events) + events_by_group = events.group_by(&:event_group) + sorted_events = [] + events.sort {|x, y| y.event_datetime <=> x.event_datetime}.each do |event| + if group_events = events_by_group.delete(event.event_group) + group_events.sort {|x, y| y.event_datetime <=> x.event_datetime}.each_with_index do |e, i| + sorted_events << [e, i > 0] + end + end + end + sorted_events + end end diff -r 0a574315af3e -r 4f746d8966dd app/helpers/admin_helper.rb --- a/app/helpers/admin_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/admin_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/helpers/application_helper.rb --- a/app/helpers/application_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/application_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,6 +24,7 @@ include Redmine::WikiFormatting::Macros::Definitions include Redmine::I18n include GravatarHelper::PublicMethods + include Redmine::Pagination::Helper extend Forwardable def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter @@ -90,14 +91,10 @@ # * :download - Force download (default: false) def link_to_attachment(attachment, options={}) text = options.delete(:text) || attachment.filename - action = options.delete(:download) ? 'download' : 'show' - opt_only_path = {} - opt_only_path[:only_path] = (options[:only_path] == false ? false : true) - options.delete(:only_path) - link_to(h(text), - {:controller => 'attachments', :action => action, - :id => attachment, :filename => attachment.filename}.merge(opt_only_path), - options) + route_method = options.delete(:download) ? :download_named_attachment_path : :named_attachment_path + html_options = options.slice!(:only_path) + url = send(route_method, attachment, attachment.filename, options) + link_to text, url, html_options end # Generates a link to a SCM revision @@ -119,13 +116,11 @@ # Generates a link to a message def link_to_message(message, options={}, html_options = nil) link_to( - h(truncate(message.subject, :length => 60)), - { :controller => 'messages', :action => 'show', - :board_id => message.board_id, - :id => (message.parent_id || message.id), + truncate(message.subject, :length => 60), + board_message_path(message.board_id, message.parent_id || message.id, { :r => (message.parent_id && message.id), :anchor => (message.parent_id ? "message-#{message.id}" : nil) - }.merge(options), + }.merge(options)), html_options ) end @@ -134,16 +129,29 @@ # Examples: # # link_to_project(project) # => link to the specified project overview - # link_to_project(project, :action=>'settings') # => link to project settings # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options # link_to_project(project, {}, :class => "project") # => html options with default url (project overview) # def link_to_project(project, options={}, html_options = nil) if project.archived? - h(project) + h(project.name) + elsif options.key?(:action) + ActiveSupport::Deprecation.warn "#link_to_project with :action option is deprecated and will be removed in Redmine 3.0." + url = {:controller => 'projects', :action => 'show', :id => project}.merge(options) + link_to project.name, url, html_options else - url = {:controller => 'projects', :action => 'show', :id => project}.merge(options) - link_to(h(project), url, html_options) + link_to project.name, project_path(project, options), html_options + end + end + + # Generates a link to a project settings if active + def link_to_project_settings(project, options={}, html_options=nil) + if project.active? + link_to project.name, settings_project_path(project, options), html_options + elsif project.archived? + h(project.name) + else + link_to project.name, project_path(project, options), html_options end end @@ -152,8 +160,8 @@ end def thumbnail_tag(attachment) - link_to image_tag(url_for(:controller => 'attachments', :action => 'thumbnail', :id => attachment)), - {:controller => 'attachments', :action => 'show', :id => attachment, :filename => attachment.filename}, + link_to image_tag(thumbnail_path(attachment)), + named_attachment_path(attachment, attachment.filename), :title => attachment.filename end @@ -187,7 +195,7 @@ def format_version_name(version) if version.project == @project - h(version) + h(version) else h("#{version.project} - #{version}") end @@ -341,7 +349,7 @@ def options_for_membership_project_select(principal, projects) options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---") options << project_tree_options_for_select(projects) do |p| - {:disabled => principal.projects.include?(p)} + {:disabled => principal.projects.to_a.include?(p)} end options end @@ -397,59 +405,6 @@ str.blank? ? nil : str end - def pagination_links_full(paginator, count=nil, options={}) - page_param = options.delete(:page_param) || :page - per_page_links = options.delete(:per_page_links) - url_param = params.dup - - html = '' - if paginator.current.previous - # \xc2\xab(utf-8) = « - html << link_to_content_update( - "\xc2\xab " + l(:label_previous), - url_param.merge(page_param => paginator.current.previous)) + ' ' - end - - html << (pagination_links_each(paginator, options) do |n| - link_to_content_update(n.to_s, url_param.merge(page_param => n)) - end || '') - - if paginator.current.next - # \xc2\xbb(utf-8) = » - html << ' ' + link_to_content_update( - (l(:label_next) + " \xc2\xbb"), - url_param.merge(page_param => paginator.current.next)) - end - - unless count.nil? - html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})" - if per_page_links != false && links = per_page_links(paginator.items_per_page, count) - html << " | #{links}" - end - end - - html.html_safe - end - - def per_page_links(selected=nil, item_count=nil) - values = Setting.per_page_options_array - if item_count && values.any? - if item_count > values.first - max = values.detect {|value| value >= item_count} || item_count - else - max = item_count - end - values = values.select {|value| value <= max || value == selected} - end - if values.empty? || (values.size == 1 && values.first == selected) - return nil - end - links = values.collect do |n| - n == selected ? n : link_to_content_update(n, params.merge(:per_page => n)) - end - l(:label_display_per_page, links.join(', ')) - end - def reorder_links(name, url, method = :post) link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), @@ -529,7 +484,11 @@ end def accesskey(s) - Redmine::AccessKeys.key_for s + @used_accesskeys ||= [] + key = Redmine::AccessKeys.key_for(s) + return nil if @used_accesskeys.include?(key) + @used_accesskeys << key + key end # Formats text according to system settings. @@ -617,8 +576,7 @@ filename, ext, alt, alttext = $1.downcase, $2, $3, $4 # search for the picture in attachments if found = Attachment.latest_attach(attachments, filename) - image_url = url_for :only_path => only_path, :controller => 'attachments', - :action => 'download', :id => found + image_url = download_named_attachment_path(found, found.filename, :only_path => only_path) desc = found.description.to_s.gsub('"', '') if !desc.blank? && alttext.blank? alt = " title=\"#{desc}\" alt=\"#{desc}\"" @@ -647,9 +605,9 @@ esc, all, page, title = $1, $2, $3, $5 if esc.nil? if page =~ /^([^\:]+)\:(.*)$/ - link_project = Project.find_by_identifier($1) || Project.find_by_name($1) - page = $2 - title ||= $1 if page.blank? + identifier, page = $1, $2 + link_project = Project.find_by_identifier(identifier) || Project.find_by_name(identifier) + title ||= identifier if page.blank? end if link_project && link_project.wiki @@ -814,14 +772,14 @@ repository = project.repository end if prefix == 'commit' - if repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%"])) + if repository && (changeset = Changeset.visible.where("repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%").first) link = link_to h("#{project_prefix}#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.identifier}, :class => 'changeset', - :title => truncate_single_line(h(changeset.comments), :length => 100) + :title => truncate_single_line(changeset.comments, :length => 100) end else if repository && User.current.allowed_to?(:browse_repository, project) - name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$} + name =~ %r{^[/\\]*(.*?)(@([^/\\@]+?))?(#(L\d+))?$} path, rev, anchor = $1, $3, $5 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => (prefix == 'export' ? 'raw' : 'entry'), :id => project, :repository_id => repository.identifier_param, :path => to_path_param(path), @@ -835,11 +793,10 @@ when 'attachment' attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil) if attachments && attachment = Attachment.latest_attach(attachments, name) - link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment}, - :class => 'attachment' + link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment') end when 'project' - if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}]) + if p = Project.visible.where("identifier = :s OR LOWER(name) = :s", :s => name.downcase).first link = link_to_project(p, {:only_path => only_path}, :class => 'project') end end @@ -1092,7 +1049,7 @@ (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) + (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe) ), :class => 'progress', :style => "width: #{width};").html_safe + - content_tag('p', legend, :class => 'pourcent').html_safe + content_tag('p', legend, :class => 'percent').html_safe end def checked_image(checked=true) @@ -1136,7 +1093,7 @@ "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " + "showOn: 'button', buttonImageOnly: true, buttonImage: '" + path_to_image('/images/calendar.png') + - "', showButtonPanel: true};") + "', showButtonPanel: true, showWeek: true, showOtherMonths: true, selectOtherMonths: true};") jquery_locale = l('jquery.locale', :default => current_language.to_s) unless jquery_locale == 'en' tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js") @@ -1240,7 +1197,7 @@ def sanitize_anchor_name(anchor) if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java' - anchor.gsub(%r{[^\p{Word}\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-') + anchor.gsub(%r{[^\s\-\p{Word}]}, '').gsub(%r{\s+(\-+\s*)?}, '-') else # TODO: remove when ruby1.8 is no longer supported anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-') @@ -1249,7 +1206,7 @@ # Returns the javascript tags that are included in the html layout head def javascript_heads - tags = javascript_include_tag('jquery-1.7.2-ui-1.8.21-ujs-2.0.3', 'application') + tags = javascript_include_tag('jquery-1.8.3-ui-1.9.2-ujs-2.0.3', 'application') unless User.current.pref.warn_on_leaving_unsaved == '0' tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });") end diff -r 0a574315af3e -r 4f746d8966dd app/helpers/attachments_helper.rb --- a/app/helpers/attachments_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/attachments_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/helpers/auth_sources_helper.rb --- a/app/helpers/auth_sources_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/auth_sources_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/helpers/boards_helper.rb --- a/app/helpers/boards_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/boards_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/helpers/calendars_helper.rb --- a/app/helpers/calendars_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/calendars_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/helpers/context_menus_helper.rb --- a/app/helpers/context_menus_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/context_menus_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -36,7 +36,7 @@ def bulk_update_custom_field_context_menu_link(field, text, value) context_menu_link h(text), - {:controller => 'issues', :action => 'bulk_update', :ids => @issue_ids, :issue => {'custom_field_values' => {field.id => value}}, :back_url => @back}, + bulk_update_issues_path(:ids => @issue_ids, :issue => {'custom_field_values' => {field.id => value}}, :back_url => @back), :method => :post, :selected => (@issue && @issue.custom_field_value(field) == value) end diff -r 0a574315af3e -r 4f746d8966dd app/helpers/custom_fields_helper.rb --- a/app/helpers/custom_fields_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/custom_fields_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,7 +24,7 @@ end # Return custom field html tag corresponding to its format - def custom_field_tag(name, custom_value) + def custom_field_tag(name, custom_value) custom_field = custom_value.custom_field field_name = "#{name}[custom_field_values][#{custom_field.id}]" field_name << "[]" if custom_field.multiple? diff -r 0a574315af3e -r 4f746d8966dd app/helpers/documents_helper.rb --- a/app/helpers/documents_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/documents_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/helpers/enumerations_helper.rb --- a/app/helpers/enumerations_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/enumerations_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/helpers/gantt_helper.rb --- a/app/helpers/gantt_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/gantt_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,19 +24,19 @@ when :in if gantt.zoom < 4 link_to_content_update l(:text_zoom_in), - params.merge(gantt.params.merge(:zoom => (gantt.zoom+1))), + params.merge(gantt.params.merge(:zoom => (gantt.zoom + 1))), :class => 'icon icon-zoom-in' else - content_tag('span', l(:text_zoom_in), :class => 'icon icon-zoom-in').html_safe + content_tag(:span, l(:text_zoom_in), :class => 'icon icon-zoom-in').html_safe end when :out if gantt.zoom > 1 link_to_content_update l(:text_zoom_out), - params.merge(gantt.params.merge(:zoom => (gantt.zoom-1))), + params.merge(gantt.params.merge(:zoom => (gantt.zoom - 1))), :class => 'icon icon-zoom-out' else - content_tag('span', l(:text_zoom_out), :class => 'icon icon-zoom-out').html_safe + content_tag(:span, l(:text_zoom_out), :class => 'icon icon-zoom-out').html_safe end end end diff -r 0a574315af3e -r 4f746d8966dd app/helpers/groups_helper.rb --- a/app/helpers/groups_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/groups_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,4 +24,19 @@ {:name => 'memberships', :partial => 'groups/memberships', :label => :label_project_plural} ] end + + def render_principals_for_new_group_users(group) + scope = User.active.sorted.not_in_group(group).like(params[:q]) + principal_count = scope.count + principal_pages = Redmine::Pagination::Paginator.new principal_count, 100, params['page'] + principals = scope.offset(principal_pages.offset).limit(principal_pages.per_page).all + + s = content_tag('div', principals_check_box_tags('user_ids[]', principals), :id => 'principals') + + links = pagination_links_full(principal_pages, principal_count, :per_page_links => false) {|text, parameters, options| + link_to text, autocomplete_for_user_group_path(group, parameters.merge(:q => params[:q], :format => 'js')), :remote => true + } + + s + content_tag('p', links, :class => 'pagination') + end end diff -r 0a574315af3e -r 4f746d8966dd app/helpers/issue_categories_helper.rb --- a/app/helpers/issue_categories_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/issue_categories_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/helpers/issue_relations_helper.rb --- a/app/helpers/issue_relations_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/issue_relations_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/helpers/issue_statuses_helper.rb --- a/app/helpers/issue_statuses_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/issue_statuses_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/helpers/issues_helper.rb --- a/app/helpers/issues_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/issues_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -184,7 +184,7 @@ def sidebar_queries unless @sidebar_queries - @sidebar_queries = Query.visible.all( + @sidebar_queries = IssueQuery.visible.all( :order => "#{Query.table_name}.name ASC", # Project specific queries and global queries :conditions => (@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id]) @@ -347,6 +347,9 @@ # Find the name of an associated record stored in the field attribute def find_name_by_reflection(field, id) + unless id.present? + return nil + end association = Issue.reflect_on_association(field.to_sym) if association record = association.class_name.constantize.find_by_id(id) @@ -370,44 +373,4 @@ end end end - - def issues_to_csv(issues, project, query, options={}) - decimal_separator = l(:general_csv_decimal_separator) - encoding = l(:general_csv_encoding) - columns = (options[:columns] == 'all' ? query.available_inline_columns : query.inline_columns) - if options[:description] - if description = query.available_columns.detect {|q| q.name == :description} - columns << description - end - end - - export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv| - # csv header fields - csv << [ "#" ] + columns.collect {|c| Redmine::CodesetUtil.from_utf8(c.caption.to_s, encoding) } - - # csv lines - issues.each do |issue| - col_values = columns.collect do |column| - s = if column.is_a?(QueryCustomFieldColumn) - cv = issue.custom_field_values.detect {|v| v.custom_field_id == column.custom_field.id} - show_value(cv) - else - value = column.value(issue) - if value.is_a?(Date) - format_date(value) - elsif value.is_a?(Time) - format_time(value) - elsif value.is_a?(Float) - ("%.2f" % value).gsub('.', decimal_separator) - else - value - end - end - s.to_s - end - csv << [ issue.id.to_s ] + col_values.collect {|c| Redmine::CodesetUtil.from_utf8(c.to_s, encoding) } - end - end - export - end end diff -r 0a574315af3e -r 4f746d8966dd app/helpers/journals_helper.rb --- a/app/helpers/journals_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/journals_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/helpers/mail_handler_helper.rb --- a/app/helpers/mail_handler_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/mail_handler_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/helpers/members_helper.rb --- a/app/helpers/members_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/members_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -18,4 +18,18 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. module MembersHelper + def render_principals_for_new_members(project) + scope = Principal.active.sorted.not_member_of(project).like(params[:q]) + principal_count = scope.count + principal_pages = Redmine::Pagination::Paginator.new principal_count, 100, params['page'] + principals = scope.offset(principal_pages.offset).limit(principal_pages.per_page).all + + s = content_tag('div', principals_check_box_tags('membership[user_ids][]', principals), :id => 'principals') + + links = pagination_links_full(principal_pages, principal_count, :per_page_links => false) {|text, parameters, options| + link_to text, autocomplete_project_memberships_path(project, parameters.merge(:q => params[:q], :format => 'js')), :remote => true + } + + s + content_tag('p', links, :class => 'pagination') + end end diff -r 0a574315af3e -r 4f746d8966dd app/helpers/messages_helper.rb --- a/app/helpers/messages_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/messages_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/helpers/my_helper.rb --- a/app/helpers/my_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/my_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -25,4 +25,54 @@ user.projects.select { |p| p.visible? }.map { |p| p.members.map { |m| m.user_id } }.flatten.sort.uniq.reject { |i| user.id == i } end + def calendar_items(startdt, enddt) + Issue.visible. + where(:project_id => User.current.projects.map(&:id)). + where("(start_date>=? and start_date<=?) or (due_date>=? and due_date<=?)", startdt, enddt, startdt, enddt). + includes(:project, :tracker, :priority, :assigned_to). + all + end + + def documents_items + Document.visible.order("#{Document.table_name}.created_on DESC").limit(10).all + end + + def issuesassignedtome_items + Issue.visible.open. + where(:assigned_to_id => ([User.current.id] + User.current.group_ids)). + limit(10). + includes(:status, :project, :tracker, :priority). + order("#{IssuePriority.table_name}.position DESC, #{Issue.table_name}.updated_on DESC"). + all + end + + def issuesreportedbyme_items + Issue.visible. + where(:author_id => User.current.id). + limit(10). + includes(:status, :project, :tracker). + order("#{Issue.table_name}.updated_on DESC"). + all + end + + def issueswatched_items + Issue.visible.on_active_project.watched_by(User.current.id).recently_updated.limit(10).all + end + + def news_items + News.visible. + where(:project_id => User.current.projects.map(&:id)). + limit(10). + includes(:project, :author). + order("#{News.table_name}.created_on DESC"). + all + end + + def timelog_items + TimeEntry. + where("#{TimeEntry.table_name}.user_id = ? AND #{TimeEntry.table_name}.spent_on BETWEEN ? AND ?", User.current.id, Date.today - 6, Date.today). + includes(:activity, :project, {:issue => [:tracker, :status]}). + order("#{TimeEntry.table_name}.spent_on DESC, #{Project.table_name}.name ASC, #{Tracker.table_name}.position ASC, #{Issue.table_name}.id ASC"). + all + end end diff -r 0a574315af3e -r 4f746d8966dd app/helpers/news_helper.rb --- a/app/helpers/news_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/news_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/helpers/projects_helper.rb --- a/app/helpers/projects_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/projects_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/helpers/queries_helper.rb --- a/app/helpers/queries_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/queries_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,32 +24,35 @@ def filters_options(query) options = [[]] - sorted_options = query.available_filters.sort do |a, b| - ord = 0 - if !(a[1][:order] == 20 && b[1][:order] == 20) - ord = a[1][:order] <=> b[1][:order] - else - cn = (CustomField::CUSTOM_FIELDS_NAMES.index(a[1][:field].class.name) <=> - CustomField::CUSTOM_FIELDS_NAMES.index(b[1][:field].class.name)) - if cn != 0 - ord = cn - else - f = (a[1][:field] <=> b[1][:field]) - if f != 0 - ord = f - else - # assigned_to or author - ord = (a[0] <=> b[0]) - end - end - end - ord - end - options += sorted_options.map do |field, field_options| + options += query.available_filters.map do |field, field_options| [field_options[:name], field] end end + def query_filters_hidden_tags(query) + tags = ''.html_safe + query.filters.each do |field, options| + tags << hidden_field_tag("f[]", field, :id => nil) + tags << hidden_field_tag("op[#{field}]", options[:operator], :id => nil) + options[:values].each do |value| + tags << hidden_field_tag("v[#{field}][]", value, :id => nil) + end + end + tags + end + + def query_columns_hidden_tags(query) + tags = ''.html_safe + query.columns.each do |column| + tags << hidden_field_tag("c[]", column.name, :id => nil) + end + tags + end + + def query_hidden_tags(query) + query_filters_hidden_tags(query) + query_columns_hidden_tags(query) + end + def available_block_columns_tags(query) tags = ''.html_safe query.available_block_columns.each do |column| @@ -58,6 +61,19 @@ tags end + def query_available_inline_columns_options(query) + (query.available_inline_columns - query.columns).reject(&:frozen?).collect {|column| [column.caption, column.name]} + end + + def query_selected_inline_columns_options(query) + (query.inline_columns & query.available_inline_columns).reject(&:frozen?).collect {|column| [column.caption, column.name]} + end + + def render_query_columns_selection(query, options={}) + tag_name = (options[:name] || 'c') + '[]' + render :partial => 'queries/columns', :locals => {:query => query, :tag_name => tag_name} + end + def column_header(column) column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption, :default_order => column.default_order) : @@ -88,7 +104,9 @@ when 'Date' format_date(value) when 'Fixnum' - if column.name == :done_ratio + if column.name == :id + link_to value, issue_path(issue) + elsif column.name == :done_ratio progress_bar(value, :width => '80px') else value.to_s @@ -106,7 +124,7 @@ when 'FalseClass' l(:general_text_No) when 'Issue' - link_to_issue(value, :subject => false) + value.visible? ? link_to_issue(value) : "##{value.id}" when 'IssueRelation' other = value.other_issue(issue) content_tag('span', @@ -117,26 +135,71 @@ end end + def csv_content(column, issue) + value = column.value(issue) + if value.is_a?(Array) + value.collect {|v| csv_value(column, issue, v)}.compact.join(', ') + else + csv_value(column, issue, value) + end + end + + def csv_value(column, issue, value) + case value.class.name + when 'Time' + format_time(value) + when 'Date' + format_date(value) + when 'Float' + sprintf("%.2f", value).gsub('.', l(:general_csv_decimal_separator)) + when 'IssueRelation' + other = value.other_issue(issue) + l(value.label_for(issue)) + " ##{other.id}" + else + value.to_s + end + end + + def query_to_csv(items, query, options={}) + encoding = l(:general_csv_encoding) + columns = (options[:columns] == 'all' ? query.available_inline_columns : query.inline_columns) + query.available_block_columns.each do |column| + if options[column.name].present? + columns << column + end + end + + export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv| + # csv header fields + csv << columns.collect {|c| Redmine::CodesetUtil.from_utf8(c.caption.to_s, encoding) } + # csv lines + items.each do |item| + csv << columns.collect {|c| Redmine::CodesetUtil.from_utf8(csv_content(c, item), encoding) } + end + end + export + end + # Retrieve query from session or build a new query def retrieve_query if !params[:query_id].blank? cond = "project_id IS NULL" cond << " OR project_id = #{@project.id}" if @project - @query = Query.find(params[:query_id], :conditions => cond) + @query = IssueQuery.find(params[:query_id], :conditions => cond) raise ::Unauthorized unless @query.visible? @query.project = @project session[:query] = {:id => @query.id, :project_id => @query.project_id} sort_clear elsif api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil) # Give it a name, required to be valid - @query = Query.new(:name => "_") + @query = IssueQuery.new(:name => "_") @query.project = @project - build_query_from_params + @query.build_from_params(params) session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names} else # retrieve from session - @query = Query.find_by_id(session[:query][:id]) if session[:query][:id] - @query ||= Query.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names]) + @query = IssueQuery.find_by_id(session[:query][:id]) if session[:query][:id] + @query ||= IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names]) @query.project = @project end end @@ -144,10 +207,10 @@ def retrieve_query_from_session if session[:query] if session[:query][:id] - @query = Query.find_by_id(session[:query][:id]) + @query = IssueQuery.find_by_id(session[:query][:id]) return unless @query else - @query = Query.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names]) + @query = IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names]) end if session[:query].has_key?(:project_id) @query.project_id = session[:query][:project_id] @@ -157,17 +220,4 @@ @query end end - - def build_query_from_params - if params[:fields] || params[:f] - @query.filters = {} - @query.add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v]) - else - @query.available_filters.keys.each do |field| - @query.add_short_filter(field, params[field]) if params[field] - end - end - @query.group_by = params[:group_by] || (params[:query] && params[:query][:group_by]) - @query.column_names = params[:c] || (params[:query] && params[:query][:column_names]) - end end diff -r 0a574315af3e -r 4f746d8966dd app/helpers/reports_helper.rb --- a/app/helpers/reports_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/reports_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -35,4 +35,9 @@ a = aggregate data, criteria a > 0 ? link_to(h(a), *args) : '-' end + + def aggregate_path(project, field, row, options={}) + parameters = {:set_filter => 1, :subproject_id => '!*', field => row.id}.merge(options) + project_issues_path(row.is_a?(Project) ? row : project, parameters) + end end diff -r 0a574315af3e -r 4f746d8966dd app/helpers/repositories_helper.rb --- a/app/helpers/repositories_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/repositories_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -43,7 +43,7 @@ end def render_changeset_changes - changes = @changeset.filechanges.find(:all, :limit => 1000, :order => 'path').collect do |change| + changes = @changeset.filechanges.limit(1000).reorder('path').all.collect do |change| case change.action when 'A' # Detects moved/copied files diff -r 0a574315af3e -r 4f746d8966dd app/helpers/roles_helper.rb --- a/app/helpers/roles_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/roles_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/helpers/routes_helper.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/helpers/routes_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,39 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2013 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 RoutesHelper + + # Returns the path to project issues or to the cross-project + # issue list if project is nil + def _project_issues_path(project, *args) + if project + project_issues_path(project, *args) + else + issues_path(*args) + end + end + + def _project_calendar_path(project, *args) + project ? project_calendar_path(project, *args) : issues_calendar_path(*args) + end + + def _project_gantt_path(project, *args) + project ? project_gantt_path(project, *args) : issues_gantt_path(*args) + end +end diff -r 0a574315af3e -r 4f746d8966dd app/helpers/search_helper.rb --- a/app/helpers/search_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/search_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/helpers/settings_helper.rb --- a/app/helpers/settings_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/settings_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -53,7 +53,7 @@ check_box_tag( "settings[#{setting}][]", value, - Setting.send(setting).include?(value), + setting_values.include?(value), :id => nil ) + text.to_s, :class => (options[:inline] ? 'inline' : 'block') diff -r 0a574315af3e -r 4f746d8966dd app/helpers/sort_helper.rb --- a/app/helpers/sort_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/sort_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -80,12 +80,13 @@ @criteria.collect {|k,o| k + (o ? '' : ':desc')}.join(',') end + # Returns an array of SQL fragments used to sort the list def to_sql sql = @criteria.collect do |k,o| if s = @available_criteria[k] - (o ? s.to_a : s.to_a.collect {|c| append_desc(c)}).join(', ') + (o ? s.to_a : s.to_a.collect {|c| append_desc(c)}) end - end.compact.join(', ') + end.flatten.compact sql.blank? ? nil : sql end diff -r 0a574315af3e -r 4f746d8966dd app/helpers/timelog_helper.rb --- a/app/helpers/timelog_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/timelog_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -56,10 +56,10 @@ end def select_hours(data, criteria, value) - if value.to_s.empty? - data.select {|row| row[criteria].blank? } + if value.to_s.empty? + data.select {|row| row[criteria].blank? } else - data.select {|row| row[criteria].to_s == value.to_s} + data.select {|row| row[criteria].to_s == value.to_s} end end @@ -86,49 +86,6 @@ value) end - def entries_to_csv(entries) - decimal_separator = l(:general_csv_decimal_separator) - custom_fields = TimeEntryCustomField.find(:all) - export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv| - # csv header fields - headers = [l(:field_spent_on), - l(:field_user), - l(:field_activity), - l(:field_project), - l(:field_issue), - l(:field_tracker), - l(:field_subject), - l(:field_hours), - l(:field_comments) - ] - # Export custom fields - headers += custom_fields.collect(&:name) - - csv << headers.collect {|c| Redmine::CodesetUtil.from_utf8( - c.to_s, - l(:general_csv_encoding) ) } - # csv lines - entries.each do |entry| - fields = [format_date(entry.spent_on), - entry.user, - entry.activity, - entry.project, - (entry.issue ? entry.issue.id : nil), - (entry.issue ? entry.issue.tracker : nil), - (entry.issue ? entry.issue.subject : nil), - entry.hours.to_s.gsub('.', decimal_separator), - entry.comments - ] - fields += custom_fields.collect {|f| show_value(entry.custom_field_values.detect {|v| v.custom_field_id == f.id}) } - - csv << fields.collect {|c| Redmine::CodesetUtil.from_utf8( - c.to_s, - l(:general_csv_encoding) ) } - end - end - export - end - def format_criteria_value(criteria_options, value) if value.blank? "[#{l(:label_none)}]" @@ -150,14 +107,14 @@ # Column headers headers = report.criteria.collect {|criteria| l(report.available_criteria[criteria][:label]) } headers += report.periods - headers << l(:label_total) + headers << l(:label_total_time) csv << headers.collect {|c| Redmine::CodesetUtil.from_utf8( c.to_s, l(:general_csv_encoding) ) } # Content report_criteria_to_csv(csv, report.available_criteria, report.columns, report.criteria, report.periods, report.hours) # Total row - str_total = Redmine::CodesetUtil.from_utf8(l(:label_total), l(:general_csv_encoding)) + str_total = Redmine::CodesetUtil.from_utf8(l(:label_total_time), l(:general_csv_encoding)) row = [ str_total ] + [''] * (report.criteria.size - 1) total = 0 report.periods.each do |period| diff -r 0a574315af3e -r 4f746d8966dd app/helpers/trackers_helper.rb --- a/app/helpers/trackers_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/trackers_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/helpers/users_helper.rb --- a/app/helpers/users_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/users_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/helpers/versions_helper.rb --- a/app/helpers/versions_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/versions_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/helpers/watchers_helper.rb --- a/app/helpers/watchers_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/watchers_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -20,24 +20,31 @@ module WatchersHelper def watcher_tag(object, user, options={}) - content_tag("span", watcher_link(object, user), :class => watcher_css(object)) + ActiveSupport::Deprecation.warn "#watcher_tag is deprecated and will be removed in Redmine 3.0. Use #watcher_link instead." + watcher_link(object, user) end - def watcher_link(object, user) - return '' unless user && user.logged? && object.respond_to?('watched_by?') - watched = object.watched_by?(user) - url = {:controller => 'watchers', - :action => (watched ? 'unwatch' : 'watch'), - :object_type => object.class.to_s.underscore, - :object_id => object.id} - link_to((watched ? l(:button_unwatch) : l(:button_watch)), url, - :remote => true, :method => 'post', :class => (watched ? 'icon icon-fav' : 'icon icon-fav-off')) + def watcher_link(objects, user) + return '' unless user && user.logged? + objects = Array.wrap(objects) + watched = objects.any? {|object| object.watched_by?(user)} + css = [watcher_css(objects), watched ? 'icon icon-fav' : 'icon icon-fav-off'].join(' ') + text = watched ? l(:button_unwatch) : l(:button_watch) + url = watch_path( + :object_type => objects.first.class.to_s.underscore, + :object_id => (objects.size == 1 ? objects.first.id : objects.map(&:id).sort) + ) + method = watched ? 'delete' : 'post' + + link_to text, url, :remote => true, :method => method, :class => css end # Returns the css class used to identify watch links for a given +object+ - def watcher_css(object) - "#{object.class.to_s.underscore}-#{object.id}-watcher" + def watcher_css(objects) + objects = Array.wrap(objects) + id = (objects.size == 1 ? objects.first.id : 'bulk') + "#{objects.first.class.to_s.underscore}-#{id}-watcher" end # Returns a comma separated list of users watching the given object @@ -56,11 +63,11 @@ :user_id => user} s << ' ' s << link_to(image_tag('delete.png'), url, - :remote => true, :method => 'post', :style => "vertical-align: middle", :class => "delete") + :remote => true, :method => 'delete', :class => "delete") end - content << content_tag('li', s) + content << content_tag('li', s, :class => "user-#{user.id}") end - content.present? ? content_tag('ul', content) : content + content.present? ? content_tag('ul', content, :class => 'watchers') : content end def watchers_checkboxes(object, users, checked=nil) diff -r 0a574315af3e -r 4f746d8966dd app/helpers/welcome_helper.rb --- a/app/helpers/welcome_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/welcome_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/helpers/wiki_helper.rb --- a/app/helpers/wiki_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/wiki_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/helpers/workflows_helper.rb --- a/app/helpers/workflows_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/helpers/workflows_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/models/attachment.rb --- a/app/models/attachment.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/attachment.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,6 +16,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require "digest/md5" +require "fileutils" class Attachment < ActiveRecord::Base belongs_to :container, :polymorphic => true @@ -92,9 +93,6 @@ def filename=(arg) write_attribute :filename, sanitize_filename(arg.to_s) - if new_record? && disk_filename.blank? - self.disk_filename = Attachment.disk_filename(filename) - end filename end @@ -102,7 +100,13 @@ # and computes its MD5 hash def files_to_final_location if @temp_file && (@temp_file.size > 0) + self.disk_directory = target_directory + self.disk_filename = Attachment.disk_filename(filename, disk_directory) logger.info("Saving attachment '#{self.diskfile}' (#{@temp_file.size} bytes)") + path = File.dirname(diskfile) + unless File.directory?(path) + FileUtils.mkdir_p(path) + end md5 = Digest::MD5.new File.open(diskfile, "wb") do |f| if @temp_file.respond_to?(:read) @@ -134,7 +138,7 @@ # Returns file's location on disk def diskfile - File.join(self.class.storage_path, disk_filename.to_s) + File.join(self.class.storage_path, disk_directory.to_s, disk_filename.to_s) end def title @@ -154,11 +158,19 @@ end def visible?(user=User.current) - container && container.attachments_visible?(user) + if container_id + container && container.attachments_visible?(user) + else + author == user + end end def deletable?(user=User.current) - container && container.attachments_deletable?(user) + if container_id + container && container.attachments_deletable?(user) + else + author == user + end end def image? @@ -251,6 +263,26 @@ Attachment.where("created_on < ? AND (container_type IS NULL OR container_type = '')", Time.now - age).destroy_all end + # Moves an existing attachment to its target directory + def move_to_target_directory! + if !new_record? & readable? + src = diskfile + self.disk_directory = target_directory + dest = diskfile + if src != dest && FileUtils.mkdir_p(File.dirname(dest)) && FileUtils.mv(src, dest) + update_column :disk_directory, disk_directory + end + end + end + + # Moves existing attachments that are stored at the root of the files + # directory (ie. created before Redmine 2.3) to their target subdirectories + def self.move_from_root_to_target_directory + Attachment.where("disk_directory IS NULL OR disk_directory = ''").find_each do |attachment| + attachment.move_to_target_directory! + end + end + private # Physically deletes the file from the file system @@ -268,8 +300,15 @@ @filename = just_filename.gsub(/[\/\?\%\*\:\|\"\'<>]+/, '_') end - # Returns an ASCII or hashed filename - def self.disk_filename(filename) + # Returns the subdirectory in which the attachment will be saved + def target_directory + time = created_on || DateTime.now + time.strftime("%Y/%m") + end + + # Returns an ASCII or hashed filename that do not + # exists yet in the given subdirectory + def self.disk_filename(filename, directory=nil) timestamp = DateTime.now.strftime("%y%m%d%H%M%S") ascii = '' if filename =~ %r{^[a-zA-Z0-9_\.\-]*$} @@ -279,7 +318,7 @@ # keep the extension if any ascii << $1 if filename =~ %r{(\.[a-zA-Z0-9]+)$} end - while File.exist?(File.join(@@storage_path, "#{timestamp}_#{ascii}")) + while File.exist?(File.join(storage_path, directory.to_s, "#{timestamp}_#{ascii}")) timestamp.succ! end "#{timestamp}_#{ascii}" diff -r 0a574315af3e -r 4f746d8966dd app/models/auth_source.rb --- a/app/models/auth_source.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/auth_source.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -48,6 +48,24 @@ write_ciphered_attribute(:account_password, arg) end + def searchable? + false + end + + def self.search(q) + results = [] + AuthSource.all.each do |source| + begin + if source.searchable? + results += source.search(q) + end + rescue AuthSourceException => e + logger.error "Error while searching users in #{source.name}: #{e.message}" + end + end + results + end + def allow_password_changes? self.class.allow_password_changes? end diff -r 0a574315af3e -r 4f746d8966dd app/models/auth_source_ldap.rb --- a/app/models/auth_source_ldap.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/auth_source_ldap.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,7 +15,6 @@ # 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 'net/ldap' require 'net/ldap/dn' require 'timeout' @@ -64,6 +63,32 @@ "LDAP" end + # Returns true if this source can be searched for users + def searchable? + !account.to_s.include?("$login") && %w(login firstname lastname mail).all? {|a| send("attr_#{a}?")} + end + + # Searches the source for users and returns an array of results + def search(q) + q = q.to_s.strip + return [] unless searchable? && q.present? + + results = [] + search_filter = base_filter & Net::LDAP::Filter.begins(self.attr_login, q) + ldap_con = initialize_ldap_con(self.account, self.account_password) + ldap_con.search(:base => self.base_dn, + :filter => search_filter, + :attributes => ['dn', self.attr_login, self.attr_firstname, self.attr_lastname, self.attr_mail], + :size => 10) do |entry| + attrs = get_user_attributes_from_ldap_entry(entry) + attrs[:login] = AuthSourceLdap.get_attr(entry, self.attr_login) + results << attrs + end + results + rescue Net::LDAP::LdapError => e + raise AuthSourceException.new(e.message) + end + private def with_timeout(&block) @@ -84,6 +109,14 @@ nil end + def base_filter + filter = Net::LDAP::Filter.eq("objectClass", "*") + if f = ldap_filter + filter = filter & f + end + filter + end + def validate_filter if filter.present? && ldap_filter.nil? errors.add(:filter, :invalid) @@ -140,14 +173,8 @@ else ldap_con = initialize_ldap_con(self.account, self.account_password) end - login_filter = Net::LDAP::Filter.eq( self.attr_login, login ) - object_filter = Net::LDAP::Filter.eq( "objectClass", "*" ) attrs = {} - - search_filter = object_filter & login_filter - if f = ldap_filter - search_filter = search_filter & f - end + search_filter = base_filter & Net::LDAP::Filter.eq(self.attr_login, login) ldap_con.search( :base => self.base_dn, :filter => search_filter, diff -r 0a574315af3e -r 4f746d8966dd app/models/board.rb --- a/app/models/board.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/board.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -30,8 +30,9 @@ validates_length_of :description, :maximum => 255 validate :validate_board - scope :visible, lambda {|*args| { :include => :project, - :conditions => Project.allowed_to_condition(args.shift || User.current, :view_messages, *args) } } + scope :visible, lambda {|*args| + includes(:project).where(Project.allowed_to_condition(args.shift || User.current, :view_messages, *args)) + } safe_attributes 'name', 'description', 'parent_id', 'move_to' diff -r 0a574315af3e -r 4f746d8966dd app/models/change.rb --- a/app/models/change.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/change.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/models/changeset.rb --- a/app/models/changeset.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/changeset.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,8 +15,6 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -require 'iconv' - class Changeset < ActiveRecord::Base belongs_to :repository belongs_to :user @@ -49,9 +47,9 @@ validates_uniqueness_of :revision, :scope => :repository_id validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true - scope :visible, - lambda {|*args| { :include => {:repository => :project}, - :conditions => Project.allowed_to_condition(args.shift || User.current, :view_changesets, *args) } } + scope :visible, lambda {|*args| + includes(:repository => :project).where(Project.allowed_to_condition(args.shift || User.current, :view_changesets, *args)) + } after_create :scan_for_issues before_create :before_create_cs diff -r 0a574315af3e -r 4f746d8966dd app/models/comment.rb --- a/app/models/comment.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/comment.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/models/comment_observer.rb --- a/app/models/comment_observer.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/comment_observer.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/models/custom_field.rb --- a/app/models/custom_field.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/custom_field.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -29,6 +29,9 @@ validate :validate_custom_field before_validation :set_searchable + after_save :handle_multiplicity_change + + scope :sorted, lambda { order("#{table_name}.position ASC") } CUSTOM_FIELDS_TABS = [ {:name => 'IssueCustomField', :partial => 'custom_fields/index', @@ -169,7 +172,7 @@ keyword end end - + # Returns a ORDER BY clause that can used to sort customized # objects by their value of the custom field. # Returns nil if the custom field can not be used for sorting. @@ -178,18 +181,12 @@ 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.base_class.name}'" + - " AND cv_sort.customized_id=#{self.class.customized_class.table_name}.id" + - " AND cv_sort.custom_field_id=#{id} LIMIT 1), '')" + "COALESCE(#{join_alias}.value, '')" 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.base_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)" + "CAST(CASE #{join_alias}.value WHEN '' THEN '0' ELSE #{join_alias}.value END AS decimal(30,3))" when 'user', 'version' value_class.fields_for_order_statement(value_join_alias) else @@ -199,16 +196,13 @@ # Returns a GROUP BY clause that can used to group by custom value # Returns nil if the custom field can not be used for grouping. - def group_statement + def group_statement return nil if multiple? case field_format when 'list', 'date', 'bool', 'int' order_statement when 'user', 'version' - "COALESCE((SELECT cv_sort.value FROM #{CustomValue.table_name} cv_sort" + - " WHERE cv_sort.customized_type='#{self.class.customized_class.base_class.name}'" + - " AND cv_sort.customized_id=#{self.class.customized_class.table_name}.id" + - " AND cv_sort.custom_field_id=#{id} LIMIT 1), '')" + "COALESCE(#{join_alias}.value, '')" else nil end @@ -227,7 +221,26 @@ " AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" + " AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)" + " LEFT OUTER JOIN #{value_class.table_name} #{value_join_alias}" + - " ON CAST(#{join_alias}.value as decimal(60,0)) = #{value_join_alias}.id" + " ON CAST(CASE #{join_alias}.value WHEN '' THEN '0' ELSE #{join_alias}.value END AS decimal(30,0)) = #{value_join_alias}.id" + when 'int', 'float' + "LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" + + " ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" + + " AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" + + " AND #{join_alias}.custom_field_id = #{id}" + + " AND #{join_alias}.value <> ''" + + " AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" + + " WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" + + " AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" + + " AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)" + when 'string', 'text', 'list', 'date', 'bool' + "LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" + + " ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" + + " AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" + + " AND #{join_alias}.custom_field_id = #{id}" + + " AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" + + " WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" + + " AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" + + " AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)" else nil end @@ -262,7 +275,7 @@ # to move in project_custom_field def self.for_all - find(:all, :conditions => ["is_for_all=?", true], :order => 'position') + where(:is_for_all => true).order('position').all end def type_name @@ -323,4 +336,20 @@ end errs end + + # Removes multiple values for the custom field after setting the multiple attribute to false + # We kepp the value with the highest id for each customized object + def handle_multiplicity_change + if !new_record? && multiple_was && !multiple + ids = custom_values. + where("EXISTS(SELECT 1 FROM #{CustomValue.table_name} cve WHERE cve.custom_field_id = #{CustomValue.table_name}.custom_field_id" + + " AND cve.customized_type = #{CustomValue.table_name}.customized_type AND cve.customized_id = #{CustomValue.table_name}.customized_id" + + " AND cve.id > #{CustomValue.table_name}.id)"). + pluck(:id) + + if ids.any? + custom_values.where(:id => ids).delete_all + end + end + end end diff -r 0a574315af3e -r 4f746d8966dd app/models/custom_field_value.rb --- a/app/models/custom_field_value.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/custom_field_value.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/models/custom_value.rb --- a/app/models/custom_value.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/custom_value.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/models/document.rb --- a/app/models/document.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/document.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,19 +19,20 @@ include Redmine::SafeAttributes belongs_to :project belongs_to :category, :class_name => "DocumentCategory", :foreign_key => "category_id" - acts_as_attachable :delete_permission => :manage_documents + acts_as_attachable :delete_permission => :delete_documents acts_as_searchable :columns => ['title', "#{table_name}.description"], :include => :project acts_as_event :title => Proc.new {|o| "#{l(:label_document)}: #{o.title}"}, - :author => Proc.new {|o| (a = o.attachments.find(:first, :order => "#{Attachment.table_name}.created_on ASC")) ? a.author : nil }, + :author => Proc.new {|o| o.attachments.reorder("#{Attachment.table_name}.created_on ASC").first.try(:author) }, :url => Proc.new {|o| {:controller => 'documents', :action => 'show', :id => o.id}} acts_as_activity_provider :find_options => {:include => :project} validates_presence_of :project, :title, :category validates_length_of :title, :maximum => 60 - scope :visible, lambda {|*args| { :include => :project, - :conditions => Project.allowed_to_condition(args.shift || User.current, :view_documents, *args) } } + scope :visible, lambda {|*args| + includes(:project).where(Project.allowed_to_condition(args.shift || User.current, :view_documents, *args)) + } safe_attributes 'category_id', 'title', 'description' diff -r 0a574315af3e -r 4f746d8966dd app/models/document_category.rb --- a/app/models/document_category.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/document_category.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/models/document_category_custom_field.rb --- a/app/models/document_category_custom_field.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/document_category_custom_field.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/models/document_observer.rb --- a/app/models/document_observer.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/document_observer.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/models/enabled_module.rb --- a/app/models/enabled_module.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/enabled_module.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/models/enumeration.rb --- a/app/models/enumeration.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/enumeration.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,7 +24,7 @@ acts_as_list :scope => 'type = \'#{type}\'' acts_as_customizable - acts_as_tree :order => 'position ASC' + acts_as_tree :order => "#{Enumeration.table_name}.position ASC" before_destroy :check_integrity before_save :check_default @@ -35,9 +35,10 @@ validates_uniqueness_of :name, :scope => [:type, :project_id] validates_length_of :name, :maximum => 30 - scope :shared, where(:project_id => nil) - scope :sorted, order("#{table_name}.position ASC") - scope :active, where(:active => true) + scope :shared, lambda { where(:project_id => nil) } + scope :sorted, lambda { order("#{table_name}.position ASC") } + scope :active, lambda { where(:active => true) } + scope :system, lambda { where(:project_id => nil) } scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)} def self.default diff -r 0a574315af3e -r 4f746d8966dd app/models/group.rb --- a/app/models/group.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/group.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -25,11 +25,12 @@ validates_presence_of :lastname validates_uniqueness_of :lastname, :case_sensitive => false - validates_length_of :lastname, :maximum => 30 + validates_length_of :lastname, :maximum => 255 before_destroy :remove_references_before_destroy - scope :sorted, order("#{table_name}.lastname ASC") + scope :sorted, lambda { order("#{table_name}.lastname ASC") } + scope :named, lambda {|arg| where("LOWER(#{table_name}.lastname) = LOWER(?)", arg.to_s.strip)} safe_attributes 'name', 'user_ids', @@ -62,8 +63,11 @@ def user_removed(user) members.each do |member| - MemberRole.find(:all, :include => :member, - :conditions => ["#{Member.table_name}.user_id = ? AND #{MemberRole.table_name}.inherited_from IN (?)", user.id, member.member_role_ids]).each(&:destroy) + MemberRole. + includes(:member). + where("#{Member.table_name}.user_id = ? AND #{MemberRole.table_name}.inherited_from IN (?)", user.id, member.member_role_ids). + all. + each(&:destroy) end end diff -r 0a574315af3e -r 4f746d8966dd app/models/group_custom_field.rb --- a/app/models/group_custom_field.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/group_custom_field.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/models/issue.rb --- a/app/models/issue.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/issue.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -67,24 +67,31 @@ validates_length_of :subject, :maximum => 255 validates_inclusion_of :done_ratio, :in => 0..100 - validates_numericality_of :estimated_hours, :allow_nil => true + validates :estimated_hours, :numericality => {:greater_than_or_equal_to => 0, :allow_nil => true, :message => :invalid} + validates :start_date, :date => true + validates :due_date, :date => true validate :validate_issue, :validate_required_fields - scope :visible, - lambda {|*args| { :include => :project, - :conditions => Issue.visible_condition(args.shift || User.current, *args) } } + scope :visible, lambda {|*args| + includes(:project).where(Issue.visible_condition(args.shift || User.current, *args)) + } scope :open, lambda {|*args| is_closed = args.size > 0 ? !args.first : false - {:conditions => ["#{IssueStatus.table_name}.is_closed = ?", is_closed], :include => :status} + includes(:status).where("#{IssueStatus.table_name}.is_closed = ?", is_closed) } - scope :recently_updated, :order => "#{Issue.table_name}.updated_on DESC" - scope :on_active_project, :include => [:status, :project, :tracker], - :conditions => ["#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"] + scope :recently_updated, lambda { order("#{Issue.table_name}.updated_on DESC") } + scope :on_active_project, lambda { + includes(:status, :project, :tracker).where("#{Project.table_name}.status = ?", Project::STATUS_ACTIVE) + } + scope :fixed_version, lambda {|versions| + ids = [versions].flatten.compact.map {|v| v.is_a?(Version) ? v.id : v} + ids.any? ? where(:fixed_version_id => ids) : where('1=0') + } before_create :default_assign - before_save :close_duplicates, :update_done_ratio_from_issue_status, :force_updated_on_change + before_save :close_duplicates, :update_done_ratio_from_issue_status, :force_updated_on_change, :update_closed_on after_save {|issue| issue.send :after_project_change if !issue.id_changed? && issue.project_id_changed?} after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes, :create_journal # Should be after_create but would be called before previous after_save callbacks @@ -133,6 +140,11 @@ end end + # Returns true if user or current user is allowed to edit or add a note to the issue + def editable?(user=User.current) + user.allowed_to?(:edit_issues, project) || user.allowed_to?(:add_issue_notes, project) + end + def initialize(attributes=nil, *args) super if new_record? @@ -143,6 +155,13 @@ end end + def create_or_update + super + ensure + @status_was = nil + end + private :create_or_update + # AR#Persistence#destroy would raise and RecordNotFound exception # if the issue was already deleted or updated (non matching lock_version). # This is a problem when bulk deleting issues or deleting a project @@ -165,10 +184,12 @@ super end + alias :base_reload :reload def reload(*args) @workflow_rule_by_attribute = nil @assignable_versions = nil - super + @relations = nil + base_reload(*args) end # Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields @@ -526,14 +547,6 @@ end def validate_issue - if due_date.nil? && @attributes['due_date'].present? - errors.add :due_date, :not_a_date - end - - if start_date.nil? && @attributes['start_date'].present? - errors.add :start_date, :not_a_date - end - if due_date && start_date && due_date < start_date errors.add :due_date, :greater_than_start_date end @@ -563,6 +576,8 @@ elsif @parent_issue if !valid_parent_project?(@parent_issue) errors.add :parent_issue_id, :invalid + elsif (@parent_issue != parent) && (all_dependent_issues.include?(@parent_issue) || @parent_issue.all_dependent_issues.include?(self)) + errors.add :parent_issue_id, :invalid elsif !new_record? # moving an existing issue if @parent_issue.root_id != root_id @@ -633,6 +648,14 @@ scope end + # Returns the initial status of the issue + # Returns nil for a new issue + def status_was + if status_id_was && status_id_was.to_i > 0 + @status_was ||= IssueStatus.find_by_id(status_id_was) + end + end + # Return true if the issue is closed, otherwise false def closed? self.status.is_closed? @@ -653,9 +676,7 @@ # Return true if the issue is being closed def closing? if !new_record? && status_id_changed? - status_was = IssueStatus.find_by_id(status_id_was) - status_new = IssueStatus.find_by_id(status_id) - if status_was && status_new && !status_was.is_closed? && status_new.is_closed? + if status_was && status && !status_was.is_closed? && status.is_closed? return true end end @@ -785,7 +806,7 @@ end def relations - @relations ||= IssueRelations.new(self, (relations_from + relations_to).sort) + @relations ||= IssueRelation::Relations.new(self, (relations_from + relations_to).sort) end # Preloads relations for a collection of issues @@ -822,7 +843,7 @@ relations_from.select {|relation| relation.issue_from_id == issue.id} + relations_to.select {|relation| relation.issue_to_id == issue.id} - issue.instance_variable_set "@relations", IssueRelations.new(issue, relations.sort) + issue.instance_variable_set "@relations", IssueRelation::Relations.new(issue, relations.sort) end end end @@ -832,14 +853,18 @@ IssueRelation.find(relation_id, :conditions => ["issue_to_id = ? OR issue_from_id = ?", id, id]) end + # Returns all the other issues that depend on the issue def all_dependent_issues(except=[]) except << self dependencies = [] - relations_from.each do |relation| - if relation.issue_to && !except.include?(relation.issue_to) - dependencies << relation.issue_to - dependencies += relation.issue_to.all_dependent_issues(except) - end + dependencies += relations_from.map(&:issue_to) + dependencies += children unless leaf? + dependencies.compact! + dependencies -= except + dependencies += dependencies.map {|issue| issue.all_dependent_issues(except)}.flatten + if parent + dependencies << parent + dependencies += parent.all_dependent_issues(except + parent.descendants) end dependencies end @@ -873,7 +898,7 @@ @soonest_start = nil if reload @soonest_start ||= ( relations_to(reload).collect{|relation| relation.successor_soonest_start} + - ancestors.collect(&:soonest_start) + [(@parent_issue || parent).try(:soonest_start)] ).compact.max end @@ -936,7 +961,7 @@ # Returns a string of css classes that apply to the issue def css_classes - s = "issue status-#{status_id} #{priority.try(:css_classes)}" + s = "issue tracker-#{tracker_id} status-#{status_id} #{priority.try(:css_classes)}" s << ' closed' if closed? s << ' overdue' if overdue? s << ' child' if child? @@ -1005,8 +1030,8 @@ end end - # Returns true if issue's project is a valid - # parent issue project + # Returns true if issue's project is a valid + # parent issue project def valid_parent_project?(issue=parent) return true if issue.nil? || issue.project_id == project_id @@ -1128,20 +1153,27 @@ end unless @copied_from.leaf? || @copy_options[:subtasks] == false - @copied_from.children.each do |child| + copy_options = (@copy_options || {}).merge(:subtasks => false) + copied_issue_ids = {@copied_from.id => self.id} + @copied_from.reload.descendants.reorder("#{Issue.table_name}.lft").each do |child| + # Do not copy self when copying an issue as a descendant of the copied issue + next if child == self + # Do not copy subtasks of issues that were not copied + next unless copied_issue_ids[child.parent_id] + # Do not copy subtasks that are not visible to avoid potential disclosure of private data unless child.visible? - # Do not copy subtasks that are not visible to avoid potential disclosure of private data logger.error "Subtask ##{child.id} was not copied during ##{@copied_from.id} copy because it is not visible to the current user" if logger next end - copy = Issue.new.copy_from(child, @copy_options) + copy = Issue.new.copy_from(child, copy_options) copy.author = author copy.project = project - copy.parent_issue_id = id - # Children subtasks are copied recursively + copy.parent_issue_id = copied_issue_ids[child.parent_id] unless copy.save logger.error "Could not copy subtask ##{child.id} while copying ##{@copied_from.id} to ##{id} due to validation errors: #{copy.errors.full_messages.join(', ')}" if logger + next end + copied_issue_ids[child.id] = copy.id end end @after_create_from_copy_handled = true @@ -1303,10 +1335,23 @@ end end - # Make sure updated_on is updated when adding a note + # Make sure updated_on is updated when adding a note and set updated_on now + # so we can set closed_on with the same value on closing def force_updated_on_change - if @current_journal + if @current_journal || changed? self.updated_on = current_time_from_proper_timezone + if new_record? + self.created_on = updated_on + end + end + end + + # Callback for setting closed_on when the issue is closed. + # The closed_on attribute stores the time of the last closing + # and is preserved when the issue is reopened. + def update_closed_on + if closing? || (new_record? && closed?) + self.closed_on = updated_on end end @@ -1316,7 +1361,7 @@ if @current_journal # attributes changes if @attributes_before_change - (Issue.column_names - %w(id root_id lft rgt lock_version created_on updated_on)).each {|c| + (Issue.column_names - %w(id root_id lft rgt lock_version created_on updated_on closed_on)).each {|c| before = @attributes_before_change[c] after = send(c) next if before == after || (before.blank? && after.blank?) diff -r 0a574315af3e -r 4f746d8966dd app/models/issue_category.rb --- a/app/models/issue_category.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/issue_category.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,7 +24,7 @@ validates_presence_of :name validates_uniqueness_of :name, :scope => [:project_id] validates_length_of :name, :maximum => 30 - + safe_attributes 'name', 'assigned_to_id' scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)} @@ -35,7 +35,7 @@ # If a category is specified, issues are reassigned to this category def destroy(reassign_to = nil) if reassign_to && reassign_to.is_a?(IssueCategory) && reassign_to.project == self.project - Issue.update_all("category_id = #{reassign_to.id}", "category_id = #{id}") + Issue.update_all({:category_id => reassign_to.id}, {:category_id => id}) end destroy_without_reassign end diff -r 0a574315af3e -r 4f746d8966dd app/models/issue_custom_field.rb --- a/app/models/issue_custom_field.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/issue_custom_field.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/models/issue_observer.rb --- a/app/models/issue_observer.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/issue_observer.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/models/issue_priority.rb --- a/app/models/issue_priority.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/issue_priority.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/models/issue_priority_custom_field.rb --- a/app/models/issue_priority_custom_field.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/issue_priority_custom_field.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/models/issue_query.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/models/issue_query.rb Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,405 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 IssueQuery < Query + + self.queried_class = Issue + + self.available_columns = [ + QueryColumn.new(:id, :sortable => "#{Issue.table_name}.id", :default_order => 'desc', :caption => '#', :frozen => true), + QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true), + QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true), + QueryColumn.new(:parent, :sortable => ["#{Issue.table_name}.root_id", "#{Issue.table_name}.lft ASC"], :default_order => 'desc', :caption => :field_parent_issue), + QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true), + QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true), + QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"), + QueryColumn.new(:author, :sortable => lambda {User.fields_for_order_statement("authors")}, :groupable => true), + QueryColumn.new(:assigned_to, :sortable => lambda {User.fields_for_order_statement}, :groupable => true), + QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'), + QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true), + QueryColumn.new(:fixed_version, :sortable => lambda {Version.fields_for_order_statement}, :groupable => true), + QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"), + QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"), + QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"), + QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true), + QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'), + QueryColumn.new(:closed_on, :sortable => "#{Issue.table_name}.closed_on", :default_order => 'desc'), + QueryColumn.new(:relations, :caption => :label_related_issues), + QueryColumn.new(:description, :inline => false) + ] + + scope :visible, lambda {|*args| + user = args.shift || User.current + base = Project.allowed_to_condition(user, :view_issues, *args) + user_id = user.logged? ? user.id : 0 + + includes(:project).where("(#{table_name}.project_id IS NULL OR (#{base})) AND (#{table_name}.is_public = ? OR #{table_name}.user_id = ?)", true, user_id) + } + + def initialize(attributes=nil, *args) + super attributes + self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} } + end + + # Returns true if the query is visible to +user+ or the current user. + def visible?(user=User.current) + (project.nil? || user.allowed_to?(:view_issues, project)) && (self.is_public? || self.user_id == user.id) + end + + def initialize_available_filters + principals = [] + subprojects = [] + versions = [] + categories = [] + issue_custom_fields = [] + + if project + principals += project.principals.sort + unless project.leaf? + subprojects = project.descendants.visible.all + principals += Principal.member_of(subprojects) + end + versions = project.shared_versions.all + categories = project.issue_categories.all + issue_custom_fields = project.all_issue_custom_fields + else + if all_projects.any? + principals += Principal.member_of(all_projects) + end + versions = Version.visible.find_all_by_sharing('system') + issue_custom_fields = IssueCustomField.where(:is_filter => true, :is_for_all => true).all + end + principals.uniq! + principals.sort! + users = principals.select {|p| p.is_a?(User)} + + + add_available_filter "status_id", + :type => :list_status, :values => IssueStatus.sorted.all.collect{|s| [s.name, s.id.to_s] } + + if project.nil? + project_values = [] + if User.current.logged? && User.current.memberships.any? + project_values << ["<< #{l(:label_my_projects).downcase} >>", "mine"] + end + project_values += all_projects_values + add_available_filter("project_id", + :type => :list, :values => project_values + ) unless project_values.empty? + end + + add_available_filter "tracker_id", + :type => :list, :values => trackers.collect{|s| [s.name, s.id.to_s] } + add_available_filter "priority_id", + :type => :list, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } + + author_values = [] + author_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged? + author_values += users.collect{|s| [s.name, s.id.to_s] } + add_available_filter("author_id", + :type => :list, :values => author_values + ) unless author_values.empty? + + assigned_to_values = [] + assigned_to_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged? + assigned_to_values += (Setting.issue_group_assignment? ? + principals : users).collect{|s| [s.name, s.id.to_s] } + add_available_filter("assigned_to_id", + :type => :list_optional, :values => assigned_to_values + ) unless assigned_to_values.empty? + + group_values = Group.all.collect {|g| [g.name, g.id.to_s] } + add_available_filter("member_of_group", + :type => :list_optional, :values => group_values + ) unless group_values.empty? + + role_values = Role.givable.collect {|r| [r.name, r.id.to_s] } + add_available_filter("assigned_to_role", + :type => :list_optional, :values => role_values + ) unless role_values.empty? + + if versions.any? + add_available_filter "fixed_version_id", + :type => :list_optional, + :values => versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } + end + + if categories.any? + add_available_filter "category_id", + :type => :list_optional, + :values => categories.collect{|s| [s.name, s.id.to_s] } + end + + add_available_filter "subject", :type => :text + add_available_filter "created_on", :type => :date_past + add_available_filter "updated_on", :type => :date_past + add_available_filter "closed_on", :type => :date_past + add_available_filter "start_date", :type => :date + add_available_filter "due_date", :type => :date + add_available_filter "estimated_hours", :type => :float + add_available_filter "done_ratio", :type => :integer + + if User.current.allowed_to?(:set_issues_private, nil, :global => true) || + User.current.allowed_to?(:set_own_issues_private, nil, :global => true) + add_available_filter "is_private", + :type => :list, + :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]] + end + + if User.current.logged? + add_available_filter "watcher_id", + :type => :list, :values => [["<< #{l(:label_me)} >>", "me"]] + end + + if subprojects.any? + add_available_filter "subproject_id", + :type => :list_subprojects, + :values => subprojects.collect{|s| [s.name, s.id.to_s] } + end + + add_custom_fields_filters(issue_custom_fields) + + add_associations_custom_fields_filters :project, :author, :assigned_to, :fixed_version + + IssueRelation::TYPES.each do |relation_type, options| + add_available_filter relation_type, :type => :relation, :label => options[:name] + end + + Tracker.disabled_core_fields(trackers).each {|field| + delete_available_filter field + } + end + + def available_columns + return @available_columns if @available_columns + @available_columns = self.class.available_columns.dup + @available_columns += (project ? + project.all_issue_custom_fields : + IssueCustomField.all + ).collect {|cf| QueryCustomFieldColumn.new(cf) } + + if User.current.allowed_to?(:view_time_entries, project, :global => true) + index = nil + @available_columns.each_with_index {|column, i| index = i if column.name == :estimated_hours} + index = (index ? index + 1 : -1) + # insert the column after estimated_hours or at the end + @available_columns.insert index, QueryColumn.new(:spent_hours, + :sortable => "COALESCE((SELECT SUM(hours) FROM #{TimeEntry.table_name} WHERE #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id), 0)", + :default_order => 'desc', + :caption => :label_spent_time + ) + end + + if User.current.allowed_to?(:set_issues_private, nil, :global => true) || + User.current.allowed_to?(:set_own_issues_private, nil, :global => true) + @available_columns << QueryColumn.new(:is_private, :sortable => "#{Issue.table_name}.is_private") + end + + disabled_fields = Tracker.disabled_core_fields(trackers).map {|field| field.sub(/_id$/, '')} + @available_columns.reject! {|column| + disabled_fields.include?(column.name.to_s) + } + + @available_columns + end + + def default_columns_names + @default_columns_names ||= begin + default_columns = Setting.issue_list_default_columns.map(&:to_sym) + + project.present? ? default_columns : [:project] | default_columns + end + end + + # Returns the issue count + def issue_count + Issue.visible.count(:include => [:status, :project], :conditions => statement) + rescue ::ActiveRecord::StatementInvalid => e + raise StatementInvalid.new(e.message) + end + + # Returns the issue count by group or nil if query is not grouped + def issue_count_by_group + r = nil + if grouped? + begin + # Rails3 will raise an (unexpected) RecordNotFound if there's only a nil group value + r = Issue.visible.count(:joins => joins_for_order_statement(group_by_statement), :group => group_by_statement, :include => [:status, :project], :conditions => statement) + rescue ActiveRecord::RecordNotFound + r = {nil => issue_count} + end + c = group_by_column + if c.is_a?(QueryCustomFieldColumn) + r = r.keys.inject({}) {|h, k| h[c.custom_field.cast_value(k)] = r[k]; h} + end + end + r + rescue ::ActiveRecord::StatementInvalid => e + raise StatementInvalid.new(e.message) + end + + # Returns the issues + # Valid options are :order, :offset, :limit, :include, :conditions + def issues(options={}) + order_option = [group_by_sort_order, options[:order]].flatten.reject(&:blank?) + + issues = Issue.visible.where(options[:conditions]).all( + :include => ([:status, :project] + (options[:include] || [])).uniq, + :conditions => statement, + :order => order_option, + :joins => joins_for_order_statement(order_option.join(',')), + :limit => options[:limit], + :offset => options[:offset] + ) + + if has_column?(:spent_hours) + Issue.load_visible_spent_hours(issues) + end + if has_column?(:relations) + Issue.load_visible_relations(issues) + end + issues + rescue ::ActiveRecord::StatementInvalid => e + raise StatementInvalid.new(e.message) + end + + # Returns the issues ids + def issue_ids(options={}) + order_option = [group_by_sort_order, options[:order]].flatten.reject(&:blank?) + + Issue.visible.scoped(:conditions => options[:conditions]).scoped(:include => ([:status, :project] + (options[:include] || [])).uniq, + :conditions => statement, + :order => order_option, + :joins => joins_for_order_statement(order_option.join(',')), + :limit => options[:limit], + :offset => options[:offset]).find_ids + rescue ::ActiveRecord::StatementInvalid => e + raise StatementInvalid.new(e.message) + end + + # Returns the journals + # Valid options are :order, :offset, :limit + def journals(options={}) + Journal.visible.all( + :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}], + :conditions => statement, + :order => options[:order], + :limit => options[:limit], + :offset => options[:offset] + ) + rescue ::ActiveRecord::StatementInvalid => e + raise StatementInvalid.new(e.message) + end + + # Returns the versions + # Valid options are :conditions + def versions(options={}) + Version.visible.where(options[:conditions]).all( + :include => :project, + :conditions => project_statement + ) + rescue ::ActiveRecord::StatementInvalid => e + raise StatementInvalid.new(e.message) + end + + def sql_for_watcher_id_field(field, operator, value) + db_table = Watcher.table_name + "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " + + sql_for_field(field, '=', value, db_table, 'user_id') + ')' + end + + def sql_for_member_of_group_field(field, operator, value) + if operator == '*' # Any group + groups = Group.all + operator = '=' # Override the operator since we want to find by assigned_to + elsif operator == "!*" + groups = Group.all + operator = '!' # Override the operator since we want to find by assigned_to + else + groups = Group.find_all_by_id(value) + end + groups ||= [] + + members_of_groups = groups.inject([]) {|user_ids, group| + user_ids + group.user_ids + [group.id] + }.uniq.compact.sort.collect(&:to_s) + + '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')' + end + + def sql_for_assigned_to_role_field(field, operator, value) + case operator + when "*", "!*" # Member / Not member + sw = operator == "!*" ? 'NOT' : '' + nl = operator == "!*" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : '' + "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}" + + " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id))" + when "=", "!" + role_cond = value.any? ? + "#{MemberRole.table_name}.role_id IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")" : + "1=0" + + sw = operator == "!" ? 'NOT' : '' + nl = operator == "!" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : '' + "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}, #{MemberRole.table_name}" + + " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id AND #{Member.table_name}.id = #{MemberRole.table_name}.member_id AND #{role_cond}))" + end + end + + def sql_for_is_private_field(field, operator, value) + op = (operator == "=" ? 'IN' : 'NOT IN') + va = value.map {|v| v == '0' ? connection.quoted_false : connection.quoted_true}.uniq.join(',') + + "#{Issue.table_name}.is_private #{op} (#{va})" + end + + def sql_for_relations(field, operator, value, options={}) + relation_options = IssueRelation::TYPES[field] + return relation_options unless relation_options + + relation_type = field + join_column, target_join_column = "issue_from_id", "issue_to_id" + if relation_options[:reverse] || options[:reverse] + relation_type = relation_options[:reverse] || relation_type + join_column, target_join_column = target_join_column, join_column + end + + sql = case operator + when "*", "!*" + op = (operator == "*" ? 'IN' : 'NOT IN') + "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}')" + when "=", "!" + op = (operator == "=" ? 'IN' : 'NOT IN') + "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = #{value.first.to_i})" + when "=p", "=!p", "!p" + op = (operator == "!p" ? 'NOT IN' : 'IN') + comp = (operator == "=!p" ? '<>' : '=') + "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name}, #{Issue.table_name} relissues WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = relissues.id AND relissues.project_id #{comp} #{value.first.to_i})" + end + + if relation_options[:sym] == field && !options[:reverse] + sqls = [sql, sql_for_relations(field, operator, value, :reverse => true)] + sqls.join(["!", "!*", "!p"].include?(operator) ? " AND " : " OR ") + else + sql + end + end + + IssueRelation::TYPES.keys.each do |relation_type| + alias_method "sql_for_#{relation_type}_field".to_sym, :sql_for_relations + end +end diff -r 0a574315af3e -r 4f746d8966dd app/models/issue_relation.rb --- a/app/models/issue_relation.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/issue_relation.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,21 +15,21 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# Class used to represent the relations of an issue -class IssueRelations < Array - include Redmine::I18n +class IssueRelation < ActiveRecord::Base + # Class used to represent the relations of an issue + class Relations < Array + include Redmine::I18n - def initialize(issue, *args) - @issue = issue - super(*args) + def initialize(issue, *args) + @issue = issue + super(*args) + end + + def to_s(*args) + map {|relation| "#{l(relation.label_for(@issue))} ##{relation.other_issue(@issue).id}"}.join(', ') + end end - def to_s(*args) - map {|relation| "#{l(relation.label_for(@issue))} ##{relation.other_issue(@issue).id}"}.join(', ') - end -end - -class IssueRelation < ActiveRecord::Base belongs_to :issue_from, :class_name => 'Issue', :foreign_key => 'issue_from_id' belongs_to :issue_to, :class_name => 'Issue', :foreign_key => 'issue_to_id' diff -r 0a574315af3e -r 4f746d8966dd app/models/issue_status.rb --- a/app/models/issue_status.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/issue_status.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -28,7 +28,7 @@ validates_length_of :name, :maximum => 30 validates_inclusion_of :default_done_ratio, :in => 0..100, :allow_nil => true - scope :sorted, order("#{table_name}.position ASC") + scope :sorted, lambda { order("#{table_name}.position ASC") } scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)} def update_default diff -r 0a574315af3e -r 4f746d8966dd app/models/journal.rb --- a/app/models/journal.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/journal.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -28,6 +28,7 @@ acts_as_event :title => Proc.new {|o| status = ((s = o.new_status) ? " (#{s})" : nil); "#{o.issue.tracker} ##{o.issue.id}#{status}: #{o.issue.subject}" }, :description => :notes, :author => :user, + :group => :issue, :type => Proc.new {|o| (s = o.new_status) ? (s.is_closed? ? 'issue-closed' : 'issue-edit') : 'issue-note' }, :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.issue.id, :anchor => "change-#{o.id}"}} diff -r 0a574315af3e -r 4f746d8966dd app/models/journal_detail.rb --- a/app/models/journal_detail.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/journal_detail.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -27,10 +27,13 @@ end def normalize(v) - if v == true + case v + when true "1" - elsif v == false + when false "0" + when Date + v.strftime("%Y-%m-%d") else v end diff -r 0a574315af3e -r 4f746d8966dd app/models/journal_observer.rb --- a/app/models/journal_observer.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/journal_observer.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/models/mail_handler.rb --- a/app/models/mail_handler.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/mail_handler.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -38,7 +38,9 @@ # Status overridable by default @@handler_options[:allow_override] << 'status' unless @@handler_options[:issue].has_key?(:status) - @@handler_options[:no_permission_check] = (@@handler_options[:no_permission_check].to_s == '1' ? true : false) + @@handler_options[:no_account_notice] = (@@handler_options[:no_account_notice].to_s == '1') + @@handler_options[:no_notification] = (@@handler_options[:no_notification].to_s == '1') + @@handler_options[:no_permission_check] = (@@handler_options[:no_permission_check].to_s == '1') email.force_encoding('ASCII-8BIT') if email.respond_to?(:force_encoding) super(email) @@ -97,7 +99,10 @@ if logger && logger.info logger.info "MailHandler: [#{@user.login}] account created" end - Mailer.account_information(@user, @user.password).deliver + add_user_to_group(@@handler_options[:default_group]) + unless @@handler_options[:no_account_notice] + Mailer.account_information(@user, @user.password).deliver + end else if logger && logger.error logger.error "MailHandler: could not create account for [#{sender_email}]" @@ -107,7 +112,7 @@ else # Default behaviour, emails from unknown users are ignored if logger && logger.info - logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]" + logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]" end return false end @@ -264,7 +269,7 @@ 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 = User.active.where('LOWER(mail) IN (?)', addresses).all watchers.each {|w| obj.add_watcher(w)} end end @@ -338,7 +343,7 @@ }.delete_if {|k, v| v.blank? } if issue.new_record? && attrs['tracker_id'].nil? - attrs['tracker_id'] = issue.project.trackers.find(:first).try(:id) + attrs['tracker_id'] = issue.project.trackers.first.try(:id) end attrs @@ -396,18 +401,19 @@ assign_string_attribute_with_limit(user, 'login', email_address, User::LOGIN_LENGTH_LIMIT) names = fullname.blank? ? email_address.gsub(/@.*$/, '').split('.') : fullname.split - assign_string_attribute_with_limit(user, 'firstname', names.shift) - assign_string_attribute_with_limit(user, 'lastname', names.join(' ')) + assign_string_attribute_with_limit(user, 'firstname', names.shift, 30) + assign_string_attribute_with_limit(user, 'lastname', names.join(' '), 30) user.lastname = '-' if user.lastname.blank? password_length = [Setting.password_min_length.to_i, 10].max user.password = Redmine::Utils.random_hex(password_length / 2 + 1) user.language = Setting.default_language + user.mail_notification = 'only_my_events' unless user.valid? user.login = "user#{Redmine::Utils.random_hex(6)}" unless user.errors[:login].blank? user.firstname = "-" unless user.errors[:firstname].blank? - user.lastname = "-" unless user.errors[:lastname].blank? + (puts user.errors[:lastname];user.lastname = "-") unless user.errors[:lastname].blank? end user @@ -423,6 +429,9 @@ end if addr.present? user = self.class.new_user_from_attributes(addr, name) + if @@handler_options[:no_notification] + user.mail_notification = 'none' + end if user.save user else @@ -435,6 +444,19 @@ end end + # Adds the newly created user to default group + def add_user_to_group(default_group) + if default_group.present? + default_group.split(',').each do |group_name| + if group = Group.named(group_name).first + group.users << @user + elsif logger + logger.warn "MailHandler: could not add user to [#{group_name}], group not found" + end + end + end + end + # Removes the email body of text after the truncation configurations. def cleanup_body(body) delimiters = Setting.mail_handler_body_delimiters.to_s.split(/[\r\n]+/).reject(&:blank?).map {|s| Regexp.escape(s)} @@ -455,7 +477,7 @@ } if assignee.nil? && keyword.match(/ /) firstname, lastname = *(keyword.split) # "First Last Throwaway" - assignee ||= assignable.detect {|a| + assignee ||= assignable.detect {|a| a.is_a?(User) && a.firstname.to_s.downcase == firstname && a.lastname.to_s.downcase == lastname } diff -r 0a574315af3e -r 4f746d8966dd app/models/mailer.rb --- a/app/models/mailer.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/mailer.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -280,7 +280,7 @@ # Mailer.account_activation_request(user).deliver => sends an email to all active administrators def account_activation_request(user) # Send the email to all active administrators - recipients = User.active.find(:all, :conditions => {:admin => true}).collect { |u| u.mail }.compact + recipients = User.active.where(:admin => true).all.collect { |u| u.mail }.compact @user = user @url = url_for(:controller => 'users', :action => 'index', :status => User::STATUS_REGISTERED, diff -r 0a574315af3e -r 4f746d8966dd app/models/member.rb --- a/app/models/member.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/member.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -27,7 +27,6 @@ validate :validate_role before_destroy :set_issue_category_nil - after_destroy :unwatch_from_permission_change def role end @@ -52,7 +51,6 @@ member_roles_to_destroy = member_roles.select {|mr| !ids.include?(mr.role_id)} if member_roles_to_destroy.any? member_roles_to_destroy.each(&:destroy) - unwatch_from_permission_change end end @@ -97,18 +95,19 @@ @membership end + # Finds or initilizes a Member for the given project and principal + def self.find_or_new(project, principal) + project_id = project.is_a?(Project) ? project.id : project + principal_id = principal.is_a?(Principal) ? principal.id : principal + + member = Member.find_by_project_id_and_user_id(project_id, principal_id) + member ||= Member.new(:project_id => project_id, :user_id => principal_id) + member + end + protected def validate_role errors.add_on_empty :role if member_roles.empty? && roles.empty? end - - private - - # Unwatch things that the user is no longer allowed to view inside project - def unwatch_from_permission_change - if user - Watcher.prune(:user => user, :project => project) - end - end end diff -r 0a574315af3e -r 4f746d8966dd app/models/member_role.rb --- a/app/models/member_role.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/member_role.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -21,8 +21,8 @@ after_destroy :remove_member_if_empty - after_create :add_role_to_group_users - after_destroy :remove_role_from_group_users + after_create :add_role_to_group_users, :add_role_to_subprojects + after_destroy :remove_inherited_roles validates_presence_of :role validate :validate_role_member @@ -44,21 +44,28 @@ end def add_role_to_group_users - if member.principal.is_a?(Group) + if member.principal.is_a?(Group) && !inherited? member.principal.users.each do |user| - user_member = Member.find_by_project_id_and_user_id(member.project_id, user.id) || Member.new(:project_id => member.project_id, :user_id => user.id) + user_member = Member.find_or_new(member.project_id, user.id) user_member.member_roles << MemberRole.new(:role => role, :inherited_from => id) user_member.save! end end end - def remove_role_from_group_users - MemberRole.find(:all, :conditions => { :inherited_from => id }).group_by(&:member).each do |member, member_roles| - member_roles.each(&:destroy) - if member && member.user - Watcher.prune(:user => member.user, :project => member.project) + def add_role_to_subprojects + member.project.children.each do |subproject| + if subproject.inherit_members? + child_member = Member.find_or_new(subproject.id, member.user_id) + child_member.member_roles << MemberRole.new(:role => role, :inherited_from => id) + child_member.save! end end end + + def remove_inherited_roles + MemberRole.where(:inherited_from => id).all.group_by(&:member).each do |member, member_roles| + member_roles.each(&:destroy) + end + end end diff -r 0a574315af3e -r 4f746d8966dd app/models/message.rb --- a/app/models/message.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/message.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -29,6 +29,7 @@ :date_column => "#{table_name}.created_on" acts_as_event :title => Proc.new {|o| "#{o.board.name}: #{o.subject}"}, :description => :content, + :group => :parent, :type => Proc.new {|o| o.parent_id.nil? ? 'message' : 'reply'}, :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, :r => o.id, :anchor => "message-#{o.id}"})} @@ -45,8 +46,9 @@ after_update :update_messages_board after_destroy :reset_counters! - scope :visible, lambda {|*args| { :include => {:board => :project}, - :conditions => Project.allowed_to_condition(args.shift || User.current, :view_messages, *args) } } + scope :visible, lambda {|*args| + includes(:board => :project).where(Project.allowed_to_condition(args.shift || User.current, :view_messages, *args)) + } safe_attributes 'subject', 'content' safe_attributes 'locked', 'sticky', 'board_id', @@ -65,7 +67,7 @@ def update_messages_board if board_id_changed? - Message.update_all("board_id = #{board_id}", ["id = ? OR parent_id = ?", root.id, root.id]) + Message.update_all({:board_id => board_id}, ["id = ? OR parent_id = ?", root.id, root.id]) Board.reset_counters!(board_id_was) Board.reset_counters!(board_id) end diff -r 0a574315af3e -r 4f746d8966dd app/models/message_observer.rb --- a/app/models/message_observer.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/message_observer.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/models/news.rb --- a/app/models/news.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/news.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -34,10 +34,9 @@ after_create :add_author_as_watcher - scope :visible, lambda {|*args| { - :include => :project, - :conditions => Project.allowed_to_condition(args.shift || User.current, :view_news, *args) - }} + scope :visible, lambda {|*args| + includes(:project).where(Project.allowed_to_condition(args.shift || User.current, :view_news, *args)) + } safe_attributes 'title', 'summary', 'description' @@ -50,6 +49,10 @@ user.allowed_to?(:comment_news, project) end + def recipients + project.users.select {|user| user.notify_about?(self)}.map(&:mail) + end + # returns latest news for projects visible by user def self.latest(user = User.current, count = 5) visible(user).includes([:author, :project]).order("#{News.table_name}.created_on DESC").limit(count).all diff -r 0a574315af3e -r 4f746d8966dd app/models/news_observer.rb --- a/app/models/news_observer.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/news_observer.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/models/principal.rb --- a/app/models/principal.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/principal.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -18,13 +18,19 @@ class Principal < ActiveRecord::Base self.table_name = "#{table_name_prefix}users#{table_name_suffix}" + # Account statuses + STATUS_ANONYMOUS = 0 + STATUS_ACTIVE = 1 + STATUS_REGISTERED = 2 + STATUS_LOCKED = 3 + has_many :members, :foreign_key => 'user_id', :dependent => :destroy has_many :memberships, :class_name => 'Member', :foreign_key => 'user_id', :include => [ :project, :roles ], :conditions => "#{Project.table_name}.status<>#{Project::STATUS_ARCHIVED}", :order => "#{Project.table_name}.name" has_many :projects, :through => :memberships has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify # Groups and active users - scope :active, :conditions => "#{Principal.table_name}.status = 1" + scope :active, lambda { where(:status => STATUS_ACTIVE) } scope :like, lambda {|q| q = q.to_s @@ -51,7 +57,7 @@ where("1=0") else ids = projects.map(&:id) - where("#{Principal.table_name}.status = 1 AND #{Principal.table_name}.id IN (SELECT DISTINCT user_id FROM #{Member.table_name} WHERE project_id IN (?))", ids) + active.uniq.joins(:members).where("#{Member.table_name}.project_id IN (?)", ids) end } # Principals that are not members of projects @@ -64,6 +70,7 @@ where("#{Principal.table_name}.id NOT IN (SELECT DISTINCT user_id FROM #{Member.table_name} WHERE project_id IN (?))", ids) end } + scope :sorted, lambda { order(*Principal.fields_for_order_statement)} before_create :set_default_empty_values @@ -82,6 +89,15 @@ end end + # Returns an array of fields names than can be used to make an order statement for principals. + # Users are sorted before Groups. + # Examples: + def self.fields_for_order_statement(table=nil) + table ||= table_name + columns = ['type DESC'] + (User.name_formatter[:order] - ['id']) + ['lastname', 'id'] + columns.uniq.map {|field| "#{table}.#{field}"} + end + protected # Make sure we don't try to insert NULL values (see #4632) diff -r 0a574315af3e -r 4f746d8966dd app/models/project.rb --- a/app/models/project.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/project.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -28,11 +28,11 @@ # Specific overidden Activities has_many :time_entry_activities - has_many :members, :include => [:principal, :roles], :conditions => "#{User.table_name}.type='User' AND #{User.table_name}.status=#{User::STATUS_ACTIVE}" + has_many :members, :include => [:principal, :roles], :conditions => "#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{Principal::STATUS_ACTIVE}" has_many :memberships, :class_name => 'Member' has_many :member_principals, :class_name => 'Member', :include => :principal, - :conditions => "#{Principal.table_name}.type='Group' OR (#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{User::STATUS_ACTIVE})" + :conditions => "#{Principal.table_name}.type='Group' OR (#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{Principal::STATUS_ACTIVE})" has_many :users, :through => :members has_many :principals, :through => :member_principals, :source => :principal @@ -42,7 +42,7 @@ has_many :issue_changes, :through => :issues, :source => :journals has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC" has_many :time_entries, :dependent => :delete_all - has_many :queries, :dependent => :delete_all + has_many :queries, :class_name => 'IssueQuery', :dependent => :delete_all has_many :documents, :dependent => :destroy has_many :news, :dependent => :destroy, :include => :author has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name" @@ -77,19 +77,22 @@ validates_length_of :homepage, :maximum => 255 validates_length_of :identifier, :in => 1..IDENTIFIER_MAX_LENGTH # donwcase letters, digits, dashes but not digits only - validates_format_of :identifier, :with => /^(?!\d+$)[a-z0-9\-_]*$/, :if => Proc.new { |p| p.identifier_changed? } + validates_format_of :identifier, :with => /\A(?!\d+$)[a-z0-9\-_]*\z/, :if => Proc.new { |p| p.identifier_changed? } # reserved words validates_exclusion_of :identifier, :in => %w( new ) after_save :update_position_under_parent, :if => Proc.new {|project| project.name_changed?} + after_save :update_inherited_members, :if => Proc.new {|project| project.inherit_members_changed?} before_destroy :delete_all_members - 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] } } - scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"} - scope :status, lambda {|arg| arg.blank? ? {} : {:conditions => {:status => arg.to_i}} } - scope :all_public, { :conditions => { :is_public => true } } - scope :visible, lambda {|*args| {:conditions => Project.visible_condition(args.shift || User.current, *args) }} + scope :has_module, lambda {|mod| + where("#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s) + } + scope :active, lambda { where(:status => STATUS_ACTIVE) } + scope :status, lambda {|arg| where(arg.blank? ? nil : {:status => arg.to_i}) } + scope :all_public, lambda { where(:is_public => true) } scope :visible_roots, lambda { { :conditions => Project.root_visible_by(User.current) } } + scope :visible, lambda {|*args| where(Project.visible_condition(args.shift || User.current, *args)) } scope :allowed_to, lambda {|*args| user = User.current permission = nil @@ -99,14 +102,14 @@ user = args.shift permission = args.shift end - { :conditions => Project.allowed_to_condition(user, permission, *args) } + where(Project.allowed_to_condition(user, permission, *args)) } scope :like, lambda {|arg| if arg.blank? - {} + where(nil) else pattern = "%#{arg.to_s.strip.downcase}%" - {:conditions => ["LOWER(identifier) LIKE :p OR LOWER(name) LIKE :p", {:p => pattern}]} + where("LOWER(identifier) LIKE :p OR LOWER(name) LIKE :p", :p => pattern) end } @@ -124,7 +127,12 @@ self.enabled_module_names = Setting.default_projects_modules end if !initialized.key?('trackers') && !initialized.key?('tracker_ids') - self.trackers = Tracker.sorted.all + default = Setting.default_projects_tracker_ids + if default.is_a?(Array) + self.trackers = Tracker.where(:id => default.map(&:to_i)).sorted.all + else + self.trackers = Tracker.sorted.all + end end end @@ -139,8 +147,8 @@ # returns latest created projects # non public projects will be returned only if user is a member of those def self.latest(user=nil, count=5) - visible(user).find(:all, :limit => count, :order => "created_on DESC") - end + visible(user).limit(count).order("created_on DESC").all + end # Returns true if the project is visible to +user+ or to the current user. def visible?(user=User.current) @@ -282,6 +290,7 @@ self.find(*args) end + alias :base_reload :reload def reload(*args) @shared_versions = nil @rolled_up_versions = nil @@ -292,7 +301,9 @@ @allowed_parents = nil @allowed_permissions = nil @actions_allowed = nil - super + @start_date = nil + @due_date = nil + base_reload(*args) end def to_param @@ -313,9 +324,12 @@ # Check that there is no issue of a non descendant project that is assigned # to one of the project or descendant versions v_ids = self_and_descendants.collect {|p| p.version_ids}.flatten - if v_ids.any? && Issue.find(:first, :include => :project, - :conditions => ["(#{Project.table_name}.lft < ? OR #{Project.table_name}.rgt > ?)" + - " AND #{Issue.table_name}.fixed_version_id IN (?)", lft, rgt, v_ids]) + if v_ids.any? && + Issue. + includes(:project). + where("#{Project.table_name}.lft < ? OR #{Project.table_name}.rgt > ?", lft, rgt). + where("#{Issue.table_name}.fixed_version_id IN (?)", v_ids). + exists? return false end Project.transaction do @@ -343,7 +357,7 @@ # by the current user def allowed_parents return @allowed_parents if @allowed_parents - @allowed_parents = Project.find(:all, :conditions => Project.allowed_to_condition(User.current, :add_subprojects)) + @allowed_parents = Project.where(Project.allowed_to_condition(User.current, :add_subprojects)).all @allowed_parents = @allowed_parents - self_and_descendants if User.current.allowed_to?(:add_project, nil, :global => true) || (!new_record? && parent.nil?) @allowed_parents << nil @@ -411,16 +425,19 @@ # 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, :joins => :projects, - :select => "DISTINCT #{Tracker.table_name}.*", - :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status <> #{STATUS_ARCHIVED}", lft, rgt], - :order => "#{Tracker.table_name}.position") + Tracker. + joins(:projects). + joins("JOIN #{EnabledModule.table_name} ON #{EnabledModule.table_name}.project_id = #{Project.table_name}.id AND #{EnabledModule.table_name}.name = 'issue_tracking'"). + select("DISTINCT #{Tracker.table_name}.*"). + where("#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status <> #{STATUS_ARCHIVED}", lft, rgt). + sorted. + all end # Closes open and locked project versions that are completed def close_completed_versions Version.transaction do - versions.find(:all, :conditions => {:status => %w(open locked)}).each do |version| + versions.where(:status => %w(open locked)).all.each do |version| if version.completed? version.update_attribute(:status, 'closed') end @@ -457,7 +474,7 @@ # Returns a hash of project users grouped by role def users_by_role - members.find(:all, :include => [:user, :roles]).inject({}) do |h, m| + members.includes(:user, :roles).all.inject({}) do |h, m| m.roles.each do |r| h[r] ||= [] h[r] << m.user @@ -550,20 +567,20 @@ # The earliest start date of a project, based on it's issues and versions def start_date - [ + @start_date ||= [ issues.minimum('start_date'), - shared_versions.collect(&:effective_date), - shared_versions.collect(&:start_date) - ].flatten.compact.min + shared_versions.minimum('effective_date'), + Issue.fixed_version(shared_versions).minimum('start_date') + ].compact.min end # The latest due date of an issue or version def due_date - [ + @due_date ||= [ issues.maximum('due_date'), - shared_versions.collect(&:effective_date), - shared_versions.collect {|v| v.fixed_issues.maximum('due_date')} - ].flatten.compact.max + shared_versions.maximum('effective_date'), + Issue.fixed_version(shared_versions).maximum('due_date') + ].compact.max end def overdue? @@ -579,7 +596,7 @@ total / self_and_descendants.count else if versions.count > 0 - total = versions.collect(&:completed_pourcent).sum + total = versions.collect(&:completed_percent).sum total / versions.count else @@ -662,6 +679,9 @@ safe_attributes 'enabled_module_names', :if => lambda {|project, user| project.new_record? || user.allowed_to?(:select_project_modules, project) } + safe_attributes 'inherit_members', + :if => lambda {|project, user| project.parent.nil? || project.parent.visible?(user)} + # Returns an array of projects that are in this project's hierarchy # # Example: parents, children, siblings @@ -673,7 +693,7 @@ # Returns an auto-generated project identifier based on the last identifier used def self.next_identifier - p = Project.find(:first, :order => 'created_on DESC') + p = Project.order('id DESC').first p.nil? ? nil : p.identifier.to_s.succ end @@ -710,27 +730,17 @@ end end - - # Copies +project+ and returns the new instance. This will not save - # the copy + # Returns a new unsaved Project instance with attributes copied from +project+ def self.copy_from(project) - begin - project = project.is_a?(Project) ? project : Project.find(project) - if project - # clear unique attributes - attributes = project.attributes.dup.except('id', 'name', 'identifier', 'status', 'parent_id', 'lft', 'rgt') - copy = Project.new(attributes) - copy.enabled_modules = project.enabled_modules - copy.trackers = project.trackers - copy.custom_values = project.custom_values.collect {|v| v.clone} - copy.issue_custom_fields = project.issue_custom_fields - return copy - else - return nil - end - rescue ActiveRecord::RecordNotFound - return nil - end + project = project.is_a?(Project) ? project : Project.find(project) + # clear unique attributes + attributes = project.attributes.dup.except('id', 'name', 'identifier', 'status', 'parent_id', 'lft', 'rgt') + copy = Project.new(attributes) + copy.enabled_modules = project.enabled_modules + copy.trackers = project.trackers + copy.custom_values = project.custom_values.collect {|v| v.clone} + copy.issue_custom_fields = project.issue_custom_fields + copy end # Yields the given block for each project with its level in the tree @@ -747,6 +757,44 @@ private + def after_parent_changed(parent_was) + remove_inherited_member_roles + add_inherited_member_roles + end + + def update_inherited_members + if parent + if inherit_members? && !inherit_members_was + remove_inherited_member_roles + add_inherited_member_roles + elsif !inherit_members? && inherit_members_was + remove_inherited_member_roles + end + end + end + + def remove_inherited_member_roles + member_roles = memberships.map(&:member_roles).flatten + member_role_ids = member_roles.map(&:id) + member_roles.each do |member_role| + if member_role.inherited_from && !member_role_ids.include?(member_role.inherited_from) + member_role.destroy + end + end + end + + def add_inherited_member_roles + if inherit_members? && parent + parent.memberships.each do |parent_member| + member = Member.find_or_new(self.id, parent_member.user_id) + parent_member.member_roles.each do |parent_member_role| + member.member_roles << MemberRole.new(:role => parent_member_role.role, :inherited_from => parent_member_role.id) + end + member.save! + end + end + end + # Copies wiki from +project+ def copy_wiki(project) # Check that the source project has a wiki first @@ -808,10 +856,13 @@ # Get issues sorted by root_id, lft so that parent issues # get copied before their children - project.issues.find(:all, :order => 'root_id, lft').each do |issue| + project.issues.reorder('root_id, lft').all.each do |issue| new_issue = Issue.new new_issue.copy_from(issue, :subtasks => false, :link => false) new_issue.project = self + # Changing project resets the custom field values + # TODO: handle this in Issue#project= + new_issue.custom_field_values = issue.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h} # Reassign fixed_versions by name, since names are unique per project if issue.fixed_version && issue.fixed_version.project == project new_issue.fixed_version = self.versions.detect {|v| v.name == issue.fixed_version.name} @@ -894,7 +945,7 @@ # Copies queries from +project+ def copy_queries(project) project.queries.each do |query| - new_query = ::Query.new + new_query = IssueQuery.new new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria") new_query.sort_criteria = query.sort_criteria if query.sort_criteria new_query.project = self @@ -951,13 +1002,11 @@ def system_activities_and_project_overrides(include_inactive=false) if include_inactive return TimeEntryActivity.shared. - find(:all, - :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) + + where("id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)).all + self.time_entry_activities else return TimeEntryActivity.shared.active. - find(:all, - :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) + + where("id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)).all + self.time_entry_activities.active end end @@ -976,6 +1025,7 @@ # Inserts/moves the project so that target's children or root projects stay alphabetically sorted def set_or_update_position_under(target_parent) + parent_was = parent sibs = (target_parent.nil? ? self.class.roots : target_parent.children) to_be_inserted_before = sibs.sort_by {|c| c.name.to_s.downcase}.detect {|c| c.name.to_s.downcase > name.to_s.downcase } @@ -992,5 +1042,8 @@ # move_to_child_of adds the project in last (ie.right) position move_to_child_of(target_parent) end + if parent_was != target_parent + after_parent_changed(parent_was) + end end end diff -r 0a574315af3e -r 4f746d8966dd app/models/project_custom_field.rb --- a/app/models/project_custom_field.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/project_custom_field.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/models/query.rb --- a/app/models/query.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/query.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -28,11 +28,12 @@ end self.default_order = options[:default_order] @inline = options.key?(:inline) ? options[:inline] : true - @caption_key = options[:caption] || "field_#{name}" + @caption_key = options[:caption] || "field_#{name}".to_sym + @frozen = options[:frozen] end def caption - l(@caption_key) + @caption_key.is_a?(Symbol) ? l(@caption_key) : @caption_key end # Returns true if the column is sortable, otherwise false @@ -48,8 +49,12 @@ @inline end - def value(issue) - issue.send name + def frozen? + @frozen + end + + def value(object) + object.send name end def css_classes @@ -75,8 +80,8 @@ @cf end - def value(issue) - cv = issue.custom_values.select {|v| v.custom_field_id == @cf.id}.collect {|v| @cf.cast_value(v.value)} + def value(object) + cv = object.custom_values.select {|v| v.custom_field_id == @cf.id}.collect {|v| @cf.cast_value(v.value)} cv.size > 1 ? cv.sort {|a,b| a.to_s <=> b.to_s} : cv.first end @@ -85,6 +90,28 @@ end end +class QueryAssociationCustomFieldColumn < QueryCustomFieldColumn + + def initialize(association, custom_field) + super(custom_field) + self.name = "#{association}.cf_#{custom_field.id}".to_sym + # TODO: support sorting/grouping by association custom field + self.sortable = false + self.groupable = false + @association = association + end + + def value(object) + if assoc = object.send(@association) + super(assoc) + end + end + + def css_classes + @css_classes ||= "#{@association}_cf_#{@cf.id} #{@cf.field_format}" + end +end + class Query < ActiveRecord::Base class StatementInvalid < ::ActiveRecord::StatementInvalid end @@ -101,85 +128,89 @@ validates_length_of :name, :maximum => 255 validate :validate_query_filters - @@operators = { "=" => :label_equals, - "!" => :label_not_equals, - "o" => :label_open_issues, - "c" => :label_closed_issues, - "!*" => :label_none, - "*" => :label_any, - ">=" => :label_greater_or_equal, - "<=" => :label_less_or_equal, - "><" => :label_between, - " :label_in_less_than, - ">t+" => :label_in_more_than, - "> :label_in_the_next_days, - "t+" => :label_in, - "t" => :label_today, - "w" => :label_this_week, - ">t-" => :label_less_than_ago, - " :label_more_than_ago, - "> :label_in_the_past_days, - "t-" => :label_ago, - "~" => :label_contains, - "!~" => :label_not_contains, - "=p" => :label_any_issues_in_project, - "=!p" => :label_any_issues_not_in_project, - "!p" => :label_no_issues_in_project} + class_attribute :operators + self.operators = { + "=" => :label_equals, + "!" => :label_not_equals, + "o" => :label_open_issues, + "c" => :label_closed_issues, + "!*" => :label_none, + "*" => :label_any, + ">=" => :label_greater_or_equal, + "<=" => :label_less_or_equal, + "><" => :label_between, + " :label_in_less_than, + ">t+" => :label_in_more_than, + "> :label_in_the_next_days, + "t+" => :label_in, + "t" => :label_today, + "ld" => :label_yesterday, + "w" => :label_this_week, + "lw" => :label_last_week, + "l2w" => [:label_last_n_weeks, {:count => 2}], + "m" => :label_this_month, + "lm" => :label_last_month, + "y" => :label_this_year, + ">t-" => :label_less_than_ago, + " :label_more_than_ago, + "> :label_in_the_past_days, + "t-" => :label_ago, + "~" => :label_contains, + "!~" => :label_not_contains, + "=p" => :label_any_issues_in_project, + "=!p" => :label_any_issues_not_in_project, + "!p" => :label_no_issues_in_project + } - cattr_reader :operators + class_attribute :operators_by_filter_type + self.operators_by_filter_type = { + :list => [ "=", "!" ], + :list_status => [ "o", "=", "!", "c", "*" ], + :list_optional => [ "=", "!", "!*", "*" ], + :list_subprojects => [ "*", "!*", "=" ], + :date => [ "=", ">=", "<=", "><", "t+", ">t-", " [ "=", ">=", "<=", "><", ">t-", " [ "=", "~", "!", "!~", "!*", "*" ], + :text => [ "~", "!~", "!*", "*" ], + :integer => [ "=", ">=", "<=", "><", "!*", "*" ], + :float => [ "=", ">=", "<=", "><", "!*", "*" ], + :relation => ["=", "=p", "=!p", "!p", "!*", "*"] + } - @@operators_by_filter_type = { :list => [ "=", "!" ], - :list_status => [ "o", "=", "!", "c", "*" ], - :list_optional => [ "=", "!", "!*", "*" ], - :list_subprojects => [ "*", "!*", "=" ], - :date => [ "=", ">=", "<=", "><", "t+", ">t-", " [ "=", ">=", "<=", "><", ">t-", " [ "=", "~", "!", "!~", "!*", "*" ], - :text => [ "~", "!~", "!*", "*" ], - :integer => [ "=", ">=", "<=", "><", "!*", "*" ], - :float => [ "=", ">=", "<=", "><", "!*", "*" ], - :relation => ["=", "=p", "=!p", "!p", "!*", "*"]} + class_attribute :available_columns + self.available_columns = [] - cattr_reader :operators_by_filter_type + class_attribute :queried_class - @@available_columns = [ - QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true), - QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true), - QueryColumn.new(:parent, :sortable => ["#{Issue.table_name}.root_id", "#{Issue.table_name}.lft ASC"], :default_order => 'desc', :caption => :field_parent_issue), - QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true), - QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true), - QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"), - QueryColumn.new(:author, :sortable => lambda {User.fields_for_order_statement("authors")}, :groupable => true), - QueryColumn.new(:assigned_to, :sortable => lambda {User.fields_for_order_statement}, :groupable => true), - QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'), - QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true), - QueryColumn.new(:fixed_version, :sortable => lambda {Version.fields_for_order_statement}, :groupable => true), - QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"), - QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"), - QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"), - QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true), - QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'), - QueryColumn.new(:relations, :caption => :label_related_issues), - QueryColumn.new(:description, :inline => false) - ] - cattr_reader :available_columns - - scope :visible, lambda {|*args| - user = args.shift || User.current - base = Project.allowed_to_condition(user, :view_issues, *args) - user_id = user.logged? ? user.id : 0 - { - :conditions => ["(#{table_name}.project_id IS NULL OR (#{base})) AND (#{table_name}.is_public = ? OR #{table_name}.user_id = ?)", true, user_id], - :include => :project - } - } + def queried_table_name + @queried_table_name ||= self.class.queried_class.table_name + end def initialize(attributes=nil, *args) super attributes - self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} } @is_for_all = project.nil? end + # Builds the query from the given params + def build_from_params(params) + if params[:fields] || params[:f] + self.filters = {} + add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v]) + else + available_filters.keys.each do |field| + add_short_filter(field, params[field]) if params[field] + end + end + self.group_by = params[:group_by] || (params[:query] && params[:query][:group_by]) + self.column_names = params[:c] || (params[:query] && params[:query][:column_names]) + self + end + + # Builds a new query from the given params and attributes + def self.build_from_params(params, attributes={}) + new(attributes).build_from_params(params) + end + def validate_query_filters filters.each_key do |field| if values_for(field) @@ -202,7 +233,7 @@ # filter requires one or more values (values_for(field) and !values_for(field).first.blank?) or # filter doesn't require any value - ["o", "c", "!*", "*", "t", "w"].include? operator_for(field) + ["o", "c", "!*", "*", "t", "ld", "w", "lw", "l2w", "m", "lm", "y"].include? operator_for(field) end if filters end @@ -211,11 +242,6 @@ errors.add(:base, m) end - # Returns true if the query is visible to +user+ or the current user. - def visible?(user=User.current) - (project.nil? || user.allowed_to?(:view_issues, project)) && (self.is_public? || self.user_id == user.id) - end - def editable_by?(user) return false unless user # Admin can edit them all and regular users can edit their private queries @@ -225,154 +251,12 @@ end def trackers - @trackers ||= project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers + @trackers ||= project.nil? ? Tracker.sorted.all : project.rolled_up_trackers end # Returns a hash of localized labels for all filter operators def self.operators_labels - operators.inject({}) {|h, operator| h[operator.first] = l(operator.last); h} - end - - def available_filters - return @available_filters if @available_filters - @available_filters = { - "status_id" => { - :type => :list_status, :order => 0, - :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } - }, - "tracker_id" => { - :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } - }, - "priority_id" => { - :type => :list, :order => 3, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } - }, - "subject" => { :type => :text, :order => 8 }, - "created_on" => { :type => :date_past, :order => 9 }, - "updated_on" => { :type => :date_past, :order => 10 }, - "start_date" => { :type => :date, :order => 11 }, - "due_date" => { :type => :date, :order => 12 }, - "estimated_hours" => { :type => :float, :order => 13 }, - "done_ratio" => { :type => :integer, :order => 14 } - } - IssueRelation::TYPES.each do |relation_type, options| - @available_filters[relation_type] = { - :type => :relation, :order => @available_filters.size + 100, - :label => options[:name] - } - end - principals = [] - if project - principals += project.principals.sort - unless project.leaf? - subprojects = project.descendants.visible.all - if subprojects.any? - @available_filters["subproject_id"] = { - :type => :list_subprojects, :order => 13, - :values => subprojects.collect{|s| [s.name, s.id.to_s] } - } - principals += Principal.member_of(subprojects) - end - end - else - if all_projects.any? - # members of visible projects - principals += Principal.member_of(all_projects) - # project filter - project_values = [] - if User.current.logged? && User.current.memberships.any? - project_values << ["<< #{l(:label_my_projects).downcase} >>", "mine"] - end - project_values += all_projects_values - @available_filters["project_id"] = { - :type => :list, :order => 1, :values => project_values - } unless project_values.empty? - end - end - principals.uniq! - principals.sort! - users = principals.select {|p| p.is_a?(User)} - - assigned_to_values = [] - assigned_to_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged? - assigned_to_values += (Setting.issue_group_assignment? ? - principals : users).collect{|s| [s.name, s.id.to_s] } - @available_filters["assigned_to_id"] = { - :type => :list_optional, :order => 4, :values => assigned_to_values - } unless assigned_to_values.empty? - - author_values = [] - author_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged? - author_values += users.collect{|s| [s.name, s.id.to_s] } - @available_filters["author_id"] = { - :type => :list, :order => 5, :values => author_values - } unless author_values.empty? - - group_values = Group.all.collect {|g| [g.name, g.id.to_s] } - @available_filters["member_of_group"] = { - :type => :list_optional, :order => 6, :values => group_values - } unless group_values.empty? - - role_values = Role.givable.collect {|r| [r.name, r.id.to_s] } - @available_filters["assigned_to_role"] = { - :type => :list_optional, :order => 7, :values => role_values - } unless role_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 - categories = project.issue_categories.all - unless categories.empty? - @available_filters["category_id"] = { - :type => :list_optional, :order => 6, - :values => categories.collect{|s| [s.name, s.id.to_s] } - } - end - versions = project.shared_versions.all - unless versions.empty? - @available_filters["fixed_version_id"] = { - :type => :list_optional, :order => 7, - :values => versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } - } - end - add_custom_fields_filters(project.all_issue_custom_fields) - else - # global filters for cross project issue list - system_shared_versions = Version.visible.find_all_by_sharing('system') - unless system_shared_versions.empty? - @available_filters["fixed_version_id"] = { - :type => :list_optional, :order => 7, - :values => system_shared_versions.sort.collect{|s| - ["#{s.project.name} - #{s.name}", s.id.to_s] - } - } - end - add_custom_fields_filters( - IssueCustomField.find(:all, - :conditions => { - :is_filter => true, - :is_for_all => true - })) - end - add_associations_custom_fields_filters :project, :author, :assigned_to, :fixed_version - if User.current.allowed_to?(:set_issues_private, nil, :global => true) || - User.current.allowed_to?(:set_own_issues_private, nil, :global => true) - @available_filters["is_private"] = { - :type => :list, :order => 16, - :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]] - } - end - Tracker.disabled_core_fields(trackers).each {|field| - @available_filters.delete field - } - @available_filters.each do |field, options| - options[:name] ||= l(options[:label] || "field_#{field}".gsub(/_id$/, '')) - end - @available_filters + operators.inject({}) {|h, operator| h[operator.first] = l(*operator.last); h} end # Returns a representation of the available filters for JSON serialization @@ -399,17 +283,43 @@ @all_projects_values = values end - def add_filter(field, operator, values) + # Adds available filters + def initialize_available_filters + # implemented by sub-classes + end + protected :initialize_available_filters + + # Adds an available filter + def add_available_filter(field, options) + @available_filters ||= ActiveSupport::OrderedHash.new + @available_filters[field] = options + @available_filters + end + + # Removes an available filter + def delete_available_filter(field) + if @available_filters + @available_filters.delete(field) + end + end + + # Return a hash of available filters + def available_filters + unless @available_filters + initialize_available_filters + @available_filters.each do |field, options| + options[:name] ||= l(options[:label] || "field_#{field}".gsub(/_id$/, '')) + end + end + @available_filters + end + + def add_filter(field, operator, values=nil) # values must be an array return unless values.nil? || values.is_a?(Array) # check if field is defined as an available filter if available_filters.has_key? field filter_options = available_filters[field] - # check if operator is allowed for that filter - #if @@operators_by_filter_type[filter_options[:type]].include? operator - # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]}) - # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator - #end filters[field] = {:operator => operator, :values => (values || [''])} end end @@ -417,9 +327,10 @@ def add_short_filter(field, expression) return unless expression && available_filters.has_key?(field) field_type = available_filters[field][:type] - @@operators_by_filter_type[field_type].sort.reverse.detect do |operator| + operators_by_filter_type[field_type].sort.reverse.detect do |operator| next unless expression =~ /^#{Regexp.escape(operator)}(.*)$/ - add_filter field, operator, $1.present? ? $1.split('|') : [''] + values = $1 + add_filter field, operator, values.present? ? values.split('|') : [''] end || add_filter(field, '=', expression.split('|')) end @@ -457,43 +368,6 @@ label ||= l("field_#{field.to_s.gsub(/_id$/, '')}", :default => field) end - def available_columns - return @available_columns if @available_columns - @available_columns = ::Query.available_columns.dup - @available_columns += (project ? - project.all_issue_custom_fields : - IssueCustomField.find(:all) - ).collect {|cf| QueryCustomFieldColumn.new(cf) } - - if User.current.allowed_to?(:view_time_entries, project, :global => true) - index = nil - @available_columns.each_with_index {|column, i| index = i if column.name == :estimated_hours} - index = (index ? index + 1 : -1) - # insert the column after estimated_hours or at the end - @available_columns.insert index, QueryColumn.new(:spent_hours, - :sortable => "(SELECT COALESCE(SUM(hours), 0) FROM #{TimeEntry.table_name} WHERE #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id)", - :default_order => 'desc', - :caption => :label_spent_time - ) - end - - if User.current.allowed_to?(:set_issues_private, nil, :global => true) || - User.current.allowed_to?(:set_own_issues_private, nil, :global => true) - @available_columns << QueryColumn.new(:is_private, :sortable => "#{Issue.table_name}.is_private") - end - - disabled_fields = Tracker.disabled_core_fields(trackers).map {|field| field.sub(/_id$/, '')} - @available_columns.reject! {|column| - disabled_fields.include?(column.name.to_s) - } - - @available_columns - end - - def self.available_columns=(v) - self.available_columns = (v) - end - def self.add_available_column(column) self.available_columns << (column) if column.is_a?(QueryColumn) end @@ -505,17 +379,18 @@ # Returns a Hash of columns and the key for sorting def sortable_columns - {'id' => "#{Issue.table_name}.id"}.merge(available_columns.inject({}) {|h, column| - h[column.name.to_s] = column.sortable - h - }) + available_columns.inject({}) {|h, column| + h[column.name.to_s] = column.sortable + h + } end def columns # preserve the column_names order - (has_default_columns? ? default_columns_names : column_names).collect do |name| + cols = (has_default_columns? ? default_columns_names : column_names).collect do |name| available_columns.find { |col| col.name == name } end.compact + available_columns.select(&:frozen?) | cols end def inline_columns @@ -535,11 +410,7 @@ end def default_columns_names - @default_columns_names ||= begin - default_columns = Setting.issue_list_default_columns.map(&:to_sym) - - project.present? ? default_columns : [:project] | default_columns - end + [] end def column_names=(names) @@ -645,7 +516,7 @@ operator = operator_for(field) # "me" value subsitution - if %w(assigned_to_id author_id watcher_id).include?(field) + if %w(assigned_to_id author_id user_id watcher_id).include?(field) if v.delete("me") if User.current.logged? v.push(User.current.id.to_s) @@ -670,7 +541,7 @@ filters_clauses << send("sql_for_#{field}_field", field, operator, v) else # regular field - filters_clauses << '(' + sql_for_field(field, operator, v, Issue.table_name, field) + ')' + filters_clauses << '(' + sql_for_field(field, operator, v, queried_table_name, field) + ')' end end if filters and valid? @@ -680,182 +551,6 @@ filters_clauses.any? ? filters_clauses.join(' AND ') : nil end - # Returns the issue count - def issue_count - Issue.visible.count(:include => [:status, :project], :conditions => statement) - rescue ::ActiveRecord::StatementInvalid => e - raise StatementInvalid.new(e.message) - end - - # Returns the issue count by group or nil if query is not grouped - def issue_count_by_group - r = nil - if grouped? - begin - # Rails3 will raise an (unexpected) RecordNotFound if there's only a nil group value - r = Issue.visible.count(:group => group_by_statement, :include => [:status, :project], :conditions => statement) - rescue ActiveRecord::RecordNotFound - r = {nil => issue_count} - end - c = group_by_column - if c.is_a?(QueryCustomFieldColumn) - r = r.keys.inject({}) {|h, k| h[c.custom_field.cast_value(k)] = r[k]; h} - end - end - r - rescue ::ActiveRecord::StatementInvalid => e - raise StatementInvalid.new(e.message) - end - - # Returns the issues - # Valid options are :order, :offset, :limit, :include, :conditions - def issues(options={}) - order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',') - order_option = nil if order_option.blank? - - issues = Issue.visible.scoped(:conditions => options[:conditions]).find :all, :include => ([:status, :project] + (options[:include] || [])).uniq, - :conditions => statement, - :order => order_option, - :joins => joins_for_order_statement(order_option), - :limit => options[:limit], - :offset => options[:offset] - - if has_column?(:spent_hours) - Issue.load_visible_spent_hours(issues) - end - if has_column?(:relations) - Issue.load_visible_relations(issues) - end - issues - rescue ::ActiveRecord::StatementInvalid => e - raise StatementInvalid.new(e.message) - end - - # Returns the issues ids - def issue_ids(options={}) - order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',') - order_option = nil if order_option.blank? - - Issue.visible.scoped(:conditions => options[:conditions]).scoped(:include => ([:status, :project] + (options[:include] || [])).uniq, - :conditions => statement, - :order => order_option, - :joins => joins_for_order_statement(order_option), - :limit => options[:limit], - :offset => options[:offset]).find_ids - rescue ::ActiveRecord::StatementInvalid => e - raise StatementInvalid.new(e.message) - end - - # Returns the journals - # Valid options are :order, :offset, :limit - def journals(options={}) - Journal.visible.find :all, :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}], - :conditions => statement, - :order => options[:order], - :limit => options[:limit], - :offset => options[:offset] - rescue ::ActiveRecord::StatementInvalid => e - raise StatementInvalid.new(e.message) - end - - # Returns the versions - # Valid options are :conditions - def versions(options={}) - Version.visible.scoped(:conditions => options[:conditions]).find :all, :include => :project, :conditions => project_statement - rescue ::ActiveRecord::StatementInvalid => e - raise StatementInvalid.new(e.message) - end - - def sql_for_watcher_id_field(field, operator, value) - db_table = Watcher.table_name - "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " + - sql_for_field(field, '=', value, db_table, 'user_id') + ')' - end - - def sql_for_member_of_group_field(field, operator, value) - if operator == '*' # Any group - groups = Group.all - operator = '=' # Override the operator since we want to find by assigned_to - elsif operator == "!*" - groups = Group.all - operator = '!' # Override the operator since we want to find by assigned_to - else - groups = Group.find_all_by_id(value) - end - groups ||= [] - - members_of_groups = groups.inject([]) {|user_ids, group| - if group && group.user_ids.present? - user_ids << group.user_ids - end - user_ids.flatten.uniq.compact - }.sort.collect(&:to_s) - - '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')' - end - - def sql_for_assigned_to_role_field(field, operator, value) - case operator - when "*", "!*" # Member / Not member - sw = operator == "!*" ? 'NOT' : '' - nl = operator == "!*" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : '' - "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}" + - " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id))" - when "=", "!" - role_cond = value.any? ? - "#{MemberRole.table_name}.role_id IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")" : - "1=0" - - sw = operator == "!" ? 'NOT' : '' - nl = operator == "!" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : '' - "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}, #{MemberRole.table_name}" + - " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id AND #{Member.table_name}.id = #{MemberRole.table_name}.member_id AND #{role_cond}))" - end - end - - def sql_for_is_private_field(field, operator, value) - op = (operator == "=" ? 'IN' : 'NOT IN') - va = value.map {|v| v == '0' ? connection.quoted_false : connection.quoted_true}.uniq.join(',') - - "#{Issue.table_name}.is_private #{op} (#{va})" - end - - def sql_for_relations(field, operator, value, options={}) - relation_options = IssueRelation::TYPES[field] - return relation_options unless relation_options - - relation_type = field - join_column, target_join_column = "issue_from_id", "issue_to_id" - if relation_options[:reverse] || options[:reverse] - relation_type = relation_options[:reverse] || relation_type - join_column, target_join_column = target_join_column, join_column - end - - sql = case operator - when "*", "!*" - op = (operator == "*" ? 'IN' : 'NOT IN') - "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}')" - when "=", "!" - op = (operator == "=" ? 'IN' : 'NOT IN') - "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = #{value.first.to_i})" - when "=p", "=!p", "!p" - op = (operator == "!p" ? 'NOT IN' : 'IN') - comp = (operator == "=!p" ? '<>' : '=') - "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name}, #{Issue.table_name} relissues WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = relissues.id AND relissues.project_id #{comp} #{value.first.to_i})" - end - - if relation_options[:sym] == field && !options[:reverse] - sqls = [sql, sql_for_relations(field, operator, value, :reverse => true)] - sqls.join(["!", "!*", "!p"].include?(operator) ? " AND " : " OR ") - else - sql - end - end - - IssueRelation::TYPES.keys.each do |relation_type| - alias_method "sql_for_#{relation_type}_field".to_sym, :sql_for_relations - end - private def sql_for_custom_field(field, operator, value, custom_field_id) @@ -875,15 +570,18 @@ not_in = 'NOT' end customized_key = "id" - customized_class = Issue + customized_class = queried_class if field =~ /^(.+)\.cf_/ assoc = $1 customized_key = "#{assoc}_id" - customized_class = Issue.reflect_on_association(assoc.to_sym).klass.base_class rescue nil - raise "Unknown Issue association #{assoc}" unless customized_class + customized_class = queried_class.reflect_on_association(assoc.to_sym).klass.base_class rescue nil + raise "Unknown #{queried_class.name} association #{assoc}" unless customized_class end - "#{Issue.table_name}.#{customized_key} #{not_in} IN (SELECT #{customized_class.table_name}.id FROM #{customized_class.table_name} LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='#{customized_class}' AND #{db_table}.customized_id=#{customized_class.table_name}.id AND #{db_table}.custom_field_id=#{custom_field_id} WHERE " + - sql_for_field(field, operator, value, db_table, db_field, true) + ')' + where = sql_for_field(field, operator, value, db_table, db_field, true) + if operator =~ /[<>]/ + where = "(#{where}) AND #{db_table}.#{db_field} <> ''" + end + "#{queried_table_name}.#{customized_key} #{not_in} IN (SELECT #{customized_class.table_name}.id FROM #{customized_class.table_name} LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='#{customized_class}' AND #{db_table}.customized_id=#{customized_class.table_name}.id AND #{db_table}.custom_field_id=#{custom_field_id} WHERE #{where})" end # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+ @@ -897,13 +595,13 @@ sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), (Date.parse(value.first) rescue nil)) when :integer if is_custom_filter - sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) = #{value.first.to_i})" + sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) = #{value.first.to_i})" else sql = "#{db_table}.#{db_field} = #{value.first.to_i}" end when :float if is_custom_filter - sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5})" + sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5})" else sql = "#{db_table}.#{db_field} BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5}" end @@ -932,7 +630,7 @@ sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), nil) else if is_custom_filter - sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) >= #{value.first.to_f})" + sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) >= #{value.first.to_f})" else sql = "#{db_table}.#{db_field} >= #{value.first.to_f}" end @@ -942,7 +640,7 @@ sql = date_clause(db_table, db_field, nil, (Date.parse(value.first) rescue nil)) else if is_custom_filter - sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) <= #{value.first.to_f})" + sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) <= #{value.first.to_f})" else sql = "#{db_table}.#{db_field} <= #{value.first.to_f}" end @@ -952,15 +650,15 @@ sql = date_clause(db_table, db_field, (Date.parse(value[0]) rescue nil), (Date.parse(value[1]) rescue nil)) else if is_custom_filter - sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) BETWEEN #{value[0].to_f} AND #{value[1].to_f})" + sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) BETWEEN #{value[0].to_f} AND #{value[1].to_f})" else sql = "#{db_table}.#{db_field} BETWEEN #{value[0].to_f} AND #{value[1].to_f}" end end when "o" - sql = "#{Issue.table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_false})" if field == "status_id" + sql = "#{queried_table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_false})" if field == "status_id" when "c" - sql = "#{Issue.table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_true})" if field == "status_id" + sql = "#{queried_table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_true})" if field == "status_id" when ">= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week) sql = relative_date_clause(db_table, db_field, - days_ago, - days_ago + 6) + when "lw" + # = last week + first_day_of_week = l(:general_first_day_of_week).to_i + day_of_week = Date.today.cwday + days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week) + sql = relative_date_clause(db_table, db_field, - days_ago - 7, - days_ago - 1) + when "l2w" + # = last 2 weeks + first_day_of_week = l(:general_first_day_of_week).to_i + day_of_week = Date.today.cwday + days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week) + sql = relative_date_clause(db_table, db_field, - days_ago - 14, - days_ago - 1) + when "m" + # = this month + date = Date.today + sql = date_clause(db_table, db_field, date.beginning_of_month, date.end_of_month) + when "lm" + # = last month + date = Date.today.prev_month + sql = date_clause(db_table, db_field, date.beginning_of_month, date.end_of_month) + when "y" + # = this year + date = Date.today + sql = date_clause(db_table, db_field, date.beginning_of_year, date.end_of_year) when "~" sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'" when "!~" @@ -1007,31 +732,30 @@ def add_custom_fields_filters(custom_fields, assoc=nil) return unless custom_fields.present? - @available_filters ||= {} - custom_fields.select(&:is_filter?).each do |field| + custom_fields.select(&:is_filter?).sort.each do |field| case field.field_format when "text" - options = { :type => :text, :order => 20 } + options = { :type => :text } when "list" - options = { :type => :list_optional, :values => field.possible_values, :order => 20} + options = { :type => :list_optional, :values => field.possible_values } when "date" - options = { :type => :date, :order => 20 } + options = { :type => :date } when "bool" - options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 } + options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]] } when "int" - options = { :type => :integer, :order => 20 } + options = { :type => :integer } when "float" - options = { :type => :float, :order => 20 } + options = { :type => :float } when "user", "version" next unless project values = field.possible_values_options(project) if User.current.logged? && field.field_format == 'user' values.unshift ["<< #{l(:label_me)} >>", "me"] end - options = { :type => :list_optional, :values => values, :order => 20} + options = { :type => :list_optional, :values => values } else - options = { :type => :string, :order => 20 } + options = { :type => :string } end filter_id = "cf_#{field.id}" filter_name = field.name @@ -1039,7 +763,7 @@ filter_id = "#{assoc}.#{filter_id}" filter_name = l("label_attribute_of_#{assoc}", :name => filter_name) end - @available_filters[filter_id] = options.merge({ + add_available_filter filter_id, options.merge({ :name => filter_name, :format => field.field_format, :field => field @@ -1050,7 +774,7 @@ def add_associations_custom_fields_filters(*associations) fields_by_class = CustomField.where(:is_filter => true).group_by(&:class) associations.each do |assoc| - association_klass = Issue.reflect_on_association(assoc).klass + association_klass = queried_class.reflect_on_association(assoc).klass fields_by_class.each do |field_class, fields| if field_class.customized_class <= association_klass add_custom_fields_filters(fields, assoc) @@ -1091,7 +815,7 @@ if order_options if order_options.include?('authors') - joins << "LEFT OUTER JOIN #{User.table_name} authors ON authors.id = #{Issue.table_name}.author_id" + joins << "LEFT OUTER JOIN #{User.table_name} authors ON authors.id = #{queried_table_name}.author_id" end order_options.scan(/cf_\d+/).uniq.each do |name| column = available_columns.detect {|c| c.name.to_s == name} diff -r 0a574315af3e -r 4f746d8966dd app/models/repository.rb --- a/app/models/repository.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/repository.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -42,7 +42,7 @@ validates_uniqueness_of :identifier, :scope => :project_id, :allow_blank => true validates_exclusion_of :identifier, :in => %w(show entry raw changes annotate diff show stats graph) # donwcase letters, digits, dashes, underscores but not digits only - validates_format_of :identifier, :with => /^(?!\d+$)[a-z0-9\-_]*$/, :allow_blank => true + validates_format_of :identifier, :with => /\A(?!\d+$)[a-z0-9\-_]*\z/, :allow_blank => true # Checks if the SCM is enabled when creating a repository validate :repo_create_validation, :on => :create @@ -234,12 +234,15 @@ def find_changeset_by_name(name) return nil if name.blank? s = name.to_s - changesets.find(:first, :conditions => (s.match(/^\d*$/) ? - ["revision = ?", s] : ["revision LIKE ?", s + '%'])) + if s.match(/^\d*$/) + changesets.where("revision = ?", s).first + else + changesets.where("revision LIKE ?", s + '%').first + end end def latest_changeset - @latest_changeset ||= changesets.find(:first) + @latest_changeset ||= changesets.first end # Returns the latest changesets for +path+ @@ -301,7 +304,7 @@ return @found_committer_users[committer] if @found_committer_users.has_key?(committer) user = nil - c = changesets.find(:first, :conditions => {:committer => committer}, :include => :user) + c = changesets.where(:committer => committer).includes(:user).first if c && c.user user = c.user elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/ @@ -337,7 +340,7 @@ # scan changeset comments to find related and fixed issues for all repositories def self.scan_changesets_for_issue_ids - find(:all).each(&:scan_changesets_for_issue_ids) + all.each(&:scan_changesets_for_issue_ids) end def self.scm_name diff -r 0a574315af3e -r 4f746d8966dd app/models/repository/bazaar.rb --- a/app/models/repository/bazaar.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/repository/bazaar.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/models/repository/cvs.rb --- a/app/models/repository/cvs.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/repository/cvs.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/models/repository/darcs.rb --- a/app/models/repository/darcs.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/repository/darcs.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/models/repository/filesystem.rb --- a/app/models/repository/filesystem.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/repository/filesystem.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 Jean-Philippe Lang # # FileSystem adapter # File written by Paul Rivier, at Demotera. diff -r 0a574315af3e -r 4f746d8966dd app/models/repository/git.rb --- a/app/models/repository/git.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/repository/git.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 Jean-Philippe Lang # Copyright (C) 2007 Patrick Aljord patcito@Å‹mail.com # # This program is free software; you can redistribute it and/or @@ -251,8 +251,7 @@ :conditions => [ "scmid IN (?)", revisions.map!{|c| c.scmid} - ], - :order => 'committed_on DESC' + ] ) end diff -r 0a574315af3e -r 4f746d8966dd app/models/repository/mercurial.rb --- a/app/models/repository/mercurial.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/repository/mercurial.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -92,11 +92,12 @@ # Sqlite3 and PostgreSQL pass. # Is this MySQL bug? def latest_changesets(path, rev, limit=10) - changesets.find(:all, - :include => :user, - :conditions => latest_changesets_cond(path, rev, limit), - :limit => limit, - :order => "#{Changeset.table_name}.id DESC") + changesets. + includes(:user). + where(latest_changesets_cond(path, rev, limit)). + limit(limit). + order("#{Changeset.table_name}.id DESC"). + all end def latest_changesets_cond(path, rev, limit) diff -r 0a574315af3e -r 4f746d8966dd app/models/repository/subversion.rb --- a/app/models/repository/subversion.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/repository/subversion.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -20,7 +20,7 @@ class Repository::Subversion < Repository attr_protected :root_url validates_presence_of :url - validates_format_of :url, :with => /^(http|https|svn(\+[^\s:\/\\]+)?|file):\/\/.+/i + validates_format_of :url, :with => /\A(http|https|svn(\+[^\s:\/\\]+)?|file):\/\/.+/i def self.scm_adapter_class Redmine::Scm::Adapters::SubversionAdapter diff -r 0a574315af3e -r 4f746d8966dd app/models/role.rb --- a/app/models/role.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/role.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -39,8 +39,8 @@ ['own', :label_issues_visibility_own] ] - scope :sorted, order("#{table_name}.builtin ASC, #{table_name}.position ASC") - scope :givable, order("#{table_name}.position ASC").where(:builtin => 0) + scope :sorted, lambda { order("#{table_name}.builtin ASC, #{table_name}.position ASC") } + scope :givable, lambda { order("#{table_name}.position ASC").where(:builtin => 0) } scope :builtin, lambda { |*args| compare = (args.first == true ? 'not' : '') where("#{compare} builtin = 0") diff -r 0a574315af3e -r 4f746d8966dd app/models/setting.rb --- a/app/models/setting.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/setting.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -18,15 +18,15 @@ class Setting < ActiveRecord::Base DATE_FORMATS = [ - '%Y-%m-%d', - '%d/%m/%Y', - '%d.%m.%Y', - '%d-%m-%Y', - '%m/%d/%Y', - '%d %b %Y', - '%d %B %Y', - '%b %d, %Y', - '%B %d, %Y' + '%Y-%m-%d', + '%d/%m/%Y', + '%d.%m.%Y', + '%d-%m-%Y', + '%m/%d/%Y', + '%d %b %Y', + '%d %B %Y', + '%b %d, %Y', + '%B %d, %Y' ] TIME_FORMATS = [ diff -r 0a574315af3e -r 4f746d8966dd app/models/time_entry.rb --- a/app/models/time_entry.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/time_entry.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -30,6 +30,7 @@ acts_as_event :title => Proc.new {|o| "#{l_hours(o.hours)} (#{(o.issue || o.project).event_title})"}, :url => Proc.new {|o| {:controller => 'timelog', :action => 'index', :project_id => o.project, :issue_id => o.issue}}, :author => :user, + :group => :issue, :description => :comments acts_as_activity_provider :timestamp => "#{table_name}.created_on", @@ -39,30 +40,28 @@ validates_presence_of :user_id, :activity_id, :project_id, :hours, :spent_on validates_numericality_of :hours, :allow_nil => true, :message => :invalid validates_length_of :comments, :maximum => 255, :allow_nil => true + validates :spent_on, :date => true before_validation :set_project_if_nil validate :validate_time_entry - scope :visible, lambda {|*args| { - :include => :project, - :conditions => Project.allowed_to_condition(args.shift || User.current, :view_time_entries, *args) - }} - scope :on_issue, lambda {|issue| { - :include => :issue, - :conditions => "#{Issue.table_name}.root_id = #{issue.root_id} AND #{Issue.table_name}.lft >= #{issue.lft} AND #{Issue.table_name}.rgt <= #{issue.rgt}" - }} - scope :on_project, lambda {|project, include_subprojects| { - :include => :project, - :conditions => project.project_condition(include_subprojects) - }} + scope :visible, lambda {|*args| + includes(:project).where(Project.allowed_to_condition(args.shift || User.current, :view_time_entries, *args)) + } + scope :on_issue, lambda {|issue| + includes(:issue).where("#{Issue.table_name}.root_id = #{issue.root_id} AND #{Issue.table_name}.lft >= #{issue.lft} AND #{Issue.table_name}.rgt <= #{issue.rgt}") + } + scope :on_project, lambda {|project, include_subprojects| + includes(:project).where(project.project_condition(include_subprojects)) + } scope :spent_between, lambda {|from, to| if from && to - {:conditions => ["#{TimeEntry.table_name}.spent_on BETWEEN ? AND ?", from, to]} + where("#{TimeEntry.table_name}.spent_on BETWEEN ? AND ?", from, to) elsif from - {:conditions => ["#{TimeEntry.table_name}.spent_on >= ?", from]} + where("#{TimeEntry.table_name}.spent_on >= ?", from) elsif to - {:conditions => ["#{TimeEntry.table_name}.spent_on <= ?", to]} + where("#{TimeEntry.table_name}.spent_on <= ?", to) else - {} + where(nil) end } diff -r 0a574315af3e -r 4f746d8966dd app/models/time_entry_activity.rb --- a/app/models/time_entry_activity.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/time_entry_activity.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,11 +24,15 @@ OptionName end + def objects + TimeEntry.where(:activity_id => self_and_descendants(1).map(&:id)) + end + def objects_count - time_entries.count + objects.count end def transfer_relations(to) - time_entries.update_all("activity_id = #{to.id}") + objects.update_all(:activity_id => to.id) end end diff -r 0a574315af3e -r 4f746d8966dd app/models/time_entry_activity_custom_field.rb --- a/app/models/time_entry_activity_custom_field.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/time_entry_activity_custom_field.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/models/time_entry_custom_field.rb --- a/app/models/time_entry_custom_field.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/time_entry_custom_field.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/models/time_entry_query.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/models/time_entry_query.rb Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,115 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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 TimeEntryQuery < Query + + self.queried_class = TimeEntry + + self.available_columns = [ + QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true), + QueryColumn.new(:spent_on, :sortable => ["#{TimeEntry.table_name}.spent_on", "#{TimeEntry.table_name}.created_on"], :default_order => 'desc', :groupable => true), + QueryColumn.new(:user, :sortable => lambda {User.fields_for_order_statement}, :groupable => true), + QueryColumn.new(:activity, :sortable => "#{TimeEntryActivity.table_name}.position", :groupable => true), + QueryColumn.new(:issue, :sortable => "#{Issue.table_name}.id"), + QueryColumn.new(:comments), + QueryColumn.new(:hours, :sortable => "#{TimeEntry.table_name}.hours"), + ] + + def initialize(attributes=nil, *args) + super attributes + self.filters ||= {} + add_filter('spent_on', '*') unless filters.present? + end + + def initialize_available_filters + add_available_filter "spent_on", :type => :date_past + + principals = [] + if project + principals += project.principals.sort + unless project.leaf? + subprojects = project.descendants.visible.all + if subprojects.any? + add_available_filter "subproject_id", + :type => :list_subprojects, + :values => subprojects.collect{|s| [s.name, s.id.to_s] } + principals += Principal.member_of(subprojects) + end + end + else + if all_projects.any? + # members of visible projects + principals += Principal.member_of(all_projects) + # project filter + project_values = [] + if User.current.logged? && User.current.memberships.any? + project_values << ["<< #{l(:label_my_projects).downcase} >>", "mine"] + end + project_values += all_projects_values + add_available_filter("project_id", + :type => :list, :values => project_values + ) unless project_values.empty? + end + end + principals.uniq! + principals.sort! + users = principals.select {|p| p.is_a?(User)} + + users_values = [] + users_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged? + users_values += users.collect{|s| [s.name, s.id.to_s] } + add_available_filter("user_id", + :type => :list_optional, :values => users_values + ) unless users_values.empty? + + activities = (project ? project.activities : TimeEntryActivity.shared.active) + add_available_filter("activity_id", + :type => :list, :values => activities.map {|a| [a.name, a.id.to_s]} + ) unless activities.empty? + + add_available_filter "comments", :type => :text + add_available_filter "hours", :type => :float + + add_custom_fields_filters(TimeEntryCustomField.where(:is_filter => true).all) + add_associations_custom_fields_filters :project, :issue, :user + end + + def available_columns + return @available_columns if @available_columns + @available_columns = self.class.available_columns.dup + @available_columns += TimeEntryCustomField.all.map {|cf| QueryCustomFieldColumn.new(cf) } + @available_columns += IssueCustomField.all.map {|cf| QueryAssociationCustomFieldColumn.new(:issue, cf) } + @available_columns + end + + def default_columns_names + @default_columns_names ||= [:project, :spent_on, :user, :activity, :issue, :comments, :hours] + end + + # Accepts :from/:to params as shortcut filters + def build_from_params(params) + super + if params[:from].present? && params[:to].present? + add_filter('spent_on', '><', [params[:from], params[:to]]) + elsif params[:from].present? + add_filter('spent_on', '>=', [params[:from]]) + elsif params[:to].present? + add_filter('spent_on', '<=', [params[:to]]) + end + self + end +end diff -r 0a574315af3e -r 4f746d8966dd app/models/token.rb --- a/app/models/token.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/token.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -37,11 +37,43 @@ Token.delete_all ["action NOT IN (?) AND created_on < ?", ['feeds', 'api'], Time.now - @@validity_time] end -private + # Returns the active user who owns the key for the given action + def self.find_active_user(action, key, validity_days=nil) + user = find_user(action, key, validity_days) + if user && user.active? + user + end + end + + # Returns the user who owns the key for the given action + def self.find_user(action, key, validity_days=nil) + token = find_token(action, key, validity_days) + if token + token.user + end + end + + # Returns the token for action and key with an optional + # validity duration (in number of days) + def self.find_token(action, key, validity_days=nil) + action = action.to_s + key = key.to_s + return nil unless action.present? && key =~ /\A[a-z0-9]+\z/i + + token = Token.where(:action => action, :value => key).first + if token && (token.action == action) && (token.value == key) && token.user + if validity_days.nil? || (token.created_on > validity_days.days.ago) + token + end + end + end + def self.generate_token_value Redmine::Utils.random_hex(20) end + private + # Removes obsolete tokens (same user and action) def delete_previous_tokens if user diff -r 0a574315af3e -r 4f746d8966dd app/models/tracker.rb --- a/app/models/tracker.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/tracker.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -35,13 +35,13 @@ has_and_belongs_to_many :custom_fields, :class_name => 'IssueCustomField', :join_table => "#{table_name_prefix}custom_fields_trackers#{table_name_suffix}", :association_foreign_key => 'custom_field_id' acts_as_list - attr_protected :field_bits + attr_protected :fields_bits validates_presence_of :name validates_uniqueness_of :name validates_length_of :name, :maximum => 30 - scope :sorted, order("#{table_name}.position ASC") + scope :sorted, lambda { order("#{table_name}.position ASC") } scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)} def to_s; name end diff -r 0a574315af3e -r 4f746d8966dd app/models/user.rb --- a/app/models/user.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/user.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -20,12 +20,6 @@ class User < Principal include Redmine::SafeAttributes - # Account statuses - STATUS_ANONYMOUS = 0 - STATUS_ACTIVE = 1 - STATUS_REGISTERED = 2 - STATUS_LOCKED = 3 - # Different ways of displaying/sorting users USER_FORMATS = { :firstname_lastname => { @@ -82,8 +76,8 @@ has_one :api_token, :class_name => 'Token', :conditions => "action='api'" belongs_to :auth_source - scope :logged, :conditions => "#{User.table_name}.status <> #{STATUS_ANONYMOUS}" - scope :status, lambda {|arg| arg.blank? ? {} : {:conditions => {:status => arg.to_i}} } + scope :logged, lambda { where("#{User.table_name}.status <> #{STATUS_ANONYMOUS}") } + scope :status, lambda {|arg| where(arg.blank? ? nil : {:status => arg.to_i}) } has_one :ssamr_user_detail, :dependent => :destroy, :class_name => 'SsamrUserDetail' accepts_nested_attributes_for :ssamr_user_detail @@ -103,12 +97,11 @@ 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_changed? && user.login.present? }, :case_sensitive => false validates_uniqueness_of :mail, :if => Proc.new { |user| user.mail_changed? && user.mail.present? }, :case_sensitive => false - - # Login must contain lettres, numbers, underscores only - validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i + # Login must contain letters, numbers, underscores only + validates_format_of :login, :with => /\A[a-z0-9_\-@\.]*\z/i validates_length_of :login, :maximum => LOGIN_LENGTH_LIMIT validates_length_of :firstname, :lastname, :maximum => 30 - validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_blank => true + validates_format_of :mail, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, :allow_blank => true validates_length_of :mail, :maximum => MAIL_LENGTH_LIMIT, :allow_nil => true validates_confirmation_of :password, :allow_nil => true validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true @@ -128,6 +121,7 @@ group_id = group.is_a?(Group) ? group.id : group.to_i where("#{User.table_name}.id NOT IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id) } + scope :sorted, lambda { order(*User.fields_for_order_statement)} def set_mail_notification self.mail_notification = Setting.default_notification_option if self.mail_notification.blank? @@ -141,10 +135,12 @@ end end + alias :base_reload :reload def reload(*args) @name = nil @projects_by_role = nil - super + @membership_by_project_id = nil + base_reload(*args) end def mail=(arg) @@ -162,7 +158,7 @@ begin write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url)) rescue OpenIdAuthentication::InvalidOpenId - # Invlaid url, don't save + # Invalid url, don't save end end self.read_attribute(:identity_url) @@ -173,19 +169,13 @@ login = login.to_s password = password.to_s - # Make sure no one can sign in with an empty password - return nil if password.empty? + # Make sure no one can sign in with an empty login or password + return nil if login.empty? || password.empty? user = find_by_login(login) if user # user is already in local database - return nil if !user.active? - if user.auth_source - # user has an external authentication method - return nil unless user.auth_source.authenticate(login, password) - else - # authentication with local password - return nil unless user.check_password?(password) - end + return nil unless user.active? + return nil unless user.check_password?(password) else # user is not yet registered, try to authenticate with available sources attrs = AuthSource.authenticate(login, password) @@ -199,7 +189,7 @@ end end end - user.update_attribute(:last_login_on, Time.now) if user && !user.new_record? + user.update_column(:last_login_on, Time.now) if user && !user.new_record? user rescue => text raise text @@ -207,14 +197,10 @@ # Returns the user who matches the given autologin +key+ or nil def self.try_to_autologin(key) - tokens = Token.find_all_by_action_and_value('autologin', key.to_s) - # Make sure there's only 1 token that matches the key - if tokens.size == 1 - token = tokens.first - if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active? - token.user.update_attribute(:last_login_on, Time.now) - token.user - end + user = Token.find_active_user('autologin', key, Setting.autologin.to_i) + if user + user.update_column(:last_login_on, Time.now) + user end end @@ -371,23 +357,24 @@ # Find a user account by matching the exact login and then a case-insensitive # version. Exact matches will be given priority. def self.find_by_login(login) - # First look for an exact match - user = where(:login => login).all.detect {|u| u.login == login} - unless user - # Fail over to case-insensitive if none was found - user = where("LOWER(login) = ?", login.to_s.downcase).first + if login.present? + login = login.to_s + # First look for an exact match + user = where(:login => login).all.detect {|u| u.login == login} + unless user + # Fail over to case-insensitive if none was found + user = where("LOWER(login) = ?", login.downcase).first + end + user end - user end def self.find_by_rss_key(key) - token = Token.find_by_action_and_value('feeds', key.to_s) - token && token.user.active? ? token.user : nil + Token.find_active_user('feeds', key) end def self.find_by_api_key(key) - token = Token.find_by_action_and_value('api', key.to_s) - token && token.user.active? ? token.user : nil + Token.find_active_user('api', key) end # Makes find_by_mail case-insensitive @@ -441,6 +428,17 @@ !logged? end + # Returns user's membership for the given project + # or nil if the user is not a member of project + def membership(project) + project_id = project.is_a?(Project) ? project.id : project + + @membership_by_project_id ||= Hash.new {|h, project_id| + h[project_id] = memberships.where(:project_id => project_id).first + } + @membership_by_project_id[project_id] + end + # Return user's roles for project def roles_for_project(project) roles = [] @@ -448,7 +446,7 @@ return roles if project.nil? || project.archived? if logged? # Find project membership - membership = memberships.detect {|m| m.project_id == project.id} + membership = membership(project) if membership roles = membership.roles else @@ -464,7 +462,7 @@ # Return true if the user is a member of project def member_of?(project) - !roles_for_project(project).detect {|role| role.member?}.nil? + projects.to_a.include?(project) end # Returns a hash of user's projects grouped by roles @@ -577,47 +575,35 @@ # # TODO: only supports Issue events currently def notify_about?(object) - case mail_notification - when 'all' + if mail_notification == 'all' true - when 'selected' - # user receives notifications for created/assigned issues on unselected projects - if object.is_a?(Issue) && (object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)) + elsif mail_notification.blank? || mail_notification == 'none' + false + else + case object + when Issue + case mail_notification + when 'selected', 'only_my_events' + # user receives notifications for created/assigned issues on unselected projects + object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was) + when 'only_assigned' + is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was) + when 'only_owner' + object.author == self + end + when News + # always send to project members except when mail_notification is set to 'none' true - else - false end - when 'none' - false - when 'only_my_events' - if object.is_a?(Issue) && (object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)) - true - else - false - end - when 'only_assigned' - if object.is_a?(Issue) && (is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)) - true - else - false - end - when 'only_owner' - if object.is_a?(Issue) && object.author == self - true - else - false - end - else - false end end def self.current=(user) - @current_user = user + Thread.current[:current_user] = user end def self.current - @current_user ||= User.anonymous + Thread.current[:current_user] ||= User.anonymous end # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only @@ -698,7 +684,7 @@ def validate_anonymous_uniqueness # There should be only one AnonymousUser in the database - errors.add :base, 'An anonymous user already exists.' if AnonymousUser.find(:first) + errors.add :base, 'An anonymous user already exists.' if AnonymousUser.exists? end def available_custom_fields @@ -717,6 +703,10 @@ UserPreference.new(:user => self) end + def member_of?(project) + false + end + # Anonymous user can not be destroyed def destroy false diff -r 0a574315af3e -r 4f746d8966dd app/models/user_custom_field.rb --- a/app/models/user_custom_field.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/user_custom_field.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/models/user_preference.rb --- a/app/models/user_preference.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/user_preference.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/models/version.rb --- a/app/models/version.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/version.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -30,13 +30,12 @@ validates_presence_of :name validates_uniqueness_of :name, :scope => [:project_id] validates_length_of :name, :maximum => 60 - validates_format_of :effective_date, :with => /^\d{4}-\d{2}-\d{2}$/, :message => :not_a_date, :allow_nil => true + validates :effective_date, :date => true validates_inclusion_of :status, :in => VERSION_STATUSES validates_inclusion_of :sharing, :in => VERSION_SHARINGS - validate :validate_version scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)} - scope :open, where(:status => 'open') + scope :open, lambda { where(:status => 'open') } scope :visible, lambda {|*args| includes(:project).where(Project.allowed_to_condition(args.first || User.current, :view_issues)) } @@ -48,7 +47,8 @@ 'wiki_page_title', 'status', 'sharing', - 'custom_field_values' + 'custom_field_values', + 'custom_fields' # Returns true if +user+ or current user is allowed to view the version def visible?(user=User.current) @@ -97,10 +97,10 @@ end def behind_schedule? - if completed_pourcent == 100 + if completed_percent == 100 return false elsif due_date && start_date - done_date = start_date + ((due_date - start_date+1)* completed_pourcent/100).floor + done_date = start_date + ((due_date - start_date+1)* completed_percent/100).floor return done_date <= Date.today else false # No issues so it's not late @@ -109,7 +109,7 @@ # Returns the completion percentage of this version based on the amount of open/closed issues # and the time spent on the open issues. - def completed_pourcent + def completed_percent if issues_count == 0 0 elsif open_issues_count == 0 @@ -119,8 +119,14 @@ end end + # TODO: remove in Redmine 3.0 + def completed_pourcent + ActiveSupport::Deprecation.warn "Version#completed_pourcent is deprecated and will be removed in Redmine 3.0. Please use #completed_percent instead." + completed_percent + end + # Returns the percentage of issues that have been marked as 'closed'. - def closed_pourcent + def closed_percent if issues_count == 0 0 else @@ -128,6 +134,12 @@ end end + # TODO: remove in Redmine 3.0 + def closed_pourcent + ActiveSupport::Deprecation.warn "Version#closed_pourcent is deprecated and will be removed in Redmine 3.0. Please use #closed_percent instead." + closed_percent + end + # Returns true if the version is overdue: due date reached and some open issues def overdue? effective_date && (effective_date < Date.today) && (open_issues_count > 0) @@ -275,10 +287,4 @@ progress end end - - def validate_version - if effective_date.nil? && @attributes['effective_date'].present? - errors.add :effective_date, :not_a_date - end - end end diff -r 0a574315af3e -r 4f746d8966dd app/models/version_custom_field.rb --- a/app/models/version_custom_field.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/version_custom_field.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/models/watcher.rb --- a/app/models/watcher.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/watcher.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -29,7 +29,7 @@ prune_single_user(options[:user], options) else pruned = 0 - User.find(:all, :conditions => "id IN (SELECT DISTINCT user_id FROM #{table_name})").each do |user| + User.where("id IN (SELECT DISTINCT user_id FROM #{table_name})").all.each do |user| pruned += prune_single_user(user, options) end pruned @@ -47,7 +47,7 @@ def self.prune_single_user(user, options={}) return unless user.is_a?(User) pruned = 0 - find(:all, :conditions => {:user_id => user.id}).each do |watcher| + where(:user_id => user.id).all.each do |watcher| next if watcher.watchable.nil? if options.has_key?(:project) diff -r 0a574315af3e -r 4f746d8966dd app/models/wiki.rb --- a/app/models/wiki.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/wiki.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,7 +24,7 @@ acts_as_watchable validates_presence_of :start_page - validates_format_of :start_page, :with => /^[^,\.\/\?\;\|\:]*$/ + validates_format_of :start_page, :with => /\A[^,\.\/\?\;\|\:]*\z/ safe_attributes 'start_page' diff -r 0a574315af3e -r 4f746d8966dd app/models/wiki_content.rb --- a/app/models/wiki_content.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/wiki_content.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -59,6 +59,7 @@ :description => :comments, :datetime => :updated_on, :type => 'wiki-page', + :group => :page, :url => Proc.new {|o| {:controller => 'wiki', :action => 'show', :project_id => o.page.wiki.project, :id => o.page.title, :version => o.version}} acts_as_activity_provider :type => 'wiki_edits', diff -r 0a574315af3e -r 4f746d8966dd app/models/wiki_content_observer.rb --- a/app/models/wiki_content_observer.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/wiki_content_observer.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/models/wiki_page.rb --- a/app/models/wiki_page.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/wiki_page.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -40,7 +40,7 @@ attr_accessor :redirect_existing_links validates_presence_of :title - validates_format_of :title, :with => /^[^,\.\/\?\;\|\s]*$/ + validates_format_of :title, :with => /\A[^,\.\/\?\;\|\s]*\z/ validates_uniqueness_of :title, :scope => :wiki_id, :case_sensitive => false validates_associated :content @@ -49,9 +49,9 @@ before_save :handle_redirects # eager load information about last updates, without loading text - scope :with_updated_on, { - :select => "#{WikiPage.table_name}.*, #{WikiContent.table_name}.updated_on, #{WikiContent.table_name}.version", - :joins => "LEFT JOIN #{WikiContent.table_name} ON #{WikiContent.table_name}.page_id = #{WikiPage.table_name}.id" + scope :with_updated_on, lambda { + select("#{WikiPage.table_name}.*, #{WikiContent.table_name}.updated_on, #{WikiContent.table_name}.version"). + joins("LEFT JOIN #{WikiContent.table_name} ON #{WikiContent.table_name}.page_id = #{WikiPage.table_name}.id") } # Wiki pages that are protected by default diff -r 0a574315af3e -r 4f746d8966dd app/models/wiki_redirect.rb --- a/app/models/wiki_redirect.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/wiki_redirect.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/models/workflow_permission.rb --- a/app/models/workflow_permission.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/workflow_permission.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/models/workflow_rule.rb --- a/app/models/workflow_rule.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/workflow_rule.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -62,8 +62,8 @@ else transaction do delete_all :tracker_id => target_tracker.id, :role_id => target_role.id - connection.insert "INSERT INTO #{WorkflowRule.table_name} (tracker_id, role_id, old_status_id, new_status_id, author, assignee, field_name, rule, type)" + - " SELECT #{target_tracker.id}, #{target_role.id}, old_status_id, new_status_id, author, assignee, field_name, rule, type" + + connection.insert "INSERT INTO #{WorkflowRule.table_name} (tracker_id, role_id, old_status_id, new_status_id, author, assignee, field_name, #{connection.quote_column_name 'rule'}, type)" + + " SELECT #{target_tracker.id}, #{target_role.id}, old_status_id, new_status_id, author, assignee, field_name, #{connection.quote_column_name 'rule'}, type" + " FROM #{WorkflowRule.table_name}" + " WHERE tracker_id = #{source_tracker.id} AND role_id = #{source_role.id}" end diff -r 0a574315af3e -r 4f746d8966dd app/models/workflow_transition.rb --- a/app/models/workflow_transition.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/models/workflow_transition.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd app/views/account/logout.html.erb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/views/account/logout.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,3 @@ +<%= form_tag(signout_path) do %> +

    <%= submit_tag l(:label_logout) %>

    +<% end %> diff -r 0a574315af3e -r 4f746d8966dd app/views/activities/index.html.erb --- a/app/views/activities/index.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/activities/index.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -15,13 +15,14 @@ <% @events_by_day.keys.sort.reverse.each do |day| %>

    <%= format_activity_day(day) %>

    -<% @events_by_day[day].sort {|x,y| y.event_datetime <=> x.event_datetime }.each do |e| -%> -
    +<% sort_activity_events(@events_by_day[day]).each do |e, in_group| -%> +
    <%= 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) %> <%= format_time(e.event_datetime, false) %> <%= content_tag('span', h(e.project), :class => 'project') if @project.nil? || @project != e.project %> - <%= link_to format_activity_title(e.event_title), e.event_url %>
    -
    <%= format_activity_description(e.event_description) %> + <%= link_to format_activity_title(e.event_title), e.event_url %> + +
    "><%= format_activity_description(e.event_description) %> <%= link_to_user(e.event_author) if e.respond_to?(:event_author) %>
    <% end -%>
    @@ -54,7 +55,7 @@

    <%= l(:label_activity) %>

    <% @activity.event_types.each do |t| %> <%= check_box_tag "show_#{t}", 1, @activity.scope.include?(t) %> - +
    <% end %>

    <% if @project && @project.descendants.active.any? %> diff -r 0a574315af3e -r 4f746d8966dd app/views/admin/plugins.html.erb --- a/app/views/admin/plugins.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/admin/plugins.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -3,14 +3,14 @@ <% if @plugins.any? %> <% @plugins.each do |plugin| %> - + - + <% end %>
    <%=h plugin.name %> <%= 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? %> <%= plugin.author_url.blank? ? h(plugin.author) : link_to(h(plugin.author), plugin.author_url) %> <%=h plugin.version %><%= link_to(l(:button_configure), :controller => 'settings', :action => 'plugin', :id => plugin.id) if plugin.configurable? %><%= link_to(l(:button_configure), plugin_settings_path(plugin)) if plugin.configurable? %>
    diff -r 0a574315af3e -r 4f746d8966dd app/views/admin/projects.html.erb --- a/app/views/admin/projects.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/admin/projects.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -27,7 +27,7 @@ <% project_tree(@projects) do |project, level| %> <%= project.css_classes %> <%= level > 0 ? "idnt idnt-#{level}" : nil %>"> - <%= link_to_project(project, {:action => (project.active? ? 'settings' : 'show')}, :title => project.short_description) %> + <%= link_to_project_settings(project, {}, :title => project.short_description) %> <%= checked_image project.is_public? %> <%= format_date(project.created_on) %> diff -r 0a574315af3e -r 4f746d8966dd app/views/attachments/_form.html.erb --- a/app/views/attachments/_form.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/attachments/_form.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -1,19 +1,31 @@ + <% if defined?(container) && container && container.saved_attachments %> <% container.saved_attachments.each_with_index do |attachment, i| %> - - <%= h(attachment.filename) %> (<%= number_to_human_size(attachment.filesize) %>) - <%= hidden_field_tag "attachments[p#{i}][token]", "#{attachment.id}.#{attachment.digest}" %> + + <%= text_field_tag("attachments[p#{i}][filename]", attachment.filename, :class => 'filename') + + text_field_tag("attachments[p#{i}][description]", attachment.description, :maxlength => 255, :placeholder => l(:label_optional_description), :class => 'description') + + link_to(' '.html_safe, attachment_path(attachment, :attachment_id => "p#{i}", :format => 'js'), :method => 'delete', :remote => true, :class => 'remove-upload') %> + <%= hidden_field_tag "attachments[p#{i}][token]", "#{attachment.token}" %> <% end %> <% end %> - - - <%= file_field_tag 'attachments[1][file]', :id => nil, :class => 'file', - :onchange => "checkFileSize(this, #{Setting.attachment_max_size.to_i.kilobytes}, '#{escape_javascript(l(:error_attachment_too_big, :max_size => number_to_human_size(Setting.attachment_max_size.to_i.kilobytes)))}');" -%> - <%= text_field_tag 'attachments[1][description]', '', :id => nil, :class => 'description', :maxlength => 255, :placeholder => l(:label_optional_description) %> - <%= link_to_function(image_tag('delete.png'), 'removeFileField(this)', :title => (l(:button_delete))) %> - + + +<%= file_field_tag 'attachments[dummy][file]', + :id => nil, + :class => 'file_selector', + :multiple => true, + :onchange => 'addInputFiles(this);', + :data => { + :max_file_size => Setting.attachment_max_size.to_i.kilobytes, + :max_file_size_message => l(:error_attachment_too_big, :max_size => number_to_human_size(Setting.attachment_max_size.to_i.kilobytes)), + :max_concurrent_uploads => Redmine::Configuration['max_concurrent_ajax_uploads'].to_i, + :upload_path => uploads_path(:format => 'js'), + :description_placeholder => l(:label_optional_description) + } %> +(<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>) -<%= link_to l(:label_add_another_file), '#', :onclick => 'addFileField(); return false;', :class => 'add_attachment' %> -(<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>) +<% content_for :header_tags do %> + <%= javascript_include_tag 'attachments' %> +<% end %> diff -r 0a574315af3e -r 4f746d8966dd app/views/attachments/destroy.js.erb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/views/attachments/destroy.js.erb Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,1 @@ +$('#attachments_<%= j params[:attachment_id] %>').remove(); diff -r 0a574315af3e -r 4f746d8966dd app/views/attachments/upload.js.erb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/views/attachments/upload.js.erb Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,9 @@ +var fileSpan = $('#attachments_<%= j params[:attachment_id] %>'); +$('', { type: 'hidden', name: 'attachments[<%= j params[:attachment_id] %>][token]' } ).val('<%= j @attachment.token %>').appendTo(fileSpan); +fileSpan.find('a.remove-upload') + .attr({ + "data-remote": true, + "data-method": 'delete', + href: '<%= j attachment_path(@attachment, :attachment_id => params[:attachment_id], :format => 'js') %>' + }) + .off('click'); diff -r 0a574315af3e -r 4f746d8966dd app/views/auth_sources/_form.html.erb --- a/app/views/auth_sources/_form.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/auth_sources/_form.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -1,13 +1,6 @@ <%= error_messages_for 'auth_source' %> -
    - -

    -<%= text_field 'auth_source', 'name' %>

    - -

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

    +
    +

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

    +

    <%= f.check_box :onthefly_register, :label => :field_onthefly %>

    - - - diff -r 0a574315af3e -r 4f746d8966dd app/views/auth_sources/_form_auth_source_ldap.html.erb --- a/app/views/auth_sources/_form_auth_source_ldap.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/auth_sources/_form_auth_source_ldap.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -1,50 +1,24 @@ <%= error_messages_for 'auth_source' %> -
    - -

    -<%= text_field 'auth_source', 'name' %>

    - -

    -<%= text_field 'auth_source', 'host' %>

    - -

    -<%= text_field 'auth_source', 'port', :size => 6 %> <%= check_box 'auth_source', 'tls' %> LDAPS

    - -

    -<%= text_field 'auth_source', 'account' %>

    - -

    -<%= password_field 'auth_source', 'account_password', :name => 'ignore', - :value => ((@auth_source.new_record? || @auth_source.account_password.blank?) ? '' : ('x'*15)), - :onfocus => "this.value=''; this.name='auth_source[account_password]';", - :onchange => "this.name='auth_source[account_password]';" %>

    - -

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

    - -

    -<%= text_field 'auth_source', 'filter', :size => 60 %>

    - -

    -<%= text_field 'auth_source', 'timeout', :size => 4 %>

    - -

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

    +
    +

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

    +

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

    +

    <%= f.text_field :port, :required => true, :size => 6 %> <%= f.check_box :tls, :no_label => true %> LDAPS

    +

    <%= f.text_field :account %>

    +

    <%= f.password_field :account_password, :label => :field_password, + :name => 'dummy_password', + :value => ((@auth_source.new_record? || @auth_source.account_password.blank?) ? '' : ('x'*15)), + :onfocus => "this.value=''; this.name='auth_source[account_password]';", + :onchange => "this.name='auth_source[account_password]';" %>

    +

    <%= f.text_field :base_dn, :required => true, :size => 60 %>

    +

    <%= f.text_field :filter, :size => 60, :label => :field_auth_source_ldap_filter %>

    +

    <%= f.text_field :timeout, :size => 4 %>

    +

    <%= f.check_box :onthefly_register, :label => :field_onthefly %>

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

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

    - -

    -<%= text_field 'auth_source', 'attr_firstname', :size => 20 %>

    - -

    -<%= text_field 'auth_source', 'attr_lastname', :size => 20 %>

    - -

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

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

    <%= f.text_field :attr_login, :required => true, :size => 20 %>

    +

    <%= f.text_field :attr_firstname, :size => 20 %>

    +

    <%= f.text_field :attr_lastname, :size => 20 %>

    +

    <%= f.text_field :attr_mail, :size => 20 %>

    - - diff -r 0a574315af3e -r 4f746d8966dd app/views/auth_sources/edit.html.erb --- a/app/views/auth_sources/edit.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/auth_sources/edit.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -1,6 +1,6 @@

    <%=l(:label_auth_source)%> (<%= h(@auth_source.auth_method_name) %>)

    -<%= form_tag({:action => 'update', :id => @auth_source}, :method => :put, :class => "tabular") do %> - <%= render :partial => auth_source_partial_name(@auth_source) %> +<%= labelled_form_for @auth_source, :as => :auth_source, :url => auth_source_path(@auth_source), :html => {:id => 'auth_source_form'} do |f| %> + <%= render :partial => auth_source_partial_name(@auth_source), :locals => { :f => f } %> <%= submit_tag l(:button_save) %> <% end %> diff -r 0a574315af3e -r 4f746d8966dd app/views/auth_sources/index.html.erb --- a/app/views/auth_sources/index.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/auth_sources/index.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -20,7 +20,7 @@ <%= h source.host %> <%= h source.users.count %> - <%= link_to l(:button_test), {:action => 'test_connection', :id => source}, :class => 'icon icon-test' %> + <%= link_to l(:button_test), try_connection_auth_source_path(source), :class => 'icon icon-test' %> <%= delete_link auth_source_path(source) %> diff -r 0a574315af3e -r 4f746d8966dd app/views/auth_sources/new.html.erb --- a/app/views/auth_sources/new.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/auth_sources/new.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@

    <%=l(:label_auth_source_new)%> (<%= h(@auth_source.auth_method_name) %>)

    -<%= form_tag({:action => 'create'}, :class => "tabular") do %> +<%= labelled_form_for @auth_source, :as => :auth_source, :url => auth_sources_path, :html => {:id => 'auth_source_form'} do |f| %> <%= hidden_field_tag 'type', @auth_source.type %> - <%= render :partial => auth_source_partial_name(@auth_source) %> + <%= render :partial => auth_source_partial_name(@auth_source), :locals => { :f => f } %> <%= submit_tag l(:button_create) %> <% end %> diff -r 0a574315af3e -r 4f746d8966dd app/views/boards/index.html.erb --- a/app/views/boards/index.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/boards/index.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -11,7 +11,7 @@ <% Board.board_tree(@boards) do |board, level| %> - <%= link_to h(board.name), {:action => 'show', :id => board}, :class => "board" %>
    + <%= link_to h(board.name), project_board_path(board.project, board), :class => "board" %>
    <%=h board.description %> <%= board.topics_count %> diff -r 0a574315af3e -r 4f746d8966dd app/views/boards/show.html.erb --- a/app/views/boards/show.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/boards/show.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -1,20 +1,20 @@ <%= board_breadcrumb(@board) %>
    -<%= link_to_if_authorized l(:label_message_new), - {:controller => 'messages', :action => 'new', :board_id => @board}, - :class => 'icon icon-add', - :onclick => 'showAndScrollTo("add-message", "message_subject"); return false;' %> -<%= watcher_tag(@board, User.current) %> +<%= link_to l(:label_message_new), + new_board_message_path(@board), + :class => 'icon icon-add', + :onclick => 'showAndScrollTo("add-message", "message_subject"); return false;' if User.current.allowed_to?(:add_messages, @board.project) %> +<%= watcher_link(@board, User.current) %>
    + +<% include_calendar_headers_tags %> diff -r 0a574315af3e -r 4f746d8966dd app/views/documents/index.html.erb --- a/app/views/documents/index.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/documents/index.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -1,6 +1,6 @@
    <%= link_to l(:label_document_new), new_project_document_path(@project), :class => 'icon icon-add', - :onclick => 'showAndScrollTo("add-document", "document_title"); return false;' if User.current.allowed_to?(:manage_documents, @project) %> + :onclick => 'showAndScrollTo("add-document", "document_title"); return false;' if User.current.allowed_to?(:add_documents, @project) %>
    +

    <%= gantt_zoom_link(@gantt, :in) %> @@ -39,18 +72,18 @@ @gantt.zoom.times { zoom = zoom * 2 } subject_width = 330 - header_heigth = 18 + header_height = 18 - headers_height = header_heigth + headers_height = header_height show_weeks = false show_days = false if @gantt.zoom > 1 show_weeks = true - headers_height = 2 * header_heigth + headers_height = 2 * header_height if @gantt.zoom > 2 show_days = true - headers_height = 3 * header_heigth + headers_height = 3 * header_height end end @@ -102,7 +135,7 @@ -

    +
    <% style = "" style += "width: #{g_width - 1}px;" @@ -115,7 +148,7 @@ <% month_f = @gantt.date_from left = 0 - height = (show_weeks ? header_heigth : header_heigth + g_height) + height = (show_weeks ? header_height : header_height + g_height) %> <% @gantt.months.times do %> <% @@ -140,7 +173,7 @@ <% if show_weeks %> <% left = 0 - height = (show_days ? header_heigth - 1 : header_heigth - 1 + g_height) + height = (show_days ? header_height - 1 : header_height - 1 + g_height) %> <% if @gantt.date_from.cwday == 1 %> <% @@ -189,7 +222,7 @@ <% if show_days %> <% left = 0 - height = g_height + header_heigth - 1 + height = g_height + header_height - 1 wday = @gantt.date_from.cwday %> <% (@gantt.date_to - @gantt.date_from + 1).to_i.times do %> @@ -229,9 +262,17 @@ style += "width:10px;" style += "border-left: 1px dashed red;" %> - <%= content_tag(:div, ' '.html_safe, :style => style) %> + <%= content_tag(:div, ' '.html_safe, :style => style, :id => 'today_line') %> <% end %> - +<% + style = "" + style += "position: absolute;" + style += "height: #{g_height}px;" + style += "top: #{headers_height + 1}px;" + style += "left: 0px;" + style += "width: #{g_width - 1}px;" +%> +<%= content_tag(:div, '', :style => style, :id => "gantt_draw_area") %>
    @@ -261,3 +302,18 @@ <% end %> <% html_title(l(:label_gantt)) -%> + +<% content_for :header_tags do %> + <%= javascript_include_tag 'raphael' %> + <%= javascript_include_tag 'gantt' %> +<% end %> + +<%= javascript_tag do %> + var issue_relation_type = <%= raw Redmine::Helpers::Gantt::DRAW_TYPES.to_json %>; + $(document).ready(drawGanttHandler); + $(window).resize(drawGanttHandler); + $(function() { + $("#draw_rels").change(drawGanttHandler); + $("#draw_progress_line").change(drawGanttHandler); + }); +<% end %> diff -r 0a574315af3e -r 4f746d8966dd app/views/groups/_form.html.erb --- a/app/views/groups/_form.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/groups/_form.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ <%= error_messages_for @group %>
    -

    <%= f.text_field :name %>

    +

    <%= f.text_field :name, :required => true, :size => 60 %>

    <% @group.custom_field_values.each do |value| %>

    <%= custom_field_tag_with_label :group, value %>

    <% end %> diff -r 0a574315af3e -r 4f746d8966dd app/views/groups/_memberships.html.erb --- a/app/views/groups/_memberships.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/groups/_memberships.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ <% roles = Role.find_all_givable %> -<% projects = Project.active.find(:all, :order => 'lft') %> +<% projects = Project.active.all %>
    <% if @group.memberships.any? %> @@ -13,7 +13,7 @@ <% @group.memberships.each do |membership| %> <% next if membership.new_record? %> - <%=h membership.project %> + <%= link_to_project membership.project %> <%=h membership.roles.sort.collect(&:to_s).join(', ') %> <%= form_for(:membership, :remote => true, diff -r 0a574315af3e -r 4f746d8966dd app/views/groups/_users.html.erb --- a/app/views/groups/_users.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/groups/_users.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -22,22 +22,18 @@
    -<% users = User.active.not_in_group(@group).all(:limit => 100) %> -<% if users.any? %> <%= form_for(@group, :remote => true, :url => group_users_path(@group), :html => {:method => :post}) do |f| %>
    <%=l(:label_user_new)%>

    <%= label_tag "user_search", l(:label_user_search) %><%= text_field_tag 'user_search', nil %>

    - <%= javascript_tag "observeSearchfield('user_search', 'users', '#{ escape_javascript autocomplete_for_user_group_path(@group) }')" %> + <%= javascript_tag "observeSearchfield('user_search', null, '#{ escape_javascript autocomplete_for_user_group_path(@group) }')" %>
    - <%= principals_check_box_tags 'user_ids[]', users %> + <%= render_principals_for_new_group_users(@group) %>

    <%= submit_tag l(:button_add) %>

    <% end %> -<% end %> -
    diff -r 0a574315af3e -r 4f746d8966dd app/views/groups/autocomplete_for_user.html.erb --- a/app/views/groups/autocomplete_for_user.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -<%= principals_check_box_tags 'user_ids[]', @users %> diff -r 0a574315af3e -r 4f746d8966dd app/views/groups/autocomplete_for_user.js.erb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/views/groups/autocomplete_for_user.js.erb Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,1 @@ +$('#users').html('<%= escape_javascript(render_principals_for_new_group_users(@group)) %>'); diff -r 0a574315af3e -r 4f746d8966dd app/views/groups/index.html.erb --- a/app/views/groups/index.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/groups/index.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -19,6 +19,7 @@ <%= delete_link group %> <% end %> + <% else %>

    <%= l(:label_no_data) %>

    diff -r 0a574315af3e -r 4f746d8966dd app/views/issues/_action_menu.html.erb --- a/app/views/issues/_action_menu.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/issues/_action_menu.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@
    -<%= link_to_if_authorized(l(:button_update), {:controller => 'issues', :action => 'edit', :id => @issue }, :onclick => 'showAndScrollTo("update", "issue_notes"); return false;', :class => 'icon icon-edit', :accesskey => accesskey(:edit)) %> +<%= link_to l(:button_update), edit_issue_path(@issue), :onclick => 'showAndScrollTo("update", "issue_notes"); return false;', :class => 'icon icon-edit', :accesskey => accesskey(:edit) if @issue.editable? %> <%= link_to l(:button_log_time), new_issue_time_entry_path(@issue), :class => 'icon icon-time-add' if User.current.allowed_to?(:log_time, @project) %> -<%= 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' %> +<%= watcher_link(@issue, User.current) %> +<%= link_to l(:button_copy), project_copy_issue_path(@project, @issue), :class => 'icon icon-copy' if User.current.allowed_to?(:add_issues, @project) %> <%= link_to l(:button_delete), issue_path(@issue), :data => {:confirm => issues_destroy_confirmation_message(@issue)}, :method => :delete, :class => 'icon icon-del' if User.current.allowed_to?(:delete_issues, @project) %>
    diff -r 0a574315af3e -r 4f746d8966dd app/views/issues/_list.html.erb --- a/app/views/issues/_list.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/issues/_list.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -9,7 +9,6 @@ :onclick => 'toggleIssuesSelection(this); return false;', :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %> - <%= sort_header_tag('id', :caption => '#', :default_order => 'desc') %> <% query.inline_columns.each do |column| %> <%= column_header(column) %> <% end %> @@ -32,13 +31,12 @@ <% end %> "> <%= check_box_tag("ids[]", issue.id, false, :id => nil) %> - <%= link_to issue.id, issue_path(issue) %> <%= raw query.inline_columns.map {|column| "#{column_content(column, issue)}"}.join %> <% @query.block_columns.each do |column| if (text = column_content(column, issue)) && text.present? -%> - <%= text %> + <%= text %> <% end -%> <% end -%> diff -r 0a574315af3e -r 4f746d8966dd app/views/issues/_list_simple.html.erb --- a/app/views/issues/_list_simple.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/issues/_list_simple.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -12,12 +12,12 @@ <%= check_box_tag("ids[]", issue.id, false, :style => 'display:none;', :id => nil) %> - <%= link_to(h(issue.id), :controller => 'issues', :action => 'show', :id => issue) %> + <%= link_to issue.id, issue_path(issue) %> <%= link_to_project(issue.project) %> <%=h issue.tracker %> - <%= link_to h(truncate(issue.subject, :length => 60)), :controller => 'issues', :action => 'show', :id => issue %> (<%=h issue.status %>) + <%= link_to truncate(issue.subject, :length => 60), issue_path(issue) %> (<%=h issue.status %>) <% end %> diff -r 0a574315af3e -r 4f746d8966dd app/views/issues/_relations.html.erb --- a/app/views/issues/_relations.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/issues/_relations.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -10,22 +10,25 @@
    <% @relations.each do |relation| %> - - - - - - - - + <% other_issue = relation.other_issue(@issue) -%> + + + + + + + + <% end %>
    <%= check_box_tag("ids[]", relation.other_issue(@issue).id, false, :id => nil) %><%= l(relation.label_for(@issue)) %> <%= "(#{l('datetime.distance_in_words.x_days', :count => 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), :truncate => 60) %> -<%=h relation.other_issue(@issue).status.name %><%= format_date(relation.other_issue(@issue).start_date) %><%= format_date(relation.other_issue(@issue).due_date) %><%= link_to image_tag('link_break.png'), - {:controller => 'issue_relations', :action => 'destroy', :id => relation}, - :remote => true, - :method => :delete, - :data => {:confirm => l(:text_are_you_sure)}, - :title => l(:label_relation_delete) if User.current.allowed_to?(:manage_issue_relations, @project) %>
    <%= check_box_tag("ids[]", other_issue.id, false, :id => nil) %> + <%= l(relation.label_for(@issue)) %> + <%= "(#{l('datetime.distance_in_words.x_days', :count => relation.delay)})" if relation.delay && relation.delay != 0 %> + <%= h(other_issue.project) + ' - ' if Setting.cross_project_issue_relations? %> + <%= link_to_issue(other_issue, :truncate => 60) %> + <%=h other_issue.status.name %><%= format_date(other_issue.start_date) %><%= format_date(other_issue.due_date) %><%= link_to image_tag('link_break.png'), + relation_path(relation), + :remote => true, + :method => :delete, + :data => {:confirm => l(:text_are_you_sure)}, + :title => l(:label_relation_delete) if User.current.allowed_to?(:manage_issue_relations, @project) %>
    @@ -33,7 +36,7 @@ <%= form_for @relation, { :as => :relation, :remote => true, - :url => {:controller => 'issue_relations', :action => 'create', :issue_id => @issue}, + :url => issue_relations_path(@issue), :method => :post, :html => {:id => 'new-relation-form', :style => (@relation ? '' : 'display: none;')} } do |f| %> diff -r 0a574315af3e -r 4f746d8966dd app/views/issues/_sidebar.html.erb --- a/app/views/issues/_sidebar.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/issues/_sidebar.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -1,15 +1,15 @@

    <%= l(:label_issue_plural) %>

    -<%= link_to l(:label_issue_view_all), { :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1 } %>
    +<%= link_to l(:label_issue_view_all), _project_issues_path(@project, :set_filter => 1) %>
    <% if @project %> -<%= link_to l(:field_summary), :controller => 'reports', :action => 'issue_report', :id => @project %>
    +<%= link_to l(:field_summary), project_issues_report_path(@project) %>
    <% end %> <%= call_hook(:view_issues_sidebar_issues_bottom) %> <% if User.current.allowed_to?(:view_calendar, @project, :global => true) %> - <%= link_to(l(:label_calendar), :controller => 'calendars', :action => 'show', :project_id => @project) %>
    + <%= link_to l(:label_calendar), _project_calendar_path(@project) %>
    <% end %> <% if User.current.allowed_to?(:view_gantt, @project, :global => true) %> - <%= link_to(l(:label_gantt), :controller => 'gantts', :action => 'show', :project_id => @project) %>
    + <%= link_to l(:label_gantt), _project_gantt_path(@project) %>
    <% end %> <%= call_hook(:view_issues_sidebar_planning_bottom) %> diff -r 0a574315af3e -r 4f746d8966dd app/views/issues/_update_form.js.erb --- a/app/views/issues/_update_form.js.erb Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -$('#all_attributes').html('<%= escape_javascript(render :partial => 'form') %>'); - -<% if User.current.allowed_to?(:log_time, @issue.project) %> - $('#log_time').show(); -<% else %> - $('#log_time').hide(); -<% end %> diff -r 0a574315af3e -r 4f746d8966dd app/views/issues/bulk_edit.html.erb --- a/app/views/issues/bulk_edit.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/issues/bulk_edit.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -1,13 +1,12 @@

    <%= @copy ? l(:button_copy) : l(:label_bulk_edit_selected_issues) %>

    -
      <%= @issues.collect {|i| - content_tag('li', - link_to(h("#{i.tracker} ##{i.id}"), - { :action => 'show', :id => i } - ) + h(": #{i.subject}")) - }.join("\n").html_safe %>
    +
      +<% @issues.each do |issue| %> + <%= content_tag 'li', link_to_issue(issue) %> +<% end %> +
    -<%= form_tag({:action => 'bulk_update'}, :id => 'bulk_edit_form') do %> +<%= form_tag(bulk_update_issues_path, :id => 'bulk_edit_form') do %> <%= @issues.collect {|i| hidden_field_tag('ids[]', i.id)}.join("\n").html_safe %>
    diff -r 0a574315af3e -r 4f746d8966dd app/views/issues/index.api.rsb --- a/app/views/issues/index.api.rsb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/issues/index.api.rsb Fri Jun 14 09:28:30 2013 +0100 @@ -23,6 +23,7 @@ api.created_on issue.created_on api.updated_on issue.updated_on + api.closed_on issue.closed_on api.array :relations do issue.relations.each do |relation| diff -r 0a574315af3e -r 4f746d8966dd app/views/issues/index.html.erb --- a/app/views/issues/index.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/issues/index.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -24,7 +24,7 @@ - + diff -r 0a574315af3e -r 4f746d8966dd app/views/issues/show.api.rsb --- a/app/views/issues/show.api.rsb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/issues/show.api.rsb Fri Jun 14 09:28:30 2013 +0100 @@ -22,6 +22,7 @@ api.created_on @issue.created_on api.updated_on @issue.updated_on + api.closed_on @issue.closed_on render_api_issue_children(@issue, api) if include_in_api_response?('children') @@ -64,4 +65,10 @@ end end end if include_in_api_response?('journals') + + api.array :watchers do + @issue.watcher_users.each do |user| + api.user :id => user.id, :name => user.name + end + end if include_in_api_response?('watchers') && User.current.allowed_to?(:view_issue_watchers, @issue.project) end diff -r 0a574315af3e -r 4f746d8966dd app/views/issues/show.html.erb --- a/app/views/issues/show.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/issues/show.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -61,7 +61,7 @@ end end if User.current.allowed_to?(:view_time_entries, @project) - rows.right l(:label_spent_time), (@issue.total_spent_hours > 0 ? (link_to l_hours(@issue.total_spent_hours), {:controller => 'timelog', :action => 'index', :project_id => @project, :issue_id => @issue}) : "-"), :class => 'spent-time' + rows.right l(:label_spent_time), (@issue.total_spent_hours > 0 ? link_to(l_hours(@issue.total_spent_hours), project_issue_time_entries_path(@project, @issue)) : "-"), :class => 'spent-time' end end %> <%= render_custom_fields_rows(@issue) %> @@ -73,11 +73,7 @@ <% if @issue.description? %>
    - <%= link_to l(:button_quote), - {:controller => 'journals', :action => 'new', :id => @issue}, - :remote => true, - :method => 'post', - :class => 'icon icon-comment' if authorize_for('issues', 'edit') %> + <%= link_to l(:button_quote), quoted_issue_path(@issue), :remote => true, :method => 'post', :class => 'icon icon-comment' if authorize_for('issues', 'edit') %>

    <%=l(:field_description)%>

    @@ -130,7 +126,7 @@ <%= render :partial => 'action_menu' %>
    -<% if authorize_for('issues', 'edit') %> +<% if @issue.editable? %>
    <% for row in rows %> "> - + <% for status in @statuses %> - + <% end %> - - - + + + <% end %> diff -r 0a574315af3e -r 4f746d8966dd app/views/reports/_simple.html.erb --- a/app/views/reports/_simple.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/reports/_simple.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -11,28 +11,10 @@ <% for row in rows %> "> - - - - + + + + <% end %> diff -r 0a574315af3e -r 4f746d8966dd app/views/reports/issue_report.html.erb --- a/app/views/reports/issue_report.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/reports/issue_report.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -1,31 +1,31 @@

    <%=l(:label_report_plural)%>

    -

    <%=l(:field_tracker)%>  <%= link_to image_tag('zoom_in.png'), :action => 'issue_report_details', :detail => 'tracker' %>

    +

    <%=l(:field_tracker)%>  <%= link_to image_tag('zoom_in.png'), project_issues_report_details_path(@project, :detail => 'tracker') %>

    <%= render :partial => 'simple', :locals => { :data => @issues_by_tracker, :field_name => "tracker_id", :rows => @trackers } %>
    -

    <%=l(:field_priority)%>  <%= link_to image_tag('zoom_in.png'), :action => 'issue_report_details', :detail => 'priority' %>

    +

    <%=l(:field_priority)%>  <%= link_to image_tag('zoom_in.png'), project_issues_report_details_path(@project, :detail => 'priority') %>

    <%= render :partial => 'simple', :locals => { :data => @issues_by_priority, :field_name => "priority_id", :rows => @priorities } %>
    -

    <%=l(:field_assigned_to)%>  <%= link_to image_tag('zoom_in.png'), :action => 'issue_report_details', :detail => 'assigned_to' %>

    +

    <%=l(:field_assigned_to)%>  <%= link_to image_tag('zoom_in.png'), project_issues_report_details_path(@project, :detail => 'assigned_to') %>

    <%= render :partial => 'simple', :locals => { :data => @issues_by_assigned_to, :field_name => "assigned_to_id", :rows => @assignees } %>
    -

    <%=l(:field_author)%>  <%= link_to image_tag('zoom_in.png'), :action => 'issue_report_details', :detail => 'author' %>

    +

    <%=l(:field_author)%>  <%= link_to image_tag('zoom_in.png'), project_issues_report_details_path(@project, :detail => 'author') %>

    <%= render :partial => 'simple', :locals => { :data => @issues_by_author, :field_name => "author_id", :rows => @authors } %>
    <%= call_hook(:view_reports_issue_report_split_content_left, :project => @project) %>
    -

    <%=l(:field_version)%>  <%= link_to image_tag('zoom_in.png'), :action => 'issue_report_details', :detail => 'version' %>

    +

    <%=l(:field_version)%>  <%= link_to image_tag('zoom_in.png'), project_issues_report_details_path(@project, :detail => 'version') %>

    <%= render :partial => 'simple', :locals => { :data => @issues_by_version, :field_name => "fixed_version_id", :rows => @versions } %>
    <% if @project.children.any? %> -

    <%=l(:field_subproject)%>  <%= link_to image_tag('zoom_in.png'), :action => 'issue_report_details', :detail => 'subproject' %>

    +

    <%=l(:field_subproject)%>  <%= link_to image_tag('zoom_in.png'), project_issues_report_details_path(@project, :detail => 'subproject') %>

    <%= render :partial => 'simple', :locals => { :data => @issues_by_subproject, :field_name => "project_id", :rows => @subprojects } %>
    <% end %> -

    <%=l(:field_category)%>  <%= link_to image_tag('zoom_in.png'), :action => 'issue_report_details', :detail => 'category' %>

    +

    <%=l(:field_category)%>  <%= link_to image_tag('zoom_in.png'), project_issues_report_details_path(@project, :detail => 'category') %>

    <%= render :partial => 'simple', :locals => { :data => @issues_by_category, :field_name => "category_id", :rows => @categories } %>
    <%= call_hook(:view_reports_issue_report_split_content_right, :project => @project) %> diff -r 0a574315af3e -r 4f746d8966dd app/views/reports/issue_report_details.html.erb --- a/app/views/reports/issue_report_details.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/reports/issue_report_details.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -3,5 +3,5 @@

    <%=@report_title%>

    <%= render :partial => 'details', :locals => { :data => @data, :field_name => @field, :rows => @rows } %>
    -<%= link_to l(:button_back), :action => 'issue_report' %> +<%= link_to l(:button_back), project_issues_report_path(@project) %> diff -r 0a574315af3e -r 4f746d8966dd app/views/roles/index.html.erb --- a/app/views/roles/index.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/roles/index.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -1,6 +1,6 @@
    <%= link_to l(:label_role_new), new_role_path, :class => 'icon icon-add' %> -<%= link_to l(:label_permissions_report), {:action => 'permissions'}, :class => 'icon icon-summary' %> +<%= link_to l(:label_permissions_report), permissions_roles_path, :class => 'icon icon-summary' %>

    <%=l(:label_role_plural)%>

    diff -r 0a574315af3e -r 4f746d8966dd app/views/settings/_authentication.html.erb --- a/app/views/settings/_authentication.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/settings/_authentication.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -19,6 +19,8 @@

    <%= setting_check_box :openid, :disabled => !Object.const_defined?(:OpenID) %>

    <%= setting_check_box :rest_api_enabled %>

    + +

    <%= setting_check_box :jsonp_enabled %>

    diff -r 0a574315af3e -r 4f746d8966dd app/views/settings/_issues.html.erb --- a/app/views/settings/_issues.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/settings/_issues.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -22,11 +22,9 @@
    <%= l(:setting_issue_list_default_columns) %> - <%= render :partial => 'queries/columns', - :locals => { - :query => Query.new(:column_names => Setting.issue_list_default_columns), - :tag_name => 'settings[issue_list_default_columns][]' - } %> + <%= render_query_columns_selection( + IssueQuery.new(:column_names => Setting.issue_list_default_columns), + :name => 'settings[issue_list_default_columns]') %>
    <%= submit_tag l(:button_save) %> diff -r 0a574315af3e -r 4f746d8966dd app/views/settings/_projects.html.erb --- a/app/views/settings/_projects.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/settings/_projects.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -6,6 +6,9 @@

    <%= setting_multiselect(:default_projects_modules, Redmine::AccessControl.available_project_modules.collect {|m| [l_or_humanize(m, :prefix => "project_module_"), m.to_s]}) %>

    +

    <%= setting_multiselect(:default_projects_tracker_ids, + Tracker.sorted.all.collect {|t| [t.name, t.id.to_s]}) %>

    +

    <%= setting_check_box :sequential_project_identifiers %>

    <%= setting_select :new_project_user_role_id, diff -r 0a574315af3e -r 4f746d8966dd app/views/settings/_repositories.html.erb --- a/app/views/settings/_repositories.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/settings/_repositories.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -68,7 +68,7 @@

    <%= setting_text_field :commit_fix_keywords, :size => 30 %>  <%= l(:label_applied_status) %>: <%= setting_select :commit_fix_status_id, [["", 0]] + - IssueStatus.find(:all).collect{ + IssueStatus.sorted.all.collect{ |status| [status.name, status.id.to_s] }, :label => false %> diff -r 0a574315af3e -r 4f746d8966dd app/views/settings/plugin.html.erb --- a/app/views/settings/plugin.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/settings/plugin.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -2,7 +2,7 @@

    <%= form_tag({:action => 'plugin'}) do %> -
    +
    <%= render :partial => @partial, :locals => {:settings => @settings}%>
    <%= submit_tag l(:button_apply) %> diff -r 0a574315af3e -r 4f746d8966dd app/views/timelog/_date_range.html.erb --- a/app/views/timelog/_date_range.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/timelog/_date_range.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -1,42 +1,34 @@ -
    -<%= l(:label_date_range) %> -
    -

    -<%= label_tag "period_type_list", l(:description_date_range_list), :class => "hidden-for-sighted" %> -<%= radio_button_tag 'period_type', '1', !@free_period, :onclick => '$("#from,#to").attr("disabled", true);$("#period").removeAttr("disabled");', :id => "period_type_list"%> -<%= select_tag 'period', options_for_period_select(params[:period]), - :onchange => 'this.form.submit();', - :onfocus => '$("#period_type_1").attr("checked", true);', - :disabled => @free_period %> -

    -

    -<%= label_tag "period_type_interval", l(:description_date_range_interval), :class => "hidden-for-sighted" %> -<%= radio_button_tag 'period_type', '2', @free_period, :onclick => '$("#from,#to").removeAttr("disabled");$("#period").attr("disabled", true);', :id => "period_type_interval" %> -<%= l(:label_date_from_to, - :start => ((label_tag "from", l(:description_date_from), :class => "hidden-for-sighted") + - text_field_tag('from', @from, :size => 10, :disabled => !@free_period) + calendar_for('from')), - :end => ((label_tag "to", l(:description_date_to), :class => "hidden-for-sighted") + - text_field_tag('to', @to, :size => 10, :disabled => !@free_period) + calendar_for('to'))).html_safe %> -

    +
    +
    "> + <%= l(:label_filter_plural) %> +
    "> + <%= render :partial => 'queries/filters', :locals => {:query => @query} %> +
    +
    +
    <%= l(:field_column_names) %><%= render :partial => 'queries/columns', :locals => {:query => @query} %><%= render_query_columns_selection(@query) %>
    <%= label_tag "selected_columns", l(:description_selected_columns) %>
    - <%= select_tag((defined?(tag_name) ? tag_name : 'c[]'), - options_for_select(query.inline_columns.collect {|column| [column.caption, column.name]}), + <%= select_tag tag_name, + options_for_select(query_selected_inline_columns_options(query)), :id => 'selected_columns', :multiple => true, :size => 10, :style => "width:150px", - :ondblclick => "moveOptions(this.form.selected_columns, this.form.available_columns);") %> + :ondblclick => "moveOptions(this.form.selected_columns, this.form.available_columns);" %>

    diff -r 0a574315af3e -r 4f746d8966dd app/views/queries/_form.html.erb --- a/app/views/queries/_form.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/queries/_form.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -49,7 +49,7 @@ <%= content_tag 'fieldset', :id => 'columns', :style => (query.has_default_columns? ? 'display:none;' : nil) do %> <%= l(:field_column_names) %> -<%= render :partial => 'queries/columns', :locals => {:query => query}%> +<%= render_query_columns_selection(query) %> <% end %> diff -r 0a574315af3e -r 4f746d8966dd app/views/reports/_details.html.erb --- a/app/views/reports/_details.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/reports/_details.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -15,36 +15,13 @@
    <%= link_to h(row.name), :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)), - :set_filter => 1, - :subproject_id => '!*', - "#{field_name}" => row.id %><%= link_to h(row.name), aggregate_path(@project, field_name, row) %><%= aggregate_link data, { field_name => row.id, "status_id" => status.id }, - :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)), - :set_filter => 1, - :subproject_id => '!*', - "status_id" => status.id, - "#{field_name}" => row.id %><%= aggregate_link data, { field_name => row.id, "status_id" => status.id }, aggregate_path(@project, field_name, row, :status_id => status.id) %><%= aggregate_link data, { field_name => row.id, "closed" => 0 }, - :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)), - :set_filter => 1, - :subproject_id => '!*', - "#{field_name}" => row.id, - "status_id" => "o" %><%= aggregate_link data, { field_name => row.id, "closed" => 1 }, - :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)), - :set_filter => 1, - :subproject_id => '!*', - "#{field_name}" => row.id, - "status_id" => "c" %><%= aggregate_link data, { field_name => row.id }, - :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)), - :set_filter => 1, - :subproject_id => '!*', - "#{field_name}" => row.id, - "status_id" => "*" %><%= aggregate_link data, { field_name => row.id, "closed" => 0 }, aggregate_path(@project, field_name, row, :status_id => "o") %><%= aggregate_link data, { field_name => row.id, "closed" => 1 }, aggregate_path(@project, field_name, row, :status_id => "c") %><%= aggregate_link data, { field_name => row.id }, aggregate_path(@project, field_name, row, :status_id => "*") %>
    <%= link_to h(row.name), :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)), - :set_filter => 1, - :subproject_id => '!*', - "#{field_name}" => row.id %><%= aggregate_link data, { field_name => row.id, "closed" => 0 }, - :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)), - :set_filter => 1, - :subproject_id => '!*', - "#{field_name}" => row.id, - "status_id" => "o" %><%= aggregate_link data, { field_name => row.id, "closed" => 1 }, - :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)), - :set_filter => 1, - :subproject_id => '!*', - "#{field_name}" => row.id, - "status_id" => "c" %><%= aggregate_link data, { field_name => row.id }, - :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)), - :set_filter => 1, - :subproject_id => '!*', - "#{field_name}" => row.id, - "status_id" => "*" %><%= link_to h(row.name), aggregate_path(@project, field_name, row) %><%= aggregate_link data, { field_name => row.id, "closed" => 0 }, aggregate_path(@project, field_name, row, :status_id => "o") %><%= aggregate_link data, { field_name => row.id, "closed" => 1 }, aggregate_path(@project, field_name, row, :status_id => "c") %><%= aggregate_link data, { field_name => row.id }, aggregate_path(@project, field_name, row, :status_id => "*") %>
    + + + + +
    <%= l(:field_column_names) %><%= render_query_columns_selection(@query) %>
    +
    +
    - -

    - <%= link_to_function l(:button_apply), '$("#query_form").submit()', :class => 'icon icon-checked' %> - <%= link_to l(:button_clear), {:controller => controller_name, :action => action_name, :project_id => @project, :issue_id => @issue}, :class => 'icon icon-reload' %> + +

    + <%= link_to_function l(:button_apply), 'submit_query_form("query_form")', :class => 'icon icon-checked' %> + <%= link_to l(:button_clear), {:project_id => @project, :issue_id => @issue}, :class => 'icon icon-reload' %>

    -<% url_params = @free_period ? { :from => @from, :to => @to } : { :period => params[:period] } %> +<% query_params = params.slice(:f, :op, :v, :sort) %>
      -
    • <%= link_to(l(:label_details), url_params.merge({:controller => 'timelog', :action => 'index', :project_id => @project, :issue_id => @issue }), +
    • <%= link_to(l(:label_details), query_params.merge({:controller => 'timelog', :action => 'index', :project_id => @project, :issue_id => @issue }), :class => (action_name == 'index' ? 'selected' : nil)) %>
    • -
    • <%= link_to(l(:label_report), url_params.merge({:controller => 'timelog', :action => 'report', :project_id => @project, :issue_id => @issue}), +
    • <%= link_to(l(:label_report), query_params.merge({:controller => 'timelog', :action => 'report', :project_id => @project, :issue_id => @issue}), :class => (action_name == 'report' ? 'selected' : nil)) %>
    - -<%= javascript_tag do %> -$('#from, #to').change(function(){ - $('#period_type_interval').attr('checked', true); $('#from,#to').removeAttr('disabled'); $('#period').attr('disabled', true); -}); -<% end %> diff -r 0a574315af3e -r 4f746d8966dd app/views/timelog/_form.html.erb --- a/app/views/timelog/_form.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/timelog/_form.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -2,22 +2,31 @@ <%= back_url_hidden_field_tag %>
    - <% if @time_entry.new_record? %> - <% if params[:project_id] || @time_entry.issue %> - <%= f.hidden_field :project_id %> - <% else %> -

    <%= f.select :project_id, project_tree_options_for_select(Project.allowed_to(:log_time).all, :selected => @time_entry.project), :required => true %>

    - <% end %> - <% end %> -

    <%= f.text_field :issue_id, :size => 6 %> <%= h("#{@time_entry.issue.tracker.name} ##{@time_entry.issue.id}: #{@time_entry.issue.subject}") if @time_entry.issue %>

    -

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

    -

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

    -

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

    -

    <%= f.select :activity_id, activity_collection_for_select_options(@time_entry), :required => true %>

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

    <%= custom_field_tag_with_label :time_entry, value %>

    - <% end %> - <%= call_hook(:view_timelog_edit_form_bottom, { :time_entry => @time_entry, :form => f }) %> + <% if @time_entry.new_record? %> + <% if params[:project_id] || @time_entry.issue %> + <%= f.hidden_field :project_id %> + <% else %> +

    <%= f.select :project_id, project_tree_options_for_select(Project.allowed_to(:log_time).all, :selected => @time_entry.project), :required => true %>

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

    + <%= f.text_field :issue_id, :size => 6 %> + <%= h("#{@time_entry.issue.tracker.name} ##{@time_entry.issue.id}: #{@time_entry.issue.subject}") if @time_entry.issue %> +

    +

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

    +

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

    +

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

    +

    <%= f.select :activity_id, activity_collection_for_select_options(@time_entry), :required => true %>

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

    <%= custom_field_tag_with_label :time_entry, value %>

    + <% end %> + <%= call_hook(:view_timelog_edit_form_bottom, { :time_entry => @time_entry, :form => f }) %>
    -<%= javascript_tag "observeAutocompleteField('time_entry_issue_id', '#{escape_javascript auto_complete_issues_path(:project_id => @project, :scope => (@project ? nil : 'all'))}')" %> +<%= javascript_tag do %> + observeAutocompleteField('time_entry_issue_id', '<%= escape_javascript auto_complete_issues_path(:project_id => @project, :scope => (@project ? nil : 'all'))%>', { + select: function(event, ui) { + $('#time_entry_issue').text(ui.item.label); + } + }); +<% end %> diff -r 0a574315af3e -r 4f746d8966dd app/views/timelog/_list.html.erb --- a/app/views/timelog/_list.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/timelog/_list.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -3,49 +3,35 @@
    - - -<%= sort_header_tag('spent_on', :caption => l(:label_date), :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') %> - -<%= sort_header_tag('hours', :caption => l(:field_hours)) %> - - + + + <% @query.inline_columns.each do |column| %> + <%= column_header(column) %> + <% end %> + + <% entries.each do |entry| -%> - hascontextmenu"> - - - - - - - - - - + hascontextmenu"> + + <%= raw @query.inline_columns.map {|column| ""}.join %> + + <% end -%>
    - <%= link_to image_tag('toggle_check.png'), - {}, - :onclick => 'toggleIssuesSelection(this); return false;', - :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %> -<%= l(:field_comments) %>
    + <%= link_to image_tag('toggle_check.png'), + {}, + :onclick => 'toggleIssuesSelection(this); return false;', + :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %> +
    <%= check_box_tag("ids[]", entry.id, false, :id => nil) %><%= format_date(entry.spent_on) %><%= link_to_user(entry.user) %><%=h entry.activity %><%= link_to_project(entry.project) %> -<% if entry.issue -%> -<%= entry.issue.visible? ? link_to_issue(entry.issue, :truncate => 50) : "##{entry.issue.id}" -%> -<% end -%> -<%=h entry.comments %><%= html_hours("%.2f" % entry.hours) %> -<% if entry.editable_by?(User.current) -%> - <%= link_to image_tag('edit.png'), edit_time_entry_path(entry), - :title => l(:button_edit) %> - <%= link_to image_tag('delete.png'), time_entry_path(entry), - :data => {:confirm => l(:text_are_you_sure)}, - :method => :delete, - :title => l(:button_delete) %> -<% end -%> -
    <%= check_box_tag("ids[]", entry.id, false, :id => nil) %>#{column_content(column, entry)} + <% if entry.editable_by?(User.current) -%> + <%= link_to image_tag('edit.png'), edit_time_entry_path(entry), + :title => l(:button_edit) %> + <%= link_to image_tag('delete.png'), time_entry_path(entry), + :data => {:confirm => l(:text_are_you_sure)}, + :method => :delete, + :title => l(:button_delete) %> + <% end -%> +
    diff -r 0a574315af3e -r 4f746d8966dd app/views/timelog/bulk_edit.html.erb --- a/app/views/timelog/bulk_edit.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/timelog/bulk_edit.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -1,13 +1,13 @@

    <%= l(:label_bulk_edit_selected_time_entries) %>

    -
      -<%= @time_entries.collect {|i| content_tag('li', - link_to(h("#{i.spent_on.strftime("%Y-%m-%d")} - #{i.project}: #{l(:label_f_hour_plural, :value => i.hours)}"), - { :action => 'edit', :id => i }) - )}.join("\n").html_safe %> +
        +<% @time_entries.each do |entry| %> + <%= content_tag 'li', + link_to("#{format_date(entry.spent_on)} - #{entry.project}: #{l(:label_f_hour_plural, :value => entry.hours)}", edit_time_entry_path(entry)) %> +<% end %>
      -<%= form_tag(:action => 'bulk_update') do %> +<%= form_tag(bulk_update_time_entries_path, :id => 'bulk_edit_form') do %> <%= @time_entries.collect {|i| hidden_field_tag('ids[]', i.id)}.join.html_safe %>
      diff -r 0a574315af3e -r 4f746d8966dd app/views/timelog/index.html.erb --- a/app/views/timelog/index.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/timelog/index.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -13,7 +13,7 @@ <% end %>
      -

      <%= l(:label_total) %>: <%= html_hours(l_hours(@total_hours)) %>

      +

      <%= l(:label_total_time) %>: <%= html_hours(l_hours(@total_hours)) %>

      <% unless @entries.empty? %> @@ -22,8 +22,23 @@ <% 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 %> + <%= f.link_to 'CSV', :url => params, :onclick => "showModal('csv-export-options', '330px'); return false;" %> <% end %> + + <% end %> <% html_title l(:label_spent_time), l(:label_details) %> diff -r 0a574315af3e -r 4f746d8966dd app/views/timelog/report.html.erb --- a/app/views/timelog/report.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/timelog/report.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -30,7 +30,7 @@ <% unless @report.criteria.empty? %>
      -

      <%= l(:label_total) %>: <%= html_hours(l_hours(@report.total_hours)) %>

      +

      <%= l(:label_total_time) %>: <%= html_hours(l_hours(@report.total_hours)) %>

      <% unless @report.hours.empty? %> @@ -45,13 +45,13 @@ <% @report.periods.each do |period| %> <%= period %> <% end %> - <%= l(:label_total) %> + <%= l(:label_total_time) %> <%= render :partial => 'report_criteria', :locals => {:criterias => @report.criteria, :hours => @report.hours, :level => 0} %> - <%= l(:label_total) %> + <%= l(:label_total_time) %> <%= ('' * (@report.criteria.size - 1)).html_safe %> <% total = 0 -%> <% @report.periods.each do |period| -%> diff -r 0a574315af3e -r 4f746d8966dd app/views/trackers/fields.html.erb --- a/app/views/trackers/fields.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/trackers/fields.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@

      <%= link_to l(:label_tracker_plural), trackers_path %> » <%= l(:field_summary) %>

      <% if @trackers.any? %> - <%= form_tag({}) do %> + <%= form_tag fields_trackers_path do %>
      diff -r 0a574315af3e -r 4f746d8966dd app/views/trackers/index.html.erb --- a/app/views/trackers/index.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/trackers/index.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -1,6 +1,6 @@
      <%= link_to l(:label_tracker_new), new_tracker_path, :class => 'icon icon-add' %> -<%= link_to l(:field_summary), {:action => 'fields'}, :class => 'icon icon-summary' %> +<%= link_to l(:field_summary), fields_trackers_path, :class => 'icon icon-summary' %>

      <%=l(:label_tracker_plural)%>

      @@ -16,8 +16,16 @@ <% for tracker in @trackers %> "> - - + + diff -r 0a574315af3e -r 4f746d8966dd app/views/users/_memberships.html.erb --- a/app/views/users/_memberships.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/users/_memberships.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ <% roles = Role.find_all_givable %> -<% projects = Project.active.find(:all, :order => 'lft') %> +<% projects = Project.active.all %>
      <% if @user.memberships.any? %> diff -r 0a574315af3e -r 4f746d8966dd app/views/users/index.html.erb --- a/app/views/users/index.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/users/index.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -4,7 +4,7 @@

      <%=l(:label_user_plural)%>

      -<%= form_tag({}, :method => :get) do %> +<%= form_tag(users_path, :method => :get) do %>
      <%= l(:label_filter_plural) %> <%= select_tag 'status', users_status_options_for_select(@status), :class => "small", :onchange => "this.form.submit(); return false;" %> diff -r 0a574315af3e -r 4f746d8966dd app/views/users/new.html.erb --- a/app/views/users/new.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/users/new.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -10,3 +10,21 @@ <%= submit_tag l(:button_create_and_continue), :name => 'continue' %>

      <% end %> + +<% if @auth_sources.present? && @auth_sources.any?(&:searchable?) %> + <%= javascript_tag do %> + observeAutocompleteField('user_login', '<%= escape_javascript autocomplete_for_new_user_auth_sources_path %>', { + select: function(event, ui) { + $('input#user_firstname').val(ui.item.firstname); + $('input#user_lastname').val(ui.item.lastname); + $('input#user_mail').val(ui.item.mail); + $('select#user_auth_source_id option').each(function(){ + if ($(this).attr('value') == ui.item.auth_source_id) { + $(this).attr('selected', true); + $('select#user_auth_source_id').trigger('change'); + } + }); + } + }); + <% end %> +<% end %> diff -r 0a574315af3e -r 4f746d8966dd app/views/users/show.api.rsb --- a/app/views/users/show.api.rsb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/users/show.api.rsb Fri Jun 14 09:28:30 2013 +0100 @@ -1,11 +1,12 @@ api.user do api.id @user.id - api.login @user.login if User.current.admin? + api.login @user.login if User.current.admin? || (User.current == @user) api.firstname @user.firstname api.lastname @user.lastname api.mail @user.mail if User.current.admin? || !@user.pref.hide_mail api.created_on @user.created_on api.last_login_on @user.last_login_on + api.api_key @user.api_key if User.current.admin? || (User.current == @user) render_api_custom_values @user.visible_custom_field_values, api diff -r 0a574315af3e -r 4f746d8966dd app/views/versions/_overview.html.erb --- a/app/views/versions/_overview.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/versions/_overview.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -16,7 +16,7 @@ <% end %> <% if version.issues_count > 0 %> - <%= progress_bar([version.closed_pourcent, version.completed_pourcent], :width => '40em', :legend => ('%0.0f%' % version.completed_pourcent)) %> + <%= progress_bar([version.closed_percent, version.completed_percent], :width => '40em', :legend => ('%0.0f%' % version.completed_percent)) %>

      <%= link_to(l(:label_x_issues, :count => version.issues_count), project_issues_path(version.project, :status_id => '*', :fixed_version_id => version, :set_filter => 1)) %> diff -r 0a574315af3e -r 4f746d8966dd app/views/watchers/_new.html.erb --- a/app/views/watchers/_new.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/watchers/_new.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -2,8 +2,9 @@ <%= form_tag({:controller => 'watchers', :action => (watched ? 'create' : 'append'), - :object_type => watched.class.name.underscore, - :object_id => watched}, + :object_type => (watched && watched.class.name.underscore), + :object_id => watched, + :project_id => @project}, :remote => true, :method => :post, :id => 'new-watcher-form') do %> @@ -11,8 +12,9 @@

      <%= label_tag 'user_search', l(:label_user_search) %><%= text_field_tag 'user_search', nil %>

      <%= javascript_tag "observeSearchfield('user_search', 'users_for_watcher', '#{ escape_javascript url_for(:controller => 'watchers', :action => 'autocomplete_for_user', - :object_type => watched.class.name.underscore, - :object_id => watched) }')" %> + :object_type => (watched && watched.class.name.underscore), + :object_id => watched, + :project_id => @project) }')" %>
      <%= principals_check_box_tags 'watcher[user_ids][]', (watched ? watched.addable_watcher_users : User.active.all(:limit => 100)) %> diff -r 0a574315af3e -r 4f746d8966dd app/views/watchers/_set_watcher.js.erb --- a/app/views/watchers/_set_watcher.js.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/watchers/_set_watcher.js.erb Fri Jun 14 09:28:30 2013 +0100 @@ -1,2 +1,2 @@ <% selector = ".#{watcher_css(watched)}" %> -$("<%= selector %>").each(function(){$(this).html("<%= escape_javascript watcher_link(watched, user) %>")}); +$("<%= selector %>").each(function(){$(this).replaceWith("<%= escape_javascript watcher_link(watched, user) %>")}); diff -r 0a574315af3e -r 4f746d8966dd app/views/wiki/date_index.html.erb --- a/app/views/wiki/date_index.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/wiki/date_index.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@
      -<%= watcher_tag(@wiki, User.current) %> +<%= watcher_link(@wiki, User.current) %>

      <%= l(:label_index_by_date) %>

      diff -r 0a574315af3e -r 4f746d8966dd app/views/wiki/index.html.erb --- a/app/views/wiki/index.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/wiki/index.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@
      -<%= watcher_tag(@wiki, User.current) %> +<%= watcher_link(@wiki, User.current) %>

      <%= l(:label_index_by_title) %>

      diff -r 0a574315af3e -r 4f746d8966dd app/views/wiki/show.html.erb --- a/app/views/wiki/show.html.erb Fri Jun 14 09:07:32 2013 +0100 +++ b/app/views/wiki/show.html.erb Fri Jun 14 09:28:30 2013 +0100 @@ -2,7 +2,7 @@ <% if @editable %> <% if @content.current_version? %> <%= link_to_if_authorized(l(:button_edit), {:action => 'edit', :id => @page.title}, :class => 'icon icon-edit', :accesskey => accesskey(:edit)) %> - <%= watcher_tag(@page, User.current) %> + <%= watcher_link(@page, User.current) %> <%= link_to_if_authorized(l(:button_lock), {:action => 'protect', :id => @page.title, :protected => 1}, :method => :post, :class => 'icon icon-lock') if !@page.protected? %> <%= link_to_if_authorized(l(:button_unlock), {:action => 'protect', :id => @page.title, :protected => 0}, :method => :post, :class => 'icon icon-unlock') if @page.protected? %> <%= link_to_if_authorized(l(:button_rename), {:action => 'rename', :id => @page.title}, :class => 'icon icon-move') %> diff -r 0a574315af3e -r 4f746d8966dd config/configuration.yml.example --- a/config/configuration.yml.example Fri Jun 14 09:07:32 2013 +0100 +++ b/config/configuration.yml.example Fri Jun 14 09:28:30 2013 +0100 @@ -133,6 +133,12 @@ scm_bazaar_command: scm_darcs_command: + # Absolute path to the SCM commands errors (stderr) log file. + # The default is to log in the 'log' directory of your Redmine instance. + # Example: + # scm_stderr_log_file: /var/log/redmine_scm_stderr.log + scm_stderr_log_file: + # Key used to encrypt sensitive data in the database (SCM and LDAP passwords). # If you don't want to enable data encryption, just leave it blank. # WARNING: losing/changing this key will make encrypted data unreadable. @@ -188,6 +194,9 @@ # rmagick_font_path: + # Maximum number of simultaneous AJAX uploads + #max_concurrent_ajax_uploads: 2 + # specific configuration options for production environment # that overrides the default ones production: diff -r 0a574315af3e -r 4f746d8966dd config/database.yml.example --- a/config/database.yml.example Fri Jun 14 09:07:32 2013 +0100 +++ b/config/database.yml.example Fri Jun 14 09:28:30 2013 +0100 @@ -1,9 +1,10 @@ -# Default setup is given for MySQL with ruby1.8. If you're running Redmine -# with MySQL and ruby1.9, replace the adapter name with `mysql2`. -# Examples for PostgreSQL and SQLite3 can be found at the end. +# Default setup is given for MySQL with ruby1.9. If you're running Redmine +# with MySQL and ruby1.8, replace the adapter name with `mysql`. +# Examples for PostgreSQL, SQLite3 and SQL Server can be found at the end. +# Line indentation must be 2 spaces (no tabs). production: - adapter: mysql + adapter: mysql2 database: redmine host: localhost username: root @@ -11,7 +12,7 @@ encoding: utf8 development: - adapter: mysql + adapter: mysql2 database: redmine_development host: localhost username: root @@ -22,20 +23,30 @@ # re-generated from your development database when you run "rake". # Do not set this db to the same as development or production. test: - adapter: mysql + adapter: mysql2 database: redmine_test host: localhost username: root password: "" encoding: utf8 -test_pgsql: - adapter: postgresql - database: redmine_test - host: localhost - username: postgres - password: "postgres" +# PostgreSQL configuration example +#production: +# adapter: postgresql +# database: redmine +# host: localhost +# username: postgres +# password: "postgres" -test_sqlite3: - adapter: sqlite3 - database: db/test.sqlite3 +# SQLite3 configuration example +#production: +# adapter: sqlite3 +# database: db/redmine.sqlite3 + +# SQL Server configuration example +#production: +# adapter: sqlserver +# database: redmine +# host: localhost +# username: jenkins +# password: jenkins diff -r 0a574315af3e -r 4f746d8966dd config/locales/ar.yml --- a/config/locales/ar.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/ar.yml Fri Jun 14 09:28:30 2013 +0100 @@ -50,8 +50,8 @@ one: "حوالي ساعة" other: "ساعات %{count}حوالي " x_hours: - one: "1 hour" - other: "%{count} hours" + one: "%{count} ساعة" + other: "%{count} ساعات" x_days: one: "يوم" other: "%{count} أيام" @@ -211,8 +211,6 @@ mail_subject_wiki_content_updated: "'%{id}' تم تحديث ØµÙØ­Ø© ويكي" mail_body_wiki_content_updated: "The '%{id}'تم تحديث ØµÙØ­Ø© ويكي من قبل %{author}." - gui_validation_error: خطأ - gui_validation_error_plural: "%{count}أخطاء" field_name: الاسم field_description: الوص٠@@ -413,7 +411,6 @@ permission_edit_own_time_entries: تعديل الدخولات الشخصية permission_manage_news: ادارة الاخبار permission_comment_news: اخبار التعليقات - permission_manage_documents: ادارة المستندات permission_view_documents: عرض المستندات permission_manage_files: ادارة Ø§Ù„Ù…Ù„ÙØ§Øª permission_view_files: عرض Ø§Ù„Ù…Ù„ÙØ§Øª @@ -543,8 +540,6 @@ label_text: نص طويل label_attribute: سمة label_attribute_plural: السمات - label_download: "تحميل" - label_download_plural: "تحميل" label_no_data: لا توجد بيانات للعرض label_change_status: تغيير الوضع label_history: التاريخ @@ -650,8 +645,6 @@ label_repository: المستودع label_repository_plural: المستودعات label_browse: ØªØµÙØ­ - label_modification: "%{count} تغير" - label_modification_plural: "%{count}تغيرات " label_branch: ÙØ±Ø¹ label_tag: ربط label_revision: مراجعة @@ -1081,3 +1074,16 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: الإجمالي diff -r 0a574315af3e -r 4f746d8966dd config/locales/az.yml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/config/locales/az.yml Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,1186 @@ +# +# Translated by Saadat Mutallimova +# Data Processing Center of the Ministry of Communication and Information Technologies +# +az: + direction: ltr + date: + formats: + default: "%d.%m.%Y" + short: "%d %b" + long: "%d %B %Y" + + day_names: [bazar, bazar ertÉ™si, çərÅŸÉ™nbÉ™ axÅŸamı, çərÅŸÉ™nbÉ™, cümÉ™ axÅŸamı, cümÉ™, ÅŸÉ™nbÉ™] + standalone_day_names: [Bazar, Bazar ertÉ™si, ÇərÅŸÉ™nbÉ™ axÅŸamı, ÇərÅŸÉ™nbÉ™, CümÉ™ axÅŸamı, CümÉ™, ŞənbÉ™] + abbr_day_names: [B, Be, Ça, Ç, Ca, C, Åž] + + month_names: [~, yanvar, fevral, mart, aprel, may, iyun, iyul, avqust, sentyabr, oktyabr, noyabr, dekabr] + # see russian gem for info on "standalone" day names + standalone_month_names: [~, Yanvar, Fevral, Mart, Aprel, May, İyun, İyul, Avqust, Sentyabr, Oktyabr, Noyabr, Dekabr] + abbr_month_names: [~, yan., fev., mart, apr., may, iyun, iyul, avq., sent., okt., noy., dek.] + standalone_abbr_month_names: [~, yan., fev., mart, apr., may, iyun, iyul, avq., sent., okt., noy., dek.] + + order: + - :day + - :month + - :year + + time: + formats: + default: "%a, %d %b %Y, %H:%M:%S %z" + time: "%H:%M" + short: "%d %b, %H:%M" + long: "%d %B %Y, %H:%M" + + am: "sÉ™hÉ™r" + pm: "axÅŸam" + + number: + format: + separator: "," + delimiter: " " + precision: 3 + + currency: + format: + format: "%n %u" + unit: "man." + separator: "." + delimiter: " " + precision: 2 + + percentage: + format: + delimiter: "" + + precision: + format: + delimiter: "" + + human: + format: + delimiter: "" + precision: 3 + # Rails 2.2 + # storage_units: [байт, КБ, МБ, ГБ, ТБ] + + # Rails 2.3 + storage_units: + # Storage units output formatting. + # %u is the storage unit, %n is the number (default: 2 MB) + format: "%n %u" + units: + byte: + one: "bayt" + few: "bayt" + many: "bayt" + other: "bayt" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + + datetime: + distance_in_words: + half_a_minute: "bir dÉ™qiqÉ™dÉ™n az" + less_than_x_seconds: + one: "%{count} saniyÉ™dÉ™n az" + few: "%{count} saniyÉ™dÉ™n az" + many: "%{count} saniyÉ™dÉ™n az" + other: "%{count} saniyÉ™dÉ™n az" + x_seconds: + one: "%{count} saniyÉ™" + few: "%{count} saniyÉ™" + many: "%{count} saniyÉ™" + other: "%{count} saniyÉ™" + less_than_x_minutes: + one: "%{count} dÉ™qiqÉ™dÉ™n az" + few: "%{count} dÉ™qiqÉ™dÉ™n az" + many: "%{count} dÉ™qiqÉ™dÉ™n az" + other: "%{count} dÉ™qiqÉ™dÉ™n az" + x_minutes: + one: "%{count} dÉ™qiqÉ™" + few: "%{count} dÉ™qiqÉ™" + many: "%{count} dÉ™qiqÉ™" + other: "%{count} dÉ™qiqÉ™" + about_x_hours: + one: "tÉ™xminÉ™n %{count} saat" + few: "tÉ™xminÉ™n %{count} saat" + many: "tÉ™xminÉ™n %{count} saat" + other: "tÉ™xminÉ™n %{count} saat" + x_hours: + one: "1 saat" + other: "%{count} saat" + x_days: + one: "%{count} gün" + few: "%{count} gün" + many: "%{count} gün" + other: "%{count} gün" + about_x_months: + one: "tÉ™xminÉ™n %{count} ay" + few: "tÉ™xminÉ™n %{count} ay" + many: "tÉ™xminÉ™n %{count} ay" + other: "tÉ™xminÉ™n %{count} ay" + x_months: + one: "%{count} ay" + few: "%{count} ay" + many: "%{count} ay" + other: "%{count} ay" + about_x_years: + one: "tÉ™xminÉ™n %{count} il" + few: "tÉ™xminÉ™n %{count} il" + many: "tÉ™xminÉ™n %{count} il" + other: "tÉ™xminÉ™n %{count} il" + over_x_years: + one: "%{count} ildÉ™n çox" + few: "%{count} ildÉ™n çox" + many: "%{count} ildÉ™n çox" + other: "%{count} ildÉ™n çox" + almost_x_years: + one: "tÉ™xminÉ™n 1 il" + few: "tÉ™xminÉ™n %{count} il" + many: "tÉ™xminÉ™n %{count} il" + other: "tÉ™xminÉ™n %{count} il" + prompts: + year: "İl" + month: "Ay" + day: "Gün" + hour: "Saat" + minute: "DÉ™qiqÉ™" + second: "SaniyÉ™" + + activerecord: + errors: + template: + header: + one: "%{model}: %{count} sÉ™hvÉ™ görÉ™ yadda saxlamaq mümkün olmadı" + few: "%{model}: %{count} sÉ™hvlÉ™rÉ™ görÉ™ yadda saxlamaq mümkün olmadı" + many: "%{model}: %{count} sÉ™hvlÉ™rÉ™ görÉ™ yadda saxlamaq mümkün olmadı" + other: "%{model}: %{count} sÉ™hvÉ™ görÉ™ yadda saxlamaq mümkün olmadı" + + body: "ProblemlÉ™r aÅŸağıdakı sahÉ™lÉ™rdÉ™ yarandı:" + + messages: + inclusion: "nÉ™zÉ™rdÉ™ tutulmamış tÉ™yinata malikdir" + exclusion: "ehtiyata götürülmÉ™miÅŸ tÉ™yinata malikdir" + invalid: "düzgün tÉ™yinat deyildir" + confirmation: "tÉ™sdiq ilÉ™ üst-üstÉ™ düşmür" + accepted: "tÉ™sdiq etmÉ™k lazımdır" + empty: "boÅŸ saxlanıla bilmÉ™z" + blank: "boÅŸ saxlanıla bilmÉ™z" + too_long: + one: "çox böyük uzunluq (%{count} simvoldan çox ola bilmÉ™z)" + few: "çox böyük uzunluq (%{count} simvoldan çox ola bilmÉ™z)" + many: "çox böyük uzunluq (%{count} simvoldan çox ola bilmÉ™z)" + other: "çox böyük uzunluq (%{count} simvoldan çox ola bilmÉ™z)" + too_short: + one: "uzunluq kifayÉ™t qÉ™dÉ™r deyildir (%{count} simvoldan az ola bilmÉ™z)" + few: "uzunluq kifayÉ™t qÉ™dÉ™r deyildir (%{count} simvoldan az ola bilmÉ™z)" + many: "uzunluq kifayÉ™t qÉ™dÉ™r deyildir (%{count} simvoldan az ola bilmÉ™z)" + other: "uzunluq kifayÉ™t qÉ™dÉ™r deyildir (%{count} simvoldan az ola bilmÉ™z)" + wrong_length: + one: "düzgün olmayan uzunluq (tam %{count} simvol ola bilÉ™r)" + few: "düzgün olmayan uzunluq (tam %{count} simvol ola bilÉ™r)" + many: "düzgün olmayan uzunluq (tam %{count} simvol ola bilÉ™r)" + other: "düzgün olmayan uzunluq (tam %{count} simvol ola bilÉ™r)" + taken: "artıq mövcuddur" + not_a_number: "say kimi hesab edilmir" + greater_than: "%{count} çox tÉ™yinata malik ola bilÉ™r" + greater_than_or_equal_to: "%{count} çox vÉ™ ya ona bÉ™rabÉ™r tÉ™yinata malik ola bilÉ™r" + equal_to: "yalnız %{count} bÉ™rabÉ™r tÉ™yinata malik ola bilÉ™r" + less_than: "%{count} az tÉ™yinata malik ola bilÉ™r" + less_than_or_equal_to: "%{count} az vÉ™ ya ona bÉ™rabÉ™r tÉ™yinata malik ola bilÉ™r" + odd: "yalnız tÉ™k tÉ™yinata malik ola bilÉ™r" + even: "yalnız cüt tÉ™yinata malik ola bilÉ™r" + greater_than_start_date: "baÅŸlanğıc tarixindÉ™n sonra olmalıdır" + not_same_project: "tÉ™kcÉ™ bir layihÉ™yÉ™ aid deyildir" + circular_dependency: "BelÉ™ É™laqÉ™ dövri asılılığa gÉ™tirib çıxaracaq" + cant_link_an_issue_with_a_descendant: "Tapşırıq özünün alt tapşırığı ilÉ™ É™laqÉ™li ola bilmÉ™z" + + support: + array: + # Rails 2.2 + sentence_connector: "vÉ™" + skip_last_comma: true + + # Rails 2.3 + words_connector: ", " + two_words_connector: " vÉ™" + last_word_connector: " vÉ™ " + + actionview_instancetag_blank_option: Seçim edin + + button_activate: AktivləşdirmÉ™k + button_add: ÆlavÉ™ etmÉ™k + button_annotate: Müəlliflik + button_apply: TÉ™tbiq etmÉ™k + button_archive: ArxivləşdirmÉ™k + button_back: GeriyÉ™ + button_cancel: İmtina + button_change_password: Parolu dÉ™yiÅŸmÉ™k + button_change: DÉ™yiÅŸmÉ™k + button_check_all: Hamını qeyd etmÉ™k + button_clear: TÉ™mizlÉ™mÉ™k + button_configure: ParametlÉ™r + button_copy: SürÉ™tini çıxarmaq + button_create: Yaratmaq + button_create_and_continue: Yaratmaq vÉ™ davam etmÉ™k + button_delete: SilmÉ™k + button_download: YüklÉ™mÉ™k + button_edit: RedaktÉ™ etmÉ™k + button_edit_associated_wikipage: "ÆlaqÉ™li wiki-sÉ™hifÉ™ni redaktÉ™ etmÉ™k: %{page_title}" + button_list: Siyahı + button_lock: Bloka salmaq + button_login: GiriÅŸ + button_log_time: SÉ™rf olunan vaxt + button_move: Yerini dÉ™yiÅŸmÉ™k + button_quote: Sitat gÉ™tirmÉ™k + button_rename: Adını dÉ™yiÅŸmÉ™k + button_reply: Cavablamaq + button_reset: Sıfırlamaq + button_rollback: Bu versiyaya qayıtmaq + button_save: Yadda saxlamaq + button_sort: ÇeÅŸidlÉ™mÉ™k + button_submit: QÉ™bul etmÉ™k + button_test: Yoxlamaq + button_unarchive: ArxivdÉ™n çıxarmaq + button_uncheck_all: TÉ™mizlÉ™mÉ™k + button_unlock: Blokdan çıxarmaq + button_unwatch: İzlÉ™mÉ™mÉ™k + button_update: YenilÉ™mÉ™k + button_view: Baxmaq + button_watch: İzlÉ™mÉ™k + + default_activity_design: LayihÉ™nin hazırlanması + default_activity_development: Hazırlanma prosesi + default_doc_category_tech: Texniki sÉ™nÉ™dləşmÉ™ + default_doc_category_user: İstifadəçi sÉ™nÉ™di + default_issue_status_in_progress: İşlÉ™nmÉ™kdÉ™dir + default_issue_status_closed: BaÄŸlanıb + default_issue_status_feedback: Æks É™laqÉ™ + default_issue_status_new: Yeni + default_issue_status_rejected: RÉ™dd etmÉ™ + default_issue_status_resolved: HÉ™ll edilib + default_priority_high: YüksÉ™k + default_priority_immediate: TÉ™xirsiz + default_priority_low: AÅŸağı + default_priority_normal: Normal + default_priority_urgent: TÉ™cili + default_role_developer: Hazırlayan + default_role_manager: Menecer + default_role_reporter: Reportyor + default_tracker_bug: SÉ™hv + default_tracker_feature: TÉ™kmilləşmÉ™ + default_tracker_support: DÉ™stÉ™k + + enumeration_activities: HÉ™rÉ™kÉ™tlÉ™r (vaxtın uçotu) + enumeration_doc_categories: SÉ™nÉ™dlÉ™rin kateqoriyası + enumeration_issue_priorities: Tapşırıqların prioriteti + + error_can_not_remove_role: Bu rol istifadÉ™ edilir vÉ™ silinÉ™ bilmÉ™z. + error_can_not_delete_custom_field: Sazlanmış sahÉ™ni silmÉ™k mümkün deyildir + error_can_not_delete_tracker: Bu treker tapşırıqlardan ibarÉ™t olduÄŸu üçün silinÉ™ bilmÉ™z. + error_can_t_load_default_data: "Susmaya görÉ™ konfiqurasiya yüklÉ™nmÉ™miÅŸdir: %{value}" + error_issue_not_found_in_project: Tapşırıq tapılmamışdır vÉ™ ya bu layihÉ™yÉ™ bÉ™rkidilmÉ™miÅŸdir + error_scm_annotate: "VerilÉ™nlÉ™r mövcud deyildir vÉ™ ya imzalana bilmÉ™z." + error_scm_command_failed: "Saxlayıcıya giriÅŸ imkanı sÉ™hvi: %{value}" + error_scm_not_found: Saxlayıcıda yazı vÉ™/ vÉ™ ya düzÉ™liÅŸ yoxdur. + error_unable_to_connect: QoÅŸulmaq mümkün deyildir (%{value}) + error_unable_delete_issue_status: Tapşırığın statusunu silmÉ™k mümkün deyildir + + field_account: İstifadəçi hesabı + field_activity: FÉ™aliyyÉ™t + field_admin: İnzibatçı + field_assignable: Tapşırıq bu rola tÉ™yin edilÉ™ bilÉ™r + field_assigned_to: TÉ™yin edilib + field_attr_firstname: Ad + field_attr_lastname: Soyad + field_attr_login: Atribut Login + field_attr_mail: e-poçt + field_author: Müəllif + field_auth_source: Autentifikasiya rejimi + field_base_dn: BaseDN + field_category: Kateqoriya + field_column_names: Sütunlar + field_comments: ŞərhlÉ™r + field_comments_sorting: ŞərhlÉ™rin tÉ™sviri + field_content: Content + field_created_on: Yaradılıb + field_default_value: Susmaya görÉ™ tÉ™yinat + field_delay: TÉ™xirÉ™ salmaq + field_description: TÉ™svir + field_done_ratio: Hazırlıq + field_downloads: YüklÉ™mÉ™lÉ™r + field_due_date: YerinÉ™ yetirilmÉ™ tarixi + field_editable: RedaktÉ™ edilÉ™n + field_effective_date: Tarix + field_estimated_hours: Vaxtın dÉ™yÉ™rlÉ™ndirilmÉ™si + field_field_format: Format + field_filename: Fayl + field_filesize: Ölçü + field_firstname: Ad + field_fixed_version: Variant + field_hide_mail: E-poçtumu gizlÉ™t + field_homepage: BaÅŸlanğıc sÉ™hifÉ™ + field_host: Kompyuter + field_hours: saat + field_identifier: Unikal identifikator + field_identity_url: OpenID URL + field_is_closed: Tapşırıq baÄŸlanıb + field_is_default: Susmaya görÉ™ tapşırıq + field_is_filter: Filtr kimi istifadÉ™ edilir + field_is_for_all: Bütün layihÉ™lÉ™r üçün + field_is_in_roadmap: Operativ planda É™ks olunan tapşırıqlar + field_is_public: Ümümaçıq + field_is_required: MütlÉ™q + field_issue_to: ÆlaqÉ™li tapşırıqlar + field_issue: Tapşırıq + field_language: Dil + field_last_login_on: Son qoÅŸulma + field_lastname: Soyad + field_login: İstifadəçi + field_mail: e-poçt + field_mail_notification: e-poçt ilÉ™ bildiriÅŸ + field_max_length: maksimal uzunluq + field_min_length: minimal uzunluq + field_name: Ad + field_new_password: Yeni parol + field_notes: Qeyd + field_onthefly: Tez bir zamanda istifadəçinin yaradılması + field_parent_title: Valideyn sÉ™hifÉ™ + field_parent: Valideyn layihÉ™ + field_parent_issue: Valideyn tapşırıq + field_password_confirmation: TÉ™sdiq + field_password: Parol + field_port: Port + field_possible_values: Mümkün olan tÉ™yinatlar + field_priority: Prioritet + field_project: LayihÉ™ + field_redirect_existing_links: Mövcud olan istinadları istiqamÉ™tlÉ™ndirmÉ™k + field_regexp: MüntÉ™zÉ™m ifadÉ™ + field_role: Rol + field_searchable: Axtarış üçün açıqdır + field_spent_on: Tarix + field_start_date: BaÅŸlanıb + field_start_page: BaÅŸlanğıc sÉ™hifÉ™ + field_status: Status + field_subject: Mövzu + field_subproject: AltlayihÉ™ + field_summary: Qısa tÉ™svir + field_text: MÉ™tn sahÉ™si + field_time_entries: SÉ™rf olunan zaman + field_time_zone: Saat qurÅŸağı + field_title: BaÅŸlıq + field_tracker: Treker + field_type: Tip + field_updated_on: YenilÉ™nib + field_url: URL + field_user: İstifadəçi + field_value: TÉ™yinat + field_version: Variant + field_watcher: NÉ™zarÉ™tçi + + general_csv_decimal_separator: ',' + general_csv_encoding: UTF-8 + general_csv_separator: ';' + general_first_day_of_week: '1' + general_lang_name: 'Azerbaijanian (Azeri)' + general_pdf_encoding: UTF-8 + general_text_no: 'xeyr' + general_text_No: 'Xeyr' + general_text_yes: 'bÉ™li' + general_text_Yes: 'BÉ™li' + + label_activity: GörülÉ™n iÅŸlÉ™r + label_add_another_file: Bir fayl daha É™lavÉ™ etmÉ™k + label_added_time_by: "ÆlavÉ™ etdi %{author} %{age} É™vvÉ™l" + label_added: É™lavÉ™ edilib + label_add_note: Qeydi É™lavÉ™ etmÉ™k + label_administration: İnzibatçılıq + label_age: YaÅŸ + label_ago: gün É™vvÉ™l + label_all_time: hÉ™r zaman + label_all_words: Bütün sözlÉ™r + label_all: hamı + label_and_its_subprojects: "%{value} vÉ™ bütün altlayihÉ™lÉ™r" + label_applied_status: TÉ™tbiq olunan status + label_ascending: Artmaya görÉ™ + label_assigned_to_me_issues: MÉ™nim tapşırıqlarım + label_associated_revisions: ÆlaqÉ™li redaksiyalar + label_attachment: Fayl + label_attachment_delete: Faylı silmÉ™k + label_attachment_new: Yeni fayl + label_attachment_plural: Fayllar + label_attribute: Atribut + label_attribute_plural: Atributlar + label_authentication: Autentifikasiya + label_auth_source: Autentifikasiyanın rejimi + label_auth_source_new: Autentifikasiyanın yeni rejimi + label_auth_source_plural: Autentifikasiyanın rejimlÉ™ri + label_blocked_by: bloklanır + label_blocks: bloklayır + label_board: Forum + label_board_new: Yeni forum + label_board_plural: Forumlar + label_boolean: MÉ™ntiqi + label_browse: Baxış + label_bulk_edit_selected_issues: SeçilÉ™n bütün tapşırıqları redaktÉ™ etmÉ™k + label_calendar: TÉ™qvim + label_calendar_filter: O cümlÉ™dÉ™n + label_calendar_no_assigned: MÉ™nim deyil + label_change_plural: DÉ™yiÅŸikliklÉ™r + label_change_properties: XassÉ™lÉ™ri dÉ™yiÅŸmÉ™k + label_change_status: Statusu dÉ™yiÅŸmÉ™k + label_change_view_all: Bütün dÉ™yiÅŸikliklÉ™rÉ™ baxmaq + label_changes_details: Bütün dÉ™yiÅŸikliklÉ™rÉ™ görÉ™ tÉ™fsilatlar + label_changeset_plural: DÉ™yiÅŸikliklÉ™r + label_chronological_order: Xronoloji ardıcıllıq ilÉ™ + label_closed_issues: BaÄŸlıdır + label_closed_issues_plural: baÄŸlıdır + label_closed_issues_plural2: baÄŸlıdır + label_closed_issues_plural5: baÄŸlıdır + label_comment: ÅŸÉ™rhlÉ™r + label_comment_add: ŞərhlÉ™ri qeyd etmÉ™k + label_comment_added: ÆlavÉ™ olunmuÅŸ ÅŸÉ™rhlÉ™r + label_comment_delete: Şərhi silmÉ™k + label_comment_plural: ŞərhlÉ™r + label_comment_plural2: ŞərhlÉ™r + label_comment_plural5: ÅŸÉ™rhlÉ™rin + label_commits_per_author: İstifadəçi üzÉ™rindÉ™ dÉ™yiÅŸikliklÉ™r + label_commits_per_month: Ay üzÉ™rindÉ™ dÉ™yiÅŸikliklÉ™r + label_confirmation: TÉ™sdiq + label_contains: tÉ™rkibi + label_copied: surÉ™ti köçürülüb + label_copy_workflow_from: görülÉ™n iÅŸlÉ™rin ardıcıllığının surÉ™tini köçürmÉ™k + label_current_status: Cari status + label_current_version: Cari variant + label_custom_field: Sazlanan sahÉ™ + label_custom_field_new: Yeni sazlanan sahÉ™ + label_custom_field_plural: Sazlanan sahÉ™lÉ™r + label_date_from: С + label_date_from_to: С %{start} по %{end} + label_date_range: vaxt intervalı + label_date_to: üzrÉ™ + label_date: Tarix + label_day_plural: gün + label_default: Susmaya görÉ™ + label_default_columns: Susmaya görÉ™ sütunlar + label_deleted: silinib + label_descending: Azalmaya görÉ™ + label_details: TÉ™fsilatlar + label_diff_inline: mÉ™tndÉ™ + label_diff_side_by_side: Yanaşı + label_disabled: söndürülüb + label_display: TÉ™svir + label_display_per_page: "SÉ™hifÉ™yÉ™: %{value}" + label_document: SÉ™nÉ™d + label_document_added: SÉ™nÉ™d É™lavÉ™ edilib + label_document_new: Yeni sÉ™nÉ™d + label_document_plural: SÉ™nÉ™dlÉ™r + label_downloads_abbr: YüklÉ™mÉ™lÉ™r + label_duplicated_by: çoxaldılır + label_duplicates: çoxaldır + label_end_to_end: sondan sona doÄŸru + label_end_to_start: sondan É™vvÉ™lÉ™ doÄŸru + label_enumeration_new: Yeni qiymÉ™t + label_enumerations: QiymÉ™tlÉ™rin siyahısı + label_environment: Mühit + label_equals: sayılır + label_example: NümunÉ™ + label_export_to: ixrac etmÉ™k + label_feed_plural: RSS + label_feeds_access_key_created_on: "RSS-É™ giriÅŸ açarı %{value} É™vvÉ™l yaradılıb" + label_f_hour: "%{value} saat" + label_f_hour_plural: "%{value} saat" + label_file_added: Fayl É™lavÉ™ edilib + label_file_plural: Fayllar + label_filter_add: Filtr É™lavÉ™ etmÉ™k + label_filter_plural: FiltrlÉ™r + label_float: HÉ™qiqi É™dÉ™d + label_follows: ÆvvÉ™lki + label_gantt: Qant diaqramması + label_general: Ümumi + label_generate_key: Açarı generasiya etmÉ™k + label_greater_or_equal: ">=" + label_help: KömÉ™k + label_history: Tarixçə + label_home: Ana sÉ™hifÉ™ + label_incoming_emails: MÉ™lumatların qÉ™bulu + label_index_by_date: SÉ™hifÉ™lÉ™rin tarixçəsi + label_index_by_title: BaÅŸlıq + label_information_plural: İnformasiya + label_information: İnformasiya + label_in_less_than: az + label_in_more_than: çox + label_integer: Tam + label_internal: Daxili + label_in: da (dÉ™) + label_issue: Tapşırıq + label_issue_added: Tapşırıq É™lavÉ™ edilib + label_issue_category_new: Yeni kateqoriya + label_issue_category_plural: Tapşırığın kateqoriyası + label_issue_category: Tapşırığın kateqoriyası + label_issue_new: Yeni tapşırıq + label_issue_plural: Tapşırıqlar + label_issues_by: "%{value} üzrÉ™ çeÅŸidlÉ™mÉ™k" + label_issue_status_new: Yeni status + label_issue_status_plural: Tapşırıqların statusu + label_issue_status: Tapşırığın statusu + label_issue_tracking: Tapşırıqlar + label_issue_updated: Tapşırıq yenilÉ™nib + label_issue_view_all: Bütün tapşırıqlara baxmaq + label_issue_watchers: NÉ™zarÉ™tçilÉ™r + label_jump_to_a_project: ... layihÉ™yÉ™ keçid + label_language_based: Dilin É™sasında + label_last_changes: "%{count} az dÉ™yiÅŸiklik" + label_last_login: Sonuncu qoÅŸulma + label_last_month: sonuncu ay + label_last_n_days: "son %{count} gün" + label_last_week: sonuncu hÉ™ftÉ™ + label_latest_revision: Sonuncu redaksiya + label_latest_revision_plural: Sonuncu redaksiyalar + label_ldap_authentication: LDAP vasitÉ™silÉ™ avtorizasiya + label_less_or_equal: <= + label_less_than_ago: gündÉ™n az + label_list: Siyahı + label_loading: YüklÉ™mÉ™... + label_logged_as: Daxil olmusunuz + label_login: Daxil olmaq + label_login_with_open_id_option: vÉ™ ya OpenID vasitÉ™silÉ™ daxil olmaq + label_logout: Çıxış + label_max_size: Maksimal ölçü + label_member_new: Yeni iÅŸtirakçı + label_member: İştirakçı + label_member_plural: İştirakçılar + label_message_last: Sonuncu mÉ™lumat + label_message_new: Yeni mÉ™lumat + label_message_plural: MÉ™lumatlar + label_message_posted: MÉ™lumat É™lavÉ™ olunub + label_me: mÉ™nÉ™ + label_min_max_length: Minimal - maksimal uzunluq + label_modified: dÉ™yiÅŸilib + label_module_plural: Modullar + label_months_from: ay + label_month: Ay + label_more_than_ago: gündÉ™n É™vvÉ™l + label_more: Çox + label_my_account: MÉ™nim hesabım + label_my_page: MÉ™nim sÉ™hifÉ™m + label_my_page_block: MÉ™nim sÉ™hifÉ™min bloku + label_my_projects: MÉ™nim layihÉ™lÉ™rim + label_new: Yeni + label_new_statuses_allowed: İcazÉ™ verilÉ™n yeni statuslar + label_news_added: XÉ™bÉ™r É™lavÉ™ edilib + label_news_latest: Son xÉ™bÉ™rlÉ™r + label_news_new: XÉ™bÉ™r É™lavÉ™ etmÉ™k + label_news_plural: XÉ™bÉ™rlÉ™r + label_news_view_all: Bütün xÉ™bÉ™rlÉ™rÉ™ baxmaq + label_news: XÉ™bÉ™rlÉ™r + label_next: NövbÉ™ti + label_nobody: heç kim + label_no_change_option: (DÉ™yiÅŸiklik yoxdur) + label_no_data: TÉ™svir üçün verilÉ™nlÉ™r yoxdur + label_none: yoxdur + label_not_contains: mövcud deyil + label_not_equals: sayılmır + label_open_issues: açıqdır + label_open_issues_plural: açıqdır + label_open_issues_plural2: açıqdır + label_open_issues_plural5: açıqdır + label_optional_description: TÉ™svir (vacib deyil) + label_options: Opsiyalar + label_overall_activity: GörülÉ™n iÅŸlÉ™rin toplu hesabatı + label_overview: Baxış + label_password_lost: Parolun bÉ™rpası + label_permissions_report: GiriÅŸ hüquqları üzrÉ™ hesabat + label_permissions: GiriÅŸ hüquqları + label_per_page: SÉ™hifÉ™yÉ™ + label_personalize_page: bu sÉ™hifÉ™ni fÉ™rdiləşdirmÉ™k + label_planning: PlanlaÅŸdırma + label_please_login: XahiÅŸ edirik, daxil olun. + label_plugins: Modullar + label_precedes: növbÉ™ti + label_preferences: Üstünlük + label_preview: İlkin baxış + label_previous: ÆvvÉ™lki + label_profile: Profil + label_project: LayihÉ™ + label_project_all: Bütün layihÉ™lÉ™r + label_project_copy_notifications: LayihÉ™nin surÉ™tinin çıxarılması zamanı elektron poçt ilÉ™ bildiriÅŸ göndÉ™rmÉ™k + label_project_latest: Son layihÉ™lÉ™r + label_project_new: Yeni layihÉ™ + label_project_plural: LayihÉ™lÉ™r + label_project_plural2: layihÉ™ni + label_project_plural5: layihÉ™lÉ™ri + label_public_projects: Ümumi layihÉ™lÉ™r + label_query: Yadda saxlanılmış sorÄŸu + label_query_new: Yeni sorÄŸu + label_query_plural: Yadda saxlanılmış sorÄŸular + label_read: Oxu... + label_register: Qeydiyyat + label_registered_on: Qeydiyyatdan keçib + label_registration_activation_by_email: e-poçt üzrÉ™ hesabımın aktivləşdirilmÉ™si + label_registration_automatic_activation: uçot qeydlÉ™rinin avtomatik aktivləşdirilmÉ™si + label_registration_manual_activation: uçot qeydlÉ™rini É™l ilÉ™ aktivləşdirmÉ™k + label_related_issues: ÆlaqÉ™li tapşırıqlar + label_relates_to: É™laqÉ™lidir + label_relation_delete: ÆlaqÉ™ni silmÉ™k + label_relation_new: Yeni münasibÉ™t + label_renamed: adını dÉ™yiÅŸmÉ™k + label_reply_plural: Cavablar + label_report: Hesabat + label_report_plural: Hesabatlar + label_reported_issues: Yaradılan tapşırıqlar + label_repository: Saxlayıcı + label_repository_plural: Saxlayıcı + label_result_plural: NÉ™ticÉ™lÉ™r + label_reverse_chronological_order: Æks ardıcıllıqda + label_revision: Redaksiya + label_revision_plural: Redaksiyalar + label_roadmap: Operativ plan + label_roadmap_due_in: "%{value} müddÉ™tindÉ™" + label_roadmap_no_issues: bu versiya üçün tapşırıq yoxdur + label_roadmap_overdue: "gecikmÉ™ %{value}" + label_role: Rol + label_role_and_permissions: Rollar vÉ™ giriÅŸ hüquqları + label_role_new: Yeni rol + label_role_plural: Rollar + label_scm: Saxlayıcının tipi + label_search: Axtarış + label_search_titles_only: Ancaq adlarda axtarmaq + label_send_information: İstifadəçiyÉ™ uçot qeydlÉ™ri üzrÉ™ informasiyanı göndÉ™rmÉ™k + label_send_test_email: Yoxlama üçün email göndÉ™rmÉ™k + label_settings: Sazlamalar + label_show_completed_versions: BitmiÅŸ variantları göstÉ™rmÉ™k + label_sort: ÇeÅŸidlÉ™mÉ™k + label_sort_by: "%{value} üzrÉ™ çeÅŸidlÉ™mÉ™k" + label_sort_higher: Yuxarı + label_sort_highest: ÆvvÉ™lÉ™ qayıt + label_sort_lower: AÅŸağı + label_sort_lowest: Sona qayıt + label_spent_time: SÉ™rf olunan vaxt + label_start_to_end: É™vvÉ™ldÉ™n axıra doÄŸru + label_start_to_start: É™vvÉ™ldÉ™n É™vvÉ™lÉ™ doÄŸru + label_statistics: Statistika + label_stay_logged_in: SistemdÉ™ qalmaq + label_string: MÉ™tn + label_subproject_plural: AltlayihÉ™lÉ™r + label_subtask_plural: Alt tapşırıqlar + label_text: Uzun mÉ™tn + label_theme: Mövzu + label_this_month: bu ay + label_this_week: bu hÉ™ftÉ™ + label_this_year: bu il + label_time_tracking: Vaxtın uçotu + label_timelog_today: Bu günÉ™ sÉ™rf olunan vaxt + label_today: bu gün + label_topic_plural: Mövzular + label_total: CÉ™mi + label_tracker: Treker + label_tracker_new: Yeni treker + label_tracker_plural: TrekerlÉ™r + label_updated_time: "%{value} É™vvÉ™l yenilÉ™nib" + label_updated_time_by: "%{author} %{age} É™vvÉ™l yenilÉ™nib" + label_used_by: İstifadÉ™ olunur + label_user: İstifasdəçi + label_user_activity: "İstifadəçinin gördüyü iÅŸlÉ™r %{value}" + label_user_mail_no_self_notified: "TÉ™rÉ™fimdÉ™n edilÉ™n dÉ™yiÅŸikliklÉ™r haqqında mÉ™ni xÉ™bÉ™rdar etmÉ™mÉ™k" + label_user_mail_option_all: "MÉ™nim layihÉ™lÉ™rimdÉ™ki bütün hadisÉ™lÉ™r haqqında" + label_user_mail_option_selected: "Yalnız seçilÉ™n layihÉ™dÉ™ki bütün hadisÉ™lÉ™r haqqında..." + label_user_mail_option_only_owner: Yalnız sahibi olduÄŸum obyektlÉ™r üçün + label_user_mail_option_only_my_events: Yalnız izlÉ™diyim vÉ™ ya iÅŸtirak etdiyim obyektlÉ™r üçün + label_user_mail_option_only_assigned: Yalnız mÉ™nÉ™ tÉ™yin edilÉ™n obyektlÉ™r üçün + label_user_new: Yeni istifadəçi + label_user_plural: İstifadəçilÉ™r + label_version: Variant + label_version_new: Yeni variant + label_version_plural: Variantlar + label_view_diff: FÉ™rqlÉ™rÉ™ baxmaq + label_view_revisions: Redaksiyalara baxmaq + label_watched_issues: Tapşırığın izlÉ™nilmÉ™si + label_week: HÉ™ftÉ™ + label_wiki: Wiki + label_wiki_edit: Wiki-nin redaktÉ™si + label_wiki_edit_plural: Wiki + label_wiki_page: Wiki sÉ™hifÉ™si + label_wiki_page_plural: Wiki sÉ™hifÉ™lÉ™ri + label_workflow: GörülÉ™n iÅŸlÉ™rin ardıcıllığı + label_x_closed_issues_abbr: + zero: "0 baÄŸlıdır" + one: "1 baÄŸlanıb" + few: "%{count} baÄŸlıdır" + many: "%{count} baÄŸlıdır" + other: "%{count} baÄŸlıdır" + label_x_comments: + zero: "ÅŸÉ™rh yoxdur" + one: "1 ÅŸÉ™rh" + few: "%{count} ÅŸÉ™rhlÉ™r" + many: "%{count} ÅŸÉ™rh" + other: "%{count} ÅŸÉ™rh" + label_x_open_issues_abbr: + zero: "0 açıqdır" + one: "1 açıq" + few: "%{count} açıqdır" + many: "%{count} açıqdır" + other: "%{count} açıqdır" + label_x_open_issues_abbr_on_total: + zero: "0 açıqdır / %{total}" + one: "1 açıqdır / %{total}" + few: "%{count} açıqdır / %{total}" + many: "%{count} açıqdır / %{total}" + other: "%{count} açıqdır / %{total}" + label_x_projects: + zero: "layihÉ™lÉ™r yoxdur" + one: "1 layihÉ™" + few: "%{count} layihÉ™" + many: "%{count} layihÉ™" + other: "%{count} layihÉ™" + label_year: İl + label_yesterday: dünÉ™n + + mail_body_account_activation_request: "Yeni istifadəçi qeydiyyatdan keçib (%{value}). Uçot qeydi Sizin tÉ™sdiqinizi gözlÉ™yir:" + mail_body_account_information: Sizin uçot qeydiniz haqqında informasiya + mail_body_account_information_external: "Siz özünüzün %{value} uçot qeydinizi giriÅŸ üçün istifadÉ™ edÉ™ bilÉ™rsiniz." + mail_body_lost_password: 'Parolun dÉ™yiÅŸdirilmÉ™si üçün aÅŸağıdakı linkÉ™ keçin:' + mail_body_register: 'Uçot qeydinin aktivləşdirilmÉ™si üçün aÅŸağıdakı linkÉ™ keçin:' + mail_body_reminder: "növbÉ™ti %{days} gün üçün SizÉ™ tÉ™yin olunan %{count}:" + mail_subject_account_activation_request: "SistemdÉ™ istifadəçinin aktivləşdirilmÉ™si üçün sorÄŸu %{value}" + mail_subject_lost_password: "Sizin %{value} parolunuz" + mail_subject_register: "Uçot qeydinin aktivləşdirilmÉ™si %{value}" + mail_subject_reminder: "yaxın %{days} gün üçün SizÉ™ tÉ™yin olunan %{count}" + + notice_account_activated: Sizin uçot qeydiniz aktivləşdirilib. SistemÉ™ daxil ola bilÉ™rsiniz. + notice_account_invalid_creditentials: İstifadəçi adı vÉ™ ya parolu düzgün deyildir + notice_account_lost_email_sent: SizÉ™ yeni parolun seçimi ilÉ™ baÄŸlı tÉ™limatı É™ks etdirÉ™n mÉ™ktub göndÉ™rilmiÅŸdir. + notice_account_password_updated: Parol müvÉ™ffÉ™qiyyÉ™tlÉ™ yenilÉ™ndi. + notice_account_pending: "Sizin uçot qeydiniz yaradıldı vÉ™ inzibatçının tÉ™sdiqini gözlÉ™yir." + notice_account_register_done: Uçot qeydi müvÉ™ffÉ™qiyyÉ™tlÉ™ yaradıldı. Sizin uçot qeydinizin aktivləşdirilmÉ™si üçün elektron poçtunuza göndÉ™rilÉ™n linkÉ™ keçin. + notice_account_unknown_email: NamÉ™lum istifadəçi. + notice_account_updated: Uçot qeydi müvÉ™ffÉ™qiyyÉ™tlÉ™ yenilÉ™ndi. + notice_account_wrong_password: Parol düzgün deyildir + notice_can_t_change_password: Bu uçot qeydi üçün xarici autentifikasiya mÉ™nbÉ™yi istifadÉ™ olunur. Parolu dÉ™yiÅŸmÉ™k mümkün deyildir. + notice_default_data_loaded: Susmaya görÉ™ konfiqurasiya yüklÉ™nilmiÅŸdir. + notice_email_error: "MÉ™ktubun göndÉ™rilmÉ™si zamanı sÉ™hv baÅŸ vermiÅŸdi (%{value})" + notice_email_sent: "MÉ™ktub göndÉ™rilib %{value}" + notice_failed_to_save_issues: "SeçilÉ™n %{total} içərisindÉ™n %{count} bÉ™ndlÉ™ri saxlamaq mümkün olmadı: %{ids}." + notice_failed_to_save_members: "İştirakçını (ları) yadda saxlamaq mümkün olmadı: %{errors}." + notice_feeds_access_key_reseted: Sizin RSS giriÅŸ açarınız sıfırlanmışdır. + notice_file_not_found: Daxil olmaÄŸa çalışdığınız sÉ™hifÉ™ mövcud deyildir vÉ™ ya silinib. + notice_locking_conflict: İnformasiya digÉ™r istifadəçi tÉ™rÉ™findÉ™n yenilÉ™nib. + notice_no_issue_selected: "Heç bir tapşırıq seçilmÉ™yib! XahiÅŸ edirik, redaktÉ™ etmÉ™k istÉ™diyiniz tapşırığı qeyd edin." + notice_not_authorized: Sizin bu sÉ™hifÉ™yÉ™ daxil olmaq hüququnuz yoxdur. + notice_successful_connection: QoÅŸulma müvÉ™ffÉ™qiyyÉ™tlÉ™ yerinÉ™ yetirilib. + notice_successful_create: Yaratma müvÉ™ffÉ™qiyyÉ™tlÉ™ yerinÉ™ yetirildi. + notice_successful_delete: SilinmÉ™ müvÉ™ffÉ™qiyyÉ™tlÉ™ yerinÉ™ yetirildi. + notice_successful_update: YenilÉ™mÉ™ müvÉ™ffÉ™qiyyÉ™tlÉ™ yerinÉ™ yetirildi. + notice_unable_delete_version: Variantı silmÉ™k mümkün olmadı. + + permission_add_issues: Tapşırıqların É™lavÉ™ edilmÉ™si + permission_add_issue_notes: QeydlÉ™rin É™lavÉ™ edilmÉ™si + permission_add_issue_watchers: NÉ™zarÉ™tçilÉ™rin É™lavÉ™ edilmÉ™si + permission_add_messages: MÉ™lumatların göndÉ™rilmÉ™si + permission_browse_repository: Saxlayıcıya baxış + permission_comment_news: XÉ™bÉ™rlÉ™rÉ™ ÅŸÉ™rh + permission_commit_access: Saxlayıcıda faylların dÉ™yiÅŸdirilmÉ™si + permission_delete_issues: Tapşırıqların silinmÉ™si + permission_delete_messages: MÉ™lumatların silinmÉ™si + permission_delete_own_messages: Şəxsi mÉ™lumatların silinmÉ™si + permission_delete_wiki_pages: Wiki-sÉ™hifÉ™lÉ™rin silinmÉ™si + permission_delete_wiki_pages_attachments: BÉ™rkidilÉ™n faylların silinmÉ™si + permission_edit_issue_notes: QeydlÉ™rin redaktÉ™ edilmÉ™si + permission_edit_issues: Tapşırıqların redaktÉ™ edilmÉ™si + permission_edit_messages: MÉ™lumatların redaktÉ™ edilmÉ™si + permission_edit_own_issue_notes: Şəxsi qeydlÉ™rin redaktÉ™ edilmÉ™si + permission_edit_own_messages: Şəxsi mÉ™lumatların redaktÉ™ edilmÉ™si + permission_edit_own_time_entries: Şəxsi vaxt uçotunun redaktÉ™ edilmÉ™si + permission_edit_project: LayihÉ™lÉ™rin redaktÉ™ edilmÉ™si + permission_edit_time_entries: Vaxt uçotunun redaktÉ™ edilmÉ™si + permission_edit_wiki_pages: Wiki-sÉ™hifÉ™nin redaktÉ™ edilmÉ™si + permission_export_wiki_pages: Wiki-sÉ™hifÉ™nin ixracı + permission_log_time: SÉ™rf olunan vaxtın uçotu + permission_view_changesets: Saxlayıcı dÉ™yiÅŸikliklÉ™rinÉ™ baxış + permission_view_time_entries: SÉ™rf olunan vaxta baxış + permission_manage_project_activities: LayihÉ™ üçün hÉ™rÉ™kÉ™t tiplÉ™rinin idarÉ™ edilmÉ™si + permission_manage_boards: Forumların idarÉ™ edilmÉ™si + permission_manage_categories: Tapşırıq kateqoriyalarının idarÉ™ edilmÉ™si + permission_manage_files: Faylların idarÉ™ edilmÉ™si + permission_manage_issue_relations: Tapşırıq baÄŸlantılarının idarÉ™ edilmÉ™si + permission_manage_members: İştirakçıların idarÉ™ edilmÉ™si + permission_manage_news: XÉ™bÉ™rlÉ™rin idarÉ™ edilmÉ™si + permission_manage_public_queries: Ümumi sorÄŸuların idarÉ™ edilmÉ™si + permission_manage_repository: Saxlayıcının idarÉ™ edilmÉ™si + permission_manage_subtasks: Alt tapşırıqların idarÉ™ edilmÉ™si + permission_manage_versions: Variantların idarÉ™ edilmÉ™si + permission_manage_wiki: Wiki-nin idarÉ™ edilmÉ™si + permission_move_issues: Tapşırıqların köçürülmÉ™si + permission_protect_wiki_pages: Wiki-sÉ™hifÉ™lÉ™rin bloklanması + permission_rename_wiki_pages: Wiki-sÉ™hifÉ™lÉ™rin adının dÉ™yiÅŸdirilmÉ™si + permission_save_queries: SorÄŸuların yadda saxlanılması + permission_select_project_modules: LayihÉ™ modulunun seçimi + permission_view_calendar: TÉ™qvimÉ™ baxış + permission_view_documents: SÉ™nÉ™dlÉ™rÉ™ baxış + permission_view_files: Fayllara baxış + permission_view_gantt: Qant diaqramına baxış + permission_view_issue_watchers: NÉ™zarÉ™tçilÉ™rin siyahılarına baxış + permission_view_messages: MÉ™lumatlara baxış + permission_view_wiki_edits: Wiki tarixçəsinÉ™ baxış + permission_view_wiki_pages: Wiki-yÉ™ baxış + + project_module_boards: Forumlar + project_module_documents: SÉ™nÉ™dlÉ™r + project_module_files: Fayllar + project_module_issue_tracking: Tapşırıqlar + project_module_news: XÉ™bÉ™rlÉ™r + project_module_repository: Saxlayıcı + project_module_time_tracking: Vaxtın uçotu + project_module_wiki: Wiki + project_module_gantt: Qant diaqramı + project_module_calendar: TÉ™qvim + + setting_activity_days_default: GörülÉ™n iÅŸlÉ™rdÉ™ É™ks olunan günlÉ™rin sayı + setting_app_subtitle: ÆlavÉ™nin sÉ™rlövhÉ™si + setting_app_title: ÆlavÉ™nin adı + setting_attachment_max_size: YerləşdirmÉ™nin maksimal ölçüsü + setting_autofetch_changesets: Saxlayıcının dÉ™yiÅŸikliklÉ™rini avtomatik izlÉ™mÉ™k + setting_autologin: Avtomatik giriÅŸ + setting_bcc_recipients: Gizli surÉ™tlÉ™ri istifadÉ™ etmÉ™k (BCC) + setting_cache_formatted_text: FormatlaÅŸdırılmış mÉ™tnin heÅŸlÉ™nmÉ™si + setting_commit_fix_keywords: Açar sözlÉ™rin tÉ™yini + setting_commit_ref_keywords: Axtarış üçün açar sözlÉ™r + setting_cross_project_issue_relations: LayihÉ™lÉ™r üzrÉ™ tapşırıqların kÉ™siÅŸmÉ™sinÉ™ icazÉ™ vermÉ™k + setting_date_format: Tarixin formatı + setting_default_language: Susmaya görÉ™ dil + setting_default_notification_option: Susmaya görÉ™ xÉ™bÉ™rdarlıq üsulu + setting_default_projects_public: Yeni layihÉ™lÉ™r ümumaçıq hesab edilir + setting_diff_max_lines_displayed: diff üçün sÉ™tirlÉ™rin maksimal sayı + setting_display_subprojects_issues: Susmaya görÉ™ altlayihÉ™lÉ™rin É™ks olunması + setting_emails_footer: MÉ™ktubun sÉ™tiraltı qeydlÉ™ri + setting_enabled_scm: Daxil edilÉ™n SCM + setting_feeds_limit: RSS axını üçün baÅŸlıqların sayının mÉ™hdudlaÅŸdırılması + setting_file_max_size_displayed: Æks olunma üçün mÉ™tn faylının maksimal ölçüsü + setting_gravatar_enabled: İstifadəçi avatarını Gravatar-dan istifadÉ™ etmÉ™k + setting_host_name: Kompyuterin adı + setting_issue_list_default_columns: Susmaya görÉ™ tapşırıqların siyahısında É™ks oluna sütunlar + setting_issues_export_limit: İxrac olunan tapşırıqlar üzrÉ™ mÉ™hdudiyyÉ™tlÉ™r + setting_login_required: Autentifikasiya vacibdir + setting_mail_from: Çıxan e-poçt ünvanı + setting_mail_handler_api_enabled: Daxil olan mÉ™lumatlar üçün veb-servisi qoÅŸmaq + setting_mail_handler_api_key: API açar + setting_openid: GiriÅŸ vÉ™ qeydiyyat üçün OpenID izacÉ™ vermÉ™k + setting_per_page_options: SÉ™hifÉ™ üçün qeydlÉ™rin sayı + setting_plain_text_mail: Yalnız sadÉ™ mÉ™tn (HTML olmadan) + setting_protocol: Protokol + setting_repository_log_display_limit: DÉ™yiÅŸikliklÉ™r jurnalında É™ks olunan redaksiyaların maksimal sayı + setting_self_registration: Özünüqeydiyyat + setting_sequential_project_identifiers: LayihÉ™lÉ™rin ardıcıl identifikatorlarını generasiya etmÉ™k + setting_sys_api_enabled: Saxlayıcının idarÉ™ edilmÉ™si üçün veb-servisi qoÅŸmaq + setting_text_formatting: MÉ™tnin formatlaÅŸdırılması + setting_time_format: Vaxtın formatı + setting_user_format: Adın É™ks olunma formatı + setting_welcome_text: Salamlama mÉ™tni + setting_wiki_compression: Wiki tarixçəsinin sıxlaÅŸdırılması + + status_active: aktivdir + status_locked: bloklanıb + status_registered: qeydiyyatdan keçib + + text_are_you_sure: Siz É™minsinizmi? + text_assign_time_entries_to_project: Qeydiyyata alınmış vaxtı layihÉ™yÉ™ bÉ™rkitmÉ™k + text_caracters_maximum: "Maksimum %{count} simvol." + text_caracters_minimum: "%{count} simvoldan az olmamalıdır." + text_comma_separated: Bir neçə qiymÉ™t mümkündür (vergül vasitÉ™silÉ™). + text_custom_field_possible_values_info: 'HÉ™r sÉ™tirÉ™ bir qiymÉ™t' + text_default_administrator_account_changed: İnzibatçının uçot qeydi susmaya görÉ™ dÉ™yiÅŸmiÅŸdir + text_destroy_time_entries_question: "Bu tapşırıq üçün sÉ™rf olunan vaxta görÉ™ %{hours} saat qeydiyyata alınıb. Siz nÉ™ etmÉ™k istÉ™yirsiniz?" + text_destroy_time_entries: Qeydiyyata alınmış vaxtı silmÉ™k + text_diff_truncated: '... Bu diff mÉ™hduddur, çünki É™ks olunan maksimal ölçünü keçir.' + text_email_delivery_not_configured: "Poçt serveri ilÉ™ iÅŸin parametrlÉ™ri sazlanmayıb vÉ™ e-poçt ilÉ™ bildiriÅŸ funksiyası aktiv deyildir.\nSizin SMTP-server üçün parametrlÉ™ri config/configuration.yml faylından sazlaya bilÉ™rsiniz. DÉ™yiÅŸikliklÉ™rin tÉ™tbiq edilmÉ™si üçün É™lavÉ™ni yenidÉ™n baÅŸladın." + text_enumeration_category_reassign_to: 'Onlara aÅŸağıdakı qiymÉ™tlÉ™ri tÉ™yin etmÉ™k:' + text_enumeration_destroy_question: "%{count} obyekt bu qiymÉ™tlÉ™ baÄŸlıdır." + text_file_repository_writable: QeydÉ™ giriÅŸ imkanı olan saxlayıcı + text_issue_added: "Yeni tapşırıq yaradılıb %{id} (%{author})." + text_issue_category_destroy_assignments: Kateqoriyanın tÉ™yinatını silmÉ™k + text_issue_category_destroy_question: "Bir neçə tapşırıq (%{count}) bu kateqoriya üçün tÉ™yin edilib. Siz nÉ™ etmÉ™k istÉ™yirsiniz?" + text_issue_category_reassign_to: Bu kateqoriya üçün tapşırığı yenidÉ™n tÉ™yin etmÉ™k + text_issues_destroy_confirmation: 'SeçilÉ™n tapşırıqları silmÉ™k istÉ™diyinizÉ™ É™minsinizmi?' + text_issues_ref_in_commit_messages: MÉ™lumatın mÉ™tnindÉ™n çıxış edÉ™rÉ™k tapşırıqların statuslarının tutuÅŸdurulması vÉ™ dÉ™yiÅŸdirilmÉ™si + text_issue_updated: "Tapşırıq %{id} yenilÉ™nib (%{author})." + text_journal_changed: "Parametr %{label} %{old} - %{new} dÉ™yiÅŸib" + text_journal_deleted: "Parametrin %{old} qiymÉ™ti %{label} silinib" + text_journal_set_to: "%{label} parametri %{value} dÉ™yiÅŸib" + text_length_between: "%{min} vÉ™ %{max} simvollar arasındakı uzunluq." + text_load_default_configuration: Susmaya görÉ™ konfiqurasiyanı yüklÉ™mÉ™k + text_min_max_length_info: 0 mÉ™hdudiyyÉ™tlÉ™rin olmadığını bildirir + text_no_configuration_data: "Rollar, trekerlÉ™r, tapşırıqların statusları vÉ™ operativ plan konfiqurasiya olunmayıblar.\nSusmaya görÉ™ konfiqurasiyanın yüklÉ™nmÉ™si tÉ™kidlÉ™ xahiÅŸ olunur. Siz onu sonradan dÉ™yiÅŸÉ™ bilÉ™rsiniz." + text_plugin_assets_writable: Modullar kataloqu qeyd üçün açıqdır + text_project_destroy_confirmation: Siz bu layihÉ™ vÉ™ ona aid olan bütün informasiyanı silmÉ™k istÉ™diyinizÉ™ É™minsinizmi? + text_reassign_time_entries: 'Qeydiyyata alınmış vaxtı aÅŸağıdakı tapşırığa keçir:' + text_regexp_info: "mÉ™sÉ™lÉ™n: ^[A-Z0-9]+$" + text_repository_usernames_mapping: "Saxlayıcının jurnalında tapılan adlarla baÄŸlı olan Redmine istifadəçisini seçin vÉ™ ya yenilÉ™yin.\nEyni ad vÉ™ e-poçta sahib olan istifadəçilÉ™r Redmine vÉ™ saxlayıcıda avtomatik É™laqÉ™lÉ™ndirilir." + text_rmagick_available: RMagick istifadÉ™si mümkündür (opsional olaraq) + text_select_mail_notifications: Elektron poçta bildiriÅŸlÉ™rin göndÉ™rilmÉ™si seçim edÉ™cÉ™yiniz hÉ™rÉ™kÉ™tlÉ™rdÉ™n asılıdır. + text_select_project_modules: 'LayihÉ™dÉ™ istifadÉ™ olunacaq modulları seçin:' + text_status_changed_by_changeset: "%{value} redaksiyada reallaÅŸdırılıb." + text_subprojects_destroy_warning: "AltlayihÉ™lÉ™r: %{value} hÉ™mçinin silinÉ™cÉ™k." + text_tip_issue_begin_day: tapşırığın baÅŸlanğıc tarixi + text_tip_issue_begin_end_day: elÉ™ hÉ™min gün tapşırığın baÅŸlanğıc vÉ™ bitmÉ™ tarixi + text_tip_issue_end_day: tapşırığın baÅŸa çatma tarixi + text_tracker_no_workflow: Bu treker üçün hÉ™rÉ™kÉ™tlÉ™rin ardıcıllığı müəyyÉ™n edimÉ™yib + text_unallowed_characters: QadaÄŸan edilmiÅŸ simvollar + text_user_mail_option: "SeçilmÉ™yÉ™n layihÉ™lÉ™r üçün Siz yalnız baxdığınız vÉ™ ya iÅŸtirak etdiyiniz layihÉ™lÉ™r barÉ™dÉ™ bildiriÅŸ alacaqsınız mÉ™sÉ™lÉ™n, müəllifi olduÄŸunuz layihÉ™lÉ™r vÉ™ ya o layihÉ™lÉ™r ki, SizÉ™ tÉ™yin edilib)." + text_user_wrote: "%{value} yazıb:" + text_wiki_destroy_confirmation: Siz bu Wiki vÉ™ onun tÉ™rkibindÉ™kilÉ™ri silmÉ™k istÉ™diyinizÉ™ É™minsinizmi? + text_workflow_edit: VÉ™ziyyÉ™tlÉ™rin ardıcıllığını redaktÉ™ etmÉ™k üçün rol vÉ™ trekeri seçin + + warning_attachments_not_saved: "faylın (ların) %{count} yadda saxlamaq mümkün deyildir." + text_wiki_page_destroy_question: Bu sÉ™hifÉ™ %{descendants} yaxın vÉ™ çox yaxın sÉ™hifÉ™lÉ™rÉ™ malikdir. Siz nÉ™ etmÉ™k istÉ™yirsiniz? + text_wiki_page_reassign_children: Cari sÉ™hifÉ™ üçün yaxın sÉ™hifÉ™lÉ™ri yenidÉ™n tÉ™yin etmÉ™k + text_wiki_page_nullify_children: Yaxın sÉ™hifÉ™lÉ™ri baÅŸ sÉ™hifÉ™lÉ™r etmÉ™k + text_wiki_page_destroy_children: Yaxın vÉ™ çox yaxın sÉ™hifÉ™lÉ™ri silmÉ™k + setting_password_min_length: Parolun minimal uzunluÄŸu + field_group_by: NÉ™ticÉ™lÉ™ri qruplaÅŸdırmaq + mail_subject_wiki_content_updated: "Wiki-sÉ™hifÉ™ '%{id}' yenilÉ™nmiÅŸdir" + label_wiki_content_added: Wiki-sÉ™hifÉ™ É™lavÉ™ olunub + mail_subject_wiki_content_added: "Wiki-sÉ™hifÉ™ '%{id}' É™lavÉ™ edilib" + mail_body_wiki_content_added: "%{author} Wiki-sÉ™hifÉ™ni '%{id}' É™lavÉ™ edib." + label_wiki_content_updated: Wiki-sÉ™hifÉ™ yenilÉ™nib + mail_body_wiki_content_updated: "%{author} Wiki-sÉ™hifÉ™ni '%{id}' yenilÉ™yib." + permission_add_project: LayihÉ™nin yaradılması + setting_new_project_user_role_id: LayihÉ™ni yaradan istifadəçiyÉ™ tÉ™yin olunan rol + label_view_all_revisions: Bütün yoxlamaları göstÉ™rmÉ™k + label_tag: NiÅŸan + label_branch: ŞöbÉ™ + error_no_tracker_in_project: Bu layihÉ™ ilÉ™ heç bir treker assosiasiya olunmayıb. LayihÉ™nin sazlamalarını yoxlayın. + error_no_default_issue_status: Susmaya görÉ™ tapşırıqların statusu müəyyÉ™n edilmÉ™yib. Sazlamaları yoxlayın (bax. "İnzibatçılıq -> Tapşırıqların statusu"). + label_group_plural: Qruplar + label_group: Qrup + label_group_new: Yeni qrup + label_time_entry_plural: SÉ™rf olunan vaxt + text_journal_added: "%{label} %{value} É™lavÉ™ edilib" + field_active: Aktiv + enumeration_system_activity: Sistemli + permission_delete_issue_watchers: NÉ™zarÉ™tçilÉ™rin silinmÉ™si + version_status_closed: BaÄŸlanıb + version_status_locked: bloklanıb + version_status_open: açıqdır + error_can_not_reopen_issue_on_closed_version: BaÄŸlı varianta tÉ™yin edilÉ™n tapşırıq yenidÉ™n açıq ola bilmÉ™z + label_user_anonymous: Anonim + button_move_and_follow: YerləşdirmÉ™k vÉ™ keçid + setting_default_projects_modules: Yeni layihÉ™lÉ™r üçün susmaya görÉ™ daxil edilÉ™n modullar + setting_gravatar_default: Susmaya görÉ™ Gravatar tÉ™sviri + field_sharing: BirgÉ™ istifadÉ™ + label_version_sharing_hierarchy: LayihÉ™lÉ™rin iyerarxiyasına görÉ™ + label_version_sharing_system: bütün layihÉ™lÉ™r ilÉ™ + label_version_sharing_descendants: Alt layihÉ™lÉ™r ilÉ™ + label_version_sharing_tree: LayihÉ™lÉ™rin iyerarxiyası ilÉ™ + label_version_sharing_none: BirgÉ™ istifadÉ™ olmadan + error_can_not_archive_project: Bu layihÉ™ arxivləşdirilÉ™ bilmÉ™z + button_duplicate: TÉ™krarlamaq + button_copy_and_follow: SurÉ™tini çıxarmaq vÉ™ davam etmÉ™k + label_copy_source: MÉ™nbÉ™ + setting_issue_done_ratio: SahÉ™nin kömÉ™yi ilÉ™ tapşırığın hazırlığını nÉ™zÉ™rÉ™ almaq + setting_issue_done_ratio_issue_status: Tapşırığın statusu + error_issue_done_ratios_not_updated: Tapşırıqların hazırlıq parametri yenilÉ™nmÉ™yib + error_workflow_copy_target: MÉ™qsÉ™dÉ™ uyÄŸun trekerlÉ™ri vÉ™ rolları seçin + setting_issue_done_ratio_issue_field: Tapşırığın hazırlıq sÉ™viyyÉ™si + label_copy_same_as_target: MÉ™qsÉ™ddÉ™ olduÄŸu kimi + label_copy_target: MÉ™qsÉ™d + notice_issue_done_ratios_updated: Parametr «hazırlıq» yenilÉ™nib. + error_workflow_copy_source: Cari trekeri vÉ™ ya rolu seçin + label_update_issue_done_ratios: Tapşırığın hazırlıq sÉ™viyyÉ™sini yenilÉ™mÉ™k + setting_start_of_week: HÉ™ftÉ™nin birinci günü + label_api_access_key: API-yÉ™ giriÅŸ açarı + text_line_separated: Bİr neçə qiymÉ™t icazÉ™ verilib (hÉ™r sÉ™tirÉ™ bir qiymÉ™t). + label_revision_id: Yoxlama %{value} + permission_view_issues: Tapşırıqlara baxış + label_display_used_statuses_only: Yalnız bu trekerdÉ™ istifadÉ™ olunan statusları É™ks etdirmÉ™k + label_api_access_key_created_on: API-yÉ™ giriÅŸ açarı %{value} É™vvÉ™l aradılıb + label_feeds_access_key: RSS giriÅŸ açarı + notice_api_access_key_reseted: Sizin API giriÅŸ açarınız sıfırlanıb. + setting_rest_api_enabled: REST veb-servisini qoÅŸmaq + button_show: GöstÉ™rmÉ™k + label_missing_api_access_key: API-yÉ™ giriÅŸ açarı mövcud deyildir + label_missing_feeds_access_key: RSS-É™ giriÅŸ açarı mövcud deyildir + setting_mail_handler_body_delimiters: Bu sÉ™tirlÉ™rin birindÉ™n sonra mÉ™ktubu qısaltmaq + permission_add_subprojects: Alt layihÉ™lÉ™rin yaradılması + label_subproject_new: Yeni alt layihÉ™ + text_own_membership_delete_confirmation: |- + Siz bÉ™zi vÉ™ ya bütün hüquqları silmÉ™yÉ™ çalışırsınız, nÉ™ticÉ™dÉ™ bu layihÉ™ni redaktÉ™ etmÉ™k hüququnu da itirÉ™ bilÉ™rsiniz. Davam etmÉ™k istÉ™diyinizÉ™ É™minsinizmi? + + label_close_versions: BaÅŸa çatmış variantları baÄŸlamaq + label_board_sticky: BÉ™rkidilib + label_board_locked: Bloklanıb + field_principal: Ad + text_zoom_out: UzaqlaÅŸdırmaq + text_zoom_in: YaxınlaÅŸdırmaq + notice_unable_delete_time_entry: Jurnalın qeydini silmÉ™k mümkün deyildir. + label_overall_spent_time: CÉ™mi sÉ™rf olunan vaxt + label_user_mail_option_none: HadisÉ™ yoxdur + field_member_of_group: TÉ™yin olunmuÅŸ qrup + field_assigned_to_role: TÉ™yin olunmuÅŸ rol + notice_not_authorized_archived_project: SorÄŸulanan layihÉ™ arxivləşdirilib. + label_principal_search: "İstifadəçini vÉ™ ya qrupu tapmaq:" + label_user_search: "İstifadəçini tapmaq:" + field_visible: GörünmÉ™ dÉ™rÉ™cÉ™si + setting_emails_header: MÉ™ktubun baÅŸlığı + + setting_commit_logtime_activity_id: Vaxtın uçotu üçün görülÉ™n hÉ™rÉ™kÉ™tlÉ™r + text_time_logged_by_changeset: "%{value} redaksiyada nÉ™zÉ™rÉ™ alınıb." + setting_commit_logtime_enabled: Vaxt uçotunu qoÅŸmaq + notice_gantt_chart_truncated: Æks oluna bilÉ™cÉ™k elementlÉ™rin maksimal sayı artdığına görÉ™ diaqram kÉ™silÉ™cÉ™k (%{max}) + setting_gantt_items_limit: Qant diaqramında É™ks olunan elementlÉ™rin maksimal sayı + field_warn_on_leaving_unsaved: Yadda saxlanılmayan mÉ™tnin sÉ™hifÉ™si baÄŸlanan zaman xÉ™bÉ™rdarlıq etmÉ™k + text_warn_on_leaving_unsaved: TÉ™rk etmÉ™k istÉ™diyiniz cari sÉ™hifÉ™dÉ™ yadda saxlanılmayan vÉ™ itÉ™ bilÉ™cÉ™k mÉ™tn vardır. + label_my_queries: MÉ™nim yadda saxlanılan sorÄŸularım + text_journal_changed_no_detail: "%{label} yenilÉ™nib" + label_news_comment_added: XÉ™bÉ™rÉ™ ÅŸÉ™rh É™lavÉ™ olunub + button_expand_all: Hamısını aç + button_collapse_all: Hamısını çevir + label_additional_workflow_transitions_for_assignee: İstifadəçi icraçı olduÄŸu zaman É™lavÉ™ keçidlÉ™r + label_additional_workflow_transitions_for_author: İstifadəçi müəllif olduÄŸu zaman É™lavÉ™ keçidlÉ™r + label_bulk_edit_selected_time_entries: SÉ™rf olunan vaxtın seçilÉ™n qeydlÉ™rinin kütlÉ™vi ÅŸÉ™kildÉ™ dÉ™yiÅŸdirilmÉ™si + text_time_entries_destroy_confirmation: Siz sÉ™rf olunan vaxtın seçilÉ™n qeydlÉ™rini silmÉ™k istÉ™diyinizÉ™ É™minsinizmi? + label_role_anonymous: Anonim + label_role_non_member: İştirakçı deyil + label_issue_note_added: Qeyd É™lavÉ™ olunub + label_issue_status_updated: Status yenilÉ™nib + label_issue_priority_updated: Prioritet yenilÉ™nib + label_issues_visibility_own: İstifadəçi üçün yaradılan vÉ™ ya ona tÉ™yin olunan tapşırıqlar + field_issues_visibility: Tapşırıqların görünmÉ™ dÉ™rÉ™cÉ™si + label_issues_visibility_all: Bütün tapşırıqlar + permission_set_own_issues_private: Şəxsi tapşırıqlar üçün görünmÉ™ dÉ™rÉ™cÉ™sinin (ümumi/ÅŸÉ™xsi) qurulması + field_is_private: Şəxsi + permission_set_issues_private: Tapşırıqlar üçün görünmÉ™ dÉ™rÉ™cÉ™sinin (ümumi/ÅŸÉ™xsi) qurulması + label_issues_visibility_public: Yalnız ümumi tapşırıqlar + text_issues_destroy_descendants_confirmation: HÉ™mçinin %{count} tapşırıq (lar) silinÉ™cÉ™k. + field_commit_logs_encoding: Saxlayıcıda ÅŸÉ™rhlÉ™rin kodlaÅŸdırılması + field_scm_path_encoding: Yolun kodlaÅŸdırılması + text_scm_path_encoding_note: "Susmaya görÉ™: UTF-8" + field_path_to_repository: Saxlayıcıya yol + field_root_directory: Kök direktoriya + field_cvs_module: Modul + field_cvsroot: CVSROOT + text_mercurial_repository_note: Lokal saxlayıcı (mÉ™sÉ™lÉ™n, /hgrepo, c:\hgrepo) + text_scm_command: Komanda + text_scm_command_version: Variant + label_git_report_last_commit: Fayllar vÉ™ direktoriyalar üçün son dÉ™yiÅŸikliklÉ™ri göstÉ™rmÉ™k + text_scm_config: Siz config/configuration.yml faylında SCM komandasını sazlaya bilÉ™rsiniz. XahiÅŸ olunur, bu faylın redaktÉ™sindÉ™n sonra É™lavÉ™ni iÅŸÉ™ salın. + text_scm_command_not_available: Variantların nÉ™zarÉ™t sisteminin komandasına giriÅŸ mümkün deyildir. XahiÅŸ olunur, inzibatçı panelindÉ™ki sazlamaları yoxlayın. + notice_issue_successful_create: Tapşırıq %{id} yaradılıb. + label_between: arasında + setting_issue_group_assignment: İstifadəçi qruplarına tÉ™yinata icazÉ™ vermÉ™k + label_diff: FÉ™rq(diff) + text_git_repository_note: "Saxlama yerini göstÉ™rin (mÉ™s: /gitrepo, c:\\gitrepo)" + description_query_sort_criteria_direction: ÇeÅŸidlÉ™mÉ™ qaydası + description_project_scope: LayihÉ™nin hÉ™cmi + description_filter: Filtr + description_user_mail_notification: E-poçt Mail xÉ™bÉ™rdarlıqlarının sazlaması + description_date_from: BaÅŸlama tarixini daxil edin + description_message_content: Mesajın kontenti + description_available_columns: Mövcud sütunlar + description_date_range_interval: TarixlÉ™r diapazonunu seçin + description_issue_category_reassign: MÉ™sÉ™lÉ™nin kateqoriyasını seçin + description_search: Axtarış sahÉ™si + description_notes: Qeyd + description_date_range_list: Siyahıdan diapazonu seçin + description_choose_project: LayihÉ™lÉ™r + description_date_to: YerinÉ™ yetirilmÉ™ tarixini daxil edin + description_query_sort_criteria_attribute: ÇeÅŸidlÉ™mÉ™ meyarları + description_wiki_subpages_reassign: Yeni valideyn sÉ™hifÉ™sini seçmÉ™k + description_selected_columns: SeçilmiÅŸ sütunlar + label_parent_revision: Valideyn + label_child_revision: Æsas + error_scm_annotate_big_text_file: MÉ™tn faylının maksimal ölçüsü artdığına görÉ™ ÅŸÉ™rh mümkün deyildir. + setting_default_issue_start_date_to_creation_date: Yeni tapşırıqlar üçün cari tarixi baÅŸlanğıc tarixi kimi istifadÉ™ etmÉ™k + button_edit_section: Bu bölmÉ™ni redaktÉ™ etmÉ™k + setting_repositories_encodings: ÆlavÉ™lÉ™rin vÉ™ saxlayıcıların kodlaÅŸdırılması + description_all_columns: Bütün sütunlar + button_export: İxrac + label_export_options: "%{export_format} ixracın parametrlÉ™ri" + error_attachment_too_big: Faylın maksimal ölçüsü artdığına görÉ™ bu faylı yüklÉ™mÉ™k mümkün deyildir (%{max_size}) + notice_failed_to_save_time_entries: "SÉ™hv N %{ids}. %{total} giriÅŸdÉ™n %{count} yaddaÅŸa saxlanıla bilmÉ™di." + label_x_issues: + zero: 0 Tapşırıq + one: 1 Tapşırıq + few: "%{count} Tapşırıq" + many: "%{count} Tapşırıq" + other: "%{count} Tapşırıq" + label_repository_new: Yeni saxlayıcı + field_repository_is_default: Susmaya görÉ™ saxlayıcı + label_copy_attachments: ÆlavÉ™nin surÉ™tini çıxarmaq + label_item_position: "%{position}/%{count}" + label_completed_versions: BaÅŸa çatdırılmış variantlar + text_project_identifier_info: Yalnız kiçik latın hÉ™rflÉ™rinÉ™ (a-z), rÉ™qÉ™mlÉ™rÉ™, tire vÉ™ çicgilÉ™rÉ™ icazÉ™ verilir.
      Yadda saxladıqdan sonra identifikatoru dÉ™yiÅŸmÉ™k olmaz. + field_multiple: Çoxsaylı qiymÉ™tlÉ™r + setting_commit_cross_project_ref: DigÉ™r bütün layihÉ™lÉ™rdÉ™ tapşırıqları düzÉ™ltmÉ™k vÉ™ istinad etmÉ™k + text_issue_conflict_resolution_add_notes: QeydlÉ™rimi É™lavÉ™ etmÉ™k vÉ™ mÉ™nim dÉ™yiÅŸikliklÉ™rimdÉ™n imtina etmÉ™k + text_issue_conflict_resolution_overwrite: DÉ™yiÅŸikliklÉ™rimi tÉ™tbiq etmÉ™k (É™vvÉ™lki bütün qeydlÉ™r yadda saxlanacaq, lakin bÉ™zi qeydlÉ™r yenidÉ™n yazıla bilÉ™r) + notice_issue_update_conflict: Tapşırığı redaktÉ™ etdiyiniz zaman kimsÉ™ onu artıq dÉ™yiÅŸib. + text_issue_conflict_resolution_cancel: MÉ™nim dÉ™yiÅŸikliklÉ™rimi ləğv etmÉ™k vÉ™ tapşırığı yenidÉ™n göstÉ™rmÉ™k %{link} + permission_manage_related_issues: ÆlaqÉ™li tapşırıqların idarÉ™ edilmÉ™si + field_auth_source_ldap_filter: LDAP filtri + label_search_for_watchers: NÉ™zarÉ™tçilÉ™ri axtarmaq + notice_account_deleted: "Sizin uçot qeydiniz tam olaraq silinib" + setting_unsubscribe: "İstifadəçilÉ™rÉ™ ÅŸÉ™xsi uçot qeydlÉ™rini silmÉ™yÉ™ icazÉ™ vermÉ™k" + button_delete_my_account: "MÉ™nim uçot qeydlÉ™rimi silmÉ™k" + text_account_destroy_confirmation: "Sizin uçot qeydiniz bir daha bÉ™rpa edilmÉ™dÉ™n tam olaraq silinÉ™cÉ™k.\nDavam etmÉ™k istÉ™diyinizÉ™ É™minsinizmi?" + error_session_expired: Sizin sessiya bitmiÅŸdir. XahiÅŸ edirik yenidÉ™n daxil olun. + text_session_expiration_settings: "DiqqÉ™t: bu sazlamaların dÉ™yiÅŸmÉ™yi cari sessiyanın baÄŸlanmasına çıxara bilÉ™r." + setting_session_lifetime: Sessiyanın maksimal Session maximum hÉ™yat müddÉ™ti + setting_session_timeout: Sessiyanın qeyri aktivlik müddÉ™ti + label_session_expiration: Sessiyanın bitmÉ™si + permission_close_project: LayihÉ™ni baÄŸla / yenidÉ™n aç + label_show_closed_projects: BaÄŸlı layihÉ™lÉ™rÉ™ baxmaq + button_close: BaÄŸla + button_reopen: YenidÉ™n aç + project_status_active: aktiv + project_status_closed: baÄŸlı + project_status_archived: arxiv + text_project_closed: Bu layihÉ™ baÄŸlıdı vÉ™ yalnız oxuma olar. + notice_user_successful_create: İstifadəçi %{id} yaradıldı. + field_core_fields: Standart sahÉ™lÉ™r + field_timeout: Zaman aşımı (saniyÉ™ ilÉ™) + setting_thumbnails_enabled: ÆlavÉ™lÉ™rin kiçik ÅŸÉ™klini göstÉ™r + setting_thumbnails_size: Kiçik ÅŸÉ™killÉ™rin ölçüsü (piksel ilÉ™) + label_status_transitions: Status keçidlÉ™ri + label_fields_permissions: SahÉ™lÉ™rin icazÉ™lÉ™ri + label_readonly: Ancaq oxumaq üçün + label_required: TÉ™lÉ™b olunur + text_repository_identifier_info: Yalnız kiçik latın hÉ™rflÉ™rinÉ™ (a-z), rÉ™qÉ™mlÉ™rÉ™, tire vÉ™ çicgilÉ™rÉ™ icazÉ™ verilir.
      Yadda saxladıqdan sonra identifikatoru dÉ™yiÅŸmÉ™k olmaz. + field_board_parent: Ana forum + label_attribute_of_project: LayihÉ™ %{name} + label_attribute_of_author: Müəllif %{name} + label_attribute_of_assigned_to: TÉ™yin edilib %{name} + label_attribute_of_fixed_version: Æsas versiya %{name} + label_copy_subtasks: Alt tapşırığın surÉ™tini çıxarmaq + label_cross_project_hierarchy: With project hierarchy + permission_edit_documents: Edit documents + button_hide: Hide + text_turning_multiple_off: If you disable multiple values, multiple values will be removed in order to preserve only one value per item. + label_any: any + label_cross_project_system: With all projects + label_last_n_weeks: last %{count} weeks + label_in_the_past_days: in the past + label_copied_to: Copied to + permission_set_notes_private: Set notes as private + label_in_the_next_days: in the next + label_attribute_of_issue: Issue's %{name} + label_any_issues_in_project: any issues in project + label_cross_project_descendants: With subprojects + field_private_notes: Private notes + setting_jsonp_enabled: Enable JSONP support + label_gantt_progress_line: Progress line + permission_add_documents: Add documents + permission_view_private_notes: View private notes + label_attribute_of_user: User's %{name} + permission_delete_documents: Delete documents + field_inherit_members: Inherit members + setting_cross_project_subtasks: Allow cross-project subtasks + label_no_issues_in_project: no issues in project + label_copied_from: Copied from + setting_non_working_week_days: Non-working days + label_any_issues_not_in_project: any issues not in project + label_cross_project_tree: With project tree + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: CÉ™mi diff -r 0a574315af3e -r 4f746d8966dd config/locales/bg.yml --- a/config/locales/bg.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/bg.yml Fri Jun 14 09:28:30 2013 +0100 @@ -51,8 +51,8 @@ one: "около 1 чаÑ" other: "около %{count} чаÑа" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 чаÑ" + other: "%{count} чаÑа" x_days: one: "1 ден" other: "%{count} дена" @@ -180,7 +180,7 @@ notice_account_deleted: ВашиÑÑ‚ профил беше премахнат без възможноÑÑ‚ за възÑтановÑване. notice_user_successful_create: Потребител %{id} е Ñъздаден. - error_can_t_load_default_data: "Грешка при зареждане на примерната информациÑ: %{value}" + error_can_t_load_default_data: "Грешка при зареждане на началната информациÑ: %{value}" error_scm_not_found: ÐеÑъщеÑтвуващ обект в хранилището. error_scm_command_failed: "Грешка при опит за ÐºÐ¾Ð¼ÑƒÐ½Ð¸ÐºÐ°Ñ†Ð¸Ñ Ñ Ñ…Ñ€Ð°Ð½Ð¸Ð»Ð¸Ñ‰Ðµ: %{value}" error_scm_annotate: "Обектът не ÑъщеÑтвува или не може да бъде анотиран." @@ -217,9 +217,6 @@ mail_subject_wiki_content_updated: "Wiki Ñтраницата '%{id}' беше обновена" mail_body_wiki_content_updated: Wiki Ñтраницата '%{id}' беше обновена от %{author}. - gui_validation_error: 1 грешка - gui_validation_error_plural: "%{count} грешки" - field_name: Име field_description: ОпиÑание field_summary: ÐÐ½Ð¾Ñ‚Ð°Ñ†Ð¸Ñ @@ -233,6 +230,7 @@ field_author: Ðвтор field_created_on: От дата field_updated_on: Обновена + field_closed_on: Затворена field_field_format: Тип field_is_for_all: За вÑички проекти field_possible_values: Възможни ÑтойноÑти @@ -249,7 +247,7 @@ field_is_closed: Затворена задача field_is_default: СъÑтоÑние по подразбиране field_tracker: Тракер - field_subject: ОтноÑно + field_subject: Заглавие field_due_date: Крайна дата field_assigned_to: Възложена на field_priority: Приоритет @@ -333,6 +331,7 @@ field_timeout: Таймаут (в Ñекунди) field_board_parent: РодителÑки форум field_private_notes: Лични бележки + field_inherit_members: ÐаÑледÑване на членовете на родителÑÐºÐ¸Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚ setting_app_title: Заглавие setting_app_subtitle: ОпиÑание @@ -361,7 +360,7 @@ setting_cross_project_subtasks: Подзадачи от други проекти setting_issue_list_default_columns: Показвани колони по подразбиране setting_repositories_encodings: Кодова таблица на прикачените файлове и хранилищата - setting_emails_header: Emails header + setting_emails_header: Email header setting_emails_footer: ПодтекÑÑ‚ за e-mail setting_protocol: Протокол setting_per_page_options: Опции за Ñтраниране @@ -401,6 +400,8 @@ setting_thumbnails_enabled: Показване на миниатюри на прикачените Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ setting_thumbnails_size: Размер на миниатюрите (в пикÑели) setting_non_working_week_days: Ðе работни дни + setting_jsonp_enabled: Разрешаване на поддръжка на JSONP + setting_default_projects_tracker_ids: Тракери по подразбиране за нови проекти permission_add_project: Създаване на проект permission_add_subprojects: Създаване на подпроекти @@ -437,8 +438,10 @@ permission_edit_own_time_entries: Редактиране на ÑобÑтвените time logs permission_manage_news: Управление на новини permission_comment_news: Коментиране на новини - permission_manage_documents: Управление на документи permission_view_documents: Разглеждане на документи + permission_add_documents: ДобавÑне на документи + permission_edit_documents: Редактиране на документи + permission_delete_documents: Изтриване на документи permission_manage_files: Управление на файлове permission_view_files: Разглеждане на файлове permission_manage_wiki: Управление на wiki @@ -569,8 +572,6 @@ label_text: Дълъг текÑÑ‚ label_attribute: Ðтрибут label_attribute_plural: Ðтрибути - label_download: "%{count} изтеглÑне" - label_download_plural: "%{count} изтеглÑниÑ" label_no_data: ÐÑма изходни данни label_change_status: ПромÑна на ÑÑŠÑтоÑнието label_history: ИÑÑ‚Ð¾Ñ€Ð¸Ñ @@ -619,11 +620,12 @@ one: 1 задача other: "%{count} задачи" label_total: Общо + label_total_time: Общо label_permissions: Права label_current_status: Текущо ÑÑŠÑтоÑние label_new_statuses_allowed: Позволени ÑÑŠÑтоÑÐ½Ð¸Ñ label_all: вÑички - label_any: коÑто и да е + label_any: без значение label_none: никакви label_nobody: никой label_next: Следващ @@ -688,8 +690,6 @@ label_repository_new: Ðово хранилище label_repository_plural: Хранилища label_browse: Разглеждане - label_modification: "%{count} промÑна" - label_modification_plural: "%{count} промени" label_branch: работен вариант label_tag: ВерÑÐ¸Ñ label_revision: Ð ÐµÐ²Ð¸Ð·Ð¸Ñ @@ -884,13 +884,16 @@ label_readonly: Само за четене label_required: Задължително label_attribute_of_project: Project's %{name} + label_attribute_of_issue: Issue's %{name} label_attribute_of_author: Author's %{name} label_attribute_of_assigned_to: Assignee's %{name} + label_attribute_of_user: User's %{name} label_attribute_of_fixed_version: Target version's %{name} label_cross_project_descendants: С подпроекти label_cross_project_tree: С дърво на проектите label_cross_project_hierarchy: С проектна Ð¹ÐµÑ€Ð°Ñ€Ñ…Ð¸Ñ label_cross_project_system: С вÑички проекти + label_gantt_progress_line: Ð›Ð¸Ð½Ð¸Ñ Ð½Ð° изпълнението button_login: Вход button_submit: Изпращане @@ -1034,6 +1037,8 @@ text_account_destroy_confirmation: "Сигурен/на ли Ñте, че желаете да продължите?\nВашиÑÑ‚ профил ще бъде премахнат без възможноÑÑ‚ за възÑтановÑване." text_session_expiration_settings: "Внимание: промÑната на тези уÑтановÑÐ²Ð°Ð½Ð¾Ñ Ð¼Ð¾Ð¶Ðµ да прекрати вÑички активни ÑеÑии, включително и вашата." text_project_closed: Този проект е затворен и е Ñамо за четене. + text_turning_multiple_off: Ðко забраните възможноÑтта за повече от една ÑтойноÑÑ‚, повечето ÑтойноÑти ще бъдат + премахнати Ñ Ñ†ÐµÐ» да оÑтане Ñамо по една ÑтойноÑÑ‚ за поле. default_role_manager: Мениджър default_role_developer: Разработчик diff -r 0a574315af3e -r 4f746d8966dd config/locales/bs.yml --- a/config/locales/bs.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/bs.yml Fri Jun 14 09:28:30 2013 +0100 @@ -49,8 +49,8 @@ one: "oko 1 sahat" other: "oko %{count} sahata" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 sahat" + other: "%{count} sahata" x_days: one: "1 dan" other: "%{count} dana" @@ -198,8 +198,6 @@ mail_subject_reminder: "%{count} aktivnost(i) u kaÅ¡njenju u narednim %{days} danima" mail_body_reminder: "%{count} aktivnost(i) koje su dodjeljenje vama u narednim %{days} danima:" - gui_validation_error: 1 greÅ¡ka - gui_validation_error_plural: "%{count} greÅ¡aka" field_name: Ime field_description: Opis @@ -357,7 +355,6 @@ permission_edit_own_time_entries: Ispravka svog utroÅ¡ka vremena permission_manage_news: Upravljaj novostima permission_comment_news: Komentiraj novosti - permission_manage_documents: Upravljaj dokumentima permission_view_documents: Pregled dokumenata permission_manage_files: Upravljaj fajlovima permission_view_files: Pregled fajlova @@ -477,8 +474,6 @@ label_text: Dugi tekst label_attribute: Atribut label_attribute_plural: Atributi - label_download: "%{count} download" - label_download_plural: "%{count} download-i" label_no_data: Nema podataka za prikaz label_change_status: Promjeni status label_history: Istorija @@ -578,8 +573,6 @@ label_repository: Repozitorij label_repository_plural: Repozitoriji label_browse: Listaj - label_modification: "%{count} promjena" - label_modification_plural: "%{count} promjene" label_revision: Revizija label_revision_plural: Revizije label_associated_revisions: Doddjeljene revizije @@ -946,7 +939,6 @@ label_principal_search: "Search for user or group:" label_user_search: "Search for user:" field_visible: Visible - setting_emails_header: Emails header setting_commit_logtime_activity_id: Activity for logged time text_time_logged_by_changeset: Applied in changeset %{value}. setting_commit_logtime_enabled: Enable time logging @@ -987,8 +979,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1095,3 +1085,19 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Ukupno + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header diff -r 0a574315af3e -r 4f746d8966dd config/locales/ca.yml --- a/config/locales/ca.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/ca.yml Fri Jun 14 09:28:30 2013 +0100 @@ -53,8 +53,8 @@ one: "aproximadament 1 hora" other: "aproximadament %{count} hores" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 hora" + other: "%{count} hores" x_days: one: "1 dia" other: "%{count} dies" @@ -209,8 +209,6 @@ mail_subject_wiki_content_updated: "S'ha actualitzat la pàgina wiki «%{id}»" mail_body_wiki_content_updated: "En %{author} ha actualitzat la pàgina wiki «%{id}»." - gui_validation_error: 1 error - gui_validation_error_plural: "%{count} errors" field_name: Nom field_description: Descripció @@ -388,7 +386,6 @@ permission_edit_own_time_entries: Edita els registres de temps propis permission_manage_news: Gestiona les noticies permission_comment_news: Comenta les noticies - permission_manage_documents: Gestiona els documents permission_view_documents: Visualitza els documents permission_manage_files: Gestiona els fitxers permission_view_files: Visualitza els fitxers @@ -514,8 +511,6 @@ label_text: Text llarg label_attribute: Atribut label_attribute_plural: Atributs - label_download: "%{count} baixada" - label_download_plural: "%{count} baixades" label_no_data: Sense dades a mostrar label_change_status: "Canvia l'estat" label_history: Historial @@ -618,8 +613,6 @@ label_repository: Dipòsit label_repository_plural: Dipòsits label_browse: Navega - label_modification: "%{count} canvi" - label_modification_plural: "%{count} canvis" label_branch: Branca label_tag: Etiqueta label_revision: Revisió @@ -935,7 +928,6 @@ label_principal_search: "Search for user or group:" label_user_search: "Search for user:" field_visible: Visible - setting_emails_header: Emails header setting_commit_logtime_activity_id: Activity for logged time text_time_logged_by_changeset: Applied in changeset %{value}. setting_commit_logtime_enabled: Enable time logging @@ -976,8 +968,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1084,3 +1074,19 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Total + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header diff -r 0a574315af3e -r 4f746d8966dd config/locales/cs.yml --- a/config/locales/cs.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/cs.yml Fri Jun 14 09:28:30 2013 +0100 @@ -55,8 +55,8 @@ one: "asi 1 hodina" other: "asi %{count} hodin" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 hodina" + other: "%{count} hodin" x_days: one: "1 den" other: "%{count} dnů" @@ -161,10 +161,10 @@ notice_successful_update: ÚspěšnÄ› aktualizováno. notice_successful_delete: ÚspěšnÄ› odstranÄ›no. notice_successful_connection: Úspěšné pÅ™ipojení. - notice_file_not_found: Stránka na kterou se snažíte zobrazit neexistuje nebo byla smazána. + notice_file_not_found: Stránka, kterou se snažíte zobrazit, neexistuje nebo byla smazána. notice_locking_conflict: Údaje byly zmÄ›nÄ›ny jiným uživatelem. notice_not_authorized: Nemáte dostateÄná práva pro zobrazení této stránky. - notice_not_authorized_archived_project: Projekt ke kterému se snažíte pÅ™istupovat byl archivován. + notice_not_authorized_archived_project: Projekt, ke kterému se snažíte pÅ™istupovat, byl archivován. notice_email_sent: "Na adresu %{value} byl odeslán email" notice_email_error: "PÅ™i odesílání emailu nastala chyba (%{value})" notice_feeds_access_key_reseted: Váš klÃ­Ä pro přístup k RSS byl resetován. @@ -175,7 +175,7 @@ notice_account_pending: "Váš úÄet byl vytvoÅ™en, nyní Äeká na schválení administrátorem." notice_default_data_loaded: Výchozí konfigurace úspěšnÄ› nahrána. notice_unable_delete_version: Nemohu odstanit verzi - notice_unable_delete_time_entry: Nelze smazat Äas ze záznamu. + notice_unable_delete_time_entry: Nelze smazat záznam Äasu. notice_issue_done_ratios_updated: Koeficienty dokonÄení úkolu byly aktualizovány. notice_gantt_chart_truncated: Graf byl oříznut, poÄet položek pÅ™esáhl limit pro zobrazení (%{max}) @@ -185,15 +185,15 @@ error_scm_annotate: "Položka neexistuje nebo nemůže být komentována." error_issue_not_found_in_project: 'Úkol nebyl nalezen nebo nepatří k tomuto projektu' error_no_tracker_in_project: Žádná fronta nebyla pÅ™iÅ™azena tomuto projektu. Prosím zkontroluje nastavení projektu. - error_no_default_issue_status: Není nastaven výchozí stav úkolu. Prosím zkontrolujte nastavení ("Administrace -> Stavy úkolů"). + error_no_default_issue_status: Není nastaven výchozí stav úkolů. Prosím zkontrolujte nastavení ("Administrace -> Stavy úkolů"). error_can_not_delete_custom_field: Nelze smazat volitelné pole - error_can_not_delete_tracker: Tato fronta obsahuje úkoly a nemůže být smazán. + error_can_not_delete_tracker: Tato fronta obsahuje úkoly a nemůže být smazána. error_can_not_remove_role: Tato role je právÄ› používaná a nelze ji smazat. error_can_not_reopen_issue_on_closed_version: Úkol pÅ™iÅ™azený k uzavÅ™ené verzi nemůže být znovu otevÅ™en error_can_not_archive_project: Tento projekt nemůže být archivován error_issue_done_ratios_not_updated: Koeficient dokonÄení úkolu nebyl aktualizován. - error_workflow_copy_source: Prosím vyberte zdrojovou frontu nebo roly - error_workflow_copy_target: Prosím vyberte cílovou frontu(y) a roly(e) + error_workflow_copy_source: Prosím vyberte zdrojovou frontu nebo roli + error_workflow_copy_target: Prosím vyberte cílovou frontu(y) a roli(e) error_unable_delete_issue_status: Nelze smazat stavy úkolů error_unable_to_connect: Nelze se pÅ™ipojit (%{value}) warning_attachments_not_saved: "%{count} soubor(ů) nebylo možné uložit." @@ -207,14 +207,12 @@ mail_subject_account_activation_request: "Aktivace %{value} úÄtu" mail_body_account_activation_request: "Byl zaregistrován nový uživatel %{value}. Aktivace jeho úÄtu závisí na vaÅ¡em potvrzení." mail_subject_reminder: "%{count} úkol(ů) má termín bÄ›hem nÄ›kolik dní (%{days})" - mail_body_reminder: "%{count} úkol(ů), které máte pÅ™iÅ™azeny má termín bÄ›hem nÄ›kolik dní (%{days}):" + mail_body_reminder: "%{count} úkol(ů), které máte pÅ™iÅ™azeny má termín bÄ›hem nÄ›kolika dní (%{days}):" mail_subject_wiki_content_added: "'%{id}' Wiki stránka byla pÅ™idána" mail_body_wiki_content_added: "'%{id}' Wiki stránka byla pÅ™idána od %{author}." mail_subject_wiki_content_updated: "'%{id}' Wiki stránka byla aktualizována" mail_body_wiki_content_updated: "'%{id}' Wiki stránka byla aktualizována od %{author}." - gui_validation_error: 1 chyba - gui_validation_error_plural: "%{count} chyb(y)" field_name: Název field_description: Popis @@ -293,7 +291,7 @@ field_issue_to: Související úkol field_delay: ZpoždÄ›ní field_assignable: Úkoly mohou být pÅ™iÅ™azeny této roli - field_redirect_existing_links: PÅ™esmÄ›rovat stvávající odkazy + field_redirect_existing_links: PÅ™esmÄ›rovat stávající odkazy field_estimated_hours: Odhadovaná doba field_column_names: Sloupce field_time_entries: Zaznamenaný Äas @@ -323,7 +321,7 @@ setting_attachment_max_size: Maximální velikost přílohy setting_issues_export_limit: Limit pro export úkolů setting_mail_from: Odesílat emaily z adresy - setting_bcc_recipients: Příjemci skryté kopie (bcc) + setting_bcc_recipients: Příjemci jako skrytá kopie (bcc) setting_plain_text_mail: pouze prostý text (ne HTML) setting_host_name: Jméno serveru setting_text_formatting: Formátování textu @@ -339,8 +337,8 @@ setting_time_format: Formát Äasu setting_cross_project_issue_relations: Povolit vazby úkolů napÅ™Ã­Ä projekty setting_issue_list_default_columns: Výchozí sloupce zobrazené v seznamu úkolů - setting_emails_header: HlaviÄka emailů - setting_emails_footer: PatiÄka emailů + setting_emails_header: Záhlaví emailů + setting_emails_footer: Zápatí emailů setting_protocol: Protokol setting_per_page_options: Povolené poÄty řádků na stránce setting_user_format: Formát zobrazení uživatele @@ -353,7 +351,7 @@ setting_sequential_project_identifiers: Generovat sekvenÄní identifikátory projektů setting_gravatar_enabled: Použít uživatelské ikony Gravatar setting_gravatar_default: Výchozí Gravatar - setting_diff_max_lines_displayed: Maximální poÄet zobrazených řádků rozdílů + setting_diff_max_lines_displayed: Maximální poÄet zobrazených řádků rozdílu setting_file_max_size_displayed: Maximální velikost textových souborů zobrazených přímo na stránce setting_repository_log_display_limit: Maximální poÄet revizí zobrazených v logu souboru setting_openid: Umožnit pÅ™ihlaÅ¡ování a registrace s OpenID @@ -369,7 +367,7 @@ setting_default_notification_option: Výchozí nastavení oznámení setting_commit_logtime_enabled: Povolit zapisování Äasu setting_commit_logtime_activity_id: Aktivita pro zapsaný Äas - setting_gantt_items_limit: Maximální poÄet položek zobrazený na ganttovÄ› grafu + setting_gantt_items_limit: Maximální poÄet položek zobrazený na ganttovÄ› diagramu permission_add_project: VytvoÅ™it projekt permission_add_subprojects: VytvoÅ™it podprojekty @@ -390,18 +388,17 @@ permission_delete_issues: Mazání úkolů permission_manage_public_queries: Správa veÅ™ejných dotazů permission_save_queries: Ukládání dotazů - permission_view_gantt: Zobrazené Ganttova diagramu + permission_view_gantt: Zobrazení ganttova diagramu permission_view_calendar: Prohlížení kalendáře - permission_view_issue_watchers: Zobrazení seznamu sledujícíh uživatelů + permission_view_issue_watchers: Zobrazení seznamu sledujících uživatelů permission_add_issue_watchers: PÅ™idání sledujících uživatelů - permission_delete_issue_watchers: Smazat pÅ™ihlížející + permission_delete_issue_watchers: Smazat sledující uživatele permission_log_time: Zaznamenávání stráveného Äasu permission_view_time_entries: Zobrazení stráveného Äasu permission_edit_time_entries: Upravování záznamů o stráveném Äasu permission_edit_own_time_entries: Upravování vlastních zázamů o stráveném Äase permission_manage_news: Spravování novinek permission_comment_news: Komentování novinek - permission_manage_documents: Správa dokumentů permission_view_documents: Prohlížení dokumentů permission_manage_files: Spravování souborů permission_view_files: Prohlížení souborů @@ -425,7 +422,7 @@ permission_delete_messages: Mazání zpráv permission_delete_own_messages: Smazat vlastní zprávy permission_export_wiki_pages: Exportovat Wiki stránky - permission_manage_subtasks: Spravovat podúkoly + permission_manage_subtasks: Spravovat dílÄí úkoly project_module_issue_tracking: Sledování úkolů project_module_time_tracking: Sledování Äasu @@ -486,7 +483,7 @@ label_enumeration_new: Nová hodnota label_information: Informace label_information_plural: Informace - label_please_login: Prosím pÅ™ihlaÅ¡te se + label_please_login: PÅ™ihlaÅ¡te se, prosím label_register: Registrovat label_login_with_open_id_option: nebo se pÅ™ihlaÅ¡te s OpenID label_password_lost: Zapomenuté heslo @@ -527,8 +524,6 @@ label_text: Dlouhý text label_attribute: Atribut label_attribute_plural: Atributy - label_download: "%{count} stažení" - label_download_plural: "%{count} stažení" label_no_data: Žádné položky label_change_status: ZmÄ›nit stav label_history: Historie @@ -586,7 +581,7 @@ label_per_page: Na stránku label_calendar: Kalendář label_months_from: mÄ›síců od - label_gantt: Ganttův graf + label_gantt: Ganttův diagram label_internal: Interní label_last_changes: "posledních %{count} zmÄ›n" label_change_view_all: Zobrazit vÅ¡echny zmÄ›ny @@ -631,8 +626,6 @@ label_repository: Repozitář label_repository_plural: Repozitáře label_browse: Procházet - label_modification: "%{count} zmÄ›na" - label_modification_plural: "%{count} zmÄ›n" label_branch: VÄ›tev label_tag: Tag label_revision: Revize @@ -695,9 +688,9 @@ label_relation_delete: Odstranit souvislost label_relates_to: související s label_duplicates: duplikuje - label_duplicated_by: zduplikován + label_duplicated_by: duplikován label_blocks: blokuje - label_blocked_by: zablokován + label_blocked_by: blokován label_precedes: pÅ™edchází label_follows: následuje label_end_to_start: od konce do zaÄátku @@ -706,12 +699,12 @@ label_start_to_end: od zaÄátku do konce label_stay_logged_in: Zůstat pÅ™ihlášený label_disabled: zakázán - label_show_completed_versions: Ukázat dokonÄené verze + label_show_completed_versions: Zobrazit dokonÄené verze label_me: já label_board: Fórum label_board_new: Nové fórum label_board_plural: Fóra - label_board_locked: UzamÄeno + label_board_locked: ZamÄeno label_board_sticky: Nálepka label_topic_plural: Témata label_message_plural: Zprávy @@ -725,7 +718,7 @@ label_week: Týden label_date_from: Od label_date_to: Do - label_language_based: Podle výchozího jazyku + label_language_based: Podle výchozího jazyka label_sort_by: "SeÅ™adit podle %{value}" label_send_test_email: Poslat testovací email label_feeds_access_key: Přístupový klÃ­Ä pro RSS @@ -737,7 +730,7 @@ label_updated_time: "Aktualizováno pÅ™ed %{value}" label_jump_to_a_project: Vyberte projekt... label_file_plural: Soubory - label_changeset_plural: Changesety + label_changeset_plural: Sady zmÄ›n label_default_columns: Výchozí sloupce label_no_change_option: (beze zmÄ›ny) label_bulk_edit_selected_issues: Hromadná úprava vybraných úkolů @@ -747,9 +740,9 @@ label_user_mail_option_all: "Pro vÅ¡echny události vÅ¡ech mých projektů" label_user_mail_option_selected: "Pro vÅ¡echny události vybraných projektů..." label_user_mail_option_none: "Žádné události" - label_user_mail_option_only_my_events: "Jen pro vÄ›ci co sleduji nebo jsem v nich zapojen" - label_user_mail_option_only_assigned: "Jen pro vÅ¡eci kterým sem pÅ™iÅ™azen" - label_user_mail_option_only_owner: "Jen pro vÄ›ci které vlastním" + label_user_mail_option_only_my_events: "Jen pro vÄ›ci, co sleduji nebo jsem v nich zapojen" + label_user_mail_option_only_assigned: "Jen pro vÄ›ci, ke kterým sem pÅ™iÅ™azen" + label_user_mail_option_only_owner: "Jen pro vÄ›ci, které vlastním" label_user_mail_no_self_notified: "Nezasílat informace o mnou vytvoÅ™ených zmÄ›nách" label_registration_activation_by_email: aktivace úÄtu emailem label_registration_manual_activation: manuální aktivace úÄtu @@ -798,7 +791,7 @@ label_missing_api_access_key: ChybÄ›jící přístupový klÃ­Ä API label_api_access_key_created_on: API přístupový klÃ­Ä vytvoÅ™en %{value} label_profile: Profil - label_subtask_plural: Podúkol + label_subtask_plural: DílÄí úkoly label_project_copy_notifications: Odeslat email oznámení v průbÄ›hu kopie projektu label_principal_search: "Hledat uživatele nebo skupinu:" label_user_search: "Hledat uživatele:" @@ -835,7 +828,7 @@ button_unwatch: Nesledovat button_reply: OdpovÄ›dÄ›t button_archive: Archivovat - button_unarchive: Odarchivovat + button_unarchive: Dearchivovat button_reset: Resetovat button_rename: PÅ™ejmenovat button_change_password: ZmÄ›nit heslo @@ -850,18 +843,18 @@ status_active: aktivní status_registered: registrovaný - status_locked: uzamÄený + status_locked: zamÄený version_status_open: otevÅ™ený - version_status_locked: uzamÄený + version_status_locked: zamÄený version_status_closed: zavÅ™ený field_active: Aktivní - text_select_mail_notifications: Vyberte akci pÅ™i které bude zasláno upozornÄ›ní emailem. + text_select_mail_notifications: Vyberte akci, pÅ™i které bude zasláno upozornÄ›ní emailem. text_regexp_info: napÅ™. ^[A-Z0-9]+$ text_min_max_length_info: 0 znamená bez limitu - text_project_destroy_confirmation: Jste si jisti, že chcete odstranit tento projekt a vÅ¡echna související data ? + text_project_destroy_confirmation: Jste si jisti, že chcete odstranit tento projekt a vÅ¡echna související data? text_subprojects_destroy_warning: "Jeho podprojek(y): %{value} budou také smazány." text_workflow_edit: Vyberte roli a frontu k editaci průbÄ›hu práce text_are_you_sure: Jste si jisti? @@ -879,7 +872,7 @@ text_unallowed_characters: Nepovolené znaky text_comma_separated: Povoleno více hodnot (oddÄ›lÄ›né Äárkou). text_line_separated: Více hodnot povoleno (jeden řádek pro každou hodnotu). - text_issues_ref_in_commit_messages: Odkazování a opravování úkolů ve zprávách commitů + text_issues_ref_in_commit_messages: Odkazování a opravování úkolů v poznámkách commitů text_issue_added: "Úkol %{id} byl vytvoÅ™en uživatelem %{author}." text_issue_updated: "Úkol %{id} byl aktualizován uživatelem %{author}." text_wiki_destroy_confirmation: Opravdu si pÅ™ejete odstranit tuto Wiki a celý její obsah? @@ -889,30 +882,30 @@ text_user_mail_option: "U projektů, které nebyly vybrány, budete dostávat oznámení pouze o vaÅ¡ich Äi o sledovaných položkách (napÅ™. o položkách jejichž jste autor nebo ke kterým jste pÅ™iÅ™azen(a))." text_no_configuration_data: "Role, fronty, stavy úkolů ani průbÄ›h práce nebyly zatím nakonfigurovány.\nVelice doporuÄujeme nahrát výchozí konfiguraci. Po té si můžete vÅ¡e upravit" text_load_default_configuration: Nahrát výchozí konfiguraci - text_status_changed_by_changeset: "Použito v changesetu %{value}." - text_time_logged_by_changeset: Aplikováno v changesetu %{value}. + text_status_changed_by_changeset: "Použito v sadÄ› zmÄ›n %{value}." + text_time_logged_by_changeset: Aplikováno v sadÄ› zmÄ›n %{value}. text_issues_destroy_confirmation: 'Opravdu si pÅ™ejete odstranit vÅ¡echny zvolené úkoly?' text_select_project_modules: 'Aktivní moduly v tomto projektu:' text_default_administrator_account_changed: Výchozí nastavení administrátorského úÄtu zmÄ›nÄ›no text_file_repository_writable: Povolen zápis do adresáře ukládání souborů text_plugin_assets_writable: Možnost zápisu do adresáře plugin assets text_rmagick_available: RMagick k dispozici (volitelné) - text_destroy_time_entries_question: "U úkolů, které chcete odstranit je evidováno %{hours} práce. Co chete udÄ›lat?" - text_destroy_time_entries: Odstranit evidované hodiny. - text_assign_time_entries_to_project: PÅ™iÅ™adit evidované hodiny projektu - text_reassign_time_entries: 'PÅ™eÅ™adit evidované hodiny k tomuto úkolu:' + text_destroy_time_entries_question: "U úkolů, které chcete odstranit, je evidováno %{hours} práce. Co chete udÄ›lat?" + text_destroy_time_entries: Odstranit zaznamenané hodiny. + text_assign_time_entries_to_project: PÅ™iÅ™adit zaznamenané hodiny projektu + text_reassign_time_entries: 'PÅ™eÅ™adit zaznamenané hodiny k tomuto úkolu:' text_user_wrote: "%{value} napsal:" text_enumeration_destroy_question: "NÄ›kolik (%{count}) objektů je pÅ™iÅ™azeno k této hodnotÄ›." text_enumeration_category_reassign_to: 'PÅ™eÅ™adit je do této:' text_email_delivery_not_configured: "DoruÄování e-mailů není nastaveno a odesílání notifikací je zakázáno.\nNastavte Váš SMTP server v souboru config/configuration.yml a restartujte aplikaci." - text_repository_usernames_mapping: "Vybrat nebo upravit mapování mezi Redmine uživateli a uživatelskými jmény nalezenými v logu repozitáře.\nUživatelé se shodným Redmine uživatelským jménem a uživatelským jménem v repozitáři jsou mapovaní automaticky." + text_repository_usernames_mapping: "Vybrat nebo upravit mapování mezi Redmine uživateli a uživatelskými jmény nalezenými v logu repozitáře.\nUživatelé se shodným Redmine uživatelským jménem a uživatelským jménem v repozitáři jsou mapováni automaticky." text_diff_truncated: '... Rozdílový soubor je zkrácen, protože jeho délka pÅ™esahuje max. limit.' text_custom_field_possible_values_info: 'Každá hodnota na novém řádku' text_wiki_page_destroy_question: Tato stránka má %{descendants} podstránek a potomků. Co chcete udÄ›lat? text_wiki_page_nullify_children: Ponechat podstránky jako koÅ™enové stránky text_wiki_page_destroy_children: Smazat podstránky a vÅ¡echny jejich potomky text_wiki_page_reassign_children: PÅ™iÅ™adit podstránky k tomuto rodiÄi - text_own_membership_delete_confirmation: "Chystáte se odebrat si nÄ›která nebo vÅ¡echny svá oprávnÄ›ní a potom již nemusíte být schopni upravit tento projekt.\nOpravdu chcete pokraÄovat?" + text_own_membership_delete_confirmation: "Chystáte se odebrat si nÄ›která nebo vÅ¡echna svá oprávnÄ›ní, potom již nemusíte být schopni upravit tento projekt.\nOpravdu chcete pokraÄovat?" text_zoom_in: PÅ™iblížit text_zoom_out: Oddálit @@ -966,7 +959,7 @@ field_is_private: Soukromý permission_set_issues_private: Nastavit úkoly jako veÅ™ejné nebo soukromé label_issues_visibility_public: VÅ¡echny úkoly, které nejsou soukromé - text_issues_destroy_descendants_confirmation: "%{count} podúkol(ů) bude rovněž smazán(o)." + text_issues_destroy_descendants_confirmation: "%{count} dílÄí(ch) úkol(ů) bude rovněž smazán(o)." field_commit_logs_encoding: Kódování zpráv pÅ™i commitu field_scm_path_encoding: Kódování cesty SCM text_scm_path_encoding_note: "Výchozí: UTF-8" @@ -1004,14 +997,14 @@ description_selected_columns: Vybraný sloupec label_parent_revision: RodiÄ label_child_revision: Potomek - error_scm_annotate_big_text_file: Vstup nemůže být anotován, protože pÅ™ekraÄuje povolenou velikost textového souboru + error_scm_annotate_big_text_file: Vstup nemůže být komentován, protože pÅ™ekraÄuje povolenou velikost textového souboru setting_default_issue_start_date_to_creation_date: Použij aktuální datum jako poÄáteÄní datum pro nové úkoly - button_edit_section: Uprav tuto sekci + button_edit_section: Uprav tuto Äást setting_repositories_encodings: Kódování příloh a repositářů description_all_columns: VÅ¡echny sloupce button_export: Export label_export_options: "nastavení exportu %{export_format}" - error_attachment_too_big: Soubor nemůže být nahrán, protože jeho velikost je vÄ›tší než maximum (%{max_size}) + error_attachment_too_big: Soubor nemůže být nahrán, protože jeho velikost je vÄ›tší než maximální (%{max_size}) notice_failed_to_save_time_entries: "Chyba pÅ™i ukládání %{count} Äasov(ých/ého) záznam(ů) z %{total} vybraného: %{ids}." label_x_issues: zero: 0 Úkol @@ -1024,7 +1017,7 @@ label_completed_versions: DokonÄené verze text_project_identifier_info: Jsou povolena pouze malá písmena (a-z), Äíslice, pomlÄky a podtržítka.
      Po uložení již nelze identifikátor mÄ›nit. field_multiple: Více hodnot - setting_commit_cross_project_ref: Povolit reference a opravy úklů ze vÅ¡ech ostatních projektů + setting_commit_cross_project_ref: Povolit reference a opravy úkolů ze vÅ¡ech ostatních projektů text_issue_conflict_resolution_add_notes: PÅ™idat moje poznámky a zahodit ostatní zmÄ›ny text_issue_conflict_resolution_overwrite: PÅ™esto pÅ™ijmout moje úpravy (pÅ™edchozí poznámky budou zachovány, ale nÄ›které zmÄ›ny mohou být pÅ™epsány) notice_issue_update_conflict: BÄ›hem vaÅ¡ich úprav byl úkol aktualizován jiným uživatelem. @@ -1066,18 +1059,18 @@ label_attribute_of_author: Autorovo %{name} label_attribute_of_assigned_to: "%{name} pÅ™iÅ™azené(ho)" label_attribute_of_fixed_version: Cílová verze %{name} - label_copy_subtasks: Kopírovat podúkoly + label_copy_subtasks: Kopírovat dílÄí úkoly label_copied_to: zkopírováno do label_copied_from: zkopírováno z label_any_issues_in_project: jakékoli úkoly v projektu - label_any_issues_not_in_project: jakékoli úkoly mimo projektu + label_any_issues_not_in_project: jakékoli úkoly mimo projekt field_private_notes: Soukromé poznámky permission_view_private_notes: Zobrazit soukromé poznámky permission_set_notes_private: Nastavit poznámky jako soukromé label_no_issues_in_project: žádné úkoly v projektu label_any: vÅ¡e label_last_n_weeks: poslední %{count} týdny - setting_cross_project_subtasks: Povolit podúkoly napÅ™Ã­Ä projekty + setting_cross_project_subtasks: Povolit dílÄí úkoly napÅ™Ã­Ä projekty label_cross_project_descendants: S podprojekty label_cross_project_tree: Se stromem projektu label_cross_project_hierarchy: S hierarchií projektu @@ -1086,3 +1079,16 @@ setting_non_working_week_days: Dny pracovního volna/klidu label_in_the_next_days: v přístích label_in_the_past_days: v minulých + label_attribute_of_user: "%{name} uživatel(e/ky)" + text_turning_multiple_off: Jestliže zakážete více hodnot, + hodnoty budou smazány za úÄelem rezervace pouze jediné hodnoty na položku. + label_attribute_of_issue: "%{name} úkolu" + permission_add_documents: PÅ™idat dokument + permission_edit_documents: Upravit dokumenty + permission_delete_documents: Smazet dokumenty + label_gantt_progress_line: Vývojová Äára + setting_jsonp_enabled: Povolit podporu JSONP + field_inherit_members: ZdÄ›dit Äleny + field_closed_on: UzavÅ™eno + setting_default_projects_tracker_ids: Výchozí fronta pro nové projekty + label_total_time: Celkem diff -r 0a574315af3e -r 4f746d8966dd config/locales/da.yml --- a/config/locales/da.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/da.yml Fri Jun 14 09:28:30 2013 +0100 @@ -52,8 +52,8 @@ one: "cirka en time" other: "cirka %{count} timer" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 time" + other: "%{count} timer" x_days: one: "en dag" other: "%{count} dage" @@ -197,8 +197,6 @@ mail_subject_account_activation_request: "%{value} kontoaktivering" mail_body_account_activation_request: "En ny bruger (%{value}) er registreret. Godkend venligst kontoen:" - gui_validation_error: 1 fejl - gui_validation_error_plural: "%{count} fejl" field_name: Navn field_description: Beskrivelse @@ -401,8 +399,6 @@ label_text: Lang tekst label_attribute: Attribut label_attribute_plural: Attributter - label_download: "%{count} Download" - label_download_plural: "%{count} Downloads" label_no_data: Ingen data at vise label_change_status: Ændringsstatus label_history: Historik @@ -502,8 +498,6 @@ label_repository: Repository label_repository_plural: Repositories label_browse: Gennemse - label_modification: "%{count} ændring" - label_modification_plural: "%{count} ændringer" label_revision: Revision label_revision_plural: Revisioner label_associated_revisions: Tilknyttede revisioner @@ -760,7 +754,6 @@ field_parent_title: Siden over text_email_delivery_not_configured: "Email-afsendelse er ikke indstillet og notifikationer er defor slÃ¥et fra.\nKonfigurér din SMTP server i config/configuration.yml og genstart applikationen for at aktivere email-afsendelse." permission_protect_wiki_pages: Beskyt wiki sider - permission_manage_documents: Administrér dokumenter permission_add_issue_watchers: Tilføj overvÃ¥gere warning_attachments_not_saved: "der var %{count} fil(er), som ikke kunne gemmes." permission_comment_news: Kommentér nyheder @@ -949,7 +942,6 @@ label_principal_search: "Søg efter bruger eller gruppe:" label_user_search: "Søg efter bruger:" field_visible: Synlig - setting_emails_header: Emails header setting_commit_logtime_activity_id: Aktivitet for registreret tid text_time_logged_by_changeset: Anvendt i changeset %{value}. setting_commit_logtime_enabled: Aktiver tidsregistrering @@ -991,8 +983,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1099,3 +1089,19 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Total + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header diff -r 0a574315af3e -r 4f746d8966dd config/locales/de.yml --- a/config/locales/de.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/de.yml Fri Jun 14 09:28:30 2013 +0100 @@ -53,8 +53,8 @@ one: 'etwa 1 Stunde' other: 'etwa %{count} Stunden' x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 Stunde" + other: "%{count} Stunden" x_days: one: '1 Tag' other: '%{count} Tagen' @@ -144,957 +144,959 @@ greater_than_start_date: "muss größer als Anfangsdatum sein" not_same_project: "gehört nicht zum selben Projekt" circular_dependency: "Diese Beziehung würde eine zyklische Abhängigkeit erzeugen" - cant_link_an_issue_with_a_descendant: "Ein Ticket kann nicht mit einer ihrer Unteraufgaben verlinkt werden" + cant_link_an_issue_with_a_descendant: "Ein Ticket kann nicht mit einer Ihrer Unteraufgaben verlinkt werden" actionview_instancetag_blank_option: Bitte auswählen + button_activate: Aktivieren + button_add: Hinzufügen + button_annotate: Annotieren + button_apply: Anwenden + button_archive: Archivieren + button_back: Zurück + button_cancel: Abbrechen + button_change: Wechseln + button_change_password: Kennwort ändern + button_check_all: Alles auswählen + button_clear: Zurücksetzen + button_close: Schließen + button_collapse_all: Alle einklappen + button_configure: Konfigurieren + button_copy: Kopieren + button_copy_and_follow: Kopieren und Ticket anzeigen + button_create: Anlegen + button_create_and_continue: Anlegen und weiter + button_delete: Löschen + button_delete_my_account: Mein Benutzerkonto löschen + button_download: Download + button_duplicate: Duplizieren + button_edit: Bearbeiten + button_edit_associated_wikipage: "Zugehörige Wikiseite bearbeiten: %{page_title}" + button_edit_section: Diesen Bereich bearbeiten + button_expand_all: Alle ausklappen + button_export: Exportieren + button_hide: Verstecken + button_list: Liste + button_lock: Sperren + button_log_time: Aufwand buchen + button_login: Anmelden + button_move: Verschieben + button_move_and_follow: Verschieben und Ticket anzeigen + button_quote: Zitieren + button_rename: Umbenennen + button_reopen: Öffnen + button_reply: Antworten + button_reset: Zurücksetzen + button_rollback: Auf diese Version zurücksetzen + button_save: Speichern + button_show: Anzeigen + button_sort: Sortieren + button_submit: OK + button_test: Testen + button_unarchive: Entarchivieren + button_uncheck_all: Alles abwählen + button_unlock: Entsperren + button_unwatch: Nicht beobachten + button_update: Bearbeiten + button_view: Anzeigen + button_watch: Beobachten + + default_activity_design: Design + default_activity_development: Entwicklung + default_doc_category_tech: Technische Dokumentation + default_doc_category_user: Benutzerdokumentation + default_issue_status_closed: Erledigt + default_issue_status_feedback: Feedback + default_issue_status_in_progress: In Bearbeitung + default_issue_status_new: Neu + default_issue_status_rejected: Abgewiesen + default_issue_status_resolved: Gelöst + default_priority_high: Hoch + default_priority_immediate: Sofort + default_priority_low: Niedrig + default_priority_normal: Normal + default_priority_urgent: Dringend + default_role_developer: Entwickler + default_role_manager: Manager + default_role_reporter: Reporter + default_tracker_bug: Fehler + default_tracker_feature: Feature + default_tracker_support: Unterstützung + + description_all_columns: Alle Spalten + description_available_columns: Verfügbare Spalten + description_choose_project: Projekte + description_date_from: Startdatum eintragen + description_date_range_interval: Zeitraum durch Start- und Enddatum festlegen + description_date_range_list: Zeitraum aus einer Liste wählen + description_date_to: Enddatum eintragen + description_filter: Filter + description_issue_category_reassign: Neue Kategorie wählen + description_message_content: Nachrichteninhalt + description_notes: Kommentare + description_project_scope: Suchbereich + description_query_sort_criteria_attribute: Sortierattribut + description_query_sort_criteria_direction: Sortierrichtung + description_search: Suchfeld + description_selected_columns: Ausgewählte Spalten + description_user_mail_notification: Mailbenachrichtigungseinstellung + description_wiki_subpages_reassign: Neue Elternseite wählen + + enumeration_activities: Aktivitäten (Zeiterfassung) + enumeration_doc_categories: Dokumentenkategorien + enumeration_issue_priorities: Ticket-Prioritäten + enumeration_system_activity: System-Aktivität + + error_attachment_too_big: Diese Datei kann nicht hochgeladen werden, da sie die maximale Dateigröße von (%{max_size}) überschreitet. + error_can_not_archive_project: Dieses Projekt kann nicht archiviert werden. + error_can_not_delete_custom_field: Kann das benutzerdefinierte Feld nicht löschen. + error_can_not_delete_tracker: Dieser Tracker enthält Tickets und kann nicht gelöscht werden. + error_can_not_remove_role: Diese Rolle wird verwendet und kann nicht gelöscht werden. + error_can_not_reopen_issue_on_closed_version: Das Ticket ist einer abgeschlossenen Version zugeordnet und kann daher nicht wieder geöffnet werden. + error_can_t_load_default_data: "Die Standard-Konfiguration konnte nicht geladen werden: %{value}" + error_issue_done_ratios_not_updated: Der Ticket-Fortschritt wurde nicht aktualisiert. + error_issue_not_found_in_project: 'Das Ticket wurde nicht gefunden oder gehört nicht zu diesem Projekt.' + error_no_default_issue_status: Es ist kein Status als Standard definiert. Bitte überprüfen Sie Ihre Konfiguration (unter "Administration -> Ticket-Status"). + error_no_tracker_in_project: Diesem Projekt ist kein Tracker zugeordnet. Bitte überprüfen Sie die Projekteinstellungen. + error_scm_annotate: "Der Eintrag existiert nicht oder kann nicht annotiert werden." + error_scm_annotate_big_text_file: Der Eintrag kann nicht umgesetzt werden, da er die maximale Textlänge überschreitet. + error_scm_command_failed: "Beim Zugriff auf das Projektarchiv ist ein Fehler aufgetreten: %{value}" + error_scm_not_found: Eintrag und/oder Revision existiert nicht im Projektarchiv. + error_session_expired: Ihre Sitzung ist abgelaufen. Bitte melden Sie sich erneut an. + error_unable_delete_issue_status: "Der Ticket-Status konnte nicht gelöscht werden." + error_unable_to_connect: Fehler beim Verbinden (%{value}) + error_workflow_copy_source: Bitte wählen Sie einen Quell-Tracker und eine Quell-Rolle. + error_workflow_copy_target: Bitte wählen Sie die Ziel-Tracker und -Rollen. + + field_account: Konto + field_active: Aktiv + field_activity: Aktivität + field_admin: Administrator + field_assignable: Tickets können dieser Rolle zugewiesen werden + field_assigned_to: Zugewiesen an + field_assigned_to_role: Zuständigkeitsrolle + field_attr_firstname: Vorname-Attribut + field_attr_lastname: Name-Attribut + field_attr_login: Mitgliedsname-Attribut + field_attr_mail: E-Mail-Attribut + field_auth_source: Authentifizierungs-Modus + field_auth_source_ldap_filter: LDAP-Filter + field_author: Autor + field_base_dn: Base DN + field_board_parent: Übergeordnetes Forum + field_category: Kategorie + field_column_names: Spalten + field_closed_on: Geschlossen am + field_comments: Kommentar + field_comments_sorting: Kommentare anzeigen + field_commit_logs_encoding: Kodierung der Commit-Log-Meldungen + field_content: Inhalt + field_core_fields: Standardwerte + field_created_on: Angelegt + field_cvs_module: Modul + field_cvsroot: CVSROOT + field_default_value: Standardwert + field_delay: Pufferzeit + field_description: Beschreibung + field_done_ratio: "% erledigt" + field_downloads: Downloads + field_due_date: Abgabedatum + field_editable: Bearbeitbar + field_effective_date: Datum + field_estimated_hours: Geschätzter Aufwand + field_field_format: Format + field_filename: Datei + field_filesize: Größe + field_firstname: Vorname + field_fixed_version: Zielversion + field_group_by: Gruppiere Ergebnisse nach + field_hide_mail: E-Mail-Adresse nicht anzeigen + field_homepage: Projekt-Homepage + field_host: Host + field_hours: Stunden + field_identifier: Kennung + field_identity_url: OpenID-URL + field_inherit_members: Benutzer vererben + field_is_closed: Ticket geschlossen + field_is_default: Standardeinstellung + field_is_filter: Als Filter benutzen + field_is_for_all: Für alle Projekte + field_is_in_roadmap: In der Roadmap anzeigen + field_is_private: Privat + field_is_public: Öffentlich + field_is_required: Erforderlich + field_issue: Ticket + field_issue_to: Zugehöriges Ticket + field_issues_visibility: Ticket Sichtbarkeit + field_language: Sprache + field_last_login_on: Letzte Anmeldung + field_lastname: Nachname + field_login: Mitgliedsname + field_mail: E-Mail + field_mail_notification: Mailbenachrichtigung + field_max_length: Maximale Länge + field_member_of_group: Zuständigkeitsgruppe + field_min_length: Minimale Länge + field_multiple: Mehrere Werte + field_name: Name + field_new_password: Neues Kennwort + field_notes: Kommentare + field_onthefly: On-the-fly-Benutzererstellung + field_parent: Unterprojekt von + field_parent_issue: Übergeordnete Aufgabe + field_parent_title: Übergeordnete Seite + field_password: Kennwort + field_password_confirmation: Bestätigung + field_path_to_repository: Pfad zum Repository + field_port: Port + field_possible_values: Mögliche Werte + field_principal: Auftraggeber + field_priority: Priorität + field_private_notes: Privater Kommentar + field_project: Projekt + field_redirect_existing_links: Existierende Links umleiten + field_regexp: Regulärer Ausdruck + field_repository_is_default: Haupt-Repository + field_role: Rolle + field_root_directory: Wurzelverzeichnis + field_scm_path_encoding: Pfad-Kodierung + field_searchable: Durchsuchbar + field_sharing: Gemeinsame Verwendung + field_spent_on: Datum + field_start_date: Beginn + field_start_page: Hauptseite + field_status: Status + field_subject: Thema + field_subproject: Unterprojekt von + field_summary: Zusammenfassung + field_text: Textfeld + field_time_entries: Logzeit + field_time_zone: Zeitzone + field_timeout: Auszeit (in Sekunden) + field_title: Titel + field_tracker: Tracker + field_type: Typ + field_updated_on: Aktualisiert + field_url: URL + field_user: Benutzer + field_value: Wert + field_version: Version + field_visible: Sichtbar + field_warn_on_leaving_unsaved: Vor dem Verlassen einer Seite mit ungesichertem Text im Editor warnen + field_watcher: Beobachter + + general_csv_decimal_separator: ',' + general_csv_encoding: ISO-8859-1 + general_csv_separator: ';' + general_first_day_of_week: '1' + general_lang_name: 'Deutsch' + general_pdf_encoding: UTF-8 general_text_No: 'Nein' general_text_Yes: 'Ja' general_text_no: 'nein' general_text_yes: 'ja' - general_lang_name: 'Deutsch' - general_csv_separator: ';' - general_csv_decimal_separator: ',' - general_csv_encoding: ISO-8859-1 - general_pdf_encoding: UTF-8 - general_first_day_of_week: '1' - notice_account_updated: Konto wurde erfolgreich aktualisiert. - notice_account_invalid_creditentials: Benutzer oder Kennwort ist ungültig. - notice_account_password_updated: Kennwort wurde erfolgreich aktualisiert. - notice_account_wrong_password: Falsches Kennwort. - notice_account_register_done: Konto wurde erfolgreich angelegt. - notice_account_unknown_email: Unbekannter Benutzer. - notice_can_t_change_password: Dieses Konto verwendet eine externe Authentifizierungs-Quelle. Unmöglich, das Kennwort zu ändern. - notice_account_lost_email_sent: Eine E-Mail mit Anweisungen, ein neues Kennwort zu wählen, wurde Ihnen geschickt. - notice_account_activated: Ihr Konto ist aktiviert. Sie können sich jetzt anmelden. - 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 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 %{value} gesendet." - notice_email_error: "Beim Senden einer E-Mail ist ein Fehler aufgetreten (%{value})." - notice_feeds_access_key_reseted: Ihr Atom-Zugriffsschlüssel wurde zurückgesetzt. - notice_api_access_key_reseted: Ihr API-Zugriffsschlüssel wurde zurückgesetzt. - notice_failed_to_save_issues: "%{count} von %{total} ausgewählten Tickets konnte(n) nicht gespeichert werden: %{ids}." - notice_failed_to_save_members: "Benutzer konnte nicht gespeichert werden: %{errors}." - notice_no_issue_selected: "Kein Ticket ausgewählt! Bitte wählen Sie die Tickets, die Sie bearbeiten möchten." - notice_account_pending: "Ihr Konto wurde erstellt und wartet jetzt auf die Genehmigung des Administrators." - notice_default_data_loaded: Die Standard-Konfiguration wurde erfolgreich geladen. - notice_unable_delete_version: Die Version konnte nicht gelöscht werden. - notice_unable_delete_time_entry: Der Zeiterfassungseintrag konnte nicht gelöscht werden. - notice_issue_done_ratios_updated: Der Ticket-Fortschritt wurde aktualisiert. - - error_can_t_load_default_data: "Die Standard-Konfiguration konnte nicht geladen werden: %{value}" - 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: %{value}" - 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.' - error_no_tracker_in_project: Diesem Projekt ist kein Tracker zugeordnet. Bitte überprüfen Sie die Projekteinstellungen. - error_no_default_issue_status: Es ist kein Status als Standard definiert. Bitte überprüfen Sie Ihre Konfiguration (unter "Administration -> Ticket-Status"). - error_can_not_delete_custom_field: Kann das benutzerdefinierte Feld nicht löschen. - error_can_not_delete_tracker: Dieser Tracker enthält Tickets und kann nicht gelöscht werden. - error_can_not_remove_role: Diese Rolle wird verwendet und kann nicht gelöscht werden. - error_can_not_reopen_issue_on_closed_version: Das Ticket ist einer abgeschlossenen Version zugeordnet und kann daher nicht wieder geöffnet werden. - error_can_not_archive_project: Dieses Projekt kann nicht archiviert werden. - error_issue_done_ratios_not_updated: Der Ticket-Fortschritt wurde nicht aktualisiert. - error_workflow_copy_source: Bitte wählen Sie einen Quell-Tracker und eine Quell-Rolle. - error_workflow_copy_target: Bitte wählen Sie die Ziel-Tracker und -Rollen. - error_unable_delete_issue_status: "Der Ticket-Status konnte nicht gelöscht werden." - error_unable_to_connect: Fehler beim Verbinden (%{value}) - warning_attachments_not_saved: "%{count} Datei(en) konnten nicht gespeichert werden." - - mail_subject_lost_password: "Ihr %{value} Kennwort" - mail_body_lost_password: 'Benutzen Sie den folgenden Link, um Ihr Kennwort zu ändern:' - mail_subject_register: "%{value} Kontoaktivierung" - mail_body_register: 'Um Ihr Konto zu aktivieren, benutzen Sie folgenden Link:' - mail_body_account_information_external: "Sie können sich mit Ihrem Konto %{value} anmelden." - mail_body_account_information: Ihre Konto-Informationen - mail_subject_account_activation_request: "Antrag auf %{value} Kontoaktivierung" - mail_body_account_activation_request: "Ein neuer Benutzer (%{value}) hat sich registriert. Sein Konto wartet auf Ihre Genehmigung:" - mail_subject_reminder: "%{count} Tickets müssen in den nächsten %{days} Tagen abgegeben werden" - mail_body_reminder: "%{count} Tickets, die Ihnen zugewiesen sind, müssen in den nächsten %{days} Tagen abgegeben werden:" - mail_subject_wiki_content_added: "Wiki-Seite '%{id}' hinzugefügt" - mail_body_wiki_content_added: "Die Wiki-Seite '%{id}' wurde von %{author} hinzugefügt." - mail_subject_wiki_content_updated: "Wiki-Seite '%{id}' erfolgreich aktualisiert" - mail_body_wiki_content_updated: "Die Wiki-Seite '%{id}' wurde von %{author} aktualisiert." - - gui_validation_error: 1 Fehler - gui_validation_error_plural: "%{count} Fehler" - - field_name: Name - field_description: Beschreibung - field_summary: Zusammenfassung - field_is_required: Erforderlich - field_firstname: Vorname - field_lastname: Nachname - field_mail: E-Mail - field_filename: Datei - field_filesize: Größe - field_downloads: Downloads - field_author: Autor - field_created_on: Angelegt - field_updated_on: Aktualisiert - field_field_format: Format - field_is_for_all: Für alle Projekte - field_possible_values: Mögliche Werte - field_regexp: Regulärer Ausdruck - field_min_length: Minimale Länge - field_max_length: Maximale Länge - field_value: Wert - field_category: Kategorie - field_title: Titel - field_project: Projekt - field_issue: Ticket - field_status: Status - field_notes: Kommentare - field_is_closed: Ticket geschlossen - field_is_default: Standardeinstellung - field_tracker: Tracker - field_subject: Thema - field_due_date: Abgabedatum - field_assigned_to: Zugewiesen an - field_priority: Priorität - field_fixed_version: Zielversion - field_user: Benutzer - field_principal: Auftraggeber - field_role: Rolle - field_homepage: Projekt-Homepage - field_is_public: Öffentlich - field_parent: Unterprojekt von - field_is_in_roadmap: In der Roadmap anzeigen - field_login: Mitgliedsname - field_mail_notification: Mailbenachrichtigung - field_admin: Administrator - field_last_login_on: Letzte Anmeldung - field_language: Sprache - field_effective_date: Datum - field_password: Kennwort - field_new_password: Neues Kennwort - field_password_confirmation: Bestätigung - field_version: Version - field_type: Typ - field_host: Host - field_port: Port - field_account: Konto - field_base_dn: Base DN - field_attr_login: Mitgliedsname-Attribut - field_attr_firstname: Vorname-Attribut - field_attr_lastname: Name-Attribut - field_attr_mail: E-Mail-Attribut - field_onthefly: On-the-fly-Benutzererstellung - field_start_date: Beginn - field_done_ratio: "% erledigt" - field_auth_source: Authentifizierungs-Modus - field_hide_mail: E-Mail-Adresse nicht anzeigen - field_comments: Kommentar - field_url: URL - field_start_page: Hauptseite - field_subproject: Unterprojekt von - field_hours: Stunden - field_activity: Aktivität - field_spent_on: Datum - field_identifier: Kennung - field_is_filter: Als Filter benutzen - field_issue_to: Zugehöriges Ticket - field_delay: Pufferzeit - field_assignable: Tickets können dieser Rolle zugewiesen werden - field_redirect_existing_links: Existierende Links umleiten - field_estimated_hours: Geschätzter Aufwand - field_column_names: Spalten - field_time_entries: Logzeit - field_time_zone: Zeitzone - field_searchable: Durchsuchbar - field_default_value: Standardwert - field_comments_sorting: Kommentare anzeigen - field_parent_title: Übergeordnete Seite - field_editable: Bearbeitbar - field_watcher: Beobachter - field_identity_url: OpenID-URL - field_content: Inhalt - field_group_by: Gruppiere Ergebnisse nach - field_sharing: Gemeinsame Verwendung - field_parent_issue: Übergeordnete Aufgabe - - setting_app_title: Applikations-Titel - setting_app_subtitle: Applikations-Untertitel - setting_welcome_text: Willkommenstext - setting_default_language: Standardsprache - setting_login_required: Authentifizierung erforderlich - setting_self_registration: Anmeldung ermöglicht - 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 - setting_feeds_limit: Max. Anzahl Einträge pro Atom-Feed - setting_default_projects_public: Neue Projekte sind standardmäßig öffentlich - setting_autofetch_changesets: Changesets automatisch abrufen - setting_sys_api_enabled: Webservice zur Verwaltung der Projektarchive benutzen - setting_commit_ref_keywords: Schlüsselwörter (Beziehungen) - setting_commit_fix_keywords: Schlüsselwörter (Status) - setting_autologin: Automatische Anmeldung - setting_date_format: Datumsformat - setting_time_format: Zeitformat - setting_cross_project_issue_relations: Ticket-Beziehungen zwischen Projekten erlauben - setting_issue_list_default_columns: Standard-Spalten in der Ticket-Auflistung - setting_emails_footer: E-Mail-Fußzeile - setting_protocol: Protokoll - setting_per_page_options: Objekte pro Seite - setting_user_format: Benutzer-Anzeigeformat - setting_activity_days_default: Anzahl Tage pro Seite der Projekt-Aktivität - setting_display_subprojects_issues: Tickets von Unterprojekten im Hauptprojekt anzeigen - setting_enabled_scm: Aktivierte Versionskontrollsysteme - setting_mail_handler_body_delimiters: "Schneide E-Mails nach einer dieser Zeilen ab" - 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_gravatar_default: Standard-Gravatar-Bild - setting_diff_max_lines_displayed: Maximale Anzahl anzuzeigender Diff-Zeilen - setting_file_max_size_displayed: Maximale Größe inline angezeigter Textdateien - setting_repository_log_display_limit: Maximale Anzahl anzuzeigender Revisionen in der Historie einer Datei - setting_openid: Erlaube OpenID-Anmeldung und -Registrierung - setting_password_min_length: Mindestlänge des Kennworts - setting_new_project_user_role_id: Rolle, die einem Nicht-Administrator zugeordnet wird, der ein Projekt erstellt - setting_default_projects_modules: Standardmäßig aktivierte Module für neue Projekte - setting_issue_done_ratio: Berechne den Ticket-Fortschritt mittels - setting_issue_done_ratio_issue_field: Ticket-Feld %-erledigt - setting_issue_done_ratio_issue_status: Ticket-Status - setting_start_of_week: Wochenanfang - setting_rest_api_enabled: REST-Schnittstelle aktivieren - setting_cache_formatted_text: Formatierten Text im Cache speichern - - permission_add_project: Projekt erstellen - permission_add_subprojects: Unterprojekte erstellen - permission_edit_project: Projekt bearbeiten - permission_select_project_modules: Projektmodule auswählen - permission_manage_members: Mitglieder verwalten - permission_manage_project_activities: Aktivitäten (Zeiterfassung) verwalten - permission_manage_versions: Versionen verwalten - permission_manage_categories: Ticket-Kategorien verwalten - permission_view_issues: Tickets anzeigen - 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_delete_issue_watchers: Beobachter löschen - 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 - permission_export_wiki_pages: Wiki-Seiten exportieren - permission_manage_subtasks: Unteraufgaben verwalten - - project_module_issue_tracking: Ticket-Verfolgung - project_module_time_tracking: Zeiterfassung - project_module_news: News - project_module_documents: Dokumente - project_module_files: Dateien - project_module_wiki: Wiki - project_module_repository: Projektarchiv - project_module_boards: Foren - project_module_calendar: Kalender - project_module_gantt: Gantt - - label_user: Benutzer - label_user_plural: Benutzer - label_user_new: Neuer Benutzer - label_user_anonymous: Anonym + label_home_heading: Hauptseite + label_activity: Aktivität + label_add_another_file: Eine weitere Datei hinzufügen + label_add_note: Kommentar hinzufügen + label_added: hinzugefügt + label_added_time_by: "Von %{author} vor %{age} hinzugefügt" + label_additional_workflow_transitions_for_assignee: Zusätzliche Berechtigungen wenn der Benutzer der Zugewiesene ist + label_additional_workflow_transitions_for_author: Zusätzliche Berechtigungen wenn der Benutzer der Autor ist + label_administration: Administration + label_age: Geändert vor + label_ago: vor + label_all: alle + label_all_time: gesamter Zeitraum + label_all_words: Alle Wörter + label_and_its_subprojects: "%{value} und dessen Unterprojekte" + label_any: alle + label_any_issues_in_project: irgendein Ticket im Projekt + label_any_issues_not_in_project: irgendein Ticket nicht im Projekt + label_api_access_key: API-Zugriffsschlüssel + label_api_access_key_created_on: Der API-Zugriffsschlüssel wurde vor %{value} erstellt + label_applied_status: Zugewiesener Status + label_ascending: Aufsteigend + label_assigned_to_me_issues: Mir zugewiesene Tickets + label_associated_revisions: Zugehörige Revisionen + label_attachment: Datei + label_attachment_delete: Anhang löschen + label_attachment_new: Neue Datei + label_attachment_plural: Dateien + label_attribute: Attribut + label_attribute_of_assigned_to: "%{name} des Bearbeiters" + label_attribute_of_author: "%{name} des Autors" + label_attribute_of_fixed_version: "%{name} der Zielversion" + label_attribute_of_issue: "%{name} des Tickets" + label_attribute_of_project: "%{name} des Projekts" + label_attribute_of_user: "%{name} des Benutzers" + label_attribute_plural: Attribute + label_auth_source: Authentifizierungs-Modus + label_auth_source_new: Neuer Authentifizierungs-Modus + label_auth_source_plural: Authentifizierungs-Arten + label_authentication: Authentifizierung + label_between: zwischen + label_blocked_by: Blockiert durch + label_blocks: Blockiert + label_board: Forum + label_board_locked: Gesperrt + label_board_new: Neues Forum + label_board_plural: Foren + label_board_sticky: Wichtig (immer oben) + label_boolean: Boolean + label_branch: Zweig + label_browse: Codebrowser + label_bulk_edit_selected_issues: Alle ausgewählten Tickets bearbeiten + label_bulk_edit_selected_time_entries: Ausgewählte Zeitaufwände bearbeiten + label_calendar: Kalender + label_change_plural: Änderungen + label_change_properties: Eigenschaften ändern + label_change_status: Statuswechsel + label_change_view_all: Alle Änderungen anzeigen + label_changes_details: Details aller Änderungen + label_changeset_plural: Changesets + label_child_revision: Nachfolger + label_chronological_order: in zeitlicher Reihenfolge + label_close_versions: Vollständige Versionen schließen + label_closed_issues: geschlossen + label_closed_issues_plural: geschlossen + label_comment: Kommentar + label_comment_add: Kommentar hinzufügen + label_comment_added: Kommentar hinzugefügt + label_comment_delete: Kommentar löschen + label_comment_plural: Kommentare + label_commits_per_author: Übertragungen pro Autor + label_commits_per_month: Übertragungen pro Monat + label_completed_versions: Abgeschlossene Versionen + label_confirmation: Bestätigung + label_contains: enthält + label_copied: kopiert + label_copied_from: Kopiert von + label_copied_to: Kopiert nach + label_copy_attachments: Anhänge kopieren + label_copy_same_as_target: So wie das Ziel + label_copy_source: Quelle + label_copy_subtasks: Unteraufgaben kopieren + label_copy_target: Ziel + label_copy_workflow_from: Workflow kopieren von + label_cross_project_descendants: Mit Unterprojekten + label_cross_project_hierarchy: Mit Projekthierarchie + label_cross_project_system: Mit allen Projekten + label_cross_project_tree: Mit Projektbaum + label_current_status: Gegenwärtiger Status + label_current_version: Gegenwärtige Version + label_custom_field: Benutzerdefiniertes Feld + label_custom_field_new: Neues Feld + label_custom_field_plural: Benutzerdefinierte Felder + label_date: Datum + label_date_from: Von + label_date_from_to: von %{start} bis %{end} + label_date_range: Zeitraum + label_date_to: Bis + label_day_plural: Tage + label_default: Standard + label_default_columns: Standard-Spalten + label_deleted: gelöscht + label_descending: Absteigend + label_details: Details + label_diff: diff + label_diff_inline: einspaltig + label_diff_side_by_side: nebeneinander + label_disabled: gesperrt + label_display: Anzeige + label_display_per_page: "Pro Seite: %{value}" + label_display_used_statuses_only: Zeige nur Status an, die von diesem Tracker verwendet werden + label_document: Dokument + label_document_added: Dokument hinzugefügt + label_document_new: Neues Dokument + label_document_plural: Dokumente + label_downloads_abbr: D/L + label_duplicated_by: Dupliziert durch + label_duplicates: Duplikat von + label_end_to_end: Ende - Ende + label_end_to_start: Ende - Anfang + label_enumeration_new: Neuer Wert + label_enumerations: Aufzählungen + label_environment: Umgebung + label_equals: ist + label_example: Beispiel + label_export_options: "%{export_format} Export-Eigenschaften" + label_export_to: "Auch abrufbar als:" + label_f_hour: "%{value} Stunde" + label_f_hour_plural: "%{value} Stunden" + label_feed_plural: Feeds + label_feeds_access_key: Atom-Zugriffsschlüssel + label_feeds_access_key_created_on: "Atom-Zugriffsschlüssel vor %{value} erstellt" + label_fields_permissions: Feldberechtigungen + label_file_added: Datei hinzugefügt + label_file_plural: Dateien + label_filter_add: Filter hinzufügen + label_filter_plural: Filter + label_float: Fließkommazahl + label_follows: Nachfolger von + label_gantt: Gantt-Diagramm + label_gantt_progress_line: Fortschrittslinie + label_general: Allgemein + label_generate_key: Generieren + label_git_report_last_commit: Bericht des letzten Commits für Dateien und Verzeichnisse + label_greater_or_equal: ">=" + label_group: Gruppe + label_group_new: Neue Gruppe + label_group_plural: Gruppen + label_help: Hilfe + label_history: Historie + label_home: Hauptseite + label_in: in + label_in_less_than: in weniger als + label_in_more_than: in mehr als + label_in_the_next_days: in den nächsten + label_in_the_past_days: in den letzten + label_incoming_emails: Eingehende E-Mails + label_index_by_date: Seiten nach Datum sortiert + label_index_by_title: Seiten nach Titel sortiert + label_information: Information + label_information_plural: Informationen + label_integer: Zahl + label_internal: Intern + label_issue: Ticket + label_issue_added: Ticket hinzugefügt + label_issue_category: Ticket-Kategorie + label_issue_category_new: Neue Kategorie + label_issue_category_plural: Ticket-Kategorien + label_issue_new: Neues Ticket + label_issue_note_added: Notiz hinzugefügt + label_issue_plural: Tickets + label_issue_priority_updated: Priorität aktualisiert + label_issue_status: Ticket-Status + label_issue_status_new: Neuer Status + label_issue_status_plural: Ticket-Status + label_issue_status_updated: Status aktualisiert + label_issue_tracking: Tickets + label_issue_updated: Ticket aktualisiert + label_issue_view_all: Alle Tickets anzeigen + label_issue_watchers: Beobachter + label_issues_by: "Tickets pro %{value}" + label_issues_visibility_all: Alle Tickets + label_issues_visibility_own: Tickets die folgender Benutzer erstellt hat oder die ihm zugewiesen sind + label_issues_visibility_public: Alle öffentlichen Tickets + label_item_position: "%{position}/%{count}" + label_jump_to_a_project: Zu einem Projekt springen... + label_language_based: Sprachabhängig + label_last_changes: "%{count} letzte Änderungen" + label_last_login: Letzte Anmeldung + label_last_month: voriger Monat + label_last_n_days: "die letzten %{count} Tage" + label_last_n_weeks: letzte %{count} Wochen + label_last_week: vorige Woche + label_latest_revision: Aktuellste Revision + label_latest_revision_plural: Aktuellste Revisionen + label_ldap_authentication: LDAP-Authentifizierung + label_less_or_equal: "<=" + label_less_than_ago: vor weniger als + label_list: Liste + label_loading: Lade... + label_logged_as: Angemeldet als + label_login: Anmelden + label_login_with_open_id_option: oder mit OpenID anmelden + label_logout: Abmelden + label_max_size: Maximale Größe + label_me: ich + label_member: Mitglied + label_member_new: Neues Mitglied + label_member_plural: Mitglieder + label_message_last: Letzter Forenbeitrag + label_message_new: Neues Thema + label_message_plural: Forenbeiträge + label_message_posted: Forenbeitrag hinzugefügt + label_min_max_length: Länge (Min. - Max.) + label_missing_api_access_key: Der API-Zugriffsschlüssel fehlt. + label_missing_feeds_access_key: Der Atom-Zugriffsschlüssel fehlt. + label_modified: geändert + label_module_plural: Module + label_month: Monat + label_months_from: Monate ab + label_more: Mehr + label_more_than_ago: vor mehr als + label_my_account: Mein Konto + label_my_page: Meine Seite + label_my_page_block: Verfügbare Widgets + label_my_projects: Meine Projekte + label_my_queries: Meine eigenen Abfragen + label_new: Neu + label_new_statuses_allowed: Neue Berechtigungen + label_news: News + label_news_added: News hinzugefügt + label_news_comment_added: Kommentar zu einer News hinzugefügt + label_news_latest: Letzte News + label_news_new: News hinzufügen + label_news_plural: News + label_news_view_all: Alle News anzeigen + label_next: Weiter + label_no_change_option: (Keine Änderung) + label_no_data: Nichts anzuzeigen + label_no_issues_in_project: keine Tickets im Projekt + label_nobody: Niemand + label_none: kein + label_not_contains: enthält nicht + label_not_equals: ist nicht + label_open_issues: offen + label_open_issues_plural: offen + label_optional_description: Beschreibung (optional) + label_options: Optionen + label_overall_activity: Aktivitäten aller Projekte anzeigen + label_overall_spent_time: Aufgewendete Zeit aller Projekte anzeigen + label_overview: Übersicht + label_parent_revision: Vorgänger + label_password_lost: Kennwort vergessen + label_per_page: Pro Seite + label_permissions: Berechtigungen + label_permissions_report: Berechtigungsübersicht + label_personalize_page: Diese Seite anpassen + label_planning: Terminplanung + label_please_login: Anmelden + label_plugins: Plugins + label_precedes: Vorgänger von + label_preferences: Präferenzen + label_preview: Vorschau + label_previous: Zurück + label_principal_search: "Nach Benutzer oder Gruppe suchen:" + label_profile: Profil label_project: Projekt + label_project_all: Alle Projekte + label_project_copy_notifications: Sende Mailbenachrichtigungen beim Kopieren des Projekts. + label_project_latest: Neueste Projekte label_project_new: Neues Projekt label_project_plural: Projekte + label_public_projects: Öffentliche Projekte + label_query: Benutzerdefinierte Abfrage + label_query_new: Neue Abfrage + label_query_plural: Benutzerdefinierte Abfragen + label_read: Lesen... + label_readonly: Nur-Lese-Zugriff + label_register: Registrieren + label_registered_on: Angemeldet am + label_registration_activation_by_email: Kontoaktivierung durch E-Mail + label_registration_automatic_activation: Automatische Kontoaktivierung + label_registration_manual_activation: Manuelle Kontoaktivierung + label_related_issues: Zugehörige Tickets + label_relates_to: Beziehung mit + label_relation_delete: Beziehung löschen + label_relation_new: Neue Beziehung + label_renamed: umbenannt + label_reply_plural: Antworten + label_report: Bericht + label_report_plural: Berichte + label_reported_issues: Erstellte Tickets + label_repository: Projektarchiv + label_repository_new: Neues Repository + label_repository_plural: Projektarchive + label_required: Erforderlich + label_result_plural: Resultate + label_reverse_chronological_order: in umgekehrter zeitlicher Reihenfolge + label_revision: Revision + label_revision_id: Revision %{value} + label_revision_plural: Revisionen + label_roadmap: Roadmap + label_roadmap_due_in: "Fällig in %{value}" + label_roadmap_no_issues: Keine Tickets für diese Version + label_roadmap_overdue: "%{value} verspätet" + label_role: Rolle + label_role_and_permissions: Rollen und Rechte + label_role_anonymous: Anonymous + label_role_new: Neue Rolle + label_role_non_member: Nichtmitglied + label_role_plural: Rollen + label_scm: Versionskontrollsystem + label_search: Suche + label_search_for_watchers: Nach hinzufügbaren Beobachtern suchen + label_search_titles_only: Nur Titel durchsuchen + label_send_information: Sende Kontoinformationen an Benutzer + label_send_test_email: Test-E-Mail senden + label_session_expiration: Ende einer Sitzung + label_settings: Konfiguration + label_show_closed_projects: Geschlossene Projekte anzeigen + label_show_completed_versions: Abgeschlossene Versionen anzeigen + label_sort: Sortierung + label_sort_by: "Sortiert nach %{value}" + label_sort_higher: Eins höher + label_sort_highest: An den Anfang + label_sort_lower: Eins tiefer + label_sort_lowest: Ans Ende + label_spent_time: Aufgewendete Zeit + label_start_to_end: Anfang - Ende + label_start_to_start: Anfang - Anfang + label_statistics: Statistiken + label_status_transitions: Statusänderungen + label_stay_logged_in: Angemeldet bleiben + label_string: Text + label_subproject_new: Neues Unterprojekt + label_subproject_plural: Unterprojekte + label_subtask_plural: Unteraufgaben + label_tag: Markierung + label_text: Langer Text + label_theme: Stil + label_this_month: aktueller Monat + label_this_week: aktuelle Woche + label_this_year: aktuelles Jahr + label_time_entry_plural: Benötigte Zeit + label_time_tracking: Zeiterfassung + label_today: heute + label_topic_plural: Themen + label_total: Gesamtzahl + label_tracker: Tracker + label_tracker_new: Neuer Tracker + label_tracker_plural: Tracker + label_update_issue_done_ratios: Ticket-Fortschritt aktualisieren + label_updated_time: "Vor %{value} aktualisiert" + label_updated_time_by: "Von %{author} vor %{age} aktualisiert" + label_used_by: Benutzt von + label_user: Benutzer + label_user_activity: "Aktivität von %{value}" + label_user_anonymous: Anonym + label_user_mail_no_self_notified: "Ich möchte nicht über Änderungen benachrichtigt werden, die ich selbst durchführe." + label_user_mail_option_all: "Für alle Ereignisse in all meinen Projekten" + label_user_mail_option_none: Keine Ereignisse + label_user_mail_option_only_assigned: Nur für Aufgaben für die ich zuständig bin + label_user_mail_option_only_my_events: Nur für Aufgaben die ich beobachte oder an welchen ich mitarbeite + label_user_mail_option_only_owner: Nur für Aufgaben die ich angelegt habe + label_user_mail_option_selected: "Für alle Ereignisse in den ausgewählten Projekten..." + label_user_new: Neuer Benutzer + label_user_plural: Benutzer + label_user_search: "Nach Benutzer suchen:" + label_version: Version + label_version_new: Neue Version + label_version_plural: Versionen + label_version_sharing_descendants: Mit Unterprojekten + label_version_sharing_hierarchy: Mit Projekthierarchie + label_version_sharing_none: Nicht gemeinsam verwenden + label_version_sharing_system: Mit allen Projekten + label_version_sharing_tree: Mit Projektbaum + label_view_all_revisions: Alle Revisionen anzeigen + label_view_diff: Unterschiede anzeigen + label_view_revisions: Revisionen anzeigen + label_watched_issues: Beobachtete Tickets + label_week: Woche + label_wiki: Wiki + label_wiki_content_added: Wiki-Seite hinzugefügt + label_wiki_content_updated: Wiki-Seite aktualisiert + label_wiki_edit: Wiki-Bearbeitung + label_wiki_edit_plural: Wiki-Bearbeitungen + label_wiki_page: Wiki-Seite + label_wiki_page_plural: Wiki-Seiten + label_workflow: Workflow + label_x_closed_issues_abbr: + zero: 0 geschlossen + one: 1 geschlossen + other: "%{count} geschlossen" + label_x_comments: + zero: keine Kommentare + one: 1 Kommentar + other: "%{count} Kommentare" + label_x_issues: + zero: 0 Tickets + one: 1 Ticket + other: "%{count} Tickets" + label_x_open_issues_abbr: + zero: 0 offen + one: 1 offen + other: "%{count} offen" + label_x_open_issues_abbr_on_total: + zero: 0 offen / %{total} + one: 1 offen / %{total} + other: "%{count} offen / %{total}" label_x_projects: zero: keine Projekte one: 1 Projekt other: "%{count} Projekte" - label_project_all: Alle Projekte - label_project_latest: Neueste Projekte - label_issue: Ticket - label_issue_new: Neues Ticket - label_issue_plural: Tickets - label_issue_view_all: Alle Tickets anzeigen - label_issues_by: "Tickets von %{value}" - label_issue_added: Ticket hinzugefügt - label_issue_updated: Ticket aktualisiert - label_document: Dokument - label_document_new: Neues Dokument - label_document_plural: Dokumente - label_document_added: Dokument hinzugefügt - label_role: Rolle - label_role_plural: Rollen - label_role_new: Neue Rolle - label_role_and_permissions: Rollen und Rechte - label_member: Mitglied - label_member_new: Neues Mitglied - label_member_plural: Mitglieder - label_tracker: Tracker - label_tracker_plural: Tracker - label_tracker_new: Neuer Tracker - label_workflow: Workflow - label_issue_status: Ticket-Status - label_issue_status_plural: Ticket-Status - label_issue_status_new: Neuer Status - label_issue_category: Ticket-Kategorie - label_issue_category_plural: Ticket-Kategorien - label_issue_category_new: Neue Kategorie - label_custom_field: Benutzerdefiniertes Feld - label_custom_field_plural: Benutzerdefinierte Felder - label_custom_field_new: Neues Feld - label_enumerations: Aufzählungen - label_enumeration_new: Neuer Wert - label_information: Information - label_information_plural: Informationen - label_please_login: Anmelden - label_register: Registrieren - label_login_with_open_id_option: oder mit OpenID anmelden - label_password_lost: Kennwort vergessen - label_home: Hauptseite - label_home_heading: Hauptseite - label_my_page: Meine Seite - label_my_account: Mein Konto - label_my_projects: Meine Projekte - label_my_page_block: Bereich "Meine Seite" - label_administration: Administration - label_login: Anmelden - label_logout: Abmelden - label_help: Hilfe - label_reported_issues: Gemeldete Tickets - label_assigned_to_me_issues: Mir zugewiesen - label_last_login: Letzte Anmeldung - label_registered_on: Angemeldet am - label_activity: Aktivität - label_overall_activity: Aktivitäten aller Projekte anzeigen - label_user_activity: "Aktivität von %{value}" - label_new: Neu - label_logged_as: Angemeldet als - label_environment: Umgebung - label_authentication: Authentifizierung - label_auth_source: Authentifizierungs-Modus - label_auth_source_new: Neuer Authentifizierungs-Modus - label_auth_source_plural: Authentifizierungs-Arten - label_subproject_plural: Unterprojekte - label_subproject_new: Neues Unterprojekt - label_and_its_subprojects: "%{value} und dessen Unterprojekte" - label_min_max_length: Länge (Min. - Max.) - label_list: Liste - label_date: Datum - label_integer: Zahl - label_float: Fließkommazahl - label_boolean: Boolean - label_string: Text - label_text: Langer Text - label_attribute: Attribut - label_attribute_plural: Attribute - label_download: "%{count} Download" - label_download_plural: "%{count} Downloads" - label_no_data: Nichts anzuzeigen - label_change_status: Statuswechsel - label_history: Historie - label_attachment: Datei - label_attachment_new: Neue Datei - label_attachment_delete: Anhang löschen - label_attachment_plural: Dateien - label_file_added: Datei hinzugefügt - label_report: Bericht - label_report_plural: Berichte - label_news: News - label_news_new: News hinzufügen - label_news_plural: News - label_news_latest: Letzte News - label_news_view_all: Alle News anzeigen - label_news_added: News hinzugefügt - label_settings: Konfiguration - label_overview: Übersicht - label_version: Version - label_version_new: Neue Version - label_version_plural: Versionen - label_close_versions: Vollständige Versionen schließen - label_confirmation: Bestätigung - label_export_to: "Auch abrufbar als:" - label_read: Lesen... - label_public_projects: Öffentliche Projekte - label_open_issues: offen - label_open_issues_plural: offen - label_closed_issues: geschlossen - label_closed_issues_plural: geschlossen - label_x_open_issues_abbr_on_total: - zero: 0 offen / %{total} - one: 1 offen / %{total} - other: "%{count} offen / %{total}" - label_x_open_issues_abbr: - zero: 0 offen - one: 1 offen - other: "%{count} offen" - label_x_closed_issues_abbr: - zero: 0 geschlossen - one: 1 geschlossen - other: "%{count} geschlossen" - label_total: Gesamtzahl - label_permissions: Berechtigungen - label_current_status: Gegenwärtiger Status - label_new_statuses_allowed: Neue Berechtigungen - label_all: alle - label_none: kein - label_nobody: Niemand - label_next: Weiter - label_previous: Zurück - label_used_by: Benutzt von - label_details: Details - label_add_note: Kommentar hinzufügen - label_per_page: Pro Seite - label_calendar: Kalender - label_months_from: Monate ab - label_gantt: Gantt-Diagramm - label_internal: Intern - label_last_changes: "%{count} letzte Änderungen" - label_change_view_all: Alle Änderungen anzeigen - label_personalize_page: Diese Seite anpassen - label_comment: Kommentar - label_comment_plural: Kommentare - label_x_comments: - zero: keine Kommentare - one: 1 Kommentar - other: "%{count} Kommentare" - label_comment_add: Kommentar hinzufügen - label_comment_added: Kommentar hinzugefügt - label_comment_delete: Kommentar löschen - label_query: Benutzerdefinierte Abfrage - label_query_plural: Benutzerdefinierte Berichte - label_query_new: Neuer Bericht - label_filter_add: Filter hinzufügen - label_filter_plural: Filter - label_equals: ist - label_not_equals: ist nicht - label_in_less_than: in weniger als - label_in_more_than: in mehr als - label_greater_or_equal: ">=" - label_less_or_equal: "<=" - label_in: an - label_today: heute - label_all_time: gesamter Zeitraum + label_year: Jahr label_yesterday: gestern - label_this_week: aktuelle Woche - label_last_week: vorige Woche - label_last_n_days: "die letzten %{count} Tage" - label_this_month: aktueller Monat - label_last_month: voriger Monat - label_this_year: aktuelles Jahr - label_date_range: Zeitraum - label_less_than_ago: vor weniger als - label_more_than_ago: vor mehr als - label_ago: vor - label_contains: enthält - label_not_contains: enthält nicht - label_day_plural: Tage - label_repository: Projektarchiv - label_repository_plural: Projektarchive - label_browse: Codebrowser - label_modification: "%{count} Änderung" - label_modification_plural: "%{count} Änderungen" - label_branch: Zweig - label_tag: Markierung - label_revision: Revision - label_revision_plural: Revisionen - label_revision_id: Revision %{value} - label_associated_revisions: Zugehörige Revisionen - label_added: hinzugefügt - label_modified: geändert - label_copied: kopiert - label_renamed: umbenannt - label_deleted: gelöscht - label_latest_revision: Aktuellste Revision - label_latest_revision_plural: Aktuellste Revisionen - label_view_revisions: Revisionen anzeigen - label_view_all_revisions: Alle Revisionen anzeigen - label_max_size: Maximale Größe - label_sort_highest: An den Anfang - 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 %{value}" - label_roadmap_overdue: "%{value} verspätet" - label_roadmap_no_issues: Keine Tickets für diese Version - label_search: Suche - label_result_plural: Resultate - label_all_words: Alle Wörter - label_wiki: Wiki - label_wiki_edit: Wiki-Bearbeitung - label_wiki_edit_plural: Wiki-Bearbeitungen - label_wiki_page: Wiki-Seite - label_wiki_page_plural: Wiki-Seiten - label_index_by_title: Seiten nach Titel sortiert - label_index_by_date: Seiten nach Datum sortiert - label_current_version: Gegenwärtige Version - label_preview: Vorschau - label_feed_plural: Feeds - label_changes_details: Details aller Änderungen - label_issue_tracking: Tickets - label_spent_time: Aufgewendete Zeit - label_overall_spent_time: Aufgewendete Zeit aller Projekte anzeigen - label_f_hour: "%{value} Stunde" - label_f_hour_plural: "%{value} Stunden" - label_time_tracking: Zeiterfassung - label_change_plural: Änderungen - label_statistics: Statistiken - label_commits_per_month: Übertragungen pro Monat - label_commits_per_author: Übertragungen pro Autor - label_view_diff: Unterschiede anzeigen - label_diff_inline: einspaltig - label_diff_side_by_side: nebeneinander - label_options: Optionen - label_copy_workflow_from: Workflow kopieren von - label_permissions_report: Berechtigungsübersicht - label_watched_issues: Beobachtete Tickets - label_related_issues: Zugehörige Tickets - label_applied_status: Zugewiesener Status - label_loading: Lade... - label_relation_new: Neue Beziehung - label_relation_delete: Beziehung löschen - label_relates_to: Beziehung mit - label_duplicates: Duplikat von - label_duplicated_by: Dupliziert durch - label_blocks: Blockiert - label_blocked_by: Blockiert durch - label_precedes: Vorgänger von - label_follows: Folgt - label_end_to_start: Ende - Anfang - label_end_to_end: Ende - Ende - label_start_to_start: Anfang - Anfang - label_start_to_end: Anfang - Ende - label_stay_logged_in: Angemeldet bleiben - label_disabled: gesperrt - label_show_completed_versions: Abgeschlossene Versionen anzeigen - label_me: ich - label_board: Forum - label_board_new: Neues Forum - label_board_plural: Foren - label_board_locked: Gesperrt - label_board_sticky: Wichtig (immer oben) - label_topic_plural: Themen - 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 an Benutzer - label_year: Jahr - label_month: Monat - label_week: Woche - label_date_from: Von - label_date_to: Bis - label_language_based: Sprachabhängig - label_sort_by: "Sortiert nach %{value}" - label_send_test_email: Test-E-Mail senden - label_feeds_access_key: RSS-Zugriffsschlüssel - label_missing_feeds_access_key: Der RSS-Zugriffsschlüssel fehlt. - label_feeds_access_key_created_on: "Atom-Zugriffsschlüssel vor %{value} erstellt" - label_module_plural: Module - label_added_time_by: "Von %{author} vor %{age} hinzugefügt" - label_updated_time_by: "Von %{author} vor %{age} aktualisiert" - label_updated_time: "Vor %{value} aktualisiert" - label_jump_to_a_project: Zu einem Projekt springen... - label_file_plural: Dateien - label_changeset_plural: Changesets - label_default_columns: Standard-Spalten - label_no_change_option: (Keine Änderung) - label_bulk_edit_selected_issues: Alle ausgewählten Tickets bearbeiten - label_theme: Stil - label_default: Standard - label_search_titles_only: Nur Titel durchsuchen - label_user_mail_option_all: "Für alle Ereignisse in all meinen Projekten" - label_user_mail_option_selected: "Für alle Ereignisse in den ausgewählten Projekten..." - label_user_mail_no_self_notified: "Ich möchte nicht über Änderungen benachrichtigt werden, die ich selbst durchführe." - label_registration_activation_by_email: Kontoaktivierung durch E-Mail - label_registration_manual_activation: Manuelle Kontoaktivierung - label_registration_automatic_activation: Automatische Kontoaktivierung - label_display_per_page: "Pro Seite: %{value}" - label_age: Geändert vor - label_change_properties: Eigenschaften ändern - label_general: Allgemein - label_more: Mehr - label_scm: Versionskontrollsystem - label_plugins: Plugins - label_ldap_authentication: LDAP-Authentifizierung - label_downloads_abbr: D/L - label_optional_description: Beschreibung (optional) - label_add_another_file: Eine weitere Datei hinzufügen - label_preferences: Präferenzen - label_chronological_order: in zeitlicher Reihenfolge - label_reverse_chronological_order: in umgekehrter zeitlicher Reihenfolge - label_planning: Terminplanung - label_incoming_emails: Eingehende E-Mails - label_generate_key: Generieren - label_issue_watchers: Beobachter - label_example: Beispiel - label_display: Anzeige - label_sort: Sortierung - label_ascending: Aufsteigend - label_descending: Absteigend - label_date_from_to: von %{start} bis %{end} - label_wiki_content_added: Die Wiki-Seite wurde erfolgreich hinzugefügt. - label_wiki_content_updated: Die Wiki-Seite wurde erfolgreich aktualisiert. - label_group: Gruppe - label_group_plural: Gruppen - label_group_new: Neue Gruppe - label_time_entry_plural: Benötigte Zeit - label_version_sharing_none: Nicht gemeinsam verwenden - label_version_sharing_descendants: Mit Unterprojekten - label_version_sharing_hierarchy: Mit Projekthierarchie - label_version_sharing_tree: Mit Projektbaum - label_version_sharing_system: Mit allen Projekten - label_update_issue_done_ratios: Ticket-Fortschritt aktualisieren - label_copy_source: Quelle - label_copy_target: Ziel - label_copy_same_as_target: So wie das Ziel - label_display_used_statuses_only: Zeige nur Status an, die von diesem Tracker verwendet werden - label_api_access_key: API-Zugriffsschlüssel - label_missing_api_access_key: Der API-Zugriffsschlüssel fehlt. - label_api_access_key_created_on: Der API-Zugriffsschlüssel wurde vor %{value} erstellt - label_profile: Profil - label_subtask_plural: Unteraufgaben - label_project_copy_notifications: Sende Mailbenachrichtigungen beim Kopieren des Projekts. - label_principal_search: "Nach Benutzer oder Gruppe suchen:" - label_user_search: "Nach Benutzer suchen:" - button_login: Anmelden - button_submit: OK - button_save: Speichern - button_check_all: Alles auswählen - button_uncheck_all: Alles abwählen - button_delete: Löschen - button_create: Anlegen - button_create_and_continue: Anlegen und weiter - button_test: Testen - button_edit: Bearbeiten - button_edit_associated_wikipage: "Zugehörige Wikiseite bearbeiten: %{page_title}" - button_add: Hinzufügen - button_change: Wechseln - button_apply: Anwenden - button_clear: Zurücksetzen - button_lock: Sperren - button_unlock: Entsperren - button_download: Download - button_list: Liste - button_view: Anzeigen - button_move: Verschieben - button_move_and_follow: Verschieben und Ticket anzeigen - button_back: Zurück - button_cancel: Abbrechen - button_activate: Aktivieren - button_sort: Sortieren - button_log_time: Aufwand buchen - button_rollback: Auf diese Version zurücksetzen - button_watch: Beobachten - button_unwatch: Nicht beobachten - button_reply: Antworten - button_archive: Archivieren - button_unarchive: Entarchivieren - button_reset: Zurücksetzen - button_rename: Umbenennen - button_change_password: Kennwort ändern - button_copy: Kopieren - button_copy_and_follow: Kopieren und Ticket anzeigen - button_annotate: Annotieren - button_update: Bearbeiten - button_configure: Konfigurieren - button_quote: Zitieren - button_duplicate: Duplizieren - button_show: Anzeigen + mail_body_account_activation_request: "Ein neuer Benutzer (%{value}) hat sich registriert. Sein Konto wartet auf Ihre Genehmigung:" + mail_body_account_information: Ihre Konto-Informationen + mail_body_account_information_external: "Sie können sich mit Ihrem Konto %{value} anmelden." + mail_body_lost_password: 'Benutzen Sie den folgenden Link, um Ihr Kennwort zu ändern:' + mail_body_register: 'Um Ihr Konto zu aktivieren, benutzen Sie folgenden Link:' + mail_body_reminder: "%{count} Tickets, die Ihnen zugewiesen sind, müssen in den nächsten %{days} Tagen abgegeben werden:" + mail_body_wiki_content_added: "Die Wiki-Seite '%{id}' wurde von %{author} hinzugefügt." + mail_body_wiki_content_updated: "Die Wiki-Seite '%{id}' wurde von %{author} aktualisiert." + mail_subject_account_activation_request: "Antrag auf %{value} Kontoaktivierung" + mail_subject_lost_password: "Ihr %{value} Kennwort" + mail_subject_register: "%{value} Kontoaktivierung" + mail_subject_reminder: "%{count} Tickets müssen in den nächsten %{days} Tagen abgegeben werden" + mail_subject_wiki_content_added: "Wiki-Seite '%{id}' hinzugefügt" + mail_subject_wiki_content_updated: "Wiki-Seite '%{id}' erfolgreich aktualisiert" + + notice_account_activated: Ihr Konto ist aktiviert. Sie können sich jetzt anmelden. + notice_account_deleted: Ihr Benutzerkonto wurde unwiderruflich gelöscht. + notice_account_invalid_creditentials: Benutzer oder Kennwort ist ungültig. + notice_account_lost_email_sent: Eine E-Mail mit Anweisungen, ein neues Kennwort zu wählen, wurde Ihnen geschickt. + notice_account_password_updated: Kennwort wurde erfolgreich aktualisiert. + notice_account_pending: "Ihr Konto wurde erstellt und wartet jetzt auf die Genehmigung des Administrators." + notice_account_register_done: Konto wurde erfolgreich angelegt. + notice_account_unknown_email: Unbekannter Benutzer. + notice_account_updated: Konto wurde erfolgreich aktualisiert. + notice_account_wrong_password: Falsches Kennwort. + notice_api_access_key_reseted: Ihr API-Zugriffsschlüssel wurde zurückgesetzt. + notice_can_t_change_password: Dieses Konto verwendet eine externe Authentifizierungs-Quelle. Unmöglich, das Kennwort zu ändern. + notice_default_data_loaded: Die Standard-Konfiguration wurde erfolgreich geladen. + notice_email_error: "Beim Senden einer E-Mail ist ein Fehler aufgetreten (%{value})." + notice_email_sent: "Eine E-Mail wurde an %{value} gesendet." + notice_failed_to_save_issues: "%{count} von %{total} ausgewählten Tickets konnte(n) nicht gespeichert werden: %{ids}." + notice_failed_to_save_members: "Benutzer konnte nicht gespeichert werden: %{errors}." + notice_failed_to_save_time_entries: "Gescheitert %{count} Zeiteinträge für %{total} von ausgewählten: %{ids} zu speichern." + notice_feeds_access_key_reseted: Ihr Atom-Zugriffsschlüssel wurde zurückgesetzt. + notice_file_not_found: Anhang existiert nicht oder ist gelöscht worden. + notice_gantt_chart_truncated: Die Grafik ist unvollständig, da das Maximum der anzeigbaren Aufgaben überschritten wurde (%{max}) + notice_issue_done_ratios_updated: Der Ticket-Fortschritt wurde aktualisiert. + notice_issue_successful_create: Ticket %{id} erstellt. + notice_issue_update_conflict: Das Ticket wurde von einem anderen Nutzer überarbeitet während Ihrer Bearbeitung. + notice_locking_conflict: Datum wurde von einem anderen Benutzer geändert. + notice_no_issue_selected: "Kein Ticket ausgewählt! Bitte wählen Sie die Tickets, die Sie bearbeiten möchten." + notice_not_authorized: Sie sind nicht berechtigt, auf diese Seite zuzugreifen. + notice_not_authorized_archived_project: Das Projekt wurde archiviert und ist daher nicht nicht verfügbar. + notice_successful_connection: Verbindung erfolgreich. + notice_successful_create: Erfolgreich angelegt + notice_successful_delete: Erfolgreich gelöscht. + notice_successful_update: Erfolgreich aktualisiert. + notice_unable_delete_time_entry: Der Zeiterfassungseintrag konnte nicht gelöscht werden. + notice_unable_delete_version: Die Version konnte nicht gelöscht werden. + notice_user_successful_create: Benutzer %{id} angelegt. + + permission_add_issue_notes: Kommentare hinzufügen + permission_add_issue_watchers: Beobachter hinzufügen + permission_add_issues: Tickets hinzufügen + permission_add_messages: Forenbeiträge hinzufügen + permission_add_project: Projekt erstellen + permission_add_subprojects: Unterprojekte erstellen + permission_add_documents: Dokumente hinzufügen + permission_browse_repository: Projektarchiv ansehen + permission_close_project: Schließen / erneutes Öffnen eines Projekts + permission_comment_news: News kommentieren + permission_commit_access: Commit-Zugriff + permission_delete_issue_watchers: Beobachter löschen + permission_delete_issues: Tickets löschen + permission_delete_messages: Forenbeiträge löschen + permission_delete_own_messages: Eigene Forenbeiträge löschen + permission_delete_wiki_pages: Wiki-Seiten löschen + permission_delete_wiki_pages_attachments: Anhänge löschen + permission_delete_documents: Dokumente löschen + permission_edit_issue_notes: Kommentare bearbeiten + permission_edit_issues: Tickets bearbeiten + permission_edit_messages: Forenbeiträge bearbeiten + permission_edit_own_issue_notes: Eigene Kommentare bearbeiten + permission_edit_own_messages: Eigene Forenbeiträge bearbeiten + permission_edit_own_time_entries: Selbst gebuchte Aufwände bearbeiten + permission_edit_project: Projekt bearbeiten + permission_edit_time_entries: Gebuchte Aufwände bearbeiten + permission_edit_wiki_pages: Wiki-Seiten bearbeiten + permission_edit_documents: Dokumente bearbeiten + permission_export_wiki_pages: Wiki-Seiten exportieren + permission_log_time: Aufwände buchen + permission_manage_boards: Foren verwalten + permission_manage_categories: Ticket-Kategorien verwalten + permission_manage_files: Dateien verwalten + permission_manage_issue_relations: Ticket-Beziehungen verwalten + permission_manage_members: Mitglieder verwalten + permission_manage_news: News verwalten + permission_manage_project_activities: Aktivitäten (Zeiterfassung) verwalten + permission_manage_public_queries: Öffentliche Filter verwalten + permission_manage_related_issues: Zugehörige Tickets verwalten + permission_manage_repository: Projektarchiv verwalten + permission_manage_subtasks: Unteraufgaben verwalten + permission_manage_versions: Versionen verwalten + permission_manage_wiki: Wiki verwalten + permission_move_issues: Tickets verschieben + permission_protect_wiki_pages: Wiki-Seiten schützen + permission_rename_wiki_pages: Wiki-Seiten umbenennen + permission_save_queries: Filter speichern + permission_select_project_modules: Projektmodule auswählen + permission_set_issues_private: Tickets privat oder öffentlich markieren + permission_set_notes_private: Kommentar als privat markieren + permission_set_own_issues_private: Eigene Tickets privat oder öffentlich markieren + permission_view_calendar: Kalender ansehen + permission_view_changesets: Changesets ansehen + permission_view_documents: Dokumente ansehen + permission_view_files: Dateien ansehen + permission_view_gantt: Gantt-Diagramm ansehen + permission_view_issue_watchers: Liste der Beobachter ansehen + permission_view_issues: Tickets anzeigen + permission_view_messages: Forenbeiträge ansehen + permission_view_private_notes: Private Kommentare sehen + permission_view_time_entries: Gebuchte Aufwände ansehen + permission_view_wiki_edits: Wiki-Versionsgeschichte ansehen + permission_view_wiki_pages: Wiki ansehen + + project_module_boards: Foren + project_module_calendar: Kalender + project_module_documents: Dokumente + project_module_files: Dateien + project_module_gantt: Gantt + project_module_issue_tracking: Ticket-Verfolgung + project_module_news: News + project_module_repository: Projektarchiv + project_module_time_tracking: Zeiterfassung + project_module_wiki: Wiki + project_status_active: aktiv + project_status_archived: archiviert + project_status_closed: geschlossen + + setting_activity_days_default: Anzahl Tage pro Seite der Projekt-Aktivität + setting_app_subtitle: Applikations-Untertitel + setting_app_title: Applikations-Titel + setting_attachment_max_size: Max. Dateigröße + setting_autofetch_changesets: Changesets automatisch abrufen + setting_autologin: Automatische Anmeldung + setting_bcc_recipients: E-Mails als Blindkopie (BCC) senden + setting_cache_formatted_text: Formatierten Text im Cache speichern + setting_commit_cross_project_ref: Erlauben auf Tickets aller anderen Projekte zu referenzieren + setting_commit_fix_keywords: Schlüsselwörter (Status) + setting_commit_logtime_activity_id: Aktivität für die Zeiterfassung + setting_commit_logtime_enabled: Aktiviere Zeitlogging + setting_commit_ref_keywords: Schlüsselwörter (Beziehungen) + setting_cross_project_issue_relations: Ticket-Beziehungen zwischen Projekten erlauben + setting_cross_project_subtasks: Projektübergreifende Unteraufgaben erlauben + setting_date_format: Datumsformat + setting_default_issue_start_date_to_creation_date: Aktuelles Datum als Beginn für neue Tickets verwenden + setting_default_language: Standardsprache + setting_default_notification_option: Standard Benachrichtigungsoptionen + setting_default_projects_modules: Standardmäßig aktivierte Module für neue Projekte + setting_default_projects_public: Neue Projekte sind standardmäßig öffentlich + setting_diff_max_lines_displayed: Maximale Anzahl anzuzeigender Diff-Zeilen + setting_display_subprojects_issues: Tickets von Unterprojekten im Hauptprojekt anzeigen + setting_emails_footer: E-Mail-Fußzeile + setting_emails_header: E-Mail-Kopfzeile + setting_enabled_scm: Aktivierte Versionskontrollsysteme + setting_feeds_limit: Max. Anzahl Einträge pro Atom-Feed + setting_file_max_size_displayed: Maximale Größe inline angezeigter Textdateien + setting_gantt_items_limit: Maximale Anzahl von Aufgaben die im Gantt-Chart angezeigt werden + setting_gravatar_default: Standard-Gravatar-Bild + setting_gravatar_enabled: Gravatar-Benutzerbilder benutzen + setting_host_name: Hostname + setting_issue_done_ratio: Berechne den Ticket-Fortschritt mittels + setting_issue_done_ratio_issue_field: Ticket-Feld %-erledigt + setting_issue_done_ratio_issue_status: Ticket-Status + setting_issue_group_assignment: Ticketzuweisung an Gruppen erlauben + setting_issue_list_default_columns: Standard-Spalten in der Ticket-Auflistung + setting_issues_export_limit: Max. Anzahl Tickets bei CSV/PDF-Export + setting_jsonp_enabled: JSONP Unterstützung aktivieren + setting_login_required: Authentifizierung erforderlich + setting_mail_from: E-Mail-Absender + setting_mail_handler_api_enabled: Abruf eingehender E-Mails aktivieren + setting_mail_handler_api_key: API-Schlüssel + setting_mail_handler_body_delimiters: "Schneide E-Mails nach einer dieser Zeilen ab" + setting_new_project_user_role_id: Rolle, die einem Nicht-Administrator zugeordnet wird, der ein Projekt erstellt + setting_non_working_week_days: Arbeitsfreie Tage + setting_openid: Erlaube OpenID-Anmeldung und -Registrierung + setting_password_min_length: Mindestlänge des Kennworts + setting_per_page_options: Objekte pro Seite + setting_plain_text_mail: Nur reinen Text (kein HTML) senden + setting_protocol: Protokoll + setting_repositories_encodings: Enkodierung von Anhängen und Repositories + setting_repository_log_display_limit: Maximale Anzahl anzuzeigender Revisionen in der Historie einer Datei + setting_rest_api_enabled: REST-Schnittstelle aktivieren + setting_self_registration: Anmeldung ermöglicht + setting_sequential_project_identifiers: Fortlaufende Projektkennungen generieren + setting_session_lifetime: Längste Dauer einer Sitzung + setting_session_timeout: Zeitüberschreitung bei Inaktivität + setting_start_of_week: Wochenanfang + setting_sys_api_enabled: Webservice zur Verwaltung der Projektarchive benutzen + setting_text_formatting: Textformatierung + setting_thumbnails_enabled: Vorschaubilder von Dateianhängen anzeigen + setting_thumbnails_size: Größe der Vorschaubilder (in Pixel) + setting_time_format: Zeitformat + setting_unsubscribe: Erlaubt Benutzern das eigene Benutzerkonto zu löschen + setting_user_format: Benutzer-Anzeigeformat + setting_welcome_text: Willkommenstext + setting_wiki_compression: Wiki-Historie komprimieren + setting_default_projects_tracker_ids: Standardmäßig aktivierte Tracker für neue Projekte status_active: aktiv + status_locked: gesperrt status_registered: nicht aktivierte - status_locked: gesperrt - version_status_open: offen - version_status_locked: gesperrt - version_status_closed: abgeschlossen - - field_active: Aktiv - - text_select_mail_notifications: Bitte wählen Sie die Aktionen aus, für die eine Mailbenachrichtigung gesendet werden soll. - text_regexp_info: z. B. ^[A-Z0-9]+$ - text_min_max_length_info: 0 heißt keine Beschränkung - text_project_destroy_confirmation: Sind Sie sicher, dass sie das Projekt löschen wollen? - text_subprojects_destroy_warning: "Dessen Unterprojekte (%{value}) werden ebenfalls gelöscht." - text_workflow_edit: Workflow zum Bearbeiten auswählen + text_account_destroy_confirmation: Möchten Sie wirklich fortfahren?\nIhr Benutzerkonto wird für immer gelöscht und kann nicht wiederhergestellt werden. text_are_you_sure: Sind Sie sicher? - text_journal_changed: "%{label} wurde von %{old} zu %{new} geändert" - text_journal_set_to: "%{label} wurde auf %{value} gesetzt" - text_journal_deleted: "%{label} %{old} wurde gelöscht" - text_journal_added: "%{label} %{value} wurde hinzugefügt" - text_tip_issue_begin_day: Aufgabe, die an diesem Tag beginnt - text_tip_issue_end_day: Aufgabe, die an diesem Tag endet - text_tip_issue_begin_end_day: Aufgabe, die an diesem Tag beginnt und endet - text_project_identifier_info: 'Kleinbuchstaben (a-z), Ziffern, Binde- und Unterstriche erlaubt.
      Einmal gespeichert, kann die Kennung nicht mehr geändert werden.' + text_assign_time_entries_to_project: Gebuchte Aufwände dem Projekt zuweisen text_caracters_maximum: "Max. %{count} Zeichen." text_caracters_minimum: "Muss mindestens %{count} Zeichen lang sein." + text_comma_separated: Mehrere Werte erlaubt (durch Komma getrennt). + text_custom_field_possible_values_info: 'Eine Zeile pro Wert' + text_default_administrator_account_changed: Administrator-Kennwort geändert + text_destroy_time_entries: Gebuchte Aufwände löschen + text_destroy_time_entries_question: Es wurden bereits %{hours} Stunden auf dieses Ticket gebucht. Was soll mit den Aufwänden geschehen? + text_diff_truncated: '... Dieser Diff wurde abgeschnitten, weil er die maximale Anzahl anzuzeigender Zeilen überschreitet.' + 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/configuration.yml vor und starten Sie die Applikation neu." + text_enumeration_category_reassign_to: 'Die Objekte stattdessen diesem Wert zuordnen:' + text_enumeration_destroy_question: "%{count} Objekt(e) sind diesem Wert zugeordnet." + text_file_repository_writable: Verzeichnis für Dateien beschreibbar + text_git_repository_note: Repository steht für sich alleine (bare) und liegt lokal (z.B. /gitrepo, c:\gitrepo) + text_issue_added: "Ticket %{id} wurde erstellt von %{author}." + text_issue_category_destroy_assignments: Kategorie-Zuordnung entfernen + text_issue_category_destroy_question: "Einige Tickets (%{count}) sind dieser Kategorie zugeodnet. Was möchten Sie tun?" + text_issue_category_reassign_to: Tickets dieser Kategorie zuordnen + text_issue_conflict_resolution_add_notes: Meine Änderungen übernehmen und alle anderen Änderungen verwerfen + text_issue_conflict_resolution_cancel: Meine Änderungen verwerfen und %{link} neu anzeigen + text_issue_conflict_resolution_overwrite: Meine Änderungen trotzdem übernehmen (vorherige Notizen bleiben erhalten aber manche können überschrieben werden) + text_issue_updated: "Ticket %{id} wurde aktualisiert von %{author}." + text_issues_destroy_confirmation: 'Sind Sie sicher, dass Sie die ausgewählten Tickets löschen möchten?' + text_issues_destroy_descendants_confirmation: Dies wird auch %{count} Unteraufgabe/n löschen. + text_issues_ref_in_commit_messages: Ticket-Beziehungen und -Status in Commit-Log-Meldungen + text_journal_added: "%{label} %{value} wurde hinzugefügt" + text_journal_changed: "%{label} wurde von %{old} zu %{new} geändert" + text_journal_changed_no_detail: "%{label} aktualisiert" + text_journal_deleted: "%{label} %{old} wurde gelöscht" + text_journal_set_to: "%{label} wurde auf %{value} gesetzt" text_length_between: "Länge zwischen %{min} und %{max} Zeichen." + text_line_separated: Mehrere Werte sind erlaubt (eine Zeile pro Wert). + text_load_default_configuration: Standard-Konfiguration laden + text_mercurial_repository_note: Lokales repository (e.g. /hgrepo, c:\hgrepo) + text_min_max_length_info: 0 heißt keine Beschränkung + text_no_configuration_data: "Rollen, Tracker, Ticket-Status und Workflows wurden noch nicht konfiguriert.\nEs ist sehr zu empfehlen, die Standard-Konfiguration zu laden. Sobald sie geladen ist, können Sie sie abändern." + text_own_membership_delete_confirmation: "Sie sind dabei, einige oder alle Ihre Berechtigungen zu entfernen. Es ist möglich, dass Sie danach das Projekt nicht mehr ansehen oder bearbeiten dürfen.\nSind Sie sicher, dass Sie dies tun möchten?" + text_plugin_assets_writable: Verzeichnis für Plugin-Assets beschreibbar + text_project_closed: Dieses Projekt ist geschlossen und kann nicht bearbeitet werden. + text_project_destroy_confirmation: Sind Sie sicher, dass Sie das Projekt löschen wollen? + text_project_identifier_info: 'Kleinbuchstaben (a-z), Ziffern, Binde- und Unterstriche erlaubt, muss mit einem Kleinbuchstaben beginnen.
      Einmal gespeichert, kann die Kennung nicht mehr geändert werden.' + text_reassign_time_entries: 'Gebuchte Aufwände diesem Ticket zuweisen:' + text_regexp_info: z. B. ^[A-Z0-9]+$ + text_repository_identifier_info: 'Kleinbuchstaben (a-z), Ziffern, Binde- und Unterstriche erlaubt.
      Einmal gespeichert, kann die Kennung nicht mehr geändert werden.' + 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_rmagick_available: RMagick verfügbar (optional) + text_scm_command: Kommando + text_scm_command_not_available: SCM-Kommando ist nicht verfügbar. Bitte prüfen Sie die Einstellungen im Administrationspanel. + text_scm_command_version: Version + text_scm_config: Die SCM-Kommandos können in der in config/configuration.yml konfiguriert werden. Redmine muss anschließend neu gestartet werden. + text_scm_path_encoding_note: "Standard: UTF-8" + text_select_mail_notifications: Bitte wählen Sie die Aktionen aus, für die eine Mailbenachrichtigung gesendet werden soll. + text_select_project_modules: 'Bitte wählen Sie die Module aus, die in diesem Projekt aktiviert sein sollen:' + text_session_expiration_settings: "Achtung: Änderungen können aktuelle Sitzungen beenden, Ihre eingeschlossen!" + text_status_changed_by_changeset: "Status geändert durch Changeset %{value}." + text_subprojects_destroy_warning: "Dessen Unterprojekte (%{value}) werden ebenfalls gelöscht." + text_time_entries_destroy_confirmation: Sind Sie sicher, dass Sie die ausgewählten Zeitaufwände löschen möchten? + text_time_logged_by_changeset: Angewendet in Changeset %{value}. + text_tip_issue_begin_day: Aufgabe, die an diesem Tag beginnt + text_tip_issue_begin_end_day: Aufgabe, die an diesem Tag beginnt und endet + text_tip_issue_end_day: Aufgabe, die an diesem Tag endet text_tracker_no_workflow: Kein Workflow für diesen Tracker definiert. + text_turning_multiple_off: Wenn Sie die Mehrfachauswahl deaktivieren, werden Felder mit Mehrfachauswahl bereinigt. + Dadurch wird sichergestellt, dass lediglich ein Wert pro Feld ausgewählt ist. text_unallowed_characters: Nicht erlaubte Zeichen - text_comma_separated: Mehrere Werte erlaubt (durch Komma getrennt). - text_line_separated: Mehrere Werte sind erlaubt (eine Zeile pro Wert). - text_issues_ref_in_commit_messages: Ticket-Beziehungen und -Status in Commit-Log-Meldungen - text_issue_added: "Ticket %{id} wurde erstellt von %{author}." - text_issue_updated: "Ticket %{id} wurde aktualisiert von %{author}." + text_user_mail_option: "Für nicht ausgewählte Projekte werden Sie nur Benachrichtigungen für Dinge erhalten, die Sie beobachten oder an denen Sie beteiligt sind (z. B. Tickets, deren Autor Sie sind oder die Ihnen zugewiesen sind)." + text_user_wrote: "%{value} schrieb:" + text_warn_on_leaving_unsaved: Die aktuellen Änderungen gehen verloren, wenn Sie diese Seite verlassen. text_wiki_destroy_confirmation: Sind Sie sicher, dass Sie dieses Wiki mit sämtlichem Inhalt löschen möchten? - text_issue_category_destroy_question: "Einige Tickets (%{count}) sind dieser Kategorie zugeodnet. Was möchten Sie tun?" - text_issue_category_destroy_assignments: Kategorie-Zuordnung entfernen - text_issue_category_reassign_to: Tickets dieser Kategorie zuordnen - text_user_mail_option: "Für nicht ausgewählte Projekte werden Sie nur Benachrichtigungen für Dinge erhalten, die Sie beobachten oder an denen Sie beteiligt sind (z. B. Tickets, deren Autor Sie sind oder die Ihnen zugewiesen sind)." - text_no_configuration_data: "Rollen, Tracker, Ticket-Status und Workflows wurden noch nicht konfiguriert.\nEs ist sehr zu empfehlen, die Standard-Konfiguration zu laden. Sobald sie geladen ist, können Sie sie abändern." - text_load_default_configuration: Standard-Konfiguration laden - text_status_changed_by_changeset: "Status geändert durch Changeset %{value}." - text_issues_destroy_confirmation: 'Sind Sie sicher, dass Sie die ausgewählten Tickets löschen möchten?' - 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 %{hours} Stunden auf dieses Ticket gebucht. Was soll mit den Aufwänden geschehen? - text_destroy_time_entries: Gebuchte Aufwände löschen - text_assign_time_entries_to_project: Gebuchte Aufwände dem Projekt zuweisen - text_reassign_time_entries: 'Gebuchte Aufwände diesem Ticket zuweisen:' - text_user_wrote: "%{value} schrieb:" - text_enumeration_destroy_question: "%{count} Objekt(e) 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/configuration.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.' - text_custom_field_possible_values_info: 'Eine Zeile pro Wert' + text_wiki_page_destroy_children: Lösche alle Unterseiten text_wiki_page_destroy_question: "Diese Seite hat %{descendants} Unterseite(n). Was möchten Sie tun?" text_wiki_page_nullify_children: Verschiebe die Unterseiten auf die oberste Ebene - text_wiki_page_destroy_children: Lösche alle Unterseiten text_wiki_page_reassign_children: Ordne die Unterseiten dieser Seite zu - text_own_membership_delete_confirmation: "Sie sind dabei, einige oder alle Ihre Berechtigungen zu entfernen. Es ist möglich, dass Sie danach das Projekt nicht mehr ansehen oder bearbeiten dürfen.\nSind Sie sicher, dass Sie dies tun möchten?" - text_zoom_in: Zoom in - text_zoom_out: Zoom out + text_workflow_edit: Workflow zum Bearbeiten auswählen + text_zoom_in: Ansicht vergrößern + text_zoom_out: Ansicht verkleinern - default_role_manager: Manager - default_role_developer: Entwickler - default_role_reporter: Reporter - default_tracker_bug: Fehler - default_tracker_feature: Feature - default_tracker_support: Unterstützung - default_issue_status_new: Neu - default_issue_status_in_progress: In Bearbeitung - default_issue_status_resolved: Gelöst - default_issue_status_feedback: Feedback - default_issue_status_closed: Erledigt - default_issue_status_rejected: Abgewiesen - default_doc_category_user: Benutzerdokumentation - default_doc_category_tech: Technische Dokumentation - default_priority_low: Niedrig - default_priority_normal: Normal - default_priority_high: Hoch - default_priority_urgent: Dringend - default_priority_immediate: Sofort - default_activity_design: Design - default_activity_development: Entwicklung + version_status_closed: abgeschlossen + version_status_locked: gesperrt + version_status_open: offen - enumeration_issue_priorities: Ticket-Prioritäten - enumeration_doc_categories: Dokumentenkategorien - enumeration_activities: Aktivitäten (Zeiterfassung) - enumeration_system_activity: System-Aktivität - - field_text: Textfeld - label_user_mail_option_only_owner: Nur für Aufgaben die ich angelegt habe - setting_default_notification_option: Standard Benachrichtigungsoptionen - label_user_mail_option_only_my_events: Nur für Aufgaben die ich beobachte oder an welchen ich mitarbeite - label_user_mail_option_only_assigned: Nur für Aufgaben für die ich zuständig bin. - notice_not_authorized_archived_project: Das Projekt wurde archiviert und ist daher nicht nicht verfügbar. - label_user_mail_option_none: keine Ereignisse - field_member_of_group: Zuständigkeitsgruppe - field_assigned_to_role: Zuständigkeitsrolle - field_visible: Sichtbar - setting_emails_header: E-Mail Betreffzeile - setting_commit_logtime_activity_id: Aktivität für die Zeiterfassung - text_time_logged_by_changeset: Angewendet in Changeset %{value}. - setting_commit_logtime_enabled: Aktiviere Zeitlogging - notice_gantt_chart_truncated: Die Grafik ist unvollständig, da das Maximum der anzeigbaren Aufgaben überschritten wurde (%{max}) - setting_gantt_items_limit: Maximale Anzahl von Aufgaben die im Gantt-Chart angezeigt werden. - field_warn_on_leaving_unsaved: vor dem Verlassen einer Seite mit ungesichertem Text im Editor warnen - text_warn_on_leaving_unsaved: Die aktuellen Änderungen gehen verloren, wenn Sie diese Seite verlassen. - label_my_queries: Meine eigenen Abfragen - text_journal_changed_no_detail: "%{label} aktualisiert" - label_news_comment_added: Kommentar zu einer News hinzugefügt - button_expand_all: Alle ausklappen - button_collapse_all: Alle einklappen - label_additional_workflow_transitions_for_assignee: Zusätzliche Berechtigungen wenn der Benutzer der Zugewiesene ist - label_additional_workflow_transitions_for_author: Zusätzliche Berechtigungen wenn der Benutzer der Autor ist - label_bulk_edit_selected_time_entries: Ausgewählte Zeitaufwände bearbeiten - text_time_entries_destroy_confirmation: Sind Sie sicher, dass Sie die ausgewählten Zeitaufwände löschen möchten? - label_role_anonymous: Anonymous - label_role_non_member: Nichtmitglied - label_issue_note_added: Notiz hinzugefügt - label_issue_status_updated: Status aktualisiert - label_issue_priority_updated: Priorität aktualisiert - label_issues_visibility_own: Tickets die folgender User erstellt hat oder die ihm zugewiesen sind - field_issues_visibility: Ticket Sichtbarkeit - label_issues_visibility_all: Alle Tickets - permission_set_own_issues_private: Eigene Tickets privat oder öffentlich markieren - field_is_private: Privat - permission_set_issues_private: Tickets privat oder öffentlich markieren - label_issues_visibility_public: Alle öffentlichen Tickets - text_issues_destroy_descendants_confirmation: Dies wird auch %{count} Unteraufgabe/n löschen. - field_commit_logs_encoding: Kodierung der Commit-Log-Meldungen - field_scm_path_encoding: Pfad Kodierung - text_scm_path_encoding_note: "Standard: UTF-8" - field_path_to_repository: Pfad zum repository - field_root_directory: Wurzelverzeichnis - field_cvs_module: Modul - field_cvsroot: CVSROOT - text_mercurial_repository_note: Lokales repository (e.g. /hgrepo, c:\hgrepo) - text_scm_command: Kommando - text_scm_command_version: Version - label_git_report_last_commit: Bericht des letzten Commits für Dateien und Verzeichnisse - text_scm_config: Die SCM-Kommandos können in der in config/configuration.yml konfiguriert werden. Redmine muss anschließend neu gestartet werden. - text_scm_command_not_available: Scm Kommando ist nicht verfügbar. Bitte prüfen Sie die Einstellungen im Administrationspanel. - - notice_issue_successful_create: Ticket %{id} erstellt. - label_between: zwischen - setting_issue_group_assignment: Ticketzuweisung an Gruppen erlauben - label_diff: diff - text_git_repository_note: Repository steht für sich alleine (bare) und liegt lokal (z.B. /gitrepo, c:\gitrepo) - - description_filter: Filter - description_search: Suchfeld - description_choose_project: Projekte - description_project_scope: Suchbereich - description_notes: Kommentare - description_message_content: Nachrichteninhalt - description_query_sort_criteria_attribute: Sortierattribut - description_query_sort_criteria_direction: Sortierrichtung - description_user_mail_notification: Mailbenachrichtigungseinstellung - description_available_columns: Verfügbare Spalten - description_selected_columns: Ausgewählte Spalten - description_issue_category_reassign: Neue Kategorie wählen - description_wiki_subpages_reassign: Neue Elternseite wählen - description_date_range_list: Zeitraum aus einer Liste wählen - description_date_range_interval: Zeitraum durch Start- und Enddatum festlegen - description_date_from: Startdatum eintragen - description_date_to: Enddatum eintragen - - label_parent_revision: Vorgänger - label_child_revision: Nachfolger - error_scm_annotate_big_text_file: Der Eintrag kann nicht umgesetzt werden, da er die maximale Textlänge überschreitet. - setting_default_issue_start_date_to_creation_date: Aktuelles Datum als Beginn für neue Tickets verwenden - button_edit_section: Diesen Bereich bearbeiten - setting_repositories_encodings: Encoding von Anhängen und Repositories - description_all_columns: Alle Spalten - button_export: Exportieren - label_export_options: "%{export_format} Export-Eigenschaften" - error_attachment_too_big: Diese Datei kann nicht hochgeladen werden, da sie die maximale Dateigröße von (%{max_size}) überschreitet. - notice_failed_to_save_time_entries: "Gescheitert %{count} Zeiteinträge für %{total} von ausgewählten: %{ids} zu speichern." - label_x_issues: - zero: 0 Tickets - one: 1 Ticket - other: "%{count} Tickets" - label_repository_new: Neues Repository - field_repository_is_default: Haupt-Repository - label_copy_attachments: Anhänge Kopieren - label_item_position: "%{position}/%{count}" - label_completed_versions: Abgeschlossene Versionen - field_multiple: Mehrere Werte - setting_commit_cross_project_ref: Erlauben auf Tickets aller anderen Projekte zu referenzieren - text_issue_conflict_resolution_add_notes: Meine Änderungen übernehmen und alle anderen Änderungen verwerfen - text_issue_conflict_resolution_overwrite: Meine Änderungen trotzdem übernehmen (vorherige Notizen bleiben erhalten aber manche können überschrieben werden) - notice_issue_update_conflict: Das Ticket wurde von einem anderen Nutzer überarbeitet während Ihrer Bearbeitung. - text_issue_conflict_resolution_cancel: Meine Änderungen verwerfen und %{link} neu anzeigen - permission_manage_related_issues: Zugehörige Tickets verwalten - field_auth_source_ldap_filter: LDAP Filter - label_search_for_watchers: Nach hinzufügbaren Beobachtern suchen - notice_account_deleted: Ihr Benutzerkonto wurde unwiderruflich gelöscht. - setting_unsubscribe: Erlaubt Benutzern das eigene Benutzerkonto zu löschen - button_delete_my_account: Mein Benutzerkonto löschen - text_account_destroy_confirmation: Möchten Sie wirklich fortfahren?\nIhr Benutzerkonto wird für immer gelöscht und kann nicht wiederhergestellt werden. - error_session_expired: Ihre Sitzung ist abgelaufen. Bitte melden Sie sich erneut an. - text_session_expiration_settings: "Achtung: Änderungen können aktuelle Sitzungen beenden, Ihre eingeschlossen!" - setting_session_lifetime: Längste Dauer einer Sitzung - setting_session_timeout: Zeitüberschreitung bei Inaktivität - label_session_expiration: Ende einer Sitzung - permission_close_project: Schließen / erneutes Öffnen eines Projekts - label_show_closed_projects: Geschlossene Projekte anzeigen - button_close: Schließen - button_reopen: Öffnen - project_status_active: aktiv - project_status_closed: geschlossen - project_status_archived: archiviert - text_project_closed: Dieses Projekt ist geschlossen und kann nicht bearbeitet werden. - notice_user_successful_create: Benutzer %{id} angelegt. - field_core_fields: Standardwerte - field_timeout: Auszeit (in Sekunden) - setting_thumbnails_enabled: Vorschaubilder von Dateianhängen anzeigen - setting_thumbnails_size: Größe der Vorschaubilder (in Pixel) - label_status_transitions: Statusänderungen - label_fields_permissions: Feldberechtigungen - label_readonly: Nur-Lese-Zugriff - label_required: Erforderlich - text_repository_identifier_info: 'Kleinbuchstaben (a-z), Ziffern, Binde- und Unterstriche erlaubt.
      Einmal gespeichert, kann die Kennung nicht mehr geändert werden.' - field_board_parent: Übergeordnetes Forum - label_attribute_of_project: "%{name} des Projekts" - label_attribute_of_author: "%{name} des Autors" - label_attribute_of_assigned_to: "%{name} des Bearbeiters" - label_attribute_of_fixed_version: "%{name} der Zielversion" - label_copy_subtasks: Unteraufgaben kopieren - label_copied_to: Kopiert nach - label_copied_from: Kopiert von - label_any_issues_in_project: irgendein Ticket im Projekt - label_any_issues_not_in_project: irgendein Ticket nicht im Projekt - field_private_notes: Privater Kommentar - permission_view_private_notes: Private Kommentare sehen - permission_set_notes_private: Kommentar als privat markieren - label_no_issues_in_project: keine Tickets im Projekt - label_any: alle - label_last_n_weeks: letzte %{count} Wochen - setting_cross_project_subtasks: Projektübergreifende Unteraufgaben erlauben - label_cross_project_descendants: Mit Unterprojekten - label_cross_project_tree: Mit Projektbaum - label_cross_project_hierarchy: Mit Projekthierarchie - label_cross_project_system: Mit allen Projekten - button_hide: Verstecken - setting_non_working_week_days: Arbeitsfreie Tage - label_in_the_next_days: in den nächsten - label_in_the_past_days: in den letzten + warning_attachments_not_saved: "%{count} Datei(en) konnten nicht gespeichert werden." + label_total_time: Gesamtzeit diff -r 0a574315af3e -r 4f746d8966dd config/locales/el.yml --- a/config/locales/el.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/el.yml Fri Jun 14 09:28:30 2013 +0100 @@ -52,8 +52,8 @@ one: "πεÏίπου 1 ÏŽÏα" other: "πεÏίπου %{count} ÏŽÏες" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 ÏŽÏα" + other: "%{count} ÏŽÏες" x_days: one: "1 ημέÏα" other: "%{count} ημέÏες" @@ -194,8 +194,6 @@ mail_subject_wiki_content_updated: "'ενημεÏώθηκε η σελίδα wiki %{id}' " mail_body_wiki_content_updated: "Η σελίδα wiki '%{id}' ενημεÏώθηκε από τον %{author}." - gui_validation_error: 1 σφάλμα - gui_validation_error_plural: "%{count} σφάλματα" field_name: Όνομα field_description: ΠεÏιγÏαφή @@ -356,7 +354,6 @@ permission_edit_own_time_entries: ΕπεξεÏγασία Î´Î¹ÎºÎ¿Ï Î¼Î¿Ï… ιστοÏÎ¹ÎºÎ¿Ï Ï‡Ïόνου permission_manage_news: ΔιαχείÏιση νέων permission_comment_news: Σχολιασμός νέων - permission_manage_documents: ΔιαχείÏιση εγγÏάφων permission_view_documents: ΠÏοβολή εγγÏάφων permission_manage_files: ΔιαχείÏιση αÏχείων permission_view_files: ΠÏοβολή αÏχείων @@ -475,8 +472,6 @@ label_text: ΜακÏοσκελές κείμενο label_attribute: Ιδιότητα label_attribute_plural: Ιδιότητες - label_download: "%{count} ΜεταφόÏτωση" - label_download_plural: "%{count} ΜεταφοÏτώσεις" label_no_data: Δεν υπάÏχουν δεδομένα label_change_status: Αλλαγή κατάστασης label_history: ΙστοÏικό @@ -578,8 +573,6 @@ label_repository: ΑποθετήÏιο label_repository_plural: ΑποθετήÏια label_browse: Πλοήγηση - label_modification: "%{count} Ï„Ïοποποίηση" - label_modification_plural: "%{count} Ï„Ïοποποιήσεις" label_branch: Branch label_tag: Tag label_revision: ΑναθεώÏηση @@ -933,7 +926,6 @@ label_principal_search: "Search for user or group:" label_user_search: "Search for user:" field_visible: Visible - setting_emails_header: Emails header setting_commit_logtime_activity_id: Activity for logged time text_time_logged_by_changeset: Applied in changeset %{value}. setting_commit_logtime_enabled: Enable time logging @@ -974,8 +966,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1082,3 +1072,19 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: ΣÏνολο + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header diff -r 0a574315af3e -r 4f746d8966dd config/locales/en.yml --- a/config/locales/en.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/en.yml Fri Jun 14 09:28:30 2013 +0100 @@ -221,9 +221,6 @@ mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated" mail_body_wiki_content_updated: "The '%{id}' wiki page has been updated by %{author}." - gui_validation_error: 1 error - gui_validation_error_plural: "%{count} errors" - field_ssamr_user_detail: description: User Description institution: Institution @@ -243,6 +240,7 @@ field_author: Author field_created_on: Created field_updated_on: Updated + field_closed_on: Closed field_field_format: Format field_is_for_all: For all projects field_possible_values: Possible values @@ -345,6 +343,7 @@ field_board_parent: Parent forum field_private_notes: Private notes field_public_or_private: "Public or Private?" + field_inherit_members: Inherit members setting_external_repository: "Select this if the project's main repository is hosted somewhere else" setting_external_repository_url: "The URL of the existing external repository. Must be publicly accessible without a password" @@ -378,8 +377,8 @@ setting_cross_project_subtasks: Allow cross-project subtasks setting_issue_list_default_columns: Default columns displayed on the issue list setting_repositories_encodings: Attachments and repositories encodings - setting_emails_header: Emails header - setting_emails_footer: Emails footer + setting_emails_header: Email header + setting_emails_footer: Email footer setting_protocol: Protocol setting_per_page_options: Objects per page options setting_user_format: Users display format @@ -421,6 +420,8 @@ setting_thumbnails_enabled: Display attachment thumbnails setting_thumbnails_size: Thumbnails size (in pixels) setting_non_working_week_days: Non-working days + setting_jsonp_enabled: Enable JSONP support + setting_default_projects_tracker_ids: Default trackers for new projects permission_add_project: Create project permission_add_subprojects: Create subprojects @@ -457,10 +458,12 @@ 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 downloads permission_view_files: View downloads + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents permission_manage_wiki: Manage wiki permission_rename_wiki_pages: Rename wiki pages permission_delete_wiki_pages: Delete wiki pages @@ -613,8 +616,6 @@ label_text: Long text label_attribute: Attribute label_attribute_plural: Attributes - label_download: "%{count} Download" - label_download_plural: "%{count} Downloads" label_no_data: No data to display label_change_status: Change status label_history: History @@ -665,6 +666,7 @@ one: 1 issue other: "%{count} issues" label_total: Total + label_total_time: Total time label_permissions: Permissions label_current_status: Current status label_new_statuses_allowed: New statuses allowed @@ -735,8 +737,6 @@ label_is_external_repository: Track an external repository label_repository_plural: Repositories label_explore_projects: Explore projects - label_modification: "%{count} change" - label_modification_plural: "%{count} changes" label_branch: Branch label_tag: Tag label_revision: Revision @@ -939,13 +939,16 @@ label_readonly: Read-only label_required: Required label_attribute_of_project: "Project's %{name}" + label_attribute_of_issue: "Issue's %{name}" label_attribute_of_author: "Author's %{name}" label_attribute_of_assigned_to: "Assignee's %{name}" + label_attribute_of_user: "User's %{name}" label_attribute_of_fixed_version: "Target version's %{name}" label_cross_project_descendants: With subprojects label_cross_project_tree: With project tree label_cross_project_hierarchy: With project hierarchy label_cross_project_system: With all projects + label_gantt_progress_line: Progress line button_login: Login button_submit: Submit @@ -1094,14 +1097,15 @@ text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) text_scm_command: Command text_scm_command_version: Version - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. text_issue_conflict_resolution_overwrite: "Apply my changes anyway (previous notes will be kept but some changes may be overwritten)" text_issue_conflict_resolution_add_notes: "Add my notes and discard my other changes" text_issue_conflict_resolution_cancel: "Discard all my changes and redisplay %{link}" text_account_destroy_confirmation: "Are you sure you want to proceed?\nYour account will be permanently deleted, with no way to reactivate it." text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." text_project_closed: This project is closed and read-only. + text_turning_multiple_off: "If you disable multiple values, multiple values will be removed in order to preserve only one value per item." text_settings_repo_explanation: External repositories

      Normally your project's primary repository will be the Mercurial repository hosted at this site.

      However, if you already have your project hosted somewhere else, you can specify your existing external repository's URL here – then this site will track that repository in a read-only “mirror” copy. External Mercurial, git and Subversion repositories can be tracked. Note that you cannot switch to an external repository if you have already made any commits to the repository hosted here. text_settings_repo_is_internal: Currently the repository hosted at this site is the primary repository for this project. text_settings_repo_is_external: Currently the repository hosted at this site is a read-only copy of an external repository. diff -r 0a574315af3e -r 4f746d8966dd config/locales/es.yml --- a/config/locales/es.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/es.yml Fri Jun 14 09:28:30 2013 +0100 @@ -80,8 +80,8 @@ one: "alrededor de 1 hora" other: "alrededor de %{count} horas" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 hora" + other: "%{count} horas" x_days: one: "1 día" other: "%{count} días" @@ -203,7 +203,7 @@ button_list: Listar button_lock: Bloquear button_log_time: Tiempo dedicado - button_login: Conexión + button_login: Acceder button_move: Mover button_quote: Citar button_rename: Renombrar @@ -345,8 +345,6 @@ general_text_Yes: 'Sí' general_text_no: 'no' general_text_yes: 'sí' - gui_validation_error: 1 error - gui_validation_error_plural: "%{count} errores" label_activity: Actividad label_add_another_file: Añadir otro fichero label_add_note: Añadir una nota @@ -439,8 +437,6 @@ label_document_added: Documento añadido label_document_new: Nuevo documento label_document_plural: Documentos - label_download: "%{count} Descarga" - label_download_plural: "%{count} Descargas" label_downloads_abbr: D/L label_duplicated_by: duplicada por label_duplicates: duplicada de @@ -508,8 +504,8 @@ label_list: Lista label_loading: Cargando... label_logged_as: Conectado como - label_login: Conexión - label_logout: Desconexión + label_login: Iniciar sesión + label_logout: Terminar sesión label_max_size: Tamaño máximo label_me: yo mismo label_member: Miembro @@ -520,8 +516,6 @@ label_message_plural: Mensajes label_message_posted: Mensaje añadido label_min_max_length: Longitud mín - máx - label_modification: "%{count} modificación" - label_modification_plural: "%{count} modificaciones" label_modified: modificado label_module_plural: Módulos label_month: Mes @@ -558,7 +552,7 @@ label_permissions_report: Informe de permisos label_personalize_page: Personalizar esta página label_planning: Planificación - label_please_login: Conexión + label_please_login: Por favor, inicie sesión label_plugins: Extensiones label_precedes: anterior a label_preferences: Preferencias @@ -622,7 +616,7 @@ 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_stay_logged_in: Mantener la sesión abierta label_string: Texto label_subproject_plural: Proyectos secundarios label_text: Texto largo @@ -720,7 +714,6 @@ 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 @@ -757,7 +750,7 @@ 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_autologin: Inicio de sesión automático setting_bcc_recipients: Ocultar las copias de carbón (bcc) setting_commit_fix_keywords: Palabras clave para la corrección setting_commit_ref_keywords: Palabras clave para la referencia @@ -1119,3 +1112,15 @@ setting_non_working_week_days: Días no laborables label_in_the_next_days: en los próximos label_in_the_past_days: en los anteriores + label_attribute_of_user: "%{name} del usuario" + text_turning_multiple_off: Si desactiva los valores múltiples, éstos serán eliminados para dejar un único valor por elemento. + label_attribute_of_issue: "%{name} de la petición" + permission_add_documents: Añadir documentos + permission_edit_documents: Editar documentos + permission_delete_documents: Borrar documentos + label_gantt_progress_line: Línea de progreso + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Total diff -r 0a574315af3e -r 4f746d8966dd config/locales/et.yml --- a/config/locales/et.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/et.yml Fri Jun 14 09:28:30 2013 +0100 @@ -67,8 +67,8 @@ one: "umbes tund" other: "umbes %{count} tundi" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 tund" + other: "%{count} tundi" x_days: one: "1 päev" other: "%{count} päeva" @@ -231,8 +231,6 @@ mail_subject_wiki_content_updated: "Uuendati '%{id}' vikilehte" mail_body_wiki_content_updated: "'%{id}' vikilehte uuendati %{author} poolt." - gui_validation_error: "1 viga" - gui_validation_error_plural: "%{count} viga" field_name: "Nimi" field_description: "Kirjeldus" @@ -438,7 +436,6 @@ permission_edit_own_time_entries: "Omi ajakulu kandeid muuta" permission_manage_news: "Uudiseid hallata" permission_comment_news: "Uudiseid kommenteerida" - permission_manage_documents: "Dokumente hallata" permission_view_documents: "Dokumente näha" permission_manage_files: "Faile hallata" permission_view_files: "Faile näha" @@ -569,8 +566,6 @@ label_text: "Pikk tekst" label_attribute: "Atribuut" label_attribute_plural: "Atribuudid" - label_download: "%{count} allalaadimine" - label_download_plural: "%{count} allalaadimist" label_no_data: "Pole" label_change_status: "Muuda olekut" label_history: "Ajalugu" @@ -681,8 +676,6 @@ label_repository_new: "Uus hoidla" label_repository_plural: "Hoidlad" label_browse: "Sirvi" - label_modification: "%{count} muudatus" - label_modification_plural: "%{count} muudatust" label_branch: "Haru" label_tag: "Sildiga" label_revision: "Sissekanne" @@ -1094,3 +1087,16 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: "Kokku" diff -r 0a574315af3e -r 4f746d8966dd config/locales/eu.yml --- a/config/locales/eu.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/eu.yml Fri Jun 14 09:28:30 2013 +0100 @@ -53,8 +53,8 @@ one: "ordu 1 inguru" other: "%{count} ordu inguru" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "ordu 1" + other: "%{count} ordu" x_days: one: "egun 1" other: "%{count} egun" @@ -202,8 +202,6 @@ mail_subject_wiki_content_updated: "'%{id}' wiki orria eguneratu da" mail_body_wiki_content_updated: "%{author}-(e)k '%{id}' wiki orria eguneratu du." - gui_validation_error: akats 1 - gui_validation_error_plural: "%{count} akats" field_name: Izena field_description: Deskribapena @@ -376,7 +374,6 @@ permission_edit_own_time_entries: Nork bere denbora egunkariak editatu permission_manage_news: Berriak kudeatu permission_comment_news: Berrien iruzkinak egin - permission_manage_documents: Dokumentuak kudeatu permission_view_documents: Dokumentuak ikusi permission_manage_files: Fitxategiak kudeatu permission_view_files: Fitxategiak ikusi @@ -497,8 +494,6 @@ label_text: Testu luzea label_attribute: Atributua label_attribute_plural: Atributuak - label_download: "Deskarga %{count}" - label_download_plural: "%{count} Deskarga" label_no_data: Ez dago erakusteko daturik label_change_status: Egoera aldatu label_history: Historikoa @@ -601,8 +596,6 @@ label_repository: Biltegia label_repository_plural: Biltegiak label_browse: Arakatu - label_modification: "aldaketa %{count}" - label_modification_plural: "%{count} aldaketa" label_branch: Adarra label_tag: Etiketa label_revision: Berrikuspena @@ -975,8 +968,6 @@ text_scm_command: Komandoa text_scm_command_version: Bertsioa label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1083,3 +1074,18 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Guztira + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. diff -r 0a574315af3e -r 4f746d8966dd config/locales/fa.yml --- a/config/locales/fa.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/fa.yml Fri Jun 14 09:28:30 2013 +0100 @@ -50,8 +50,8 @@ one: "نزدیک 1 ساعت" other: "نزدیک %{count} ساعت" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 ساعت" + other: "%{count} ساعت" x_days: one: "1 روز" other: "%{count} روز" @@ -208,8 +208,6 @@ mail_subject_wiki_content_updated: "برگه ویکی «%{id}» بروز شد" mail_body_wiki_content_updated: "برگه ویکی «%{id}» به دست %{author} بروز شد." - gui_validation_error: 1 ایراد - gui_validation_error_plural: "%{count} ایراد" field_name: نام field_description: یادداشت @@ -396,7 +394,6 @@ permission_edit_own_time_entries: ویرایش زمان گذاشته شده خود permission_manage_news: سرپرستی رویدادها permission_comment_news: گذاشتن دیدگاه روی رویدادها - permission_manage_documents: سرپرستی نوشتارها permission_view_documents: دیدن نوشتارها permission_manage_files: سرپرستی پرونده‌ها permission_view_files: دیدن پرونده‌ها @@ -521,8 +518,6 @@ label_text: نوشته بلند label_attribute: نشانه label_attribute_plural: نشانه - label_download: "%{count} بار Ø¯Ø±ÛŒØ§ÙØª شده" - label_download_plural: "%{count} بار Ø¯Ø±ÛŒØ§ÙØª شده" label_no_data: هیچ داده‌ای برای نمایش نیست label_change_status: جایگزینی وضعیت label_history: پیشینه @@ -625,8 +620,6 @@ label_repository: انباره label_repository_plural: انباره label_browse: چریدن - label_modification: "%{count} جایگذاری" - label_modification_plural: "%{count} جایگذاری" label_branch: شاخه label_tag: برچسب label_revision: بازبینی @@ -975,8 +968,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1083,3 +1074,18 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: جمله + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. diff -r 0a574315af3e -r 4f746d8966dd config/locales/fi.yml --- a/config/locales/fi.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/fi.yml Fri Jun 14 09:28:30 2013 +0100 @@ -95,8 +95,8 @@ one: "noin tunti" other: "noin %{count} tuntia" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 tunti" + other: "%{count} tuntia" x_days: one: "päivä" other: "%{count} päivää" @@ -205,8 +205,6 @@ mail_subject_account_activation_request: "%{value} tilin aktivointi pyyntö" mail_body_account_activation_request: "Uusi käyttäjä (%{value}) on rekisteröitynyt. Hänen tili odottaa hyväksyntääsi:" - gui_validation_error: 1 virhe - gui_validation_error_plural: "%{count} virhettä" field_name: Nimi field_description: Kuvaus @@ -397,8 +395,6 @@ label_text: Pitkä merkkijono label_attribute: Määre label_attribute_plural: Määreet - label_download: "%{count} Lataus" - label_download_plural: "%{count} Lataukset" label_no_data: Ei tietoa näytettäväksi label_change_status: Muutos tila label_history: Historia @@ -487,8 +483,6 @@ label_repository: Tietovarasto label_repository_plural: Tietovarastot label_browse: Selaus - label_modification: "%{count} muutos" - label_modification_plural: "%{count} muutettu" label_revision: Versio label_revision_plural: Versiot label_added: lisätty @@ -779,7 +773,6 @@ 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 @@ -954,7 +947,6 @@ label_principal_search: "Search for user or group:" label_user_search: "Search for user:" field_visible: Visible - setting_emails_header: Emails header setting_commit_logtime_activity_id: Activity for logged time text_time_logged_by_changeset: Applied in changeset %{value}. setting_commit_logtime_enabled: Enable time logging @@ -995,8 +987,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1103,3 +1093,19 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Yhteensä + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header diff -r 0a574315af3e -r 4f746d8966dd config/locales/fr.yml --- a/config/locales/fr.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/fr.yml Fri Jun 14 09:28:30 2013 +0100 @@ -224,8 +224,6 @@ mail_subject_wiki_content_updated: "Page wiki '%{id}' mise à jour" mail_body_wiki_content_updated: "La page wiki '%{id}' a été mise à jour par %{author}." - gui_validation_error: 1 erreur - gui_validation_error_plural: "%{count} erreurs" field_name: Nom field_description: Description @@ -240,6 +238,7 @@ field_author: Auteur field_created_on: "Créé " field_updated_on: "Mis-à-jour " + field_closed_on: Fermé field_field_format: Format field_is_for_all: Pour tous les projets field_possible_values: Valeurs possibles @@ -331,6 +330,7 @@ field_timeout: "Timeout (en secondes)" field_board_parent: Forum parent field_private_notes: Notes privées + field_inherit_members: Hériter les membres setting_app_title: Titre de l'application setting_app_subtitle: Sous-titre de l'application @@ -396,6 +396,8 @@ setting_thumbnails_enabled: Afficher les vignettes des images setting_thumbnails_size: Taille des vignettes (en pixels) setting_non_working_week_days: Jours non travaillés + setting_jsonp_enabled: Activer le support JSONP + setting_default_projects_tracker_ids: Trackers par défaut pour les nouveaux projets permission_add_project: Créer un projet permission_add_subprojects: Créer des sous-projets @@ -431,8 +433,10 @@ 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_add_documents: Ajouter des documents + permission_edit_documents: Modifier les documents + permission_delete_documents: Supprimer les documents permission_manage_files: Gérer les fichiers permission_view_files: Voir les fichiers permission_manage_wiki: Gérer le wiki @@ -562,8 +566,6 @@ label_text: Texte long label_attribute: Attribut label_attribute_plural: Attributs - label_download: "%{count} téléchargement" - label_download_plural: "%{count} téléchargements" label_no_data: Aucune donnée à afficher label_change_status: Changer le statut label_history: Historique @@ -611,6 +613,7 @@ one: 1 demande other: "%{count} demandes" label_total: Total + label_total_time: Temps total label_permissions: Permissions label_current_status: Statut actuel label_new_statuses_allowed: Nouveaux statuts autorisés @@ -677,8 +680,6 @@ label_repository_new: Nouveau dépôt label_repository_plural: Dépôts label_browse: Parcourir - label_modification: "%{count} modification" - label_modification_plural: "%{count} modifications" label_revision: "Révision " label_revision_plural: Révisions label_associated_revisions: Révisions associées @@ -858,13 +859,16 @@ label_readonly: Lecture label_required: Obligatoire label_attribute_of_project: "%{name} du projet" + label_attribute_of_issue: "%{name} de la demande" label_attribute_of_author: "%{name} de l'auteur" label_attribute_of_assigned_to: "%{name} de l'assigné" + label_attribute_of_user: "%{name} de l'utilisateur" label_attribute_of_fixed_version: "%{name} de la version cible" label_cross_project_descendants: Avec les sous-projets label_cross_project_tree: Avec tout l'arbre label_cross_project_hierarchy: Avec toute la hiérarchie label_cross_project_system: Avec tous les projets + label_gantt_progress_line: Ligne de progression button_login: Connexion button_submit: Soumettre @@ -940,7 +944,7 @@ text_tip_issue_begin_day: tâche commençant ce jour text_tip_issue_end_day: tâche finissant ce jour text_tip_issue_begin_end_day: tâche commençant et finissant ce jour - text_project_identifier_info: 'Seuls les lettres minuscules (a-z), chiffres, tirets et underscore sont autorisés.
      Un fois sauvegardé, l''identifiant ne pourra plus être modifié.' + text_project_identifier_info: 'Seuls les lettres minuscules (a-z), chiffres, tirets et underscore sont autorisés, doit commencer par une minuscule.
      Un fois sauvegardé, l''identifiant ne pourra plus être modifié.' text_caracters_maximum: "%{count} caractères maximum." text_caracters_minimum: "%{count} caractères minimum." text_length_between: "Longueur comprise entre %{min} et %{max} caractères." @@ -990,6 +994,7 @@ text_account_destroy_confirmation: "Êtes-vous sûr de vouloir continuer ?\nVotre compte sera définitivement supprimé, sans aucune possibilité de le réactiver." text_session_expiration_settings: "Attention : le changement de ces paramètres peut entrainer l'expiration des sessions utilisateurs en cours, y compris la vôtre." text_project_closed: Ce projet est fermé et accessible en lecture seule. + text_turning_multiple_off: "Si vous désactivez les valeurs multiples, les valeurs multiples seront supprimées pour n'en conserver qu'une par objet." default_role_manager: "Manager " default_role_developer: "Développeur " diff -r 0a574315af3e -r 4f746d8966dd config/locales/gl.yml --- a/config/locales/gl.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/gl.yml Fri Jun 14 09:28:30 2013 +0100 @@ -91,8 +91,8 @@ one: 'aproximadamente unha hora' other: '%{count} horas' x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 hora" + other: "%{count} horas" x_days: one: '1 día' other: '%{count} días' @@ -320,8 +320,6 @@ general_text_Yes: 'Si' general_text_no: 'non' general_text_yes: 'si' - gui_validation_error: 1 erro - gui_validation_error_plural: "%{count} erros" label_activity: Actividade label_add_another_file: Engadir outro arquivo label_add_note: Engadir unha nota @@ -414,8 +412,6 @@ label_document_added: Documento engadido label_document_new: Novo documento label_document_plural: Documentos - label_download: "%{count} Descarga" - label_download_plural: "%{count} Descargas" label_downloads_abbr: D/L label_duplicated_by: duplicada por label_duplicates: duplicada de @@ -495,8 +491,6 @@ label_message_plural: Mensaxes label_message_posted: Mensaxe engadida label_min_max_length: Lonxitude mín - máx - label_modification: "%{count} modificación" - label_modification_plural: "%{count} modificacións" label_modified: modificado label_module_plural: Módulos label_month: Mes @@ -695,7 +689,6 @@ 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 @@ -944,7 +937,6 @@ label_principal_search: "Search for user or group:" label_user_search: "Search for user:" field_visible: Visible - setting_emails_header: Emails header setting_commit_logtime_activity_id: Activity for logged time text_time_logged_by_changeset: Applied in changeset %{value}. setting_commit_logtime_enabled: Enable time logging @@ -985,8 +977,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1093,3 +1083,19 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Total + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header diff -r 0a574315af3e -r 4f746d8966dd config/locales/he.yml --- a/config/locales/he.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/he.yml Fri Jun 14 09:28:30 2013 +0100 @@ -56,8 +56,8 @@ one: 'בערך שעה ×חת' other: 'בערך %{count} שעות' x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 שעה" + other: "%{count} שעות" x_days: one: '×™×•× ×חד' other: '%{count} ימי×' @@ -212,8 +212,6 @@ mail_subject_wiki_content_updated: "דף ×”Ö¾wiki â€'%{id}' עודכן" mail_body_wiki_content_updated: דף ×”Ö¾wiki â€'%{id}' עודכן ×¢"×™ %{author}. - gui_validation_error: שגי××” 1 - gui_validation_error_plural: "%{count} שגי×ות" field_name: ×©× field_description: תי×ור @@ -393,7 +391,6 @@ permission_edit_own_time_entries: עריכת ×¨×™×©×•× ×”×–×ž× ×™× ×©×œ עצמו permission_manage_news: ניהול חדשות permission_comment_news: תגובה לחדשות - permission_manage_documents: ניהול ×ž×¡×ž×›×™× permission_view_documents: צפיה ×‘×ž×¡×ž×›×™× permission_manage_files: ניהול ×§×‘×¦×™× permission_view_files: צפיה ×‘×§×‘×¦×™× @@ -519,8 +516,6 @@ label_text: טקסט ×רוך label_attribute: תכונה label_attribute_plural: תכונות - label_download: "הורדה %{count}" - label_download_plural: "%{count} הורדות" label_no_data: ×ין מידע להציג label_change_status: שנה מצב label_history: היסטוריה @@ -623,8 +618,6 @@ label_repository: מ×גר label_repository_plural: מ××’×¨×™× label_browse: סייר - label_modification: "שינוי %{count}" - label_modification_plural: "%{count} שינויי×" label_branch: ×¢× ×£ label_tag: סימון label_revision: מהדורה @@ -938,7 +931,6 @@ label_principal_search: "Search for user or group:" label_user_search: "Search for user:" field_visible: Visible - setting_emails_header: Emails header setting_commit_logtime_activity_id: Activity for logged time text_time_logged_by_changeset: Applied in changeset %{value}. setting_commit_logtime_enabled: Enable time logging @@ -979,8 +971,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1087,3 +1077,19 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: סה"×› + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header diff -r 0a574315af3e -r 4f746d8966dd config/locales/hr.yml --- a/config/locales/hr.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/hr.yml Fri Jun 14 09:28:30 2013 +0100 @@ -49,8 +49,8 @@ one: "oko sat vremena" other: "oko %{count} sati" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 sata" + other: "%{count} sati" x_days: one: "1 dan" other: "%{count} dana" @@ -195,8 +195,6 @@ mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated" mail_body_wiki_content_updated: "The '%{id}' wiki page has been updated by %{author}." - gui_validation_error: 1 pogreÅ¡ka - gui_validation_error_plural: "%{count} pogreÅ¡aka" field_name: Ime field_description: Opis @@ -368,7 +366,6 @@ permission_edit_own_time_entries: Edit own time logs permission_manage_news: Upravljaj novostima permission_comment_news: Komentiraj novosti - permission_manage_documents: Upravljaj dokumentima permission_view_documents: Pregledaj dokumente permission_manage_files: Upravljaj datotekama permission_view_files: Pregledaj datoteke @@ -489,8 +486,6 @@ label_text: Long text label_attribute: Atribut label_attribute_plural: Atributi - label_download: "%{count} Download" - label_download_plural: "%{count} Downloads" label_no_data: Nema podataka za prikaz label_change_status: Promjena statusa label_history: Povijest @@ -592,8 +587,6 @@ label_repository: SkladiÅ¡te label_repository_plural: SkladiÅ¡ta label_browse: Pregled - label_modification: "%{count} promjena" - label_modification_plural: "%{count} promjena" label_branch: Branch label_tag: Tag label_revision: Revizija @@ -934,7 +927,6 @@ label_principal_search: "Search for user or group:" label_user_search: "Search for user:" field_visible: Visible - setting_emails_header: Emails header setting_commit_logtime_activity_id: Activity for logged time text_time_logged_by_changeset: Applied in changeset %{value}. setting_commit_logtime_enabled: Enable time logging @@ -975,8 +967,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1083,3 +1073,19 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Ukupno + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header diff -r 0a574315af3e -r 4f746d8966dd config/locales/hu.yml --- a/config/locales/hu.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/hu.yml Fri Jun 14 09:28:30 2013 +0100 @@ -51,8 +51,8 @@ one: 'csaknem 1 órája' other: 'csaknem %{count} órája' x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 óra" + other: "%{count} óra" x_days: one: '1 napja' other: '%{count} napja' @@ -203,8 +203,6 @@ mail_subject_account_activation_request: Redmine azonosító aktiválási kérelem mail_body_account_activation_request: "Egy új felhasználó (%{value}) regisztrált, azonosítója jóváhasgyásra várakozik:" - gui_validation_error: 1 hiba - gui_validation_error_plural: "%{count} hiba" field_name: Név field_description: Leírás @@ -415,8 +413,6 @@ label_text: Hosszú szöveg label_attribute: Tulajdonság label_attribute_plural: Tulajdonságok - label_download: "%{count} Letöltés" - label_download_plural: "%{count} Letöltés" label_no_data: Nincs megjeleníthetÅ‘ adat label_change_status: Státusz módosítása label_history: Történet @@ -516,8 +512,6 @@ label_repository: Forráskód label_repository_plural: Forráskódok label_browse: Tallóz - label_modification: "%{count} változás" - label_modification_plural: "%{count} változás" label_revision: Revízió label_revision_plural: Revíziók label_associated_revisions: Kapcsolt revíziók @@ -777,7 +771,6 @@ 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 @@ -993,8 +986,6 @@ text_scm_command: Parancs text_scm_command_version: Verzió label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1101,3 +1092,18 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Összesen + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. diff -r 0a574315af3e -r 4f746d8966dd config/locales/id.yml --- a/config/locales/id.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/id.yml Fri Jun 14 09:28:30 2013 +0100 @@ -47,8 +47,8 @@ one: "sekitar sejam" other: "sekitar %{count} jam" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 jam" + other: "%{count} jam" x_days: one: "sehari" other: "%{count} hari" @@ -195,8 +195,6 @@ mail_subject_wiki_content_updated: "'%{id}' halaman wiki sudah diperbarui" mail_body_wiki_content_updated: "The '%{id}' halaman wiki sudah diperbarui oleh %{author}." - gui_validation_error: 1 kesalahan - gui_validation_error_plural: "%{count} kesalahan" field_name: Nama field_description: Deskripsi @@ -361,7 +359,6 @@ permission_edit_own_time_entries: Sunting catatan waktu saya permission_manage_news: Atur berita permission_comment_news: Komentari berita - permission_manage_documents: Atur dokumen permission_view_documents: Tampilkan dokumen permission_manage_files: Atur berkas permission_view_files: Tampilkan berkas @@ -481,8 +478,6 @@ label_text: Long text label_attribute: Atribut label_attribute_plural: Atribut - label_download: "%{count} Unduhan" - label_download_plural: "%{count} Unduhan" label_no_data: Tidak ada data untuk ditampilkan label_change_status: Status perubahan label_history: Riwayat @@ -584,8 +579,6 @@ label_repository: Repositori label_repository_plural: Repositori label_browse: Jelajah - label_modification: "%{count} perubahan" - label_modification_plural: "%{count} perubahan" label_branch: Cabang label_tag: Tag label_revision: Revisi @@ -937,7 +930,6 @@ label_principal_search: "Search for user or group:" label_user_search: "Search for user:" field_visible: Visible - setting_emails_header: Emails header setting_commit_logtime_activity_id: Activity for logged time text_time_logged_by_changeset: Applied in changeset %{value}. setting_commit_logtime_enabled: Enable time logging @@ -978,8 +970,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1086,3 +1076,19 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Total + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header diff -r 0a574315af3e -r 4f746d8966dd config/locales/it.yml --- a/config/locales/it.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/it.yml Fri Jun 14 09:28:30 2013 +0100 @@ -55,8 +55,8 @@ one: "circa un'ora" other: "circa %{count} ore" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 ora" + other: "%{count} ore" x_days: one: "1 giorno" other: "%{count} giorni" @@ -176,8 +176,6 @@ mail_subject_register: "Attivazione utente %{value}" mail_body_register: "Per attivare l'utente, usa il seguente collegamento:" - gui_validation_error: 1 errore - gui_validation_error_plural: "%{count} errori" field_name: Nome field_description: Descrizione @@ -357,8 +355,6 @@ label_text: Testo esteso label_attribute: Attributo label_attribute_plural: Attributi - label_download: "%{count} Download" - label_download_plural: "%{count} Download" label_no_data: Nessun dato disponibile label_change_status: Cambia stato label_history: Cronologia @@ -446,8 +442,6 @@ label_day_plural: giorni label_repository: Repository label_browse: Sfoglia - label_modification: "%{count} modifica" - label_modification_plural: "%{count} modifiche" label_revision: Versione label_revision_plural: Versioni label_added: aggiunto @@ -589,8 +583,8 @@ text_unallowed_characters: Caratteri non permessi text_comma_separated: Valori multipli permessi (separati da virgole). text_issues_ref_in_commit_messages: Segnalazioni di riferimento e chiusura nei messaggi di commit - text_issue_added: "E' stata segnalata l'anomalia %{id} da %{author}." - text_issue_updated: "L'anomalia %{id} è stata aggiornata da %{author}." + text_issue_added: "%{author} ha aggiunto la segnalazione %{id}." + text_issue_updated: "La segnalazione %{id} è stata aggiornata da %{author}." text_wiki_destroy_confirmation: Sicuro di voler eliminare questo wiki e tutti i suoi contenuti? text_issue_category_destroy_question: "Alcune segnalazioni (%{count}) risultano assegnate a questa categoria. Cosa vuoi fare ?" text_issue_category_destroy_assignments: Rimuovi le assegnazioni a questa categoria @@ -759,7 +753,6 @@ 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 @@ -1009,77 +1002,87 @@ button_export: Esporta label_export_options: "%{export_format} opzioni per l'export" error_attachment_too_big: Questo file non può essere caricato in quanto la sua dimensione supera la massima consentita (%{max_size}) - notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}." + notice_failed_to_save_time_entries: "Non ho potuto salvare %{count} registrazioni di tempo impiegato su %{total} selezionate: %{ids}." label_x_issues: zero: 0 segnalazione one: 1 segnalazione other: "%{count} segnalazioni" - label_repository_new: New repository - field_repository_is_default: Main repository - label_copy_attachments: Copy attachments + label_repository_new: Nuovo repository + field_repository_is_default: Repository principale + label_copy_attachments: Copia allegati label_item_position: "%{position}/%{count}" label_completed_versions: Completed versions - text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
      Once saved, the identifier cannot be changed. - field_multiple: Multiple values + text_project_identifier_info: Consentiti solo lettere minuscole (a-z), numeri, trattini e trattini bassi.
      Una volta salvato, l'identificatore non può essere modificato. + field_multiple: Valori multipli setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed - text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes - text_issue_conflict_resolution_overwrite: Apply my changes anyway (previous notes will be kept but some changes may be overwritten) - notice_issue_update_conflict: The issue has been updated by an other user while you were editing it. - text_issue_conflict_resolution_cancel: Discard all my changes and redisplay %{link} - permission_manage_related_issues: Manage related issues - field_auth_source_ldap_filter: LDAP filter - label_search_for_watchers: Search for watchers to add - notice_account_deleted: Your account has been permanently deleted. - setting_unsubscribe: Allow users to delete their own account - button_delete_my_account: Delete my account - text_account_destroy_confirmation: |- - Are you sure you want to proceed? - Your account will be permanently deleted, with no way to reactivate it. - error_session_expired: Your session has expired. Please login again. - text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours." - setting_session_lifetime: Session maximum lifetime - setting_session_timeout: Session inactivity timeout - label_session_expiration: Session expiration - permission_close_project: Close / reopen the project - label_show_closed_projects: View closed projects - button_close: Close - button_reopen: Reopen - project_status_active: active - project_status_closed: closed - project_status_archived: archived - text_project_closed: This project is closed and read-only. - notice_user_successful_create: User %{id} created. - field_core_fields: Standard fields - field_timeout: Timeout (in seconds) - setting_thumbnails_enabled: Display attachment thumbnails - setting_thumbnails_size: Thumbnails size (in pixels) - label_status_transitions: Status transitions - label_fields_permissions: Fields permissions - label_readonly: Read-only - label_required: Required - text_repository_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
      Once saved, the identifier cannot be changed. + text_issue_conflict_resolution_add_notes: Aggiunge le mie note e non salvare le mie ulteriori modifiche + text_issue_conflict_resolution_overwrite: Applica comunque le mie modifiche (le note precedenti verranno mantenute ma alcuni cambiamenti potrebbero essere sovrascritti) + notice_issue_update_conflict: La segnalazione è stata aggiornata da un altro utente mentre la stavi editando. + text_issue_conflict_resolution_cancel: Cancella ogni modifica e rivisualizza %{link} + permission_manage_related_issues: Gestisci relative segnalazioni + field_auth_source_ldap_filter: Filtro LDAP + label_search_for_watchers: Cerca osservatori da aggiungere + notice_account_deleted: Il tuo account sarà definitivamente rimosso. + setting_unsubscribe: Consentire agli utenti di cancellare il proprio account + button_delete_my_account: Cancella il mio account + text_account_destroy_confirmation: "Sei sicuro di voler procedere?\nIl tuo account sarà definitivamente cancellato, senza alcuna possibilità di ripristino." + error_session_expired: "La tua sessione è scaduta. Effettua nuovamente il login." + text_session_expiration_settings: "Attenzione: la modifica di queste impostazioni può far scadere le sessioni correnti, compresa la tua." + setting_session_lifetime: Massima durata di una sessione + setting_session_timeout: Timeout di inattività di una sessione + label_session_expiration: Scadenza sessione + permission_close_project: Chiusura / riapertura progetto + label_show_closed_projects: Vedi progetti chiusi + button_close: Chiudi + button_reopen: Riapri + project_status_active: attivo + project_status_closed: chiuso + project_status_archived: archiviato + text_project_closed: Questo progetto è chiuso e in sola lettura. + notice_user_successful_create: Creato utente %{id}. + field_core_fields: Campi standard + field_timeout: Timeout (in secondi) + setting_thumbnails_enabled: Mostra miniature degli allegati + setting_thumbnails_size: Dimensioni delle miniature (in pixels) + label_status_transitions: Transizioni di stato + label_fields_permissions: Permessi sui campi + label_readonly: Sola lettura + label_required: Richiesto + text_repository_identifier_info: Consentiti solo lettere minuscole (a-z), numeri, trattini e trattini bassi.
      Una volta salvato, ll'identificatore non può essere modificato. field_board_parent: Parent forum label_attribute_of_project: Project's %{name} label_attribute_of_author: Author's %{name} - label_attribute_of_assigned_to: Assignee's %{name} + label_attribute_of_assigned_to: Assegnatari %{name} label_attribute_of_fixed_version: Target version's %{name} - label_copy_subtasks: Copy subtasks - label_copied_to: copied to - label_copied_from: copied from - label_any_issues_in_project: any issues in project - label_any_issues_not_in_project: any issues not in project - field_private_notes: Private notes - permission_view_private_notes: View private notes - permission_set_notes_private: Set notes as private - label_no_issues_in_project: no issues in project + label_copy_subtasks: Copia sottoattività + label_copied_to: copia a + label_copied_from: copia da + label_any_issues_in_project: ogni segnalazione del progetto + label_any_issues_not_in_project: ogni segnalazione non nel progetto + field_private_notes: Note private + permission_view_private_notes: Visualizza note private + permission_set_notes_private: Imposta note come private + label_no_issues_in_project: progetto privo di segnalazioni label_any: tutti - label_last_n_weeks: last %{count} weeks - setting_cross_project_subtasks: Allow cross-project subtasks + label_last_n_weeks: ultime %{count} settimane + setting_cross_project_subtasks: Consenti sottoattività cross-project label_cross_project_descendants: Con sottoprogetti label_cross_project_tree: Con progetto padre label_cross_project_hierarchy: Con gerarchia progetto label_cross_project_system: Con tutti i progetti - button_hide: Hide - setting_non_working_week_days: Non-working days - label_in_the_next_days: in the next - label_in_the_past_days: in the past + button_hide: Nascondi + setting_non_working_week_days: Giorni non lavorativi + label_in_the_next_days: nei prossimi + label_in_the_past_days: nei passati + label_attribute_of_user: Utente %{name} + text_turning_multiple_off: Disabilitando valori multipli, i valori multipli verranno rimossi, in modo da mantenere un solo valore per item. + label_attribute_of_issue: Segnalazione %{name} + permission_add_documents: Aggiungi documenti + permission_edit_documents: Edita documenti + permission_delete_documents: Cancella documenti + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Abilita supporto a JSONP + field_inherit_members: Eredita membri + field_closed_on: Chiuso + setting_default_projects_tracker_ids: Trackers di default per nuovi progetti + label_total_time: Totale diff -r 0a574315af3e -r 4f746d8966dd config/locales/ja.yml --- a/config/locales/ja.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/ja.yml Fri Jun 14 09:28:30 2013 +0100 @@ -176,7 +176,7 @@ notice_account_invalid_creditentials: ユーザーåã‚‚ã—ãã¯ãƒ‘スワードãŒç„¡åйã§ã™ notice_account_password_updated: ãƒ‘ã‚¹ãƒ¯ãƒ¼ãƒ‰ãŒæ›´æ–°ã•れã¾ã—ãŸã€‚ notice_account_wrong_password: パスワードãŒé•ã„ã¾ã™ - notice_account_register_done: アカウントãŒä½œæˆã•れã¾ã—ãŸã€‚ + notice_account_register_done: アカウントãŒä½œæˆã•れã¾ã—ãŸã€‚有効ã«ã™ã‚‹ã«ã¯é€ä¿¡ã—ãŸãƒ¡ãƒ¼ãƒ«ã«ã‚るリンクをクリックã—ã¦ãã ã•ã„。 notice_account_unknown_email: ユーザーãŒå­˜åœ¨ã—ã¾ã›ã‚“。 notice_can_t_change_password: ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã§ã¯å¤–部èªè¨¼ã‚’使ã£ã¦ã„ã¾ã™ã€‚パスワードã¯å¤‰æ›´ã§ãã¾ã›ã‚“。 notice_account_lost_email_sent: æ–°ã—ã„パスワードã®ãƒ¡ãƒ¼ãƒ«ã‚’é€ä¿¡ã—ã¾ã—ãŸã€‚ @@ -238,8 +238,6 @@ mail_subject_wiki_content_updated: "Wikiページ %{id} ãŒæ›´æ–°ã•れã¾ã—ãŸ" mail_body_wiki_content_updated: "%{author} ã«ã‚ˆã£ã¦Wikiページ %{id} ãŒæ›´æ–°ã•れã¾ã—ãŸã€‚" - gui_validation_error: 1ä»¶ã®ã‚¨ãƒ©ãƒ¼ - gui_validation_error_plural: "%{count}ä»¶ã®ã‚¨ãƒ©ãƒ¼" field_name: åç§° field_description: 説明 @@ -403,6 +401,7 @@ setting_commit_logtime_enabled: コミット時ã«ä½œæ¥­æ™‚間を記録ã™ã‚‹ setting_commit_logtime_activity_id: 作業時間ã®ä½œæ¥­åˆ†é¡ž setting_gantt_items_limit: ガントãƒãƒ£ãƒ¼ãƒˆæœ€å¤§è¡¨ç¤ºé …目数 + setting_default_projects_tracker_ids: æ–°è¦ãƒ—ロジェクトã«ãŠã„ã¦ãƒ‡ãƒ•ã‚©ãƒ«ãƒˆã§æœ‰åйã«ãªã‚‹ãƒˆãƒ©ãƒƒã‚«ãƒ¼ permission_add_project: プロジェクトã®è¿½åŠ  permission_add_subprojects: サブプロジェクトã®è¿½åŠ  @@ -434,7 +433,6 @@ permission_manage_project_activities: 作業分類 (時間トラッキング) ã®ç®¡ç† permission_manage_news: ニュースã®ç®¡ç† permission_comment_news: ニュースã¸ã®ã‚³ãƒ¡ãƒ³ãƒˆ - permission_manage_documents: 文書ã®ç®¡ç† permission_view_documents: 文書ã®é–²è¦§ permission_manage_files: ファイルã®ç®¡ç† permission_view_files: ファイルã®é–²è¦§ @@ -450,7 +448,7 @@ permission_manage_repository: リãƒã‚¸ãƒˆãƒªã®ç®¡ç† permission_browse_repository: リãƒã‚¸ãƒˆãƒªã®é–²è¦§ permission_view_changesets: 更新履歴ã®é–²è¦§ - permission_commit_access: コミットã®é–²è¦§ + permission_commit_access: ã‚³ãƒŸãƒƒãƒˆæ¨©é™ permission_manage_boards: フォーラムã®ç®¡ç† permission_view_messages: メッセージã®é–²è¦§ permission_add_messages: メッセージã®è¿½åŠ  @@ -561,8 +559,6 @@ label_text: é•·ã„テキスト label_attribute: 属性 label_attribute_plural: 属性 - label_download: "%{count}ダウンロード" - label_download_plural: "%{count}ダウンロード" label_no_data: 表示ã™ã‚‹ãƒ‡ãƒ¼ã‚¿ãŒã‚りã¾ã›ã‚“ label_change_status: ステータスã®å¤‰æ›´ label_history: 履歴 @@ -667,8 +663,6 @@ label_repository: リãƒã‚¸ãƒˆãƒª label_repository_plural: リãƒã‚¸ãƒˆãƒª label_browse: ブラウズ - label_modification: "%{count}点ã®å¤‰æ›´" - label_modification_plural: "%{count}点ã®å¤‰æ›´" label_branch: ブランムlabel_tag: ã‚¿ã‚° label_revision: リビジョン @@ -841,6 +835,7 @@ label_git_report_last_commit: ファイルã¨ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã®æœ€æ–°ã‚³ãƒŸãƒƒãƒˆã‚’表示ã™ã‚‹ label_parent_revision: 親 label_child_revision: å­ + label_gantt_progress_line: イナズマ線 button_login: ログイン button_submit: é€ä¿¡ @@ -863,7 +858,7 @@ button_unlock: アンロック button_download: ダウンロード button_list: 一覧 - button_view: 見る + button_view: 表示 button_move: 移動 button_move_and_follow: 移動後表示 button_back: 戻る @@ -911,9 +906,9 @@ text_journal_set_to: "%{label} ã‚’ %{value} ã«ã‚»ãƒƒãƒˆ" text_journal_deleted: "%{label} を削除 (%{old})" text_journal_added: "%{label} %{value} を追加" - text_tip_issue_begin_day: ã“ã®æ—¥ã«é–‹å§‹ã™ã‚‹ã‚¿ã‚¹ã‚¯ - text_tip_issue_end_day: ã“ã®æ—¥ã«çµ‚了ã™ã‚‹ã‚¿ã‚¹ã‚¯ - text_tip_issue_begin_end_day: ã“ã®æ—¥ã®ã†ã¡ã«é–‹å§‹ã—ã¦çµ‚了ã™ã‚‹ã‚¿ã‚¹ã‚¯ + text_tip_issue_begin_day: ã“ã®æ—¥ã«é–‹å§‹ã™ã‚‹ãƒã‚±ãƒƒãƒˆ + text_tip_issue_end_day: ã“ã®æ—¥ã«çµ‚了ã™ã‚‹ãƒã‚±ãƒƒãƒˆ + text_tip_issue_begin_end_day: ã“ã®æ—¥ã«é–‹å§‹ãƒ»çµ‚了ã™ã‚‹ãƒã‚±ãƒƒãƒˆ text_caracters_maximum: "最大%{count}文字ã§ã™ã€‚" text_caracters_minimum: "最低%{count}文字ã®é•·ã•ãŒå¿…è¦ã§ã™" text_length_between: "é•·ã•ã¯%{min}ã‹ã‚‰%{max}文字ã¾ã§ã§ã™ã€‚" @@ -1048,7 +1043,7 @@ label_copy_attachments: 添付ファイルをコピー label_item_position: "%{position}/%{count}" label_completed_versions: 完了ã—ãŸãƒãƒ¼ã‚¸ãƒ§ãƒ³ - text_project_identifier_info: ã‚¢ãƒ«ãƒ•ã‚¡ãƒ™ãƒƒãƒˆå°æ–‡å­—(a-z)・数字・ãƒã‚¤ãƒ•ン・アンダースコアãŒä½¿ãˆã¾ã™ã€‚
      識別å­ã¯å¾Œã§å¤‰æ›´ã™ã‚‹ã“ã¨ã¯ã§ãã¾ã›ã‚“。 + text_project_identifier_info: ã‚¢ãƒ«ãƒ•ã‚¡ãƒ™ãƒƒãƒˆå°æ–‡å­—(a-z)・数字・ãƒã‚¤ãƒ•ン・アンダースコアãŒä½¿ãˆã¾ã™ã€‚最åˆã®æ–‡å­—ã¯ã‚¢ãƒ«ãƒ•ァベットã®å°æ–‡å­—ã«ã—ã¦ãã ã•ã„。
      識別å­ã¯å¾Œã§å¤‰æ›´ã™ã‚‹ã“ã¨ã¯ã§ãã¾ã›ã‚“。 field_multiple: è¤‡æ•°é¸æŠžå¯ setting_commit_cross_project_ref: ç•°ãªã‚‹ãƒ—ロジェクトã®ãƒã‚±ãƒƒãƒˆã®å‚ç…§/ä¿®æ­£ã‚’è¨±å¯ text_issue_conflict_resolution_add_notes: 自分ã®ç·¨é›†å†…å®¹ã‚’ç ´æ£„ã—æ³¨è¨˜ã®ã¿è¿½åŠ  @@ -1112,3 +1107,13 @@ setting_non_working_week_days: 休業日 label_in_the_next_days: 今後○日 label_in_the_past_days: éŽåŽ»â—‹æ—¥ + label_attribute_of_user: ユーザー㮠%{name} + text_turning_multiple_off: ã“ã®è¨­å®šã‚’無効ã«ã™ã‚‹ã¨ã€è¤‡æ•°é¸æŠžã•れã¦ã„る値ã®ã†ã¡1個ã ã‘ãŒä¿æŒã•れ残りã¯é¸æŠžè§£é™¤ã•れã¾ã™ã€‚ + label_attribute_of_issue: ãƒã‚±ãƒƒãƒˆã® %{name} + permission_add_documents: 文書ã®è¿½åŠ  + permission_edit_documents: 文書ã®ç·¨é›† + permission_delete_documents: 文書ã®å‰Šé™¤ + setting_jsonp_enabled: JSONPを有効ã«ã™ã‚‹ + field_inherit_members: メンãƒãƒ¼ã‚’継承 + field_closed_on: 終了日 + label_total_time: åˆè¨ˆ diff -r 0a574315af3e -r 4f746d8966dd config/locales/ko.yml --- a/config/locales/ko.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/ko.yml Fri Jun 14 09:28:30 2013 +0100 @@ -50,8 +50,8 @@ one: "약 한시간" other: "약 %{count}시간" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 시간" + other: "%{count} 시간" x_days: one: "하루" other: "%{count}ì¼" @@ -231,15 +231,13 @@ mail_body_account_information: 계정 ì •ë³´ mail_subject_account_activation_request: "%{value} 계정 활성화 요청" mail_body_account_activation_request: "새 사용ìž(%{value})ê°€ 등ë¡ë˜ì—ˆìŠµë‹ˆë‹¤. 관리ìžë‹˜ì˜ 승ì¸ì„ 기다리고 있습니다.:" - mail_body_reminder: "ë‹¹ì‹ ì´ ë§¡ê³  있는 ì¼ê° %{count}ê°œì˜ ì™„ë£Œ ê¸°í•œì´ %{days}ì¼ í›„ 입니다." + mail_body_reminder: "ë‹¹ì‹ ì´ ë§¡ê³  있는 ì¼ê° %{count}ê°œì˜ ì™„ë£Œê¸°í•œì´ %{days}ì¼ í›„ 입니다." mail_subject_reminder: "ë‚´ì¼ì´ ë§Œê¸°ì¸ ì¼ê° %{count}ê°œ (%{days})" mail_subject_wiki_content_added: "위키페ì´ì§€ '%{id}'ì´(ê°€) 추가ë˜ì—ˆìŠµë‹ˆë‹¤." mail_subject_wiki_content_updated: "'위키페ì´ì§€ %{id}'ì´(ê°€) 수정ë˜ì—ˆìŠµë‹ˆë‹¤." mail_body_wiki_content_added: "%{author}ì´(ê°€) 위키페ì´ì§€ '%{id}'ì„(를) 추가하였습니다." mail_body_wiki_content_updated: "%{author}ì´(ê°€) 위키페ì´ì§€ '%{id}'ì„(를) 수정하였습니다." - gui_validation_error: ì—러 - gui_validation_error_plural: "%{count}ê°œ ì—러" field_name: ì´ë¦„ field_description: 설명 @@ -271,7 +269,7 @@ field_is_default: 기본값 field_tracker: 유형 field_subject: 제목 - field_due_date: 완료 기한 + field_due_date: 완료기한 field_assigned_to: ë‹´ë‹¹ìž field_priority: 우선순위 field_fixed_version: 목표버전 @@ -326,7 +324,7 @@ field_comments_sorting: 댓글 ì •ë ¬ field_parent_title: ìƒìœ„ 제목 field_editable: 편집가능 - field_watcher: ì¼ê°ì§€í‚´ì´ + field_watcher: ì¼ê°ê´€ëžŒìž field_identity_url: OpenID URL field_content: ë‚´ìš© field_group_by: 결과를 묶어 보여줄 기준 @@ -392,15 +390,14 @@ permission_save_queries: ê²€ìƒ‰ì–‘ì‹ ì €ìž¥ permission_view_gantt: Gantt차트 보기 permission_view_calendar: 달력 보기 - permission_view_issue_watchers: ì¼ê°ì§€í‚´ì´ 보기 - permission_add_issue_watchers: ì¼ê°ì§€í‚´ì´ 추가 + 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: 파ì¼ë³´ê¸° @@ -460,9 +457,9 @@ label_role_plural: ì—­í•  label_role_new: 새 ì—­í•  label_role_and_permissions: ì—­í•  ë° ê¶Œí•œ - label_member: ë‹´ë‹¹ìž - label_member_new: 새 ë‹´ë‹¹ìž - label_member_plural: ë‹´ë‹¹ìž + label_member: êµ¬ì„±ì› + label_member_new: 새 êµ¬ì„±ì› + label_member_plural: êµ¬ì„±ì› label_tracker: ì¼ê° 유형 label_tracker_plural: ì¼ê° 유형 label_tracker_new: 새 ì¼ê° 유형 @@ -519,8 +516,6 @@ label_text: í…스트 label_attribute: ì†ì„± label_attribute_plural: ì†ì„± - label_download: "%{count}회 다운로드" - label_download_plural: "%{count}회 다운로드" label_no_data: 표시할 ë°ì´í„°ê°€ 없습니다. label_change_status: ìƒíƒœ 변경 label_history: ì´ë ¥ @@ -622,8 +617,6 @@ label_repository: 저장소 label_repository_plural: 저장소 label_browse: 저장소 둘러보기 - label_modification: "%{count} 변경" - label_modification_plural: "%{count} 변경" label_revision: ê°œì •íŒ label_revision_plural: ê°œì •íŒ label_associated_revisions: ê´€ë ¨ëœ ê°œì •íŒë“¤ @@ -668,8 +661,8 @@ label_commits_per_month: 월별 커밋 ë‚´ì—­ label_commits_per_author: ì €ìžë³„ 커밋 ë‚´ì—­ label_view_diff: ì°¨ì´ì  보기 - label_diff_inline: 한줄로 - label_diff_side_by_side: ë‘줄로 + label_diff_inline: ë‘줄로 + label_diff_side_by_side: 한줄로 label_options: 옵션 label_copy_workflow_from: 업무í름 복사하기 label_permissions_report: 권한 보고서 @@ -749,7 +742,7 @@ label_planning: 프로ì íŠ¸ê³„íš label_incoming_emails: 수신 ë©”ì¼ label_generate_key: 키 ìƒì„± - label_issue_watchers: ì¼ê°ì§€í‚´ì´ + label_issue_watchers: ì¼ê°ê´€ëžŒìž label_example: 예 label_display: í‘œì‹œë°©ì‹ label_sort: ì •ë ¬ @@ -795,7 +788,7 @@ button_change_password: 비밀번호 바꾸기 button_copy: 복사 button_annotate: ì´ë ¥í•´ì„¤ - button_update: 수정 + button_update: ì—…ë°ì´íЏ button_configure: 설정 button_quote: 댓글달기 @@ -894,7 +887,7 @@ text_journal_added: "%{label}ì— %{value}ì´(ê°€) 추가ë˜ì—ˆìŠµë‹ˆë‹¤." field_active: 사용중 enumeration_system_activity: 시스템 작업 - permission_delete_issue_watchers: ì¼ê°ì§€í‚´ì´ 지우기 + permission_delete_issue_watchers: ì¼ê°ê´€ëžŒìž 지우기 version_status_closed: 닫힘 version_status_locked: ìž ê¹€ version_status_open: ì§„í–‰ @@ -1010,7 +1003,7 @@ permission_set_own_issues_private: "ìžì‹ ì˜ ì¼ê°ì„ 공개나 비공개로 설정" field_is_private: "비공개" permission_set_issues_private: "ì¼ê°ì„ 공개나 비공개로 설정" - label_issues_visibility_public: "모든 비공개 ì¼ê°" + label_issues_visibility_public: "비공개 ì¼ê° 제외" text_issues_destroy_descendants_confirmation: "%{count} ê°œì˜ í•˜ìœ„ ì¼ê°ì„ 삭제할 것입니다." field_commit_logs_encoding: "커밋(commit) ê¸°ë¡ ì¸ì½”딩" field_scm_path_encoding: "경로 ì¸ì½”딩" @@ -1077,7 +1070,7 @@ text_issue_conflict_resolution_cancel: "ë³€ê²½ë‚´ìš©ì„ ë˜ëŒë¦¬ê³  다시 표시 %{link}" permission_manage_related_issues: ì—°ê²°ëœ ì¼ê° 관리 field_auth_source_ldap_filter: LDAP í•„í„° - label_search_for_watchers: 추가할 ì¼ê°ì§€í‚´ì´ 검색 + label_search_for_watchers: 추가할 ì¼ê°ê´€ëžŒìž 검색 notice_account_deleted: ë‹¹ì‹ ì˜ ê³„ì •ì´ ì™„ì „ížˆ ì‚­ì œë˜ì—ˆìŠµë‹ˆë‹¤. setting_unsubscribe: 사용ìžë“¤ì´ ìžì‹ ì˜ ê³„ì •ì„ ì‚­ì œí† ë¡ í—ˆìš© button_delete_my_account: ë‚˜ì˜ ê³„ì • ì‚­ì œ @@ -1120,7 +1113,7 @@ field_private_notes: 비공개 ë§ê¸€ permission_view_private_notes: 비공개 ë§ê¸€ 보기 permission_set_notes_private: ë§ê¸€ì„ 비공개로 설정 - label_no_issues_in_project: ë‹¤ìŒ í”„ë¡œì íЏ ë‚´ì—서 해당 ì¼ê° ì—†ìŒ + label_no_issues_in_project: ë‹¤ìŒ í”„ë¡œì íŠ¸ì˜ ì¼ê° 제외 label_any: ëª¨ë‘ label_last_n_weeks: 최근 %{count} 주 setting_cross_project_subtasks: 다른 프로ì íŠ¸ì˜ ì¼ê°ì„ ìƒìœ„ ì¼ê°ìœ¼ë¡œ 지정하는 ê²ƒì„ í—ˆìš© @@ -1132,3 +1125,15 @@ setting_non_working_week_days: ë¹„ê·¼ë¬´ì¼ (non-working days) label_in_the_next_days: ë‹¤ìŒ label_in_the_past_days: 지난 + label_attribute_of_user: "사용ìžì˜ %{name}" + text_turning_multiple_off: 복수선íƒì„ 비활성화하면, í•˜ë‚˜ì˜ ê°’ì„ ì œì™¸í•œ 나머지 ê°’ë“¤ì´ ì§€ì›Œì§‘ë‹ˆë‹¤. + label_attribute_of_issue: "ì¼ê°ì˜ %{name}" + permission_add_documents: 문서 추가 + permission_edit_documents: 문서 편집 + permission_delete_documents: 문서 ì‚­ì œ + label_gantt_progress_line: Progress line + setting_jsonp_enabled: JSONP 허용 + field_inherit_members: ìƒìœ„ 프로ì íŠ¸ë¡œë¶€í„° 구성ì›ì„ ìƒì† + field_closed_on: ì™„ë£Œì¼ + setting_default_projects_tracker_ids: 새 프로ì íŠ¸ì— ê¸°ë³¸ì ìœ¼ë¡œ 추가할 ì¼ê° 유형 + label_total_time: 합계 diff -r 0a574315af3e -r 4f746d8966dd config/locales/lt.yml --- a/config/locales/lt.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/lt.yml Fri Jun 14 09:28:30 2013 +0100 @@ -1,8 +1,10 @@ # Lithuanian translations for Ruby on Rails # by Laurynas Butkus (laurynas.butkus@gmail.com) # Redmine translation by Gediminas Muižis gediminas.muizis@gmail.com -# and Sergej Jegorov sergej.jegorov@gmail.com -# and Gytis Gurklys gytis.gurklys@gmail.com +# and Sergej Jegorov sergej.jegorov@gmail.com +# and Gytis Gurklys gytis.gurklys@gmail.com +# and Andrius KriuÄkovas andrius.kriuckovas@gmail.com + lt: direction: ltr date: @@ -228,8 +230,8 @@ notice_default_data_loaded: Numatytoji konfiguracija sÄ—kmingai užkrauta. notice_unable_delete_version: Neįmanoma panaikinti versijÄ…. notice_unable_delete_time_entry: Neįmano iÅ¡trinti laiko žurnalo įrašą. - notice_issue_done_ratios_updated: Issue done ratios updated. - notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) + notice_issue_done_ratios_updated: Problemos baigtumo rodikliai atnaujinti. + notice_gantt_chart_truncated: Grafikas buvo sutrumpintas, kadangi jis virÅ¡ija maksimalų (%{max}) leistinų atvaizduoti elementų kiekį notice_issue_successful_create: Darbas %{id} sukurtas. error_can_t_load_default_data: "Numatytoji konfiguracija negali bÅ«ti užkrauta: %{value}" @@ -241,16 +243,16 @@ error_no_tracker_in_project: 'Joks pÄ—dsekys nesusietas su Å¡iuo projektu. PraÅ¡om patikrinti Projekto nustatymus.' error_no_default_issue_status: Nenustatyta numatytoji darbų bÅ«sena. PraÅ¡ome patikrinti konfigÅ«ravimÄ… ("Administravimas -> Darbų bÅ«senos"). error_can_not_delete_custom_field: Negalima iÅ¡trinti kliento lauko - error_can_not_delete_tracker: "This tracker contains issues and cannot be deleted." - error_can_not_remove_role: "This role is in use and cannot be deleted." + error_can_not_delete_tracker: "Å is pÄ—dsekys turi įraÅ¡us ir todÄ—l negali bÅ«ti iÅ¡trintas." + error_can_not_remove_role: "Å i rolÄ— yra naudojama ir negali bÅ«ti iÅ¡trinta." error_can_not_reopen_issue_on_closed_version: Uždarytai versijai priskirtas darbas negali bÅ«ti atnaujintas. error_can_not_archive_project: Å io projekto negalima suarchyvuoti - error_issue_done_ratios_not_updated: "Issue done ratios not updated." - error_workflow_copy_source: 'Please select a source tracker or role' - error_workflow_copy_target: 'Please select target tracker(s) and role(s)' + error_issue_done_ratios_not_updated: "Ä®raÅ¡o baigtumo rodikliai nebuvo atnaujinti. " + error_workflow_copy_source: 'PraÅ¡ome pasirinkti pirminį Å¡altinio seklį arba rolÄ™' + error_workflow_copy_target: 'PraÅ¡ome pasirinkti galutinį paskirties seklį(-ius) arba rolÄ™(-s)' error_unable_delete_issue_status: 'Negalima iÅ¡trinti darbo statuso' error_unable_to_connect: Negalima prisijungti (%{value}) - error_attachment_too_big: "This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size})" + error_attachment_too_big: "Å i byla negali bÅ«ti įkelta, nes virÅ¡ija maksimaliÄ… (%{max_size}) leistinÄ… bylos apimtį" warning_attachments_not_saved: "%{count} byla(ų) negali bÅ«ti iÅ¡saugota." mail_subject_lost_password: "JÅ«sų %{value} slaptažodis" @@ -268,8 +270,6 @@ mail_subject_wiki_content_updated: "'%{id}' atnaujintas wiki puslapis" mail_body_wiki_content_updated: "'%{id}' wiki puslapį atnaujino %{author}." - gui_validation_error: 1 klaida - gui_validation_error_plural: "%{count} klaidų(os)" field_name: Pavadinimas field_description: ApraÅ¡as @@ -433,7 +433,7 @@ setting_cache_formatted_text: PaslÄ—pti formatuotÄ… tekstÄ… setting_default_notification_option: Numatytosios praneÅ¡imų nuostatos setting_commit_logtime_enabled: Ä®jungti laiko registravimÄ… - setting_commit_logtime_activity_id: Activity for logged time + setting_commit_logtime_activity_id: Laiko įrašų veikla setting_gantt_items_limit: Maksimalus rodmenų skaiÄius rodomas Gantt'o grafike setting_issue_group_assignment: Leisti darbo priskirimÄ… grupÄ—ms setting_default_issue_start_date_to_creation_date: Naudoti dabartinÄ™ datÄ… kaip naujų darbų pradžios datÄ… @@ -470,7 +470,6 @@ permission_edit_own_time_entries: Redguoti savo laiko įraÅ¡us permission_manage_news: Valdyti naujienas permission_comment_news: Komentuoti naujienas - permission_manage_documents: Valdyti dokumentus permission_view_documents: Matyti dokumentus permission_manage_files: Valdyti failus permission_view_files: Matyti failus @@ -601,8 +600,6 @@ label_text: Ilgas tekstas label_attribute: Požymis label_attribute_plural: Požymiai - label_download: "%{count} persiuntimas" - label_download_plural: "%{count} persiuntimai" label_no_data: NÄ—ra kÄ… atvaizduoti label_change_status: Pakeitimo bÅ«sena label_history: Istorija @@ -708,8 +705,6 @@ label_repository: Saugykla label_repository_plural: Saugyklos label_browse: NarÅ¡yti - label_modification: "%{count} pakeitimas" - label_modification_plural: "%{count} pakeitimai" label_branch: Å aka label_tag: Tag label_revision: Revizija @@ -885,8 +880,8 @@ label_issues_visibility_public: Visi vieÅ¡i darbai label_issues_visibility_own: Darbai, sukurti vartotojo arba jam priskirti label_git_report_last_commit: Nurodyti paskutinį failų ir katalogų pakeitimÄ… - label_parent_revision: Parent - label_child_revision: Child + label_parent_revision: PirminÄ— revizija + label_child_revision: Sekanti revizija label_export_options: "%{export_format} eksportavimo nustatymai" button_login: Registruotis @@ -999,25 +994,22 @@ text_enumeration_destroy_question: "%{count} objektai(ų) priskirti Å¡iai reikÅ¡mei." text_enumeration_category_reassign_to: 'Priskirti juos Å¡iai reikÅ¡mei:' text_email_delivery_not_configured: "El.paÅ¡to siuntimas nesukonfigÅ«ruotas, ir perspÄ—jimai neaktyvus.\nSukonfigÅ«ruokite savo SMTP serverį byloje config/configuration.yml ir perleiskite programÄ… norÄ—dami pritaikyti pakeitimus." - 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_repository_usernames_mapping: "Parinkite ar atnaujinkite Redmine vartotojÄ…, kuris paminÄ—tas saugyklos log'e.\nVartotojai, turintys tÄ… patį Redmine ir saugyklos vardÄ… ar el.paÅ¡tÄ… yra automatiÅ¡kai suriÅ¡ti." text_diff_truncated: "... Å is diff'as nukarpytas, nes jis virÅ¡ijo maksimalų rodomų eiluÄių skaiÄių." text_custom_field_possible_values_info: 'Po vienÄ… eilutÄ™ kiekvienai reikÅ¡mei' - text_wiki_page_destroy_question: "This page has %{descendants} child page(s) and descendant(s). What do you want to do?" + text_wiki_page_destroy_question: "Å is puslapis turi %{descendants} susijusių arba iÅ¡vestinių puslapių. KÄ… norÄ—tumÄ—te daryti?" text_wiki_page_nullify_children: Laikyti child puslapius kaip pagrindinius puslapius text_wiki_page_destroy_children: "PaÅ¡alinti child puslapius ir jų palikuonis" text_wiki_page_reassign_children: "Priskirkite iÅ¡ naujo 'child' puslapius Å¡iam pagrindiniam puslapiui" - text_own_membership_delete_confirmation: "You are about to remove some or all of your permissions and may no longer be able to edit this project after that.\nAre you sure you want to continue?" + text_own_membership_delete_confirmation: "JÅ«s esate pasiruošęs panaikinti dalį arba visus leidimus ir po Å¡io pakeitimo galite prarasti Å¡io projekto redagavimo galimybÄ™. \n Ar jÅ«s esate įsitikinÄ™s ir tÄ™sti?" text_zoom_in: Priartinti text_zoom_out: Nutolinti text_warn_on_leaving_unsaved: "Dabartinis puslapis turi neiÅ¡saugoto teksto, kuris bus prarastas, jeigu paliksite šį puslapį." text_scm_path_encoding_note: "Numatytasis: UTF-8" - text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo) + text_git_repository_note: Saugykla (repository) yra plika ir vietinÄ— (pvz. /gitrepo, c:\gitrepo) text_mercurial_repository_note: VietinÄ— saugykla (e.g. /hgrepo, c:\hgrepo) text_scm_command: Komanda text_scm_command_version: Versija - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. default_role_manager: Vadovas default_role_developer: Projektuotojas @@ -1079,7 +1071,7 @@ label_completed_versions: Užbaigtos versijos text_project_identifier_info: Leidžiamos tik mažosios raidÄ—s (a-z), skaitmenys, brÅ«kÅ¡neliai ir pabraukimo simboliai.
      KartÄ… iÅ¡saugojus pakeitimai negalimi field_multiple: Keletas reikÅ¡mių - setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed + setting_commit_cross_project_ref: Leisti visų kitų projektų įraÅ¡us susieti nuorodomis ir sutaisyti text_issue_conflict_resolution_add_notes: IÅ¡saugoti mano žinutÄ™ ir atmesti likusius mano pataisymus text_issue_conflict_resolution_overwrite: IÅ¡saugoti mano pakeitimus (ankstesnių pakeitimų žinutÄ—s bus iÅ¡saugotos, taÄiau kai kurie pakeitimai bus perraÅ¡yti) notice_issue_update_conflict: Darbas buvo pakoreguotas kito vartotojo kol jÅ«s atlikote pakeitimus. @@ -1141,3 +1133,17 @@ setting_non_working_week_days: Nedarbo dienos label_in_the_next_days: per ateinanÄias label_in_the_past_days: per paskutines + label_attribute_of_user: Vartotojo %{name} + text_turning_multiple_off: Jei jÅ«s iÅ¡jungsite kelių reikÅ¡mių pasirinkimÄ…, visos iÅ¡vardintos reikÅ¡mÄ—s bus paÅ¡alintos ir palikta tik viena reikÅ¡mÄ— kiekvienam laukui. + label_attribute_of_issue: Ä®raÅ¡ai %{name} + permission_add_documents: PridÄ—ti dokumentus + permission_edit_documents: Redaguoti dokumentus + permission_delete_documents: Trinti dokumentus + label_gantt_progress_line: Progreso linija + setting_jsonp_enabled: Ä®galinti JSONP palaikymÄ… + field_inherit_members: PaveldÄ—ti narius + field_closed_on: Uždarytas + setting_default_projects_tracker_ids: Sekliai pagal nutylÄ—jimÄ… naujiems projektams + label_total_time: IÅ¡ viso + text_scm_config: JÅ«s galite pakeisti SCM komandas byloje config/configuration.yml. PraÅ¡ome perkrauti programÄ… po redagavimo, idant įgalinti pakeitimus. + text_scm_command_not_available: SCM komanda nepasiekiama. Patikrinkite administravimo skydelio nustatymus. diff -r 0a574315af3e -r 4f746d8966dd config/locales/lv.yml --- a/config/locales/lv.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/lv.yml Fri Jun 14 09:28:30 2013 +0100 @@ -46,8 +46,8 @@ one: "aptuveni 1 stunda" other: "aptuveni %{count} stundas" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 stunda" + other: "%{count} stundas" x_days: one: "1 diena" other: "%{count} dienas" @@ -194,8 +194,6 @@ mail_subject_wiki_content_updated: "'%{id}' Wiki lapa atjaunota" mail_body_wiki_content_updated: "The '%{id}' Wiki lapu atjaunojis %{author}." - gui_validation_error: 1 kļūda - gui_validation_error_plural: "%{count} kļūdas" field_name: Nosaukums field_description: Apraksts @@ -370,7 +368,6 @@ permission_edit_own_time_entries: Rediģēt savus laika reÄ£istrus permission_manage_news: PÄrvaldÄ«t jaunumus permission_comment_news: KomentÄ“t jaunumus - permission_manage_documents: PÄrvaldÄ«t dokumentus permission_view_documents: SkatÄ«t dokumentus permission_manage_files: PÄrvaldÄ«t failus permission_view_files: SkatÄ«t failus @@ -492,8 +489,6 @@ label_text: GarÅ¡ teksts label_attribute: AtribÅ«ts label_attribute_plural: AtribÅ«ti - label_download: "%{count} LejupielÄde" - label_download_plural: "%{count} LejupielÄdes" label_no_data: Nav datu, ko parÄdÄ«t label_change_status: MainÄ«t statusu label_history: VÄ“sture @@ -596,8 +591,6 @@ label_repository: Repozitorijs label_repository_plural: Repozitoriji label_browse: PÄrlÅ«kot - label_modification: "%{count} izmaiņa" - label_modification_plural: "%{count} izmaiņas" label_branch: Zars label_tag: Birka label_revision: RevÄ«zija @@ -927,7 +920,6 @@ label_principal_search: "Search for user or group:" label_user_search: "Search for user:" field_visible: Visible - setting_emails_header: Emails header setting_commit_logtime_activity_id: Activity for logged time text_time_logged_by_changeset: Applied in changeset %{value}. setting_commit_logtime_enabled: Enable time logging @@ -968,8 +960,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1076,3 +1066,19 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: KopÄ + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header diff -r 0a574315af3e -r 4f746d8966dd config/locales/mk.yml --- a/config/locales/mk.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/mk.yml Fri Jun 14 09:28:30 2013 +0100 @@ -50,8 +50,8 @@ one: "околу 1 чаÑ" other: "околу %{count} чаÑа" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 чаÑ" + other: "%{count} чаÑа" x_days: one: "1 ден" other: "%{count} дена" @@ -207,8 +207,6 @@ mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated" mail_body_wiki_content_updated: "The '%{id}' wiki page has been updated by %{author}." - gui_validation_error: 1 грешка - gui_validation_error_plural: "%{count} грешки" field_name: Име field_description: ÐžÐ¿Ð¸Ñ @@ -329,7 +327,6 @@ setting_time_format: Формат на време setting_cross_project_issue_relations: Дозволи релации на задачи меѓу проекти setting_issue_list_default_columns: Default columns displayed on the issue list - setting_emails_footer: Emails footer setting_protocol: Протокол setting_per_page_options: Objects per page options setting_user_format: Приказ на кориÑниците @@ -386,7 +383,6 @@ permission_edit_own_time_entries: Уредувај ÑопÑтвени белешки за потрошено време permission_manage_news: Manage news permission_comment_news: Коментирај на веÑти - permission_manage_documents: Manage documents permission_view_documents: Прегледувај документи permission_manage_files: Manage files permission_view_files: Прегледувај датотеки @@ -512,8 +508,6 @@ label_text: Долг текÑÑ‚ label_attribute: Ðтрибут label_attribute_plural: Ðтрибути - label_download: "%{count} превземање" - label_download_plural: "%{count} превземања" label_no_data: Ðема податоци за прикажување label_change_status: Промени ÑÑ‚Ð°Ñ‚ÑƒÑ label_history: ИÑторија @@ -616,8 +610,6 @@ label_repository: Складиште label_repository_plural: Складишта label_browse: ПрелиÑтувај - label_modification: "%{count} промени" - label_modification_plural: "%{count} промени" label_branch: Гранка label_tag: Tag label_revision: Ревизија @@ -933,7 +925,6 @@ label_principal_search: "Search for user or group:" label_user_search: "Search for user:" field_visible: Visible - setting_emails_header: Emails header setting_commit_logtime_activity_id: Activity for logged time text_time_logged_by_changeset: Applied in changeset %{value}. setting_commit_logtime_enabled: Enable time logging @@ -974,8 +965,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1082,3 +1071,20 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Вкупно + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_footer: Email footer + setting_emails_header: Email header diff -r 0a574315af3e -r 4f746d8966dd config/locales/mn.yml --- a/config/locales/mn.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/mn.yml Fri Jun 14 09:28:30 2013 +0100 @@ -51,8 +51,8 @@ one: "1 цаг орчим" other: "ойролцоогоор %{count} цаг" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 цаг" + other: "%{count} цаг" x_days: one: "1 өдөр" other: "%{count} өдөр" @@ -200,8 +200,6 @@ mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated" mail_body_wiki_content_updated: "The '%{id}' wiki page has been updated by %{author}." - gui_validation_error: 1 алдаа - gui_validation_error_plural: "%{count} алдаа" field_name: ÐÑÑ€ field_description: Тайлбар @@ -376,7 +374,6 @@ permission_edit_own_time_entries: Өөрийн хугацааны логуудыг заÑварлах permission_manage_news: МÑдÑÑ Ð¼ÑдÑÑллүүд permission_comment_news: МÑдÑÑнд тайлбар үлдÑÑÑ… - permission_manage_documents: Бичиг баримтууд permission_view_documents: Бичиг баримтуудыг харах permission_manage_files: Файлууд permission_view_files: Файлуудыг харах @@ -498,8 +495,6 @@ label_text: Урт текÑÑ‚ label_attribute: Ðттрибут label_attribute_plural: Ðттрибутууд - label_download: "%{count} Татаж авÑан зүйл" - label_download_plural: "%{count} Татаж авÑан зүйлÑ" label_no_data: ҮзүүлÑÑ… өгөгдөл байхгүй байна label_change_status: Төлвийг өөрчлөх label_history: Түүх @@ -602,8 +597,6 @@ label_repository: Репозитори label_repository_plural: Репозиторууд label_browse: ҮзÑÑ… - label_modification: "%{count} өөрчлөлт" - label_modification_plural: "%{count} өөрчлөлтүүд" label_branch: Салбар label_tag: Шошго label_revision: Хувилбар @@ -934,7 +927,6 @@ label_principal_search: "Search for user or group:" label_user_search: "Search for user:" field_visible: Visible - setting_emails_header: Emails header setting_commit_logtime_activity_id: Activity for logged time text_time_logged_by_changeset: Applied in changeset %{value}. setting_commit_logtime_enabled: Enable time logging @@ -975,8 +967,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1083,3 +1073,19 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Ðийт + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header diff -r 0a574315af3e -r 4f746d8966dd config/locales/nl.yml --- a/config/locales/nl.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/nl.yml Fri Jun 14 09:28:30 2013 +0100 @@ -50,7 +50,7 @@ other: "ongeveer %{count} uren" x_hours: one: "1 uur" - other: "%{count} hours" + other: "%{count} uren" x_days: one: "1 dag" other: "%{count} dagen" @@ -291,8 +291,6 @@ general_text_Yes: 'Ja' general_text_no: 'nee' general_text_yes: 'ja' - gui_validation_error: 1 fout - gui_validation_error_plural: "%{count} fouten" label_activity: Activiteit label_add_another_file: Ander bestand toevoegen label_add_note: Voeg een notitie toe @@ -345,9 +343,9 @@ one: 1 open other: "%{count} open" label_x_closed_issues_abbr: - zero: 0 closed - one: 1 closed - other: "%{count} closed" + zero: 0 gesloten + one: 1 gesloten + other: "%{count} gesloten" label_comment: Commentaar label_comment_add: Voeg commentaar toe label_comment_added: Commentaar toegevoegd @@ -385,8 +383,6 @@ label_document_added: Document toegevoegd label_document_new: Nieuw document label_document_plural: Documenten - label_download: "%{count} Download" - label_download_plural: "%{count} Downloads" label_downloads_abbr: D/L label_duplicated_by: gedupliceerd door label_duplicates: dupliceert @@ -466,8 +462,6 @@ label_message_plural: Berichten label_message_posted: Bericht toegevoegd label_min_max_length: Min-max lengte - label_modification: "%{count} wijziging" - label_modification_plural: "%{count} wijzigingen" label_modified: gewijzigd label_module_plural: Modules label_month: Maand @@ -666,7 +660,6 @@ 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 @@ -916,7 +909,6 @@ label_principal_search: "Zoek naar gebruiker of groep:" label_user_search: "Zoek naar gebruiker:" field_visible: Zichtbaar - setting_emails_header: Emails header setting_commit_logtime_activity_id: Standaard activiteit voor tijdregistratie text_time_logged_by_changeset: Toegepast in changeset %{value}. setting_commit_logtime_enabled: Activeer tijdregistratie @@ -1031,7 +1023,7 @@ text_project_closed: Dit project is gesloten en op alleen-lezen notice_user_successful_create: Gebruiker %{id} aangemaakt. field_core_fields: Standaard verleden - field_timeout: Timeout (in seconds) + field_timeout: Timeout (in seconden) setting_thumbnails_enabled: Geef bijlage miniaturen weer setting_thumbnails_size: Grootte miniaturen (in pixels) label_status_transitions: Status transitie @@ -1045,22 +1037,35 @@ label_attribute_of_assigned_to: Toegewezen %{name} label_attribute_of_fixed_version: Target versions %{name} label_copy_subtasks: Kopieer subtaken - label_copied_to: copied to - label_copied_from: copied from + label_copied_to: gekopieerd naar + label_copied_from: gekopieerd van label_any_issues_in_project: any issues in project label_any_issues_not_in_project: any issues not in project - field_private_notes: Private notes - permission_view_private_notes: View private notes - permission_set_notes_private: Set notes as private - label_no_issues_in_project: no issues in project + field_private_notes: Privé notities + permission_view_private_notes: Bekijk privé notities + permission_set_notes_private: Maak notities privé + label_no_issues_in_project: geen issues in project label_any: alle - label_last_n_weeks: last %{count} weeks - setting_cross_project_subtasks: Allow cross-project subtasks + label_last_n_weeks: afgelopen %{count} weken + setting_cross_project_subtasks: Sta subtaken in andere projecten toe label_cross_project_descendants: Met subprojecten label_cross_project_tree: Met project boom label_cross_project_hierarchy: Met project hiërarchie label_cross_project_system: Met alle projecten - button_hide: Hide - setting_non_working_week_days: Non-working days - label_in_the_next_days: in the next - label_in_the_past_days: in the past + button_hide: Verberg + setting_non_working_week_days: Niet-werkdagen + label_in_the_next_days: in de volgende + label_in_the_past_days: in de afgelopen + label_attribute_of_user: User's %{name} + text_turning_multiple_off: Bij het uitschakelen van meerdere waardes zal er maar een waarde bewaard blijven. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Voeg documenten toe + permission_edit_documents: Bewerk documenten + permission_delete_documents: Verwijder documenten + label_gantt_progress_line: Voortgangslijn + setting_jsonp_enabled: Schakel JSONP support in + field_inherit_members: Neem leden over + field_closed_on: Gesloten + setting_default_projects_tracker_ids: Standaard trackers voor nieuwe projecten + label_total_time: Totaal + setting_emails_header: Email header diff -r 0a574315af3e -r 4f746d8966dd config/locales/no.yml --- a/config/locales/no.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/no.yml Fri Jun 14 09:28:30 2013 +0100 @@ -44,8 +44,8 @@ one: "rundt 1 time" other: "rundt %{count} timer" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 time" + other: "%{count} timer" x_days: one: "1 dag" other: "%{count} dager" @@ -174,8 +174,6 @@ mail_subject_reminder: "%{count} sak(er) har frist de kommende %{days} dagene" mail_body_reminder: "%{count} sak(er) som er tildelt deg har frist de kommende %{days} dager:" - gui_validation_error: 1 feil - gui_validation_error_plural: "%{count} feil" field_name: Navn field_description: Beskrivelse @@ -386,8 +384,6 @@ label_text: Lang tekst label_attribute: Attributt label_attribute_plural: Attributter - label_download: "%{count} Nedlasting" - label_download_plural: "%{count} Nedlastinger" label_no_data: Ingen data Ã¥ vise label_change_status: Endre status label_history: Historikk @@ -487,8 +483,6 @@ label_repository: Depot label_repository_plural: Depoter label_browse: Utforsk - label_modification: "%{count} endring" - label_modification_plural: "%{count} endringer" label_revision: Revisjon label_revision_plural: Revisjoner label_associated_revisions: Assosierte revisjoner @@ -745,7 +739,6 @@ permission_comment_news: Kommentere nyheter permission_delete_messages: Slette meldinger permission_select_project_modules: Velge prosjektmoduler - permission_manage_documents: Administrere dokumenter permission_edit_wiki_pages: Redigere wiki-sider permission_add_issue_watchers: Legge til overvÃ¥kere permission_view_gantt: Vise gantt-diagram @@ -1072,3 +1065,16 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Totalt diff -r 0a574315af3e -r 4f746d8966dd config/locales/pl.yml --- a/config/locales/pl.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/pl.yml Fri Jun 14 09:28:30 2013 +0100 @@ -82,8 +82,8 @@ one: "okoÅ‚o godziny" other: "okoÅ‚o %{count} godzin" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 godzina" + other: "%{count} godzin" x_days: one: "1 dzieÅ„" other: "%{count} dni" @@ -102,8 +102,9 @@ few: "ponad %{count} lata" other: "ponad %{count} lat" almost_x_years: - one: "prawie rok" - other: "prawie %{count} lata" + one: "prawie 1 rok" + few: "prawie %{count} lata" + other: "prawie %{count} lat" activerecord: errors: @@ -134,7 +135,7 @@ even: "musi być parzyste" greater_than_start_date: "musi być wiÄ™ksze niż poczÄ…tkowa data" not_same_project: "nie należy do tego samego projektu" - circular_dependency: "Ta relacja może wytworzyć koÅ‚owÄ… zależność" + circular_dependency: "Ta relacja może wytworzyć zapÄ™tlonÄ… zależność" cant_link_an_issue_with_a_descendant: "Zagadnienie nie może zostać powiÄ…zane z jednym z wÅ‚asnych podzagadnieÅ„" support: @@ -145,6 +146,9 @@ # 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, + # Wiktor Wandachowicz , 2010 + + actionview_instancetag_blank_option: ProszÄ™ wybrać actionview_instancetag_blank_option: ProszÄ™ wybierz button_activate: Aktywuj @@ -189,7 +193,7 @@ default_activity_development: Rozwój default_doc_category_tech: Dokumentacja techniczna default_doc_category_user: Dokumentacja użytkownika - default_issue_status_in_progress: W Toku + default_issue_status_in_progress: W toku default_issue_status_closed: ZamkniÄ™ty default_issue_status_feedback: Odpowiedź default_issue_status_new: Nowy @@ -202,7 +206,7 @@ default_priority_urgent: Pilny default_role_developer: Programista default_role_manager: Kierownik - default_role_reporter: Wprowadzajacy + default_role_reporter: ZgÅ‚aszajÄ…cy default_tracker_bug: Błąd default_tracker_feature: Zadanie default_tracker_support: Wsparcie @@ -210,7 +214,7 @@ 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: %{value}" - error_issue_not_found_in_project: 'Zaganienie nie zostaÅ‚o znalezione lub nie należy do tego projektu' + error_issue_not_found_in_project: 'Zagadnienie 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: %{value}" error_scm_not_found: "Obiekt lub wersja nie zostaÅ‚y znalezione w repozytorium." @@ -218,11 +222,11 @@ field_activity: Aktywność field_admin: Administrator field_assignable: Zagadnienia mogÄ… być przypisane do tej roli - field_assigned_to: Przydzielony do + field_assigned_to: Przypisany do field_attr_firstname: ImiÄ™ atrybut field_attr_lastname: Nazwisko atrybut field_attr_login: Login atrybut - field_attr_mail: Email atrybut + field_attr_mail: E-mail atrybut field_auth_source: Tryb identyfikacji field_author: Autor field_base_dn: Base DN @@ -244,11 +248,11 @@ field_filesize: Rozmiar field_firstname: ImiÄ™ field_fixed_version: Wersja docelowa - field_hide_mail: Ukryj mój adres email + field_hide_mail: Ukryj mój adres e-mail field_homepage: Strona www field_host: Host field_hours: Godzin - field_identifier: Identifikator + field_identifier: Identyfikator field_is_closed: Zagadnienie zamkniÄ™te field_is_default: DomyÅ›lny status field_is_filter: Atrybut filtrowania @@ -262,15 +266,15 @@ field_last_login_on: Ostatnie połączenie field_lastname: Nazwisko field_login: Login - field_mail: Email - field_mail_notification: Powiadomienia Email + field_mail: E-mail + field_mail_notification: Powiadomienia e-mail 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: Projekt nadrzÄ™dny field_parent_title: Strona rodzica field_password: HasÅ‚o field_password_confirmation: Potwierdzenie @@ -283,7 +287,7 @@ field_role: Rola field_searchable: Przeszukiwalne field_spent_on: Data - field_start_date: Start + field_start_date: Data rozpoczÄ™cia field_start_page: Strona startowa field_status: Status field_subject: Temat @@ -310,10 +314,7 @@ general_text_Yes: 'Tak' general_text_no: 'nie' general_text_yes: 'tak' - gui_validation_error: 1 błąd - gui_validation_error_plural234: "%{count} błędy" - gui_validation_error_plural5: "%{count} błędów" - gui_validation_error_plural: "%{count} błędów" + label_activity: Aktywność label_add_another_file: Dodaj kolejny plik label_add_note: Dodaj notatkÄ™ @@ -339,7 +340,7 @@ label_auth_source_new: Nowy tryb identyfikacji label_auth_source_plural: Tryby identyfikacji label_authentication: Identyfikacja - label_blocked_by: zablokowane przez + label_blocked_by: blokowane przez label_blocks: blokuje label_board: Forum label_board_new: Nowe forum @@ -360,42 +361,46 @@ label_closed_issues_plural5: zamkniÄ™te label_closed_issues_plural: zamkniÄ™te label_x_open_issues_abbr_on_total: - zero: 0 open / %{total} - one: 1 open / %{total} - other: "%{count} open / %{total}" + zero: 0 otwartych / %{total} + one: 1 otwarty / %{total} + few: "%{count} otwarte / %{total}" + other: "%{count} otwartych / %{total}" label_x_open_issues_abbr: - zero: 0 open - one: 1 open - other: "%{count} open" + zero: 0 otwartych + one: 1 otwarty + few: "%{count} otwarte" + other: "%{count} otwartych" label_x_closed_issues_abbr: - zero: 0 closed - one: 1 closed - other: "%{count} closed" + zero: 0 zamkniÄ™tych + one: 1 zamkniÄ™ty + few: "%{count} zamkniÄ™te" + other: "%{count} zamkniÄ™tych" label_comment: Komentarz label_comment_add: Dodaj komentarz label_comment_added: Komentarz dodany label_comment_delete: UsuÅ„ komentarze label_comment_plural234: Komentarze - label_comment_plural5: Komentarze + label_comment_plural5: Komentarzy label_comment_plural: Komentarze label_x_comments: - zero: no comments - one: 1 comment - other: "%{count} comments" + zero: brak komentarzy + one: 1 komentarz + few: "%{count} komentarze" + other: "%{count} komentarzy" 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_copy_workflow_from: Kopiuj przepÅ‚yw pracy 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_custom_field: Pole niestandardowe + label_custom_field_new: Nowe pole niestandardowe + label_custom_field_plural: Pola niestandardowe label_date: Data - label_date_from: Z - label_date_range: Zakres datowy + label_date_from: Od + label_date_range: Zakres dat label_date_to: Do label_day_plural: dni label_default: DomyÅ›lne @@ -410,10 +415,6 @@ label_document_added: Dodano dokument label_document_new: Nowy dokument label_document_plural: Dokumenty - label_download: "%{count} Pobranie" - label_download_plural234: "%{count} Pobrania" - label_download_plural5: "%{count} PobraÅ„" - label_download_plural: "%{count} Pobrania" label_downloads_abbr: Pobieranie label_duplicated_by: zduplikowane przez label_duplicates: duplikuje @@ -427,13 +428,13 @@ label_export_to: Eksportuj do label_f_hour: "%{value} godzina" label_f_hour_plural: "%{value} godzin" - label_feed_plural: Ilość RSS - label_feeds_access_key_created_on: "Klucz dostÄ™pu RSS stworzony %{value} dni temu" + label_feed_plural: Ilość Atom + label_feeds_access_key_created_on: "Klucz dostÄ™pu do kanaÅ‚u Atom stworzony %{value} temu" label_file_added: Dodano plik label_file_plural: Pliki label_filter_add: Dodaj filtr label_filter_plural: Filtry - label_float: Liczba rzeczywista + label_float: Liczba zmiennoprzecinkowa label_follows: nastÄ™puje po label_gantt: Gantt label_general: Ogólne @@ -493,10 +494,6 @@ label_message_plural: WiadomoÅ›ci label_message_posted: Dodano wiadomość label_min_max_length: Min - Maks dÅ‚ugość - label_modification: "%{count} modyfikacja" - label_modification_plural234: "%{count} modyfikacje" - label_modification_plural5: "%{count} modyfikacji" - label_modification_plural: "%{count} modyfikacje" label_modified: zmodyfikowane label_module_plural: ModuÅ‚y label_month: MiesiÄ…c @@ -523,7 +520,7 @@ label_not_equals: różni siÄ™ label_open_issues: otwarte label_open_issues_plural234: otwarte - label_open_issues_plural5: otwarte + label_open_issues_plural5: otwartych label_open_issues_plural: otwarte label_optional_description: Opcjonalny opis label_options: Opcje @@ -533,7 +530,7 @@ label_per_page: Na stronÄ™ label_permissions: Uprawnienia label_permissions_report: Raport uprawnieÅ„ - label_personalize_page: Personalizuj tÄ… stronÄ™ + label_personalize_page: Personalizuj tÄ™ stronÄ™ label_planning: Planowanie label_please_login: Zaloguj siÄ™ label_plugins: Wtyczki @@ -550,7 +547,8 @@ label_project_plural: Projekty label_x_projects: zero: brak projektów - one: jeden projekt + one: 1 projekt + few: "%{count} projekty" other: "%{count} projektów" label_public_projects: Projekty publiczne label_query: Kwerenda @@ -566,7 +564,7 @@ label_relates_to: powiÄ…zane z label_relation_delete: UsuÅ„ powiÄ…zanie label_relation_new: Nowe powiÄ…zanie - label_renamed: przemianowano + label_renamed: zmieniono nazwÄ™ label_reply_plural: Odpowiedzi label_report: Raport label_report_plural: Raporty @@ -582,14 +580,14 @@ label_roadmap_no_issues: Brak zagadnieÅ„ do tej wersji label_roadmap_overdue: "%{value} spóźnienia" label_role: Rola - label_role_and_permissions: Role i Uprawnienia + label_role_and_permissions: Role i uprawnienia label_role_new: Nowa rola label_role_plural: Role label_scm: SCM label_search: Szukaj 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_send_test_email: WyÅ›lij próbny e-mail label_settings: Ustawienia label_show_completed_versions: Pokaż kompletne wersje label_sort_by: "Sortuj po %{value}" @@ -621,7 +619,7 @@ 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_selected: "Tylko dla każdego zdarzenia w wybranych projektach..." + label_user_mail_option_selected: "Dla każdego zdarzenia w wybranych projektach..." label_user_new: Nowy użytkownik label_user_plural: Użytkownicy label_version: Wersja @@ -636,12 +634,12 @@ label_wiki_edit_plural: Edycje wiki label_wiki_page: Strona wiki label_wiki_page_plural: Strony wiki - label_workflow: PrzepÅ‚yw + label_workflow: PrzepÅ‚yw pracy label_year: Rok label_yesterday: wczoraj mail_body_account_activation_request: "Zarejestrowano nowego użytkownika: (%{value}). Konto oczekuje na twoje zatwierdzenie:" mail_body_account_information: Twoje konto - mail_body_account_information_external: "Możesz użyć twojego %{value} konta do zalogowania." + mail_body_account_information_external: "Możesz użyć Twojego konta %{value} 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 %{count} dni" @@ -651,23 +649,23 @@ mail_subject_reminder: "Uwaga na terminy, masz zagadnienia do obsÅ‚użenia w ciÄ…gu nastÄ™pnych %{count} dni! (%{days})" 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_lost_email_sent: E-mail 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." - notice_account_register_done: Konto prawidÅ‚owo stworzone. + notice_account_register_done: Konto prawidÅ‚owo utworzone. 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. - notice_email_error: "WystÄ…piÅ‚ błąd w trakcie wysyÅ‚ania maila (%{value})" - notice_email_sent: "Email zostaÅ‚ wysÅ‚any do %{value}" + notice_email_error: "WystÄ…piÅ‚ błąd w trakcie wysyÅ‚ania e-maila (%{value})" + notice_email_sent: "E-mail zostaÅ‚ wysÅ‚any do %{value}" notice_failed_to_save_issues: "Błąd podczas zapisu zagadnieÅ„ %{count} z %{total} zaznaczonych: %{ids}." - notice_feeds_access_key_reseted: Twój klucz dostÄ™pu RSS zostaÅ‚ zrestetowany. + notice_feeds_access_key_reseted: Twój klucz dostÄ™pu do kanaÅ‚u Atom zostaÅ‚ zresetowany. 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_not_authorized: Nie posiadasz autoryzacji do oglÄ…dania tej strony. notice_successful_connection: Udane nawiÄ…zanie połączenia. notice_successful_create: Utworzenie zakoÅ„czone sukcesem. notice_successful_delete: UsuniÄ™cie zakoÅ„czone sukcesem. @@ -696,8 +694,7 @@ permission_edit_wiki_pages: Edycja stron wiki permission_log_time: Zapisywanie przepracowanego czasu permission_manage_boards: ZarzÄ…dzanie forami - permission_manage_categories: ZarzÄ…dzanie kategoriami zaganieÅ„ - permission_manage_documents: ZarzÄ…dzanie dokumentami + permission_manage_categories: ZarzÄ…dzanie kategoriami zagadnieÅ„ permission_manage_files: ZarzÄ…dzanie plikami permission_manage_issue_relations: ZarzÄ…dzanie powiÄ…zaniami zagadnieÅ„ permission_manage_members: ZarzÄ…dzanie uczestnikami @@ -734,7 +731,7 @@ setting_app_title: TytuÅ‚ aplikacji setting_attachment_max_size: Maks. rozm. załącznika setting_autofetch_changesets: Automatyczne pobieranie zmian - setting_autologin: Auto logowanie + setting_autologin: Automatyczne logowanie setting_bcc_recipients: Odbiorcy kopii tajnej (kt/bcc) setting_commit_fix_keywords: SÅ‚owa zmieniajÄ…ce status setting_commit_ref_keywords: SÅ‚owa tworzÄ…ce powiÄ…zania @@ -745,24 +742,24 @@ 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_feeds_limit: Limit danych Atom 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 wyÅ›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_login_required: Wymagane zalogowanie + setting_mail_from: Adres e-mail 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_protocol: Protokół setting_self_registration: Samodzielna rejestracja użytkowników 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_user_format: WÅ‚asny format wyÅ›wietlania setting_welcome_text: Tekst powitalny setting_wiki_compression: Kompresja historii Wiki status_active: aktywny @@ -772,43 +769,43 @@ text_assign_time_entries_to_project: Przypisz wpisy dziennika do projektu text_caracters_maximum: "%{count} znaków maksymalnie." text_caracters_minimum: "Musi być nie krótsze niż %{count} znaków." - text_comma_separated: Wielokrotne wartoÅ›ci dozwolone (rozdzielone przecinkami). + text_comma_separated: Dozwolone wielokrotne wartoÅ›ci (rozdzielone przecinkami). text_default_administrator_account_changed: Zmieniono domyÅ›lne hasÅ‚o administratora text_destroy_time_entries: UsuÅ„ wpisy dziennika text_destroy_time_entries_question: Przepracowano %{hours} 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/configuration.yml a nastÄ™pnie zrestartuj aplikacjÄ™ i uaktywnij to." + 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: "%{count} obiektów jest przypisana do tej wartoÅ›ci." + text_enumeration_destroy_question: "%{count} obiektów jest przypisanych do tej wartoÅ›ci." text_file_repository_writable: Zapisywalne repozytorium plików - text_issue_added: "Zagadnienie %{id} zostaÅ‚o wprowadzone (by %{author})." + text_issue_added: "Zagadnienie %{id} zostaÅ‚o wprowadzone (przez %{author})." text_issue_category_destroy_assignments: UsuÅ„ przydziaÅ‚y kategorii - text_issue_category_destroy_question: "Zagadnienia (%{count}) sÄ… przypisane do tej kategorii. Co chcesz zrobić?" + text_issue_category_destroy_question: "Do tej kategorii sÄ… przypisane zagadnienia (%{count}). Co chcesz zrobić?" text_issue_category_reassign_to: Przydziel zagadnienie do tej kategorii - text_issue_updated: "Zagadnienie %{id} zostaÅ‚o zaktualizowane (by %{author})." - text_issues_destroy_confirmation: 'Czy jestes pewien, że chcesz usunąć wskazane zagadnienia?' + text_issue_updated: "Zagadnienie %{id} zostaÅ‚o zaktualizowane (przez %{author})." + text_issues_destroy_confirmation: 'Czy jesteÅ› pewien, że chcesz usunąć wskazane zagadnienia?' text_issues_ref_in_commit_messages: OdwoÅ‚ania do zagadnieÅ„ w komentarzach zatwierdzeÅ„ text_length_between: "DÅ‚ugość pomiÄ™dzy %{min} i %{max} 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_no_configuration_data: "Role użytkowników, typy zagadnieÅ„, statusy zagadnieÅ„ oraz przepÅ‚yw pracy nie zostaÅ‚y jeszcze skonfigurowane.\nWysoce zalecane jest 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 wszystkie powiÄ…zane dane? text_reassign_time_entries: 'Przepnij przepracowany czas do tego zagadnienia:' 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_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 e-mail 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_mail_notifications: Zaznacz czynnoÅ›ci przy których użytkownik powinien być powiadomiony e-mailem. text_select_project_modules: 'Wybierz moduÅ‚y do aktywacji w tym projekcie:' text_status_changed_by_changeset: "Zastosowane w zmianach %{value}." text_subprojects_destroy_warning: "Podprojekt(y): %{value} zostanÄ… także usuniÄ™te." text_tip_issue_begin_day: zadanie zaczynajÄ…ce siÄ™ dzisiaj text_tip_issue_begin_end_day: zadanie zaczynajÄ…ce i koÅ„czÄ…ce siÄ™ dzisiaj text_tip_issue_end_day: zadanie koÅ„czÄ…ce siÄ™ dzisiaj - text_tracker_no_workflow: Brak przepÅ‚ywu zdefiniowanego dla tego typu zagadnienia + text_tracker_no_workflow: Brak przepÅ‚ywu pracy zdefiniowanego 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 zagadnieÅ„, które obserwujesz, lub w których bierzesz udziaÅ‚ (np. jesteÅ› autorem lub adresatem)." - text_user_wrote: "%{value} 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 + text_user_mail_option: "W przypadku niezaznaczonych projektów, bÄ™dziesz otrzymywaÅ‚ powiadomienia tylko na temat zagadnieÅ„ które obserwujesz, lub w których bierzesz udziaÅ‚ (np. jesteÅ› autorem lub adresatem)." + text_user_wrote: "%{value} napisaÅ‚(a):" + 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 pracy label_user_activity: "Aktywność: %{value}" label_updated_time_by: "Uaktualnione przez %{author} %{age} temu" @@ -851,7 +848,7 @@ label_tag: SÅ‚owo kluczowe label_branch: Gałąź error_no_tracker_in_project: Projekt nie posiada powiÄ…zanych typów zagadnieÅ„. Sprawdź ustawienia projektu. - error_no_default_issue_status: Nie zdefiniowano domyÅ›lnego statusu zagadnieÅ„. Sprawdź konfiguracjÄ™ (Przejdź do "Administracja -> Statusy zagadnieÅ„). + error_no_default_issue_status: Nie zdefiniowano domyÅ›lnego statusu zagadnieÅ„. Sprawdź konfiguracjÄ™ (Przejdź do "Administracja -> Statusy zagadnieÅ„"). text_journal_changed: "Zmieniono %{label} z %{old} na %{new}" text_journal_set_to: "Ustawiono %{label} na %{value}" text_journal_deleted: "UsuniÄ™to %{label} (%{old})" @@ -861,7 +858,7 @@ label_time_entry_plural: Przepracowany czas text_journal_added: "Dodano %{label} %{value}" field_active: Aktywne - enumeration_system_activity: Aktywność Systemowa + enumeration_system_activity: Aktywność systemowa button_copy_and_follow: Kopiuj i przejdź do kopii zagadnienia button_duplicate: Duplikuj button_move_and_follow: PrzenieÅ› i przejdź do zagadnienia @@ -879,9 +876,9 @@ label_copy_source: ŹródÅ‚o label_copy_target: Cel label_display_used_statuses_only: WyÅ›wietlaj tylko statusy używane przez ten typ zagadnienia - label_feeds_access_key: Klucz dostÄ™pu do RSS + label_feeds_access_key: Klucz dostÄ™pu do kanaÅ‚u Atom label_missing_api_access_key: Brakuje klucza dostÄ™pu do API - label_missing_feeds_access_key: Brakuje klucza dostÄ™pu do RSS + label_missing_feeds_access_key: Brakuje klucza dostÄ™pu do kanaÅ‚u Atom label_revision_id: Rewizja %{value} label_subproject_new: Nowy podprojekt label_update_issue_done_ratios: Uaktualnij % wykonania @@ -922,7 +919,7 @@ permission_manage_subtasks: ZarzÄ…dzanie podzagadnieniami field_parent_issue: Zagadnienie nadrzÄ™dne label_subtask_plural: Podzagadnienia - label_project_copy_notifications: WyÅ›lij powiadomienia mailowe przy kopiowaniu projektu + label_project_copy_notifications: WyÅ›lij powiadomienia e-mailowe przy kopiowaniu projektu error_can_not_delete_custom_field: Nie można usunąć tego pola error_unable_to_connect: Nie można połączyć (%{value}) error_can_not_remove_role: Ta rola przypisana jest niektórym użytkownikom i nie może zostać usuniÄ™ta. @@ -930,8 +927,8 @@ field_principal: PrzeÅ‚ożony label_my_page_block: Elementy notice_failed_to_save_members: "Nie można zapisać uczestników: %{errors}." - text_zoom_out: Zmniejsz czcionkÄ™ - text_zoom_in: PowiÄ™ksz czcionkÄ™ + text_zoom_out: Zmniejsz + text_zoom_in: PowiÄ™ksz notice_unable_delete_time_entry: Nie można usunąć wpisu z dziennika. label_overall_spent_time: Przepracowany czas field_time_entries: Dziennik @@ -943,14 +940,13 @@ setting_default_notification_option: Default notification option label_user_mail_option_only_my_events: Only for things I watch or I'm involved in label_user_mail_option_only_assigned: Only for things I am assigned to - label_user_mail_option_none: No events + label_user_mail_option_none: "Tylko to, co obserwujÄ™ lub w czym biorÄ™ udziaÅ‚" field_member_of_group: Assignee's group field_assigned_to_role: Assignee's role notice_not_authorized_archived_project: The project you're trying to access has been archived. - label_principal_search: "Search for user or group:" - label_user_search: "Search for user:" + label_principal_search: "Szukaj użytkownika lub grupy:" + label_user_search: "Szukaj użytkownika:" field_visible: Visible - setting_emails_header: Emails header setting_commit_logtime_activity_id: Activity for logged time text_time_logged_by_changeset: Applied in changeset %{value}. setting_commit_logtime_enabled: Enable time logging @@ -991,8 +987,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1035,7 +1029,7 @@ label_copy_attachments: Copy attachments label_item_position: "%{position}/%{count}" label_completed_versions: Completed versions - text_project_identifier_info: Only lower case letters (a-z), numbers, dashes and underscores are allowed.
      Once saved, the identifier cannot be changed. + text_project_identifier_info: 'Dozwolone małe litery (a-z), liczby i myślniki.
      Raz zapisany, identyfikator nie może być zmieniony.' field_multiple: Multiple values setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed text_issue_conflict_resolution_add_notes: Add my notes and discard my other changes @@ -1099,3 +1093,19 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Ogółem + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header diff -r 0a574315af3e -r 4f746d8966dd config/locales/pt-BR.yml --- a/config/locales/pt-BR.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/pt-BR.yml Fri Jun 14 09:28:30 2013 +0100 @@ -122,8 +122,8 @@ errors: template: header: - one: "model não pode ser salvo: 1 erro" - other: "model não pode ser salvo: %{count} erros." + one: "modelo não pode ser salvo: 1 erro" + other: "modelo não pode ser salvo: %{count} erros." body: "Por favor, verifique os seguintes campos:" messages: inclusion: "não está incluso na lista" @@ -134,7 +134,7 @@ empty: "não pode ficar vazio" blank: "não pode ficar vazio" too_long: "é muito longo (máximo: %{count} caracteres)" - too_short: "é muito curto (mínimon: %{count} caracteres)" + too_short: "é muito curto (mínimo: %{count} caracteres)" wrong_length: "deve ter %{count} caracteres" taken: "não está disponível" not_a_number: "não é um número" @@ -206,8 +206,6 @@ mail_subject_reminder: "%{count} tarefa(s) com data prevista para os próximos %{days} dias" mail_body_reminder: "%{count} tarefa(s) para você com data prevista para os próximos %{days} dias:" - gui_validation_error: 1 erro - gui_validation_error_plural: "%{count} erros" field_name: Nome field_description: Descrição @@ -425,8 +423,6 @@ label_text: Texto longo label_attribute: Atributo label_attribute_plural: Atributos - label_download: "%{count} Download" - label_download_plural: "%{count} Downloads" label_no_data: Nenhuma informação disponível label_change_status: Alterar situação label_history: Histórico @@ -526,8 +522,6 @@ label_repository: Repositório label_repository_plural: Repositórios label_browse: Procurar - label_modification: "%{count} alteração" - label_modification_plural: "%{count} alterações" label_revision: Revisão label_revision_plural: Revisões label_associated_revisions: Revisões associadas @@ -780,7 +774,6 @@ 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 observadores permission_view_gantt: Ver gráfico gantt @@ -887,12 +880,12 @@ label_copy_source: Origem setting_issue_done_ratio: Calcular o percentual de conclusão da tarefa setting_issue_done_ratio_issue_status: Usar a situação da tarefa - error_issue_done_ratios_not_updated: O pecentual de conclusão das tarefas não foi atualizado. + error_issue_done_ratios_not_updated: O percentual de conclusão das tarefas não foi atualizado. error_workflow_copy_target: Por favor, selecione os tipos de tarefa e os papéis alvo setting_issue_done_ratio_issue_field: Use o campo da tarefa label_copy_same_as_target: Mesmo alvo label_copy_target: Alvo - notice_issue_done_ratios_updated: Percentual de conslusão atualizados. + notice_issue_done_ratios_updated: Percentual de conclusão atualizados. error_workflow_copy_source: Por favor, selecione um tipo de tarefa e papel de origem label_update_issue_done_ratios: Atualizar percentual de conclusão das tarefas setting_start_of_week: Início da semana @@ -905,7 +898,7 @@ label_api_access_key_created_on: Chave de acesso a API criado a %{value} atrás label_feeds_access_key: Chave de acesso ao RSS notice_api_access_key_reseted: Sua chave de acesso a API foi redefinida. - setting_rest_api_enabled: Habilitar REST web service + setting_rest_api_enabled: Habilitar a api REST label_missing_api_access_key: Chave de acesso a API faltando label_missing_feeds_access_key: Chave de acesso ao RSS faltando text_line_separated: Múltiplos valores permitidos (uma linha para cada valor). @@ -923,7 +916,7 @@ permission_manage_project_activities: Gerenciar atividades do projeto error_unable_delete_issue_status: Não foi possível excluir situação da tarefa label_profile: Perfil - permission_manage_subtasks: Gerenciar subtarefas + permission_manage_subtasks: Gerenciar sub-tarefas field_parent_issue: Tarefa pai label_subtask_plural: Subtarefas label_project_copy_notifications: Enviar notificações por e-mail ao copiar projeto @@ -1071,7 +1064,7 @@ notice_user_successful_create: Usuário %{id} criado. field_core_fields: campos padrão field_timeout: Tempo de espera (em segundos) - setting_thumbnails_enabled: exibir miniaturas de anexos + setting_thumbnails_enabled: Exibir miniaturas de anexos setting_thumbnails_size: Tamanho das miniaturas (em pixels) label_status_transitions: Estados das transições label_fields_permissions: Permissões de campos @@ -1090,16 +1083,28 @@ label_any_issues_not_in_project: todas as questões que não estão em projeto field_private_notes: notas privadas permission_view_private_notes: Ver notas privadas - permission_set_notes_private: Defina notas como privada + permission_set_notes_private: Permitir alterar notas para privada label_no_issues_in_project: sem problemas em projeto label_any: todos label_last_n_weeks: "últimas %{count} semanas" - setting_cross_project_subtasks: Permitir cruzamento de sub-tarefas entre projetos - label_cross_project_descendants: Com sub-projetos - label_cross_project_tree: Com a árvore do projeto - label_cross_project_hierarchy: Com a hierarquia do projeto - label_cross_project_system: Com todos os projetos + setting_cross_project_subtasks: Permitir cruzamento de sub-tarefas entre projetos + label_cross_project_descendants: com sub-Projetos + label_cross_project_tree: Com uma Ãrvore fazer o Projeto + label_cross_project_hierarchy: Com uma hierarquia fazer o Projeto + label_cross_project_system: Com de Todos os Projetos button_hide: Esconder setting_non_working_week_days: dias não úteis label_in_the_next_days: na próxima label_in_the_past_days: no passado + label_attribute_of_user: Usuário %{name} + text_turning_multiple_off: Se você desativar vários valores, vários valores serão removidas, a fim de preservar a somente um valor por item. + label_attribute_of_issue: Emissão de %{name} + permission_add_documents: Adicionar documentos + permission_edit_documents: Editar documentos + permission_delete_documents: excluir documentos + label_gantt_progress_line: Linha de progresso + setting_jsonp_enabled: Ativar suporte JSONP + field_inherit_members: Herdar membros + field_closed_on: Fechado + setting_default_projects_tracker_ids: Tipos padrões para novos projeto + label_total_time: Total diff -r 0a574315af3e -r 4f746d8966dd config/locales/pt.yml --- a/config/locales/pt.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/pt.yml Fri Jun 14 09:28:30 2013 +0100 @@ -194,8 +194,6 @@ mail_subject_reminder: "%{count} tarefa(s) para entregar nos próximos %{days} dias" mail_body_reminder: "%{count} tarefa(s) que estão atribuídas a si estão agendadas para estarem completas nos próximos %{days} dias:" - gui_validation_error: 1 erro - gui_validation_error_plural: "%{count} erros" field_name: Nome field_description: Descrição @@ -410,8 +408,6 @@ label_text: Texto longo label_attribute: Atributo label_attribute_plural: Atributos - label_download: "%{count} Download" - label_download_plural: "%{count} Downloads" label_no_data: Sem dados para mostrar label_change_status: Mudar estado label_history: Histórico @@ -511,8 +507,6 @@ label_repository: Repositório label_repository_plural: Repositórios label_browse: Navegar - label_modification: "%{count} alteração" - label_modification_plural: "%{count} alterações" label_revision: Revisão label_revision_plural: Revisões label_associated_revisions: Revisões associadas @@ -764,7 +758,6 @@ 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 @@ -943,7 +936,7 @@ setting_commit_logtime_activity_id: Actividade para tempo registado text_time_logged_by_changeset: Aplicado no conjunto de alterações %{value}. setting_commit_logtime_enabled: Activar registo de tempo - notice_gantt_chart_truncated: O gráfico foi truncado porque excede o número máximo de itens visível (%{máx.}) + notice_gantt_chart_truncated: O gráfico foi truncado porque excede o número máximo de itens visíveis (%{max.}) setting_gantt_items_limit: Número máximo de itens exibidos no gráfico Gantt field_warn_on_leaving_unsaved: Avisar-me quando deixar uma página com texto por salvar text_warn_on_leaving_unsaved: A página actual contém texto por salvar que será perdido caso saia desta página. @@ -1016,7 +1009,7 @@ error_attachment_too_big: Este ficheiro não pode ser carregado pois excede o tamanho máximo permitido por ficheiro (%{max_size}) notice_failed_to_save_time_entries: "Falha ao guardar %{count} registo(s) de tempo dos %{total} seleccionados: %{ids}." label_x_issues: - zero: 0 tarefa + zero: 0 tarefas one: 1 tarefa other: "%{count} tarefas" label_repository_new: Novo repositório @@ -1088,3 +1081,16 @@ setting_non_working_week_days: Dias não úteis label_in_the_next_days: no futuro label_in_the_past_days: no passado + label_attribute_of_user: Do utilizador %{name} + text_turning_multiple_off: Se desactivar a escolha múltipla, + a escolha múltipla será apagada de modo a manter apenas um valor por item. + label_attribute_of_issue: Tarefa de %{name} + permission_add_documents: Adicionar documentos + permission_edit_documents: Editar documentos + permission_delete_documents: Apagar documentos + label_gantt_progress_line: Barra de progresso + setting_jsonp_enabled: Activar suporte JSONP + field_inherit_members: Herdar membros + field_closed_on: Fechado + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Total diff -r 0a574315af3e -r 4f746d8966dd config/locales/ro.yml --- a/config/locales/ro.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/ro.yml Fri Jun 14 09:28:30 2013 +0100 @@ -10,10 +10,8 @@ day_names: [Duminică, Luni, Marti, Miercuri, Joi, Vineri, Sâmbătă] abbr_day_names: [Dum, Lun, Mar, Mie, Joi, Vin, Sâm] - # Don't forget the nil at the beginning; there's no such thing as a 0th month month_names: [~, Ianuarie, Februarie, Martie, Aprilie, Mai, Iunie, Iulie, August, Septembrie, Octombrie, Noiembrie, Decembrie] abbr_month_names: [~, Ian, Feb, Mar, Apr, Mai, Iun, Iul, Aug, Sep, Oct, Noi, Dec] - # Used in date_select and datime_select. order: - :day - :month @@ -47,8 +45,8 @@ one: "aproximativ o oră" other: "aproximativ %{count} ore" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 oră" + other: "%{count} ore" x_days: one: "o zi" other: "%{count} zile" @@ -184,8 +182,6 @@ mail_subject_reminder: "%{count} tichete trebuie rezolvate în următoarele %{days} zile" mail_body_reminder: "%{count} tichete atribuite dumneavoastră trebuie rezolvate în următoarele %{days} zile:" - gui_validation_error: o eroare - gui_validation_error_plural: "%{count} erori" field_name: Nume field_description: Descriere @@ -342,7 +338,6 @@ permission_edit_own_time_entries: Editează jurnalele proprii cu timpul de lucru permission_manage_news: Editează È™tiri permission_comment_news: Comentează È™tirile - permission_manage_documents: Editează documente permission_view_documents: AfiÈ™ează documente permission_manage_files: Editează fiÈ™iere permission_view_files: AfiÈ™ează fiÈ™iere @@ -461,8 +456,6 @@ label_text: Text lung label_attribute: Atribut label_attribute_plural: Atribute - label_download: "%{count} descărcare" - label_download_plural: "%{count} descărcări" label_no_data: Nu există date de afiÈ™at label_change_status: Schimbă starea label_history: Istoric @@ -562,8 +555,6 @@ label_repository: Depozit label_repository_plural: Depozite label_browse: AfiÈ™ează - label_modification: "%{count} schimbare" - label_modification_plural: "%{count} schimbări" label_revision: Revizie label_revision_plural: Revizii label_associated_revisions: Revizii asociate @@ -930,7 +921,6 @@ label_principal_search: "Search for user or group:" label_user_search: "Search for user:" field_visible: Visible - setting_emails_header: Emails header setting_commit_logtime_activity_id: Activity for logged time text_time_logged_by_changeset: Applied in changeset %{value}. setting_commit_logtime_enabled: Enable time logging @@ -971,8 +961,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1079,3 +1067,19 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Total + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header diff -r 0a574315af3e -r 4f746d8966dd config/locales/ru.yml --- a/config/locales/ru.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/ru.yml Fri Jun 14 09:28:30 2013 +0100 @@ -117,8 +117,10 @@ many: "около %{count} чаÑов" other: "около %{count} чаÑа" x_hours: - one: "1 чаÑ" - other: "%{count} чаÑов" + one: "%{count} чаÑ" + few: "%{count} чаÑа" + many: "%{count} чаÑов" + other: "%{count} чаÑа" x_days: one: "%{count} день" few: "%{count} днÑ" @@ -145,7 +147,7 @@ many: "больше %{count} лет" other: "больше %{count} лет" almost_x_years: - one: "почти 1 год" + one: "почти %{count} год" few: "почти %{count} года" many: "почти %{count} лет" other: "почти %{count} года" @@ -399,11 +401,6 @@ general_text_yes: 'да' general_text_Yes: 'Да' - gui_validation_error: 1 ошибка - gui_validation_error_plural: "%{count} ошибок" - gui_validation_error_plural2: "%{count} ошибки" - gui_validation_error_plural5: "%{count} ошибок" - label_activity: ДейÑÑ‚Ð²Ð¸Ñ label_add_another_file: Добавить ещё один файл label_added_time_by: "Добавил(а) %{author} %{age} назад" @@ -490,10 +487,6 @@ label_document_added: Добавлен документ label_document_new: Ðовый документ label_document_plural: Документы - label_download: "%{count} загрузка" - label_download_plural: "%{count} Ñкачиваний" - label_download_plural2: "%{count} загрузки" - label_download_plural5: "%{count} загрузок" label_downloads_abbr: Скачиваний label_duplicated_by: дублируетÑÑ label_duplicates: дублирует @@ -576,10 +569,6 @@ label_message_posted: Добавлено Ñообщение label_me: мне label_min_max_length: ÐœÐ¸Ð½Ð¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ - макÑÐ¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð´Ð»Ð¸Ð½Ð° - label_modification: "%{count} изменение" - label_modification_plural: "%{count} изменений" - label_modification_plural2: "%{count} изменениÑ" - label_modification_plural5: "%{count} изменений" label_modified: изменено label_module_plural: Модули label_months_from: меÑÑцев(ца) Ñ @@ -825,7 +814,6 @@ permission_manage_project_activities: Управление типами дейÑтвий Ð´Ð»Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚Ð° permission_manage_boards: Управление форумами permission_manage_categories: Управление категориÑми задач - permission_manage_documents: Управление документами permission_manage_files: Управление файлами permission_manage_issue_relations: Управление ÑвÑзыванием задач permission_manage_members: Управление учаÑтниками @@ -1123,11 +1111,10 @@ error_attachment_too_big: Этот файл Ð½ÐµÐ»ÑŒÐ·Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·Ð¸Ñ‚ÑŒ из-за Ð¿Ñ€ÐµÐ²Ñ‹ÑˆÐµÐ½Ð¸Ñ Ð¼Ð°ÐºÑимального размера файла (%{max_size}) notice_failed_to_save_time_entries: "Ðевозможно Ñохранить %{count} затраченное Ð²Ñ€ÐµÐ¼Ñ Ð´Ð»Ñ %{total} выбранных: %{ids}." label_x_issues: - zero: 0 Задач - one: 1 Задача - few: "%{count} Задач" - many: "%{count} Задач" - other: "%{count} Задач" + one: "%{count} задача" + few: "%{count} задачи" + many: "%{count} задач" + other: "%{count} Задачи" label_repository_new: Ðовое хранилище field_repository_is_default: Хранилище по умолчанию label_copy_attachments: Копировать Ð²Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ @@ -1185,13 +1172,29 @@ permission_set_notes_private: Размещение приватных комментариев label_no_issues_in_project: нет задач в проекте label_any: вÑе - label_last_n_weeks: поÑледние %{count} недель + label_last_n_weeks: + one: "поÑледнÑÑ %{count} неделÑ" + few: "поÑледние %{count} недели" + many: "поÑледние %{count} недель" + other: "поÑледние %{count} недели" setting_cross_project_subtasks: Разрешить подзадачи в между проектами label_cross_project_descendants: С подпроектами label_cross_project_tree: С деревом проектов label_cross_project_hierarchy: С иерархией проектов label_cross_project_system: Со вÑеми проектами button_hide: Скрыть - setting_non_working_week_days: Ðе рабочие дни + setting_non_working_week_days: Ðерабочие дни label_in_the_next_days: в Ñредующие дни label_in_the_past_days: в прошлые дни + label_attribute_of_user: Пользователь %{name} + text_turning_multiple_off: ЕÑли отключить множеÑтвенные значениÑ, лишние Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð¸Ð· ÑпиÑка будут удалены, чтобы оÑталоÑÑŒ только по одному значению. + label_attribute_of_issue: Задача %{name} + permission_add_documents: Добавить документы + permission_edit_documents: Редактировать документы + permission_delete_documents: Удалить документы + label_gantt_progress_line: Ð›Ð¸Ð½Ð¸Ñ Ð¿Ñ€Ð¾Ð³Ñ€ÐµÑÑа + setting_jsonp_enabled: Поддержка JSONP + field_inherit_members: ÐаÑледовать учаÑтников + field_closed_on: Закрыта + setting_default_projects_tracker_ids: Трекеры по умолчанию Ð´Ð»Ñ Ð½Ð¾Ð²Ñ‹Ñ… проектов + label_total_time: Общее Ð²Ñ€ÐµÐ¼Ñ diff -r 0a574315af3e -r 4f746d8966dd config/locales/sk.yml --- a/config/locales/sk.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/sk.yml Fri Jun 14 09:28:30 2013 +0100 @@ -2,9 +2,6 @@ direction: ltr date: formats: - # Use the strftime parameters for formats. - # When no format has been given, it uses default. - # You can provide other formats here if you like! default: "%Y-%m-%d" short: "%b %d" long: "%B %d, %Y" @@ -49,8 +46,8 @@ one: "okolo 1 hodiny" other: "okolo %{count} hodín" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 hodina" + other: "%{count} hodín" x_days: one: "1 deň" other: "%{count} dní" @@ -183,8 +180,6 @@ mail_subject_account_activation_request: "Aktivácia %{value} úÄtu" mail_body_account_activation_request: "Bol zaregistrovaný nový uživateľ %{value}. Aktivácia jeho úÄtu závisí na vaÅ¡om potvrdení." - gui_validation_error: 1 chyba - gui_validation_error_plural: "%{count} chyb(y)" field_name: Názov field_description: Popis @@ -394,8 +389,6 @@ label_text: Dlhý text label_attribute: Atribut label_attribute_plural: Atributy - label_download: "%{count} Download" - label_download_plural: "%{count} Downloady" label_no_data: Žiadné položky label_change_status: ZmeniÅ¥ stav label_history: História @@ -495,8 +488,6 @@ label_repository: Repozitár label_repository_plural: Repozitáre label_browse: PrechádzaÅ¥ - label_modification: "%{count} zmena" - label_modification_plural: "%{count} zmien" label_revision: Revízia label_revision_plural: Revízií label_associated_revisions: Súvisiace verzie @@ -758,7 +749,6 @@ 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: Úprava Wiki strániek permission_add_issue_watchers: Pridanie pozorovateľov permission_view_gantt: Zobrazenie Ganttovho diagramu @@ -933,7 +923,6 @@ label_principal_search: "Search for user or group:" label_user_search: "Search for user:" field_visible: Visible - setting_emails_header: Emails header setting_commit_logtime_activity_id: Activity for logged time text_time_logged_by_changeset: Applied in changeset %{value}. setting_commit_logtime_enabled: Enable time logging @@ -974,8 +963,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1082,3 +1069,19 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Celkovo + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header diff -r 0a574315af3e -r 4f746d8966dd config/locales/sl.yml --- a/config/locales/sl.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/sl.yml Fri Jun 14 09:28:30 2013 +0100 @@ -50,8 +50,8 @@ one: "okrog 1. ure" other: "okrog %{count} ur" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 ura" + other: "%{count} ur" x_days: one: "1 dan" other: "%{count} dni" @@ -184,8 +184,6 @@ mail_subject_reminder: "%{count} zahtevek(zahtevki) zapadejo v naslednjih %{days} dneh" mail_body_reminder: "%{count} zahtevek(zahtevki), ki so vam dodeljeni bodo zapadli v naslednjih %{days} dneh:" - gui_validation_error: 1 napaka - gui_validation_error_plural: "%{count} napak" field_name: Ime field_description: Opis @@ -335,7 +333,6 @@ 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 @@ -453,8 +450,6 @@ label_text: Dolgo besedilo label_attribute: Lastnost label_attribute_plural: Lastnosti - label_download: "%{count} Prenos" - label_download_plural: "%{count} Prenosi" label_no_data: Ni podatkov za prikaz label_change_status: Spremeni stanje label_history: Zgodovina @@ -554,8 +549,6 @@ label_repository: Shramba label_repository_plural: Shrambe label_browse: Prebrskaj - label_modification: "%{count} sprememba" - label_modification_plural: "%{count} spremembe" label_revision: Revizija label_revision_plural: Revizije label_associated_revisions: Povezane revizije @@ -1082,3 +1075,16 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Skupaj diff -r 0a574315af3e -r 4f746d8966dd config/locales/sq.yml --- a/config/locales/sq.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/sq.yml Fri Jun 14 09:28:30 2013 +0100 @@ -50,8 +50,8 @@ one: "about 1 hour" other: "about %{count} hours" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 ore" + other: "%{count} ore" x_days: one: "1 day" other: "%{count} days" @@ -214,8 +214,6 @@ mail_subject_wiki_content_updated: "'%{id}' wiki page eshte modifikuar" mail_body_wiki_content_updated: "The '%{id}' wiki page eshte modifikuar nga %{author}." - gui_validation_error: 1 error - gui_validation_error_plural: "%{count} gabime" field_name: Emri field_description: Pershkrimi @@ -353,8 +351,6 @@ 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: Attachments and repositories encodings - setting_emails_header: Emails header - setting_emails_footer: Emails footer setting_protocol: Protocol setting_per_page_options: Objects per page options setting_user_format: Users display format @@ -421,7 +417,6 @@ 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 @@ -552,8 +547,6 @@ label_text: Long text label_attribute: Attribute label_attribute_plural: Attributes - label_download: "%{count} Download" - label_download_plural: "%{count} Downloads" label_no_data: No data to display label_change_status: Change status label_history: Histori @@ -664,8 +657,6 @@ label_repository_new: New repository label_repository_plural: Repositories label_browse: Browse - label_modification: "%{count} ndryshim" - label_modification_plural: "%{count} ndryshime" label_branch: Dege label_tag: Tag label_revision: Revizion @@ -978,8 +969,6 @@ text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo) text_scm_command: Command text_scm_command_version: Version - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. text_issue_conflict_resolution_overwrite: "Apply my changes anyway (previous notes will be kept but some changes may be overwritten)" text_issue_conflict_resolution_add_notes: "Add my notes and discard my other changes" text_issue_conflict_resolution_cancel: "Discard all my changes and redisplay %{link}" @@ -1077,3 +1066,20 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Total + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_footer: Email footer + setting_emails_header: Email header diff -r 0a574315af3e -r 4f746d8966dd config/locales/sr-YU.yml --- a/config/locales/sr-YU.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/sr-YU.yml Fri Jun 14 09:28:30 2013 +0100 @@ -53,8 +53,8 @@ one: "približno jedan sat" other: "približno %{count} sati" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 sat" + other: "%{count} sati" x_days: one: "jedan dan" other: "%{count} dana" @@ -209,8 +209,6 @@ mail_subject_wiki_content_updated: "Wiki stranica '%{id}' je ažurirana" mail_body_wiki_content_updated: "%{author} je ažurirao wiki stranicu '%{id}'." - gui_validation_error: jedna greÅ¡ka - gui_validation_error_plural: "%{count} greÅ¡aka" field_name: Naziv field_description: Opis @@ -290,7 +288,7 @@ field_delay: KaÅ¡njenje field_assignable: Problem može biti dodeljen ovoj ulozi field_redirect_existing_links: Preusmeri postojeće veze - field_estimated_hours: Proteklo vreme + field_estimated_hours: Procenjeno vreme field_column_names: Kolone field_time_zone: Vremenska zona field_searchable: Može da se pretražuje @@ -387,7 +385,6 @@ permission_edit_own_time_entries: Izmena sopstvenog utroÅ¡enog vremena permission_manage_news: Upravljanje vestima permission_comment_news: Komentarisanje vesti - permission_manage_documents: Upravljanje dokumentima permission_view_documents: Pregledanje dokumenata permission_manage_files: Upravljanje datotekama permission_view_files: Pregledanje datoteka @@ -511,8 +508,6 @@ label_text: Dugi tekst label_attribute: Osobina label_attribute_plural: Osobine - label_download: "%{count} preuzimanje" - label_download_plural: "%{count} preuzimanja" label_no_data: Nema podataka za prikazivanje label_change_status: Promena statusa label_history: Istorija @@ -615,8 +610,6 @@ label_repository: SpremiÅ¡te label_repository_plural: SpremiÅ¡ta label_browse: Pregledanje - label_modification: "%{count} promena" - label_modification_plural: "%{count} promena" label_branch: Grana label_tag: Oznaka label_revision: Revizija @@ -976,8 +969,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1084,3 +1075,18 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Ukupno + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. diff -r 0a574315af3e -r 4f746d8966dd config/locales/sr.yml --- a/config/locales/sr.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/sr.yml Fri Jun 14 09:28:30 2013 +0100 @@ -51,8 +51,8 @@ one: "приближно један Ñат" other: "приближно %{count} Ñати" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 Ñат" + other: "%{count} Ñати" x_days: one: "један дан" other: "%{count} дана" @@ -207,8 +207,6 @@ mail_subject_wiki_content_updated: "Wiki Ñтраница '%{id}' је ажурирана" mail_body_wiki_content_updated: "%{author} је ажурирао wiki Ñтраницу '%{id}'." - gui_validation_error: једна грешка - gui_validation_error_plural: "%{count} грешака" field_name: Ðазив field_description: ÐžÐ¿Ð¸Ñ @@ -385,7 +383,6 @@ permission_edit_own_time_entries: Измена ÑопÑтвеног утрошеног времена permission_manage_news: Управљање веÑтима permission_comment_news: КоментариÑање веÑти - permission_manage_documents: Управљање документима permission_view_documents: Прегледање докумената permission_manage_files: Управљање датотекама permission_view_files: Прегледање датотека @@ -509,8 +506,6 @@ label_text: Дуги текÑÑ‚ label_attribute: ОÑобина label_attribute_plural: ОÑобине - label_download: "%{count} преузимање" - label_download_plural: "%{count} преузимања" label_no_data: Ðема података за приказивање label_change_status: Промена ÑтатуÑа label_history: ИÑторија @@ -613,8 +608,6 @@ label_repository: Спремиште label_repository_plural: Спремишта label_browse: Прегледање - label_modification: "%{count} промена" - label_modification_plural: "%{count} промена" label_branch: Грана label_tag: Ознака label_revision: Ревизија @@ -934,7 +927,6 @@ label_principal_search: "Search for user or group:" label_user_search: "Search for user:" field_visible: Visible - setting_emails_header: Emails header setting_commit_logtime_activity_id: Activity for logged time text_time_logged_by_changeset: Applied in changeset %{value}. setting_commit_logtime_enabled: Enable time logging @@ -975,8 +967,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1083,3 +1073,19 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Укупно + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header diff -r 0a574315af3e -r 4f746d8966dd config/locales/sv.yml --- a/config/locales/sv.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/sv.yml Fri Jun 14 09:28:30 2013 +0100 @@ -215,10 +215,10 @@ notice_unable_delete_version: Denna version var inte möjlig att ta bort. notice_unable_delete_time_entry: Tidloggning kunde inte tas bort. notice_issue_done_ratios_updated: "% klart uppdaterade." - notice_gantt_chart_truncated: "Schemat förminskades eftersom det överskrider det maximala antalet aktiviteter som fÃ¥r visas (%{max})" - notice_issue_successful_create: Ärende %{id} skapades. - notice_issue_update_conflict: Detta ärende har uppdaterats av en annan användare samtidigt som du redigerade det. - notice_account_deleted: Ditt konto har avslutats permanent. + notice_gantt_chart_truncated: "Schemat förminskades eftersom det överskrider det maximala antalet aktiviteter som kan visas (%{max})" + notice_issue_successful_create: "Ärende %{id} skapades." + notice_issue_update_conflict: "Detta ärende har uppdaterats av en annan användare samtidigt som du redigerade det." + notice_account_deleted: "Ditt konto har avslutats permanent." notice_user_successful_create: "Användare %{id} skapad." error_can_t_load_default_data: "Standardkonfiguration gick inte att läsa in: %{value}" @@ -239,7 +239,7 @@ error_workflow_copy_target: 'Vänligen välj ärendetyp(er) och roll(er) för mÃ¥l' error_unable_delete_issue_status: 'Ärendestatus kunde inte tas bort' error_unable_to_connect: "Kan inte ansluta (%{value})" - error_attachment_too_big: Denna fil kan inte laddas upp eftersom den överstiger maximalt tillÃ¥ten filstorlek (%{max_size}) + error_attachment_too_big: "Denna fil kan inte laddas upp eftersom den överstiger maximalt tillÃ¥ten filstorlek (%{max_size})" error_session_expired: "Din session har gÃ¥tt ut. Vänligen logga in pÃ¥ nytt." warning_attachments_not_saved: "%{count} fil(er) kunde inte sparas." @@ -258,8 +258,6 @@ mail_subject_wiki_content_updated: "'%{id}' wikisida har uppdaterats" mail_body_wiki_content_updated: "The '%{id}' wikisida har uppdaterats av %{author}." - gui_validation_error: 1 fel - gui_validation_error_plural: "%{count} fel" field_name: Namn field_description: Beskrivning @@ -274,6 +272,7 @@ field_author: Författare field_created_on: Skapad field_updated_on: Uppdaterad + field_closed_on: Stängd field_field_format: Format field_is_for_all: För alla projekt field_possible_values: Möjliga värden @@ -374,6 +373,7 @@ field_timeout: "Timeout (i sekunder)" field_board_parent: Förälderforum field_private_notes: Privata anteckningar + field_inherit_members: Ärv medlemmar setting_app_title: Applikationsrubrik setting_app_subtitle: Applikationsunderrubrik @@ -442,6 +442,8 @@ setting_thumbnails_enabled: Visa miniatyrbilder av bilagor setting_thumbnails_size: Storlek pÃ¥ miniatyrbilder (i pixlar) setting_non_working_week_days: Lediga dagar + setting_jsonp_enabled: Aktivera JSONP-stöd + setting_default_projects_tracker_ids: Standardärendetyper för nya projekt permission_add_project: Skapa projekt permission_add_subprojects: Skapa underprojekt @@ -458,9 +460,9 @@ permission_manage_issue_relations: Hantera ärenderelationer permission_set_issues_private: Sätta ärenden publika eller privata permission_set_own_issues_private: Sätta egna ärenden publika eller privata - permission_add_issue_notes: Lägga till ärendenotering - permission_edit_issue_notes: Ändra ärendenoteringar - permission_edit_own_issue_notes: Ändra egna ärendenoteringar + permission_add_issue_notes: Lägga till ärendeanteckning + permission_edit_issue_notes: Ändra ärendeanteckningar + permission_edit_own_issue_notes: Ändra egna ärendeanteckningar permission_view_private_notes: Visa privata anteckningar permission_set_notes_private: Ställa in anteckningar som privata permission_move_issues: Flytta ärenden @@ -478,8 +480,10 @@ permission_edit_own_time_entries: Ändra egna tidloggningar permission_manage_news: Hantera nyheter permission_comment_news: Kommentera nyheter - permission_manage_documents: Hantera dokument permission_view_documents: Visa dokument + permission_add_documents: Lägga till dokument + permission_edit_documents: Ändra dokument + permission_delete_documents: Ta bort dokument permission_manage_files: Hantera filer permission_view_files: Visa filer permission_manage_wiki: Hantera wiki @@ -610,8 +614,6 @@ label_text: LÃ¥ng text label_attribute: Attribut label_attribute_plural: Attribut - label_download: "%{count} Nerladdning" - label_download_plural: "%{count} Nerladdningar" label_no_data: Ingen data att visa label_change_status: Ändra status label_history: Historia @@ -660,11 +662,13 @@ one: 1 ärende other: "%{count} ärenden" label_total: Total + label_total_time: Total tid label_permissions: Behörigheter label_current_status: Nuvarande status label_new_statuses_allowed: Nya tillÃ¥tna statusvärden label_all: alla - label_none: ingen + label_any: vad/vem som helst + label_none: inget/ingen label_nobody: ingen label_next: Nästa label_previous: FöregÃ¥ende @@ -728,8 +732,6 @@ label_repository_new: Nytt versionsarkiv label_repository_plural: Versionsarkiv label_browse: Bläddra - label_modification: "%{count} ändring" - label_modification_plural: "%{count} ändringar" label_branch: Branch label_tag: Tag label_revision: Revision @@ -791,13 +793,13 @@ 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_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_copied_to: Kopierad till label_copied_from: Kopierad frÃ¥n label_end_to_start: slut till start @@ -924,9 +926,16 @@ label_readonly: Skrivskyddad label_required: Nödvändig label_attribute_of_project: Projektets %{name} + label_attribute_of_issue: Ärendets %{name} label_attribute_of_author: Författarens %{name} - label_attribute_of_assigned_to: Tilldelads %{name} + label_attribute_of_assigned_to: Tilldelad användares %{name} + label_attribute_of_user: Användarens %{name} label_attribute_of_fixed_version: MÃ¥lversionens %{name} + label_cross_project_descendants: Med underprojekt + label_cross_project_tree: Med projektträd + label_cross_project_hierarchy: Med projekthierarki + label_cross_project_system: Med alla projekt + label_gantt_progress_line: Framstegslinje button_login: Logga in button_submit: Skicka @@ -1010,7 +1019,7 @@ text_tip_issue_begin_day: ärende som börjar denna dag text_tip_issue_end_day: ärende som slutar denna dag text_tip_issue_begin_end_day: ärende som börjar och slutar denna dag - text_project_identifier_info: Ändast gemener (a-z), siffror, streck och understreck är tillÃ¥tna.
      När identifieraren sparats kan den inte ändras. + text_project_identifier_info: 'Endast gemener (a-z), siffror, streck och understreck är tillåtna, måste börja med en bokstav.
      När identifieraren sparats kan den inte ändras.' text_caracters_maximum: "max %{count} tecken." text_caracters_minimum: "Måste vara minst %{count} tecken lång." text_length_between: "Längd mellan %{min} och %{max} tecken." @@ -1056,7 +1065,7 @@ text_own_membership_delete_confirmation: "Några av, eller alla, dina behörigheter kommer att tas bort och du kanske inte längre kommer kunna göra ändringar i det här projektet.\nVill du verkligen fortsätta?" text_zoom_out: Zooma ut text_zoom_in: Zooma in - text_warn_on_leaving_unsaved: Nuvarande sida innehåller osparad text som kommer försvinna om du lämnar sidan. + text_warn_on_leaving_unsaved: "Nuvarande sida innehåller osparad text som kommer försvinna om du lämnar sidan." text_scm_path_encoding_note: "Standard: UTF-8" text_git_repository_note: Versionsarkiv är tomt och lokalt (t.ex. /gitrepo, c:\gitrepo) text_mercurial_repository_note: Lokalt versionsarkiv (t.ex. /hgrepo, c:\hgrepo) @@ -1064,12 +1073,13 @@ text_scm_command_version: Version text_scm_config: Du kan konfigurera dina scm-kommando i config/configuration.yml. Vänligen starta om applikationen när ändringar gjorts. text_scm_command_not_available: Scm-kommando är inte tillgängligt. Vänligen kontrollera inställningarna i administratörspanelen. - text_issue_conflict_resolution_overwrite: Använd mina ändringar i alla fall (tidigare anteckningar kommer behållas men några ändringar kan bli överskrivna) - text_issue_conflict_resolution_add_notes: Lägg till mina anteckningar och kasta mina andra ändringar - text_issue_conflict_resolution_cancel: Kasta alla mina ändringar och visa igen %{link} + text_issue_conflict_resolution_overwrite: "Använd mina ändringar i alla fall (tidigare anteckningar kommer behållas men några ändringar kan bli överskrivna)" + text_issue_conflict_resolution_add_notes: "Lägg till mina anteckningar och kasta mina andra ändringar" + text_issue_conflict_resolution_cancel: "Kasta alla mina ändringar och visa igen %{link}" text_account_destroy_confirmation: "Är du säker på att du vill fortsätta?\nDitt konto kommer tas bort permanent, utan möjlighet att återaktivera det." text_session_expiration_settings: "Varning: ändring av dessa inställningar kan få alla nuvarande sessioner, inklusive din egen, att gå ut." text_project_closed: Detta projekt är stängt och skrivskyddat. + text_turning_multiple_off: "Om du inaktiverar möjligheten till flera värden kommer endast ett värde per objekt behållas." default_role_manager: Projektledare default_role_developer: Utvecklare @@ -1115,9 +1125,4 @@ description_date_range_interval: Ange intervall genom att välja start- och slutdatum description_date_from: Ange startdatum description_date_to: Ange slutdatum - text_repository_identifier_info: Ändast gemener (a-z), siffror, streck och understreck är tillåtna.
      När identifieraren sparats kan den inte ändras. - label_any: alla - label_cross_project_descendants: Med underprojekt - label_cross_project_tree: Med projektträd - label_cross_project_hierarchy: Med projekthierarki - label_cross_project_system: Med alla projekt + text_repository_identifier_info: 'Endast gemener (a-z), siffror, streck och understreck är tillåtna.
      När identifieraren sparats kan den inte ändras.' diff -r 0a574315af3e -r 4f746d8966dd config/locales/th.yml --- a/config/locales/th.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/th.yml Fri Jun 14 09:28:30 2013 +0100 @@ -180,8 +180,6 @@ mail_subject_account_activation_request: "à¸à¸£à¸¸à¸“าเปิดบัà¸à¸Šà¸µ %{value}" mail_body_account_activation_request: "ผู้ใช้ใหม่ (%{value}) ได้ลงทะเบียน. บัà¸à¸Šà¸µà¸‚องเขาà¸à¸³à¸¥à¸±à¸‡à¸£à¸­à¸­à¸™à¸¸à¸¡à¸±à¸•ิ:" - gui_validation_error: 1 ข้อผิดพลาด - gui_validation_error_plural: "%{count} ข้อผิดพลาด" field_name: ชื่อ field_description: รายละเอียด @@ -392,8 +390,6 @@ label_text: ข้อความขนาดยาว label_attribute: คุณลัà¸à¸©à¸“ะ label_attribute_plural: คุณลัà¸à¸©à¸“ะ - label_download: "%{count} ดาวน์โหลด" - label_download_plural: "%{count} ดาวน์โหลด" label_no_data: จำนวนข้อมูลที่à¹à¸ªà¸”ง label_change_status: เปลี่ยนสถานะ label_history: ประวัติ @@ -493,8 +489,6 @@ label_repository: ที่เà¸à¹‡à¸šà¸•้นฉบับ label_repository_plural: ที่เà¸à¹‡à¸šà¸•้นฉบับ label_browse: เปิดหา - label_modification: "%{count} เปลี่ยนà¹à¸›à¸¥à¸‡" - label_modification_plural: "%{count} เปลี่ยนà¹à¸›à¸¥à¸‡" label_revision: à¸à¸²à¸£à¹à¸à¹‰à¹„ข label_revision_plural: à¸à¸²à¸£à¹à¸à¹‰à¹„ข label_associated_revisions: à¸à¸²à¸£à¹à¸à¹‰à¹„ขที่เà¸à¸µà¹ˆà¸¢à¸§à¸‚้อง @@ -755,7 +749,6 @@ 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 @@ -930,7 +923,6 @@ label_principal_search: "Search for user or group:" label_user_search: "Search for user:" field_visible: Visible - setting_emails_header: Emails header setting_commit_logtime_activity_id: Activity for logged time text_time_logged_by_changeset: Applied in changeset %{value}. setting_commit_logtime_enabled: Enable time logging @@ -971,8 +963,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1079,3 +1069,19 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: จำนวนรวม + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header diff -r 0a574315af3e -r 4f746d8966dd config/locales/tr.yml --- a/config/locales/tr.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/tr.yml Fri Jun 14 09:28:30 2013 +0100 @@ -56,8 +56,8 @@ one: 'yaklaşık 1 saat' other: 'yaklaşık %{count} saat' x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 saat" + other: "%{count} saat" x_days: one: '1 gün' other: '%{count} gün' @@ -204,8 +204,6 @@ mail_subject_account_activation_request: "%{value} hesabı etkinleÅŸtirme isteÄŸi" mail_body_account_activation_request: "Yeni bir kullanıcı (%{value}) kaydedildi. Hesap onaylanmayı bekliyor:" - gui_validation_error: 1 hata - gui_validation_error_plural: "%{count} hata" field_name: İsim field_description: Yorum @@ -414,8 +412,6 @@ label_text: Uzun Metin label_attribute: Nitelik label_attribute_plural: Nitelikler - label_download: "%{count} indirme" - label_download_plural: "%{count} indirme" label_no_data: Gösterilecek veri yok label_change_status: DeÄŸiÅŸim Durumu label_history: GeçmiÅŸ @@ -515,8 +511,6 @@ label_repository: Depo label_repository_plural: Depolar label_browse: Gözat - label_modification: "%{count} deÄŸiÅŸim" - label_modification_plural: "%{count} deÄŸiÅŸim" label_revision: DeÄŸiÅŸiklik label_revision_plural: DeÄŸiÅŸiklikler label_associated_revisions: BirleÅŸtirilmiÅŸ deÄŸiÅŸiklikler @@ -779,7 +773,6 @@ permission_comment_news: Haberlere yorum yapma permission_delete_messages: Mesaj silme permission_select_project_modules: Proje modüllerini seçme - permission_manage_documents: Belgeleri yönetme permission_edit_wiki_pages: Wiki sayfalarını düzenleme permission_add_issue_watchers: Takipçi ekleme permission_view_gantt: İş-Zaman çizelgesi görme @@ -993,8 +986,6 @@ text_scm_command: Komut text_scm_command_version: Sürüm label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1101,3 +1092,18 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Toplam + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. diff -r 0a574315af3e -r 4f746d8966dd config/locales/uk.yml --- a/config/locales/uk.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/uk.yml Fri Jun 14 09:28:30 2013 +0100 @@ -174,8 +174,6 @@ mail_subject_account_activation_request: "Запит на активацію облікового запиÑу %{value}" mail_body_account_activation_request: "Ðовий кориÑтувач (%{value}) зареєÑтрувавÑÑ. Його обліковий Ð·Ð°Ð¿Ð¸Ñ Ñ‡ÐµÐºÐ°Ñ” на ваше підтвердженнÑ:" - gui_validation_error: 1 помилка - gui_validation_error_plural: "%{count} помилки(ок)" field_name: Ім'Ñ field_description: ÐžÐ¿Ð¸Ñ @@ -364,8 +362,6 @@ label_text: Довгий текÑÑ‚ label_attribute: Ðтрибут label_attribute_plural: атрибути - label_download: "%{count} Завантажено" - label_download_plural: "%{count} Завантажень" label_no_data: Ðемає даних Ð´Ð»Ñ Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ label_change_status: Змінити ÑÑ‚Ð°Ñ‚ÑƒÑ label_history: ІÑÑ‚Ð¾Ñ€Ñ–Ñ @@ -454,8 +450,6 @@ label_day_plural: днів(Ñ) label_repository: Репозиторій label_browse: ПроглÑнути - label_modification: "%{count} зміна" - label_modification_plural: "%{count} змін" label_revision: ВерÑÑ–Ñ label_revision_plural: ВерÑій label_added: додано @@ -755,7 +749,6 @@ 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 @@ -930,7 +923,6 @@ label_principal_search: "Search for user or group:" label_user_search: "Search for user:" field_visible: Visible - setting_emails_header: Emails header setting_commit_logtime_activity_id: Activity for logged time text_time_logged_by_changeset: Applied in changeset %{value}. setting_commit_logtime_enabled: Enable time logging @@ -971,8 +963,6 @@ text_scm_command: Command text_scm_command_version: Version label_git_report_last_commit: Report last commit for files and directories - text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. - text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. notice_issue_successful_create: Issue %{id} created. label_between: between setting_issue_group_assignment: Allow issue assignment to groups @@ -1077,3 +1067,19 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Ð’Ñього + text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it. + text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel. + setting_emails_header: Email header diff -r 0a574315af3e -r 4f746d8966dd config/locales/vi.yml --- a/config/locales/vi.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/vi.yml Fri Jun 14 09:28:30 2013 +0100 @@ -236,16 +236,13 @@ mail_subject_account_activation_request: "%{value}: Yêu cầu chứng thá»±c tài khoản" mail_body_account_activation_request: "Ngưá»i dùng (%{value}) má»›i đăng ký và cần bạn xác nhận:" mail_subject_reminder: "%{count} vấn đỠhết hạn trong các %{days} ngày tá»›i" - mail_body_reminder: "%{count} vấn đỠgán cho bạn sẽ hết hạn trong %{days} ngày tá»›i:" + mail_body_reminder: "%{count} công việc bạn được phân công sẽ hết hạn trong %{days} ngày tá»›i:" - gui_validation_error: 1 lá»—i - gui_validation_error_plural: "%{count} lá»—i" - - field_name: Tên + field_name: Tên dá»± á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_firstname: Tên đệm và Tên field_lastname: Há» field_mail: Email field_filename: Tập tin @@ -269,11 +266,11 @@ 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_tracker: Kiểu vấn đỠfield_subject: Chá»§ đỠfield_due_date: Hết hạn - field_assigned_to: Gán cho - field_priority: Ưu tiên + field_assigned_to: Phân công cho + field_priority: Mức ưu tiên field_fixed_version: Phiên bản field_user: Ngưá»i dùng field_role: Quyá»n @@ -287,9 +284,9 @@ 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_password: Mật khẩu + field_new_password: Mật khẩu má»›i + field_password_confirmation: Nhập lại mật khẩu field_version: Phiên bản field_type: Kiểu field_host: Host @@ -304,7 +301,7 @@ field_start_date: Bắt đầu field_done_ratio: Tiến độ field_auth_source: Chế độ xác thá»±c - field_hide_mail: Không làm lá»™ email cá»§a bạn + field_hide_mail: Không hiện email cá»§a tôi field_comments: Bình luận field_url: URL field_start_page: Trang bắt đầu @@ -313,12 +310,12 @@ 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: Vấn Ä‘á»n liên quan + field_is_filter: Dùng như bá»™ lá»c + field_issue_to: Vấ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_estimated_hours: Thá»i gian ước lượng field_column_names: Cá»™t field_time_zone: Múi giá» field_searchable: Tìm kiếm được @@ -340,7 +337,7 @@ setting_text_formatting: Äịnh dạng bài viết setting_wiki_compression: Nén lịch sá»­ Wiki 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_default_projects_public: Dá»± án mặc định là public setting_autofetch_changesets: Tá»± động tìm nạp commits setting_sys_api_enabled: Cho phép WS quản lý kho chứa setting_commit_ref_keywords: Từ khóa tham khảo @@ -400,9 +397,9 @@ 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_tracker: Kiểu vấn đỠ+ label_tracker_plural: Kiểu vấn đỠ+ label_tracker_new: Tạo kiểu vấn đỠmá»›i label_workflow: Quy trình làm việc label_issue_status: Trạng thái vấn đỠlabel_issue_status_plural: Trạng thái vấn đỠ@@ -429,8 +426,8 @@ 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_reported_issues: Công việc bạn phân công + label_assigned_to_me_issues: Công việc được phân công label_last_login: Kết nối cuối label_registered_on: Ngày tham gia label_activity: Hoạt động @@ -454,8 +451,6 @@ label_text: Văn bản dài label_attribute: Thuá»™c tính label_attribute_plural: Các thuá»™c tính - label_download: "%{count} lần tải" - label_download_plural: "%{count} 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á»­ @@ -501,7 +496,7 @@ 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_all: Tất cả label_none: không label_nobody: Chẳng ai label_next: Sau @@ -555,8 +550,6 @@ label_repository: Kho lưu trữ label_repository_plural: Kho lưu trữ label_browse: Duyệt - label_modification: "%{count} thay đổi" - label_modification_plural: "%{count} thay đổi" label_revision: Bản Ä‘iá»u chỉnh label_revision_plural: Bản Ä‘iá»u chỉnh label_associated_revisions: Các bản Ä‘iá»u chỉnh được ghép @@ -624,8 +617,8 @@ 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_disabled: Bị vô hiệu + label_show_completed_versions: Xem phiên bản đã hoàn thành label_me: tôi label_board: Diá»…n đàn label_board_new: Tạo diá»…n đàn má»›i @@ -646,8 +639,8 @@ label_sort_by: "Sắp xếp theo %{value}" label_send_test_email: Gá»­i má»™t email kiểm tra label_feeds_access_key_created_on: "Mã chứng thá»±c RSS được tạo ra cách đây %{value}" - label_module_plural: Mô-Ä‘un - label_added_time_by: "thêm bởi %{author} cách đây %{age}" + label_module_plural: Module + label_added_time_by: "Thêm bởi %{author} cách đây %{age}" label_updated_time: "Cập nhật cách đây %{value}" label_jump_to_a_project: Nhảy đến dá»± án... label_file_plural: Tập tin @@ -658,9 +651,9 @@ 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_all: "Má»i sá»± kiện trên má»i dá»± án cá»§a tôi" label_user_mail_option_selected: "Má»i sá»± kiện trên các dá»± án được chá»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_user_mail_no_self_notified: "Äừng gá»­i email vá» các thay đổi do chính tôi thá»±c hiện" label_registration_activation_by_email: kích hoạt tài khoản qua email label_registration_manual_activation: kích hoạt tài khoản thá»§ công label_registration_automatic_activation: kích hoạt tài khoản tá»± động @@ -670,9 +663,9 @@ label_general: Tổng quan label_more: Chi tiết label_scm: SCM - label_plugins: Mô-Ä‘un + label_plugins: Module label_ldap_authentication: Chứng thá»±c LDAP - label_downloads_abbr: Tải vá» + label_downloads_abbr: Số lượng Download label_optional_description: Mô tả bổ sung label_add_another_file: Thêm tập tin khác label_preferences: Cấu hình @@ -716,17 +709,17 @@ button_reset: Tạo lại button_rename: Äổi tên button_change_password: Äổi mật mã - button_copy: Chép + button_copy: Sao 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 + status_active: Äang hoạt động + status_registered: Má»›i đă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_select_mail_notifications: Chá»n hành động đối vá»›i má»—i email 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: Bạn có chắc chắn muốn xóa dá»± án này và các dữ liệu liên quan ? @@ -754,7 +747,7 @@ text_load_default_configuration: Nạp lại cấu hình mặc định text_status_changed_by_changeset: "Ãp dụng trong changeset : %{value}." text_issues_destroy_confirmation: 'Bạn có chắc chắn muốn xóa các vấn đỠđã chá»n ?' - text_select_project_modules: 'Chá»n các mô-Ä‘un cho dá»± án:' + text_select_project_modules: 'Chá»n các module cho dá»± án:' text_default_administrator_account_changed: Thay đổi tài khoản quản trị mặc định text_file_repository_writable: Cho phép ghi thư mục đính kèm text_rmagick_available: Trạng thái RMagick @@ -767,17 +760,17 @@ text_enumeration_category_reassign_to: 'Gán lại giá trị này:' text_email_delivery_not_configured: "Cấu hình gá»­i Email chưa được đặt, và chức năng thông báo bị loại bá».\nCấu hình máy chá»§ SMTP cá»§a bạn ở file config/configuration.yml và khởi động lại để kích hoạt chúng." - default_role_manager: Äiá»u hành - default_role_developer: Phát triển + default_role_manager: 'Äiá»u hành ' + default_role_developer: '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_in_progress: Äang tiến hành - default_issue_status_resolved: Quyết tâm + default_issue_status_resolved: Äã được giải quyết default_issue_status_feedback: Phản hồi - default_issue_status_closed: Äóng + 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 @@ -790,13 +783,13 @@ 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) + enumeration_doc_categories: Danh mục tài liệu + enumeration_activities: Hoạt động - setting_plain_text_mail: mail dạng text đơn giản (không dùng HTML) + 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_select_project_modules: Chá»n Module 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á»§ đỠ@@ -808,19 +801,18 @@ 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_manage_public_queries: Quản lý truy vấ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_view_issue_watchers: Xem những 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_log_time: Lưu thá»i gian đã qua + permission_view_time_entries: Xem thá»i gian đã qua 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 @@ -844,7 +836,7 @@ 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ạ." + text_repository_usernames_mapping: "Lá»±a 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ữ.\nKhi ngưá»i dùng trùng hợp vá» tên và email sẽ được tá»± động ánh xạ." permission_delete_own_messages: Xóa thông Ä‘iệp label_user_activity: "%{value} hoạt động" label_updated_time_by: "Cập nhật bởi %{author} cách đây %{age}" @@ -1137,3 +1129,15 @@ setting_non_working_week_days: Các ngày không làm việc label_in_the_next_days: Trong tương lai label_in_the_past_days: Trong quá khứ + label_attribute_of_user: "Cá»§a ngưá»i dùng %{name}" + text_turning_multiple_off: Nếu bạn vô hiệu hóa nhiá»u giá trị, chúng sẽ bị loại bỠđể duy trì chỉ có má»™t giá trị cho má»—i mục. + label_attribute_of_issue: "Vấn đỠcá»§a %{name}" + permission_add_documents: Thêm tài liệu + permission_edit_documents: Soạn thảo tài liệu + permission_delete_documents: Xóa tài liệu + label_gantt_progress_line: Tiến độ + setting_jsonp_enabled: Cho phép trợ giúp JSONP + field_inherit_members: Các thành viên kế thừa + field_closed_on: Äã đóng + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: Tổng cá»™ng diff -r 0a574315af3e -r 4f746d8966dd config/locales/zh-TW.yml --- a/config/locales/zh-TW.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/zh-TW.yml Fri Jun 14 09:28:30 2013 +0100 @@ -4,6 +4,8 @@ "zh-TW": direction: ltr + jquery: + locale: "zh-TW" date: formats: # Use the strftime parameters for formats. @@ -121,8 +123,8 @@ one: "ç´„ 1 å°æ™‚" other: "ç´„ %{count} å°æ™‚" x_hours: - one: "1 hour" - other: "%{count} hours" + one: "1 å°æ™‚" + other: "%{count} å°æ™‚" x_days: one: "1 天" other: "%{count} 天" @@ -298,8 +300,6 @@ mail_subject_wiki_content_updated: "'%{id}' wiki é é¢å·²è¢«æ›´æ–°" mail_body_wiki_content_updated: "æ­¤ '%{id}' wiki é é¢å·²è¢« %{author} 更新。" - gui_validation_error: 1 個錯誤 - gui_validation_error_plural: "%{count} 個錯誤" field_name: å稱 field_description: 概述 @@ -313,7 +313,8 @@ field_downloads: 下載次數 field_author: 作者 field_created_on: 建立日期 - field_updated_on: æ›´æ–° + field_updated_on: 更新日期 + field_closed_on: çµæŸæ—¥æœŸ field_field_format: æ ¼å¼ field_is_for_all: 給全部的專案 field_possible_values: å¯èƒ½å€¼ @@ -414,6 +415,7 @@ field_timeout: "逾時 (å–®ä½: ç§’)" field_board_parent: 父論壇 field_private_notes: ç§äººç­†è¨˜ + field_inherit_members: 繼承父專案æˆå“¡ setting_app_title: 標題 setting_app_subtitle: 副標題 @@ -482,6 +484,8 @@ setting_thumbnails_enabled: 顯示附加檔案的縮圖 setting_thumbnails_size: "ç¸®åœ–å¤§å° (å–®ä½: åƒç´  pixels)" setting_non_working_week_days: éžå·¥ä½œæ—¥ + setting_jsonp_enabled: 啟用 JSONP æ”¯æ´ + setting_default_projects_tracker_ids: 新專案é è¨­ä½¿ç”¨çš„追蹤標籤 permission_add_project: 建立專案 permission_add_subprojects: 建立å­å°ˆæ¡ˆ @@ -518,8 +522,10 @@ permission_edit_own_time_entries: 編輯自己的工時記錄 permission_manage_news: ç®¡ç†æ–°èž permission_comment_news: å›žæ‡‰æ–°èž - permission_manage_documents: ç®¡ç†æ–‡ä»¶ permission_view_documents: 檢視文件 + permission_add_documents: 新增文件 + permission_edit_documents: 編輯文件 + permission_delete_documents: 刪除文件 permission_manage_files: ç®¡ç†æª”案 permission_view_files: 檢視檔案 permission_manage_wiki: ç®¡ç† wiki @@ -650,8 +656,6 @@ label_text: 長文字 label_attribute: 屬性 label_attribute_plural: 屬性 - label_download: "%{count} 個下載" - label_download_plural: "%{count} 個下載" label_no_data: 沒有任何資料å¯ä¾›é¡¯ç¤º label_change_status: 變更狀態 label_history: æ­·å² @@ -769,8 +773,6 @@ label_repository_new: 建立新儲存機制 label_repository_plural: 儲存機制清單 label_browse: ç€è¦½ - label_modification: "%{count} 變更" - label_modification_plural: "%{count} 變更" label_branch: 分支 label_tag: 標籤 label_revision: 修訂版 @@ -965,13 +967,16 @@ label_readonly: 唯讀 label_required: å¿…å¡« label_attribute_of_project: "專案是 %{name}" + label_attribute_of_issue: "å•題是 %{name}" label_attribute_of_author: "作者是 %{name}" label_attribute_of_assigned_to: "被指派者是 %{name}" + label_attribute_of_user: "用戶是 %{name}" label_attribute_of_fixed_version: "版本是 %{name}" label_cross_project_descendants: 與å­å°ˆæ¡ˆå…±ç”¨ label_cross_project_tree: 與專案樹共用 label_cross_project_hierarchy: 與專案階層架構共用 label_cross_project_system: 與全部的專案共用 + label_gantt_progress_line: 進度線 button_login: 登入 button_submit: é€å‡º @@ -1117,6 +1122,7 @@ æ‚¨çš„å¸³æˆ¶å°‡æœƒè¢«æ°¸ä¹…åˆªé™¤ï¼Œä¸”ç„¡æ³•è¢«é‡æ–°å•Ÿç”¨ã€‚ text_session_expiration_settings: "è­¦å‘Šï¼šè®Šæ›´é€™äº›è¨­å®šå°‡æœƒå°Žè‡´åŒ…å«æ‚¨åœ¨å…§çš„æ‰€æœ‰å·¥ä½œéšŽæ®µéŽæœŸã€‚" text_project_closed: 此專案已被關閉,僅供唯讀使用。 + text_turning_multiple_off: "若您åœç”¨å¤šé‡å€¼è¨­å®šï¼Œé‡è¤‡çš„值將會被移除,以使æ¯å€‹é …目僅ä¿ç•™ä¸€å€‹å€¼ã€‚" default_role_manager: 管ç†äººå“¡ default_role_developer: 開發人員 @@ -1163,3 +1169,4 @@ description_date_from: 輸入起始日期 description_date_to: è¼¸å…¥çµæŸæ—¥æœŸ text_repository_identifier_info: '僅å…許使用å°å¯«è‹±æ–‡å­—æ¯ (a-z), 阿拉伯數字, 虛線與底線。
      一旦儲存之後, ä»£ç¢¼ä¾¿ç„¡æ³•å†æ¬¡è¢«æ›´æ”¹ã€‚' + label_total_time: 總計 diff -r 0a574315af3e -r 4f746d8966dd config/locales/zh.yml --- a/config/locales/zh.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/locales/zh.yml Fri Jun 14 09:28:30 2013 +0100 @@ -212,8 +212,6 @@ mail_subject_wiki_content_updated: "'%{id}' wiki页é¢å·²æ›´æ–°ã€‚" mail_body_wiki_content_updated: "'%{id}' wiki页é¢å·²ç”± %{author} 更新。" - gui_validation_error: 1 个错误 - gui_validation_error_plural: "%{count} 个错误" field_name: åç§° field_description: æè¿° @@ -400,7 +398,6 @@ permission_edit_own_time_entries: 编辑自己的耗时 permission_manage_news: ç®¡ç†æ–°é—» permission_comment_news: 为新闻添加评论 - permission_manage_documents: ç®¡ç†æ–‡æ¡£ permission_view_documents: 查看文档 permission_manage_files: ç®¡ç†æ–‡ä»¶ permission_view_files: 查看文件 @@ -526,8 +523,6 @@ label_text: 文本 label_attribute: 属性 label_attribute_plural: 属性 - label_download: "%{count} 次下载" - label_download_plural: "%{count} 次下载" label_no_data: 没有任何数æ®å¯ä¾›æ˜¾ç¤º label_change_status: å˜æ›´çŠ¶æ€ label_history: 历å²è®°å½• @@ -630,8 +625,6 @@ label_repository: 版本库 label_repository_plural: 版本库 label_browse: æµè§ˆ - label_modification: "%{count} 个更新" - label_modification_plural: "%{count} 个更新" label_branch: 分支 label_tag: 标签 label_revision: 修订 @@ -1086,3 +1079,16 @@ setting_non_working_week_days: Non-working days label_in_the_next_days: in the next label_in_the_past_days: in the past + label_attribute_of_user: User's %{name} + text_turning_multiple_off: If you disable multiple values, multiple values will be + removed in order to preserve only one value per item. + label_attribute_of_issue: Issue's %{name} + permission_add_documents: Add documents + permission_edit_documents: Edit documents + permission_delete_documents: Delete documents + label_gantt_progress_line: Progress line + setting_jsonp_enabled: Enable JSONP support + field_inherit_members: Inherit members + field_closed_on: Closed + setting_default_projects_tracker_ids: Default trackers for new projects + label_total_time: åˆè®¡ diff -r 0a574315af3e -r 4f746d8966dd config/routes.rb --- a/config/routes.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/config/routes.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -18,47 +18,47 @@ RedmineApp::Application.routes.draw do root :to => 'welcome#index', :as => 'home' - match 'login', :to => 'account#login', :as => 'signin' - match 'logout', :to => 'account#logout', :as => 'signout' + match 'login', :to => 'account#login', :as => 'signin', :via => [:get, :post] + match 'logout', :to => 'account#logout', :as => 'signout', :via => [:get, :post] match 'account/register', :to => 'account#register', :via => [:get, :post], :as => 'register' match 'account/lost_password', :to => 'account#lost_password', :via => [:get, :post], :as => 'lost_password' match 'account/activate', :to => 'account#activate', :via => :get - match '/news/preview', :controller => 'previews', :action => 'news', :as => 'preview_news' - match '/issues/preview/new/:project_id', :to => 'previews#issue', :as => 'preview_new_issue' - match '/issues/preview/edit/:id', :to => 'previews#issue', :as => 'preview_edit_issue' - match '/issues/preview', :to => 'previews#issue', :as => 'preview_issue' + match '/news/preview', :controller => 'previews', :action => 'news', :as => 'preview_news', :via => [:get, :post, :put] + match '/issues/preview/new/:project_id', :to => 'previews#issue', :as => 'preview_new_issue', :via => [:get, :post, :put] + match '/issues/preview/edit/:id', :to => 'previews#issue', :as => 'preview_edit_issue', :via => [:get, :post, :put] + match '/issues/preview', :to => 'previews#issue', :as => 'preview_issue', :via => [:get, :post, :put] match 'projects/:id/wiki', :to => 'wikis#edit', :via => :post match 'projects/:id/wiki/destroy', :to => 'wikis#destroy', :via => [:get, :post] - match 'boards/:board_id/topics/new', :to => 'messages#new', :via => [:get, :post] + match 'boards/:board_id/topics/new', :to => 'messages#new', :via => [:get, :post], :as => 'new_board_message' get 'boards/:board_id/topics/:id', :to => 'messages#show', :as => 'board_message' match 'boards/:board_id/topics/quote/:id', :to => 'messages#quote', :via => [:get, :post] get 'boards/:board_id/topics/:id/edit', :to => 'messages#edit' - post 'boards/:board_id/topics/preview', :to => 'messages#preview' + post 'boards/:board_id/topics/preview', :to => 'messages#preview', :as => 'preview_board_message' post 'boards/:board_id/topics/:id/replies', :to => 'messages#reply' post 'boards/:board_id/topics/:id/edit', :to => 'messages#edit' post 'boards/:board_id/topics/:id/destroy', :to => 'messages#destroy' # Misc issue routes. TODO: move into resources match '/issues/auto_complete', :to => 'auto_completes#issues', :via => :get, :as => 'auto_complete_issues' - match '/issues/context_menu', :to => 'context_menus#issues', :as => 'issues_context_menu' - match '/issues/changes', :to => 'journals#index', :as => 'issue_changes' + match '/issues/context_menu', :to => 'context_menus#issues', :as => 'issues_context_menu', :via => [:get, :post] + match '/issues/changes', :to => 'journals#index', :as => 'issue_changes', :via => :get match '/issues/:id/quoted', :to => 'journals#new', :id => /\d+/, :via => :post, :as => 'quoted_issue' match '/journals/diff/:id', :to => 'journals#diff', :id => /\d+/, :via => :get match '/journals/edit/:id', :to => 'journals#edit', :id => /\d+/, :via => [:get, :post] - match '/projects/:project_id/issues/gantt', :to => 'gantts#show' - match '/issues/gantt', :to => 'gantts#show' + get '/projects/:project_id/issues/gantt', :to => 'gantts#show', :as => 'project_gantt' + get '/issues/gantt', :to => 'gantts#show' - match '/projects/:project_id/issues/calendar', :to => 'calendars#show' - match '/issues/calendar', :to => 'calendars#show' + get '/projects/:project_id/issues/calendar', :to => 'calendars#show', :as => 'project_calendar' + get '/issues/calendar', :to => 'calendars#show' - match 'projects/:id/issues/report', :to => 'reports#issue_report', :via => :get - match 'projects/:id/issues/report/:detail', :to => 'reports#issue_report_details', :via => :get + get 'projects/:id/issues/report', :to => 'reports#issue_report', :as => 'project_issues_report' + get 'projects/:id/issues/report/:detail', :to => 'reports#issue_report_details', :as => 'project_issues_report_details' match 'my/account', :controller => 'my', :action => 'account', :via => [:get, :post] match 'my/account/destroy', :controller => 'my', :action => 'destroy', :via => [:get, :post] @@ -77,20 +77,21 @@ match 'users/:id/memberships/:membership_id', :to => 'users#destroy_membership', :via => :delete match 'users/:id/memberships', :to => 'users#edit_membership', :via => :post, :as => 'user_memberships' - match 'watchers/new', :controller=> 'watchers', :action => 'new', :via => :get - match 'watchers', :controller=> 'watchers', :action => 'create', :via => :post - match 'watchers/append', :controller=> 'watchers', :action => 'append', :via => :post - match 'watchers/destroy', :controller=> 'watchers', :action => 'destroy', :via => :post - match 'watchers/watch', :controller=> 'watchers', :action => 'watch', :via => :post - match 'watchers/unwatch', :controller=> 'watchers', :action => 'unwatch', :via => :post - match 'watchers/autocomplete_for_user', :controller=> 'watchers', :action => 'autocomplete_for_user', :via => :get - - match 'projects/:id/settings/:tab', :to => "projects#settings" + post 'watchers/watch', :to => 'watchers#watch', :as => 'watch' + delete 'watchers/watch', :to => 'watchers#unwatch' + get 'watchers/new', :to => 'watchers#new' + post 'watchers', :to => 'watchers#create' + post 'watchers/append', :to => 'watchers#append' + delete 'watchers', :to => 'watchers#destroy' + get 'watchers/autocomplete_for_user', :to => 'watchers#autocomplete_for_user' + # Specific routes for issue watchers API + post 'issues/:object_id/watchers', :to => 'watchers#create', :object_type => 'issue' + delete 'issues/:object_id/watchers/:user_id' => 'watchers#destroy', :object_type => 'issue' match 'projects/:id/overview', :to => "projects#overview" resources :projects do member do - get 'settings' + get 'settings(/:tab)', :action => 'settings', :as => 'settings' post 'modules' post 'archive' post 'unarchive' @@ -99,20 +100,22 @@ match 'copy', :via => [:get, :post] end - resources :members, :shallow => true, :controller => 'members', :only => [:index, :show, :new, :create, :update, :destroy] do - collection do + shallow do + resources :memberships, :controller => 'members', :only => [:index, :show, :new, :create, :update, :destroy] do get 'autocomplete' end end - resources :memberships, :shallow => true, :controller => 'members', :only => [:index, :show, :new, :create, :update, :destroy] do - collection do - get 'autocomplete' + shallow do + resources :members, :shallow => true, :controller => 'members', :only => [:index, :show, :new, :create, :update, :destroy] do + collection do + get 'autocomplete' + end end end resource :enumerations, :controller => 'project_enumerations', :only => [:update, :destroy] - match 'issues/:copy_from/copy', :to => 'issues#new' + get 'issues/:copy_from/copy', :to => 'issues#new', :as => 'copy_issue' resources :issues, :only => [:index, :new, :create] do resources :time_entries, :controller => 'timelog' do collection do @@ -121,7 +124,7 @@ end end # issue form update - match 'issues/new', :controller => 'issues', :action => 'new', :via => [:put, :post], :as => 'issue_form' + match 'issues/update_form', :controller => 'issues', :action => 'update_form', :via => [:put, :post], :as => 'issue_form' resources :files, :only => [:index, :new, :create] @@ -130,26 +133,30 @@ put 'close_completed' end end - match 'versions.:format', :to => 'versions#index' - match 'roadmap', :to => 'versions#index', :format => false - match 'versions', :to => 'versions#index' + get 'versions.:format', :to => 'versions#index' + get 'roadmap', :to => 'versions#index', :format => false + get 'versions', :to => 'versions#index' resources :news, :except => [:show, :edit, :update, :destroy] resources :time_entries, :controller => 'timelog' do get 'report', :on => :collection end resources :queries, :only => [:new, :create] - resources :issue_categories, :shallow => true + shallow do + resources :issue_categories + end resources :documents, :except => [:show, :edit, :update, :destroy] resources :boards - resources :repositories, :shallow => true, :except => [:index, :show] do - member do - match 'committers', :via => [:get, :post] + shallow do + resources :repositories, :except => [:index, :show] do + member do + match 'committers', :via => [:get, :post] + end end end - + match 'wiki/index', :controller => 'wiki', :action => 'index', :via => :get - resources :wiki, :except => [:index, :new, :create] do + resources :wiki, :except => [:index, :new, :create], :as => 'wiki_page' do member do get 'rename' post 'rename' @@ -165,7 +172,7 @@ end end match 'wiki', :controller => 'wiki', :action => 'show', :via => :get - get 'wiki/:id/:version', :to => 'wiki#show' + get 'wiki/:id/:version', :to => 'wiki#show', :constraints => {:version => /\d+/} delete 'wiki/:id/:version', :to => 'wiki#destroy_version' get 'wiki/:id/:version/annotate', :to => 'wiki#annotate' get 'wiki/:id/:version/diff', :to => 'wiki#diff' @@ -181,7 +188,9 @@ get 'report' end end - resources :relations, :shallow => true, :controller => 'issue_relations', :only => [:index, :show, :create, :destroy] + shallow do + resources :relations, :controller => 'issue_relations', :only => [:index, :show, :create, :destroy] + end end match '/issues', :controller => 'issues', :action => 'destroy', :via => :delete @@ -202,7 +211,7 @@ post 'add_attachment', :on => :member end - match '/time_entries/context_menu', :to => 'context_menus#time_entries', :as => :time_entries_context_menu + match '/time_entries/context_menu', :to => 'context_menus#time_entries', :as => :time_entries_context_menu, :via => [:get, :post] resources :time_entries, :controller => 'timelog', :except => :destroy do collection do @@ -215,9 +224,6 @@ # TODO: delete /time_entries for bulk deletion match '/time_entries/destroy', :to => 'timelog#destroy', :via => :delete - # TODO: port to be part of the resources route(s) - match 'projects/:id/settings/:tab', :to => 'projects#settings', :via => :get - get 'projects/:id/activity', :to => 'activities#index' get 'projects/:id/activity.:format', :to => 'activities#index' get 'activity', :to => 'activities#index' @@ -271,11 +277,11 @@ get 'projects/:id/repository', :to => 'repositories#show', :path => nil # additional routes for having the file name at the end of url - match 'attachments/:id/:filename', :controller => 'attachments', :action => 'show', :id => /\d+/, :filename => /.*/, :via => :get - match 'attachments/download/:id/:filename', :controller => 'attachments', :action => 'download', :id => /\d+/, :filename => /.*/, :via => :get - match 'attachments/download/:id', :controller => 'attachments', :action => 'download', :id => /\d+/, :via => :get + get 'attachments/:id/:filename', :to => 'attachments#show', :id => /\d+/, :filename => /.*/, :as => 'named_attachment' + get 'attachments/download/:id/:filename', :to => 'attachments#download', :id => /\d+/, :filename => /.*/, :as => 'download_named_attachment' + get 'attachments/download/:id', :to => 'attachments#download', :id => /\d+/ + get 'attachments/thumbnail/:id(/:size)', :to => 'attachments#thumbnail', :id => /\d+/, :size => /\d+/, :as => 'thumbnail' match 'attachments/toggle_active/:id', :controller => 'attachments', :action => 'toggle_active', :id => /\d+/, :via => :get - match 'attachments/thumbnail/:id(/:size)', :controller => 'attachments', :action => 'thumbnail', :id => /\d+/, :via => :get, :size => /\d+/ resources :attachments, :only => [:show, :destroy] resources :groups do @@ -322,7 +328,10 @@ resources :auth_sources do member do - get 'test_connection' + get 'test_connection', :as => 'try_connection' + end + collection do + get 'autocomplete_for_new_user' end end @@ -332,7 +341,7 @@ match 'workflows/copy', :controller => 'workflows', :action => 'copy', :via => [:get, :post] match 'settings', :controller => 'settings', :action => 'index', :via => :get match 'settings/edit', :controller => 'settings', :action => 'edit', :via => [:get, :post] - match 'settings/plugin/:id', :controller => 'settings', :action => 'plugin', :via => [:get, :post] + match 'settings/plugin/:id', :controller => 'settings', :action => 'plugin', :via => [:get, :post], :as => 'plugin_settings' match 'sys/projects', :to => 'sys#projects', :via => :get match 'sys/projects/:id/repository', :to => 'sys#create_project_repository', :via => :post diff -r 0a574315af3e -r 4f746d8966dd config/settings.yml --- a/config/settings.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/config/settings.yml Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -183,6 +183,9 @@ - boards - calendar - gantt +default_projects_tracker_ids: + serialized: true + default: # Role given to a non-admin user who creates a project new_project_user_role_id: format: int @@ -215,6 +218,8 @@ default: '' rest_api_enabled: default: 0 +jsonp_enabled: + default: 0 default_notification_option: default: 'only_my_events' emails_header: diff -r 0a574315af3e -r 4f746d8966dd db/migrate/001_setup.rb --- a/db/migrate/001_setup.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/db/migrate/001_setup.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,4 +1,4 @@ -# redMine - project management software +# Redmine - project management software # Copyright (C) 2006 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or @@ -292,7 +292,6 @@ :lastname => "Admin", :mail => "admin@example.net", :mail_notification => true, - :language => "en", :status => 1 end diff -r 0a574315af3e -r 4f746d8966dd db/migrate/002_issue_move.rb --- a/db/migrate/002_issue_move.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/db/migrate/002_issue_move.rb Fri Jun 14 09:28:30 2013 +0100 @@ -7,6 +7,6 @@ end def self.down - Permission.find(:first, :conditions => ["controller=? and action=?", 'projects', 'move_issues']).destroy + Permission.where("controller=? and action=?", 'projects', 'move_issues').first.destroy end end diff -r 0a574315af3e -r 4f746d8966dd db/migrate/003_issue_add_note.rb --- a/db/migrate/003_issue_add_note.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/db/migrate/003_issue_add_note.rb Fri Jun 14 09:28:30 2013 +0100 @@ -7,6 +7,6 @@ end def self.down - Permission.find(:first, :conditions => ["controller=? and action=?", 'issues', 'add_note']).destroy + Permission.where("controller=? and action=?", 'issues', 'add_note').first.destroy end end diff -r 0a574315af3e -r 4f746d8966dd db/migrate/004_export_pdf.rb --- a/db/migrate/004_export_pdf.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/db/migrate/004_export_pdf.rb Fri Jun 14 09:28:30 2013 +0100 @@ -8,7 +8,7 @@ end def self.down - Permission.find(:first, :conditions => ["controller=? and action=?", 'projects', 'export_issues_pdf']).destroy - Permission.find(:first, :conditions => ["controller=? and action=?", 'issues', 'export_pdf']).destroy + Permission.where("controller=? and action=?", 'projects', 'export_issues_pdf').first.destroy + Permission.where("controller=? and action=?", 'issues', 'export_pdf').first.destroy end end diff -r 0a574315af3e -r 4f746d8966dd db/migrate/006_calendar_and_activity.rb --- a/db/migrate/006_calendar_and_activity.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/db/migrate/006_calendar_and_activity.rb Fri Jun 14 09:28:30 2013 +0100 @@ -9,8 +9,8 @@ end def self.down - Permission.find(:first, :conditions => ["controller=? and action=?", 'projects', 'activity']).destroy - Permission.find(:first, :conditions => ["controller=? and action=?", 'projects', 'calendar']).destroy - Permission.find(:first, :conditions => ["controller=? and action=?", 'projects', 'gantt']).destroy + Permission.where("controller=? and action=?", 'projects', 'activity').first.destroy + Permission.where("controller=? and action=?", 'projects', 'calendar').first.destroy + Permission.where("controller=? and action=?", 'projects', 'gantt').first.destroy end end diff -r 0a574315af3e -r 4f746d8966dd db/migrate/007_create_journals.rb --- a/db/migrate/007_create_journals.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/db/migrate/007_create_journals.rb Fri Jun 14 09:28:30 2013 +0100 @@ -28,7 +28,7 @@ Permission.create :controller => "issues", :action => "history", :description => "label_history", :sort => 1006, :is_public => true, :mail_option => 0, :mail_enabled => 0 # data migration - IssueHistory.find(:all, :include => :issue).each {|h| + IssueHistory.all.each {|h| j = Journal.new(:journalized => h.issue, :user_id => h.author_id, :notes => h.notes, :created_on => h.created_on) j.details << JournalDetail.new(:property => 'attr', :prop_key => 'status_id', :value => h.status_id) j.save @@ -51,6 +51,6 @@ add_index "issue_histories", ["issue_id"], :name => "issue_histories_issue_id" - Permission.find(:first, :conditions => ["controller=? and action=?", 'issues', 'history']).destroy + Permission.where("controller=? and action=?", 'issues', 'history').first.destroy end end diff -r 0a574315af3e -r 4f746d8966dd db/migrate/012_add_comments_permissions.rb --- a/db/migrate/012_add_comments_permissions.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/db/migrate/012_add_comments_permissions.rb Fri Jun 14 09:28:30 2013 +0100 @@ -8,7 +8,7 @@ end def self.down - Permission.find(:first, :conditions => ["controller=? and action=?", 'news', 'add_comment']).destroy - Permission.find(:first, :conditions => ["controller=? and action=?", 'news', 'destroy_comment']).destroy + Permission.where("controller=? and action=?", 'news', 'add_comment').first.destroy + Permission.where("controller=? and action=?", 'news', 'destroy_comment').first.destroy end end diff -r 0a574315af3e -r 4f746d8966dd db/migrate/014_add_queries_permissions.rb --- a/db/migrate/014_add_queries_permissions.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/db/migrate/014_add_queries_permissions.rb Fri Jun 14 09:28:30 2013 +0100 @@ -7,6 +7,6 @@ end def self.down - Permission.find(:first, :conditions => ["controller=? and action=?", 'projects', 'add_query']).destroy + Permission.where("controller=? and action=?", 'projects', 'add_query').first.destroy end end diff -r 0a574315af3e -r 4f746d8966dd db/migrate/016_add_repositories_permissions.rb --- a/db/migrate/016_add_repositories_permissions.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/db/migrate/016_add_repositories_permissions.rb Fri Jun 14 09:28:30 2013 +0100 @@ -12,11 +12,11 @@ end def self.down - Permission.find(:first, :conditions => ["controller=? and action=?", 'repositories', 'show']).destroy - Permission.find(:first, :conditions => ["controller=? and action=?", 'repositories', 'browse']).destroy - Permission.find(:first, :conditions => ["controller=? and action=?", 'repositories', 'entry']).destroy - Permission.find(:first, :conditions => ["controller=? and action=?", 'repositories', 'revisions']).destroy - Permission.find(:first, :conditions => ["controller=? and action=?", 'repositories', 'revision']).destroy - Permission.find(:first, :conditions => ["controller=? and action=?", 'repositories', 'diff']).destroy + Permission.where("controller=? and action=?", 'repositories', 'show').first.destroy + Permission.where("controller=? and action=?", 'repositories', 'browse').first.destroy + Permission.where("controller=? and action=?", 'repositories', 'entry').first.destroy + Permission.where("controller=? and action=?", 'repositories', 'revisions').first.destroy + Permission.where("controller=? and action=?", 'repositories', 'revision').first.destroy + Permission.where("controller=? and action=?", 'repositories', 'diff').first.destroy end end diff -r 0a574315af3e -r 4f746d8966dd db/migrate/019_add_issue_status_position.rb --- a/db/migrate/019_add_issue_status_position.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/db/migrate/019_add_issue_status_position.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ class AddIssueStatusPosition < ActiveRecord::Migration def self.up add_column :issue_statuses, :position, :integer, :default => 1 - IssueStatus.find(:all).each_with_index {|status, i| status.update_attribute(:position, i+1)} + IssueStatus.all.each_with_index {|status, i| status.update_attribute(:position, i+1)} end def self.down diff -r 0a574315af3e -r 4f746d8966dd db/migrate/021_add_tracker_position.rb --- a/db/migrate/021_add_tracker_position.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/db/migrate/021_add_tracker_position.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ class AddTrackerPosition < ActiveRecord::Migration def self.up add_column :trackers, :position, :integer, :default => 1 - Tracker.find(:all).each_with_index {|tracker, i| tracker.update_attribute(:position, i+1)} + Tracker.all.each_with_index {|tracker, i| tracker.update_attribute(:position, i+1)} end def self.down diff -r 0a574315af3e -r 4f746d8966dd db/migrate/022_serialize_possibles_values.rb --- a/db/migrate/022_serialize_possibles_values.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/db/migrate/022_serialize_possibles_values.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,6 +1,6 @@ class SerializePossiblesValues < ActiveRecord::Migration def self.up - CustomField.find(:all).each do |field| + CustomField.all.each do |field| if field.possible_values and field.possible_values.is_a? String field.possible_values = field.possible_values.split('|') field.save diff -r 0a574315af3e -r 4f746d8966dd db/migrate/024_add_roadmap_permission.rb --- a/db/migrate/024_add_roadmap_permission.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/db/migrate/024_add_roadmap_permission.rb Fri Jun 14 09:28:30 2013 +0100 @@ -7,6 +7,6 @@ end def self.down - Permission.find(:first, :conditions => ["controller=? and action=?", 'projects', 'roadmap']).destroy + Permission.where("controller=? and action=?", 'projects', 'roadmap').first.destroy end end diff -r 0a574315af3e -r 4f746d8966dd db/migrate/065_add_settings_updated_on.rb --- a/db/migrate/065_add_settings_updated_on.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/db/migrate/065_add_settings_updated_on.rb Fri Jun 14 09:28:30 2013 +0100 @@ -2,7 +2,7 @@ def self.up add_column :settings, :updated_on, :timestamp # set updated_on - Setting.find(:all).each(&:save) + Setting.all.each(&:save) end def self.down diff -r 0a574315af3e -r 4f746d8966dd db/migrate/068_create_enabled_modules.rb --- a/db/migrate/068_create_enabled_modules.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/db/migrate/068_create_enabled_modules.rb Fri Jun 14 09:28:30 2013 +0100 @@ -7,7 +7,7 @@ add_index :enabled_modules, [:project_id], :name => :enabled_modules_project_id # Enable all modules for existing projects - Project.find(:all).each do |project| + Project.all.each do |project| project.enabled_module_names = Redmine::AccessControl.available_project_modules end end diff -r 0a574315af3e -r 4f746d8966dd db/migrate/072_add_enumerations_position.rb --- a/db/migrate/072_add_enumerations_position.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/db/migrate/072_add_enumerations_position.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ class AddEnumerationsPosition < ActiveRecord::Migration def self.up add_column(:enumerations, :position, :integer, :default => 1) unless Enumeration.column_names.include?('position') - Enumeration.find(:all).group_by(&:opt).each do |opt, enums| + Enumeration.all.group_by(&:opt).each do |opt, enums| enums.each_with_index do |enum, i| # do not call model callbacks Enumeration.update_all "position = #{i+1}", {:id => enum.id} diff -r 0a574315af3e -r 4f746d8966dd db/migrate/078_add_custom_fields_position.rb --- a/db/migrate/078_add_custom_fields_position.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/db/migrate/078_add_custom_fields_position.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ class AddCustomFieldsPosition < ActiveRecord::Migration def self.up add_column(:custom_fields, :position, :integer, :default => 1) - CustomField.find(:all).group_by(&:type).each do |t, fields| + CustomField.all.group_by(&:type).each do |t, fields| fields.each_with_index do |field, i| # do not call model callbacks CustomField.update_all "position = #{i+1}", {:id => field.id} diff -r 0a574315af3e -r 4f746d8966dd db/migrate/081_create_projects_trackers.rb --- a/db/migrate/081_create_projects_trackers.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/db/migrate/081_create_projects_trackers.rb Fri Jun 14 09:28:30 2013 +0100 @@ -7,8 +7,8 @@ add_index :projects_trackers, :project_id, :name => :projects_trackers_project_id # Associates all trackers to all projects (as it was before) - tracker_ids = Tracker.find(:all).collect(&:id) - Project.find(:all).each do |project| + tracker_ids = Tracker.all.collect(&:id) + Project.all.each do |project| project.tracker_ids = tracker_ids end end diff -r 0a574315af3e -r 4f746d8966dd db/migrate/091_change_changesets_revision_to_string.rb --- a/db/migrate/091_change_changesets_revision_to_string.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/db/migrate/091_change_changesets_revision_to_string.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,9 +1,32 @@ class ChangeChangesetsRevisionToString < ActiveRecord::Migration def self.up + # Some backends (eg. SQLServer 2012) do not support changing the type + # of an indexed column so the index needs to be dropped first + # BUT this index is renamed with some backends (at least SQLite3) for + # some (unknown) reasons, thus we check for the other name as well + # so we don't end up with 2 identical indexes + if index_exists? :changesets, [:repository_id, :revision], :name => :changesets_repos_rev + remove_index :changesets, :name => :changesets_repos_rev + end + if index_exists? :changesets, [:repository_id, :revision], :name => :altered_changesets_repos_rev + remove_index :changesets, :name => :altered_changesets_repos_rev + end + change_column :changesets, :revision, :string, :null => false + + add_index :changesets, [:repository_id, :revision], :unique => true, :name => :changesets_repos_rev end def self.down + if index_exists? :changesets, :changesets_repos_rev + remove_index :changesets, :name => :changesets_repos_rev + end + if index_exists? :changesets, [:repository_id, :revision], :name => :altered_changesets_repos_rev + remove_index :changesets, :name => :altered_changesets_repos_rev + end + change_column :changesets, :revision, :integer, :null => false + + add_index :changesets, [:repository_id, :revision], :unique => true, :name => :changesets_repos_rev end end diff -r 0a574315af3e -r 4f746d8966dd db/migrate/096_add_commit_access_permission.rb --- a/db/migrate/096_add_commit_access_permission.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/db/migrate/096_add_commit_access_permission.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,12 +1,12 @@ class AddCommitAccessPermission < ActiveRecord::Migration def self.up - Role.find(:all).select { |r| not r.builtin? }.each do |r| + Role.all.select { |r| not r.builtin? }.each do |r| r.add_permission!(:commit_access) end end def self.down - Role.find(:all).select { |r| not r.builtin? }.each do |r| + Role.all.select { |r| not r.builtin? }.each do |r| r.remove_permission!(:commit_access) end end diff -r 0a574315af3e -r 4f746d8966dd db/migrate/097_add_view_wiki_edits_permission.rb --- a/db/migrate/097_add_view_wiki_edits_permission.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/db/migrate/097_add_view_wiki_edits_permission.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,12 +1,12 @@ class AddViewWikiEditsPermission < ActiveRecord::Migration def self.up - Role.find(:all).each do |r| + Role.all.each do |r| r.add_permission!(:view_wiki_edits) if r.has_permission?(:view_wiki_pages) end end def self.down - Role.find(:all).each do |r| + Role.all.each do |r| r.remove_permission!(:view_wiki_edits) end end diff -r 0a574315af3e -r 4f746d8966dd db/migrate/099_add_delete_wiki_pages_attachments_permission.rb --- a/db/migrate/099_add_delete_wiki_pages_attachments_permission.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/db/migrate/099_add_delete_wiki_pages_attachments_permission.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,12 +1,12 @@ class AddDeleteWikiPagesAttachmentsPermission < ActiveRecord::Migration def self.up - Role.find(:all).each do |r| + Role.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| + Role.all.each do |r| r.remove_permission!(:delete_wiki_pages_attachments) end end diff -r 0a574315af3e -r 4f746d8966dd db/migrate/20090312194159_add_projects_trackers_unique_index.rb --- a/db/migrate/20090312194159_add_projects_trackers_unique_index.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/db/migrate/20090312194159_add_projects_trackers_unique_index.rb Fri Jun 14 09:28:30 2013 +0100 @@ -10,7 +10,7 @@ # Removes duplicates in projects_trackers table def self.remove_duplicates - Project.find(:all).each do |project| + Project.all.each do |project| ids = project.trackers.collect(&:id) unless ids == ids.uniq project.trackers.clear diff -r 0a574315af3e -r 4f746d8966dd db/migrate/20090503121505_populate_member_roles.rb --- a/db/migrate/20090503121505_populate_member_roles.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/db/migrate/20090503121505_populate_member_roles.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ class PopulateMemberRoles < ActiveRecord::Migration def self.up MemberRole.delete_all - Member.find(:all).each do |member| + Member.all.each do |member| MemberRole.create!(:member_id => member.id, :role_id => member.role_id) end end diff -r 0a574315af3e -r 4f746d8966dd db/migrate/20091114105931_add_view_issues_permission.rb --- a/db/migrate/20091114105931_add_view_issues_permission.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/db/migrate/20091114105931_add_view_issues_permission.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,12 +1,12 @@ class AddViewIssuesPermission < ActiveRecord::Migration def self.up - Role.find(:all).each do |r| + Role.all.each do |r| r.add_permission!(:view_issues) end end def self.down - Role.find(:all).each do |r| + Role.all.each do |r| r.remove_permission!(:view_issues) end end diff -r 0a574315af3e -r 4f746d8966dd db/migrate/20100819172912_enable_calendar_and_gantt_modules_where_appropriate.rb --- a/db/migrate/20100819172912_enable_calendar_and_gantt_modules_where_appropriate.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/db/migrate/20100819172912_enable_calendar_and_gantt_modules_where_appropriate.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,6 +1,6 @@ class EnableCalendarAndGanttModulesWhereAppropriate < ActiveRecord::Migration def self.up - EnabledModule.find(:all, :conditions => ["name = ?", 'issue_tracking']).each do |e| + EnabledModule.where(:name => 'issue_tracking').all.each do |e| EnabledModule.create(:name => 'calendar', :project_id => e.project_id) EnabledModule.create(:name => 'gantt', :project_id => e.project_id) end diff -r 0a574315af3e -r 4f746d8966dd db/migrate/20121209123234_add_queries_type.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20121209123234_add_queries_type.rb Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,9 @@ +class AddQueriesType < ActiveRecord::Migration + def up + add_column :queries, :type, :string + end + + def down + remove_column :queries, :type + end +end diff -r 0a574315af3e -r 4f746d8966dd db/migrate/20121209123358_update_queries_to_sti.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20121209123358_update_queries_to_sti.rb Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,9 @@ +class UpdateQueriesToSti < ActiveRecord::Migration + def up + ::Query.update_all :type => 'IssueQuery' + end + + def down + ::Query.update_all :type => nil + end +end diff -r 0a574315af3e -r 4f746d8966dd db/migrate/20121213084931_add_attachments_disk_directory.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20121213084931_add_attachments_disk_directory.rb Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,9 @@ +class AddAttachmentsDiskDirectory < ActiveRecord::Migration + def up + add_column :attachments, :disk_directory, :string + end + + def down + remove_column :attachments, :disk_directory + end +end diff -r 0a574315af3e -r 4f746d8966dd db/migrate/20130110122628_split_documents_permissions.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20130110122628_split_documents_permissions.rb Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,23 @@ +class SplitDocumentsPermissions < ActiveRecord::Migration + def up + # :manage_documents permission split into 3 permissions: + # :add_documents, :edit_documents and :delete_documents + Role.all.each do |role| + if role.has_permission?(:manage_documents) + role.add_permission! :add_documents, :edit_documents, :delete_documents + role.remove_permission! :manage_documents + end + end + end + + def down + Role.all.each do |role| + if role.has_permission?(:add_documents) || + role.has_permission?(:edit_documents) || + role.has_permission?(:delete_documents) + role.remove_permission! :add_documents, :edit_documents, :delete_documents + role.add_permission! :manage_documents + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd db/migrate/20130201184705_add_unique_index_on_tokens_value.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20130201184705_add_unique_index_on_tokens_value.rb Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,15 @@ +class AddUniqueIndexOnTokensValue < ActiveRecord::Migration + def up + say_with_time "Adding unique index on tokens, this may take some time..." do + # Just in case + duplicates = Token.connection.select_values("SELECT value FROM #{Token.table_name} GROUP BY value HAVING COUNT(id) > 1") + Token.where(:value => duplicates).delete_all + + add_index :tokens, :value, :unique => true, :name => 'tokens_value' + end + end + + def down + remove_index :tokens, :name => 'tokens_value' + end +end diff -r 0a574315af3e -r 4f746d8966dd db/migrate/20130202090625_add_projects_inherit_members.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20130202090625_add_projects_inherit_members.rb Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,9 @@ +class AddProjectsInheritMembers < ActiveRecord::Migration + def up + add_column :projects, :inherit_members, :boolean, :default => false, :null => false + end + + def down + remove_column :projects, :inherit_members + end +end diff -r 0a574315af3e -r 4f746d8966dd db/migrate/20130207175206_add_unique_index_on_custom_fields_trackers.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20130207175206_add_unique_index_on_custom_fields_trackers.rb Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,24 @@ +class AddUniqueIndexOnCustomFieldsTrackers < ActiveRecord::Migration + def up + table_name = "#{CustomField.table_name_prefix}custom_fields_trackers#{CustomField.table_name_suffix}" + duplicates = CustomField.connection.select_rows("SELECT custom_field_id, tracker_id FROM #{table_name} GROUP BY custom_field_id, tracker_id HAVING COUNT(*) > 1") + duplicates.each do |custom_field_id, tracker_id| + # Removes duplicate rows + CustomField.connection.execute("DELETE FROM #{table_name} WHERE custom_field_id=#{custom_field_id} AND tracker_id=#{tracker_id}") + # And insert one + CustomField.connection.execute("INSERT INTO #{table_name} (custom_field_id, tracker_id) VALUES (#{custom_field_id}, #{tracker_id})") + end + + if index_exists? :custom_fields_trackers, [:custom_field_id, :tracker_id] + remove_index :custom_fields_trackers, [:custom_field_id, :tracker_id] + end + add_index :custom_fields_trackers, [:custom_field_id, :tracker_id], :unique => true + end + + def down + if index_exists? :custom_fields_trackers, [:custom_field_id, :tracker_id] + remove_index :custom_fields_trackers, [:custom_field_id, :tracker_id] + end + add_index :custom_fields_trackers, [:custom_field_id, :tracker_id] + end +end diff -r 0a574315af3e -r 4f746d8966dd db/migrate/20130207181455_add_unique_index_on_custom_fields_projects.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20130207181455_add_unique_index_on_custom_fields_projects.rb Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,24 @@ +class AddUniqueIndexOnCustomFieldsProjects < ActiveRecord::Migration + def up + table_name = "#{CustomField.table_name_prefix}custom_fields_projects#{CustomField.table_name_suffix}" + duplicates = CustomField.connection.select_rows("SELECT custom_field_id, project_id FROM #{table_name} GROUP BY custom_field_id, project_id HAVING COUNT(*) > 1") + duplicates.each do |custom_field_id, project_id| + # Removes duplicate rows + CustomField.connection.execute("DELETE FROM #{table_name} WHERE custom_field_id=#{custom_field_id} AND project_id=#{project_id}") + # And insert one + CustomField.connection.execute("INSERT INTO #{table_name} (custom_field_id, project_id) VALUES (#{custom_field_id}, #{project_id})") + end + + if index_exists? :custom_fields_projects, [:custom_field_id, :project_id] + remove_index :custom_fields_projects, [:custom_field_id, :project_id] + end + add_index :custom_fields_projects, [:custom_field_id, :project_id], :unique => true + end + + def down + if index_exists? :custom_fields_projects, [:custom_field_id, :project_id] + remove_index :custom_fields_projects, [:custom_field_id, :project_id] + end + add_index :custom_fields_projects, [:custom_field_id, :project_id] + end +end diff -r 0a574315af3e -r 4f746d8966dd db/migrate/20130215073721_change_users_lastname_length_to_255.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20130215073721_change_users_lastname_length_to_255.rb Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,9 @@ +class ChangeUsersLastnameLengthTo255 < ActiveRecord::Migration + def self.up + change_column :users, :lastname, :string, :limit => 255, :default => '', :null => false + end + + def self.down + change_column :users, :lastname, :string, :limit => 30, :default => '', :null => false + end +end diff -r 0a574315af3e -r 4f746d8966dd db/migrate/20130215111127_add_issues_closed_on.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20130215111127_add_issues_closed_on.rb Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,9 @@ +class AddIssuesClosedOn < ActiveRecord::Migration + def up + add_column :issues, :closed_on, :datetime, :default => nil + end + + def down + remove_column :issues, :closed_on + end +end diff -r 0a574315af3e -r 4f746d8966dd db/migrate/20130215111141_populate_issues_closed_on.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20130215111141_populate_issues_closed_on.rb Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,25 @@ +class PopulateIssuesClosedOn < ActiveRecord::Migration + def up + closed_status_ids = IssueStatus.where(:is_closed => true).pluck(:id) + if closed_status_ids.any? + # First set closed_on for issues that have been closed once + closed_status_values = closed_status_ids.map {|status_id| "'#{status_id}'"}.join(',') + subselect = "SELECT MAX(#{Journal.table_name}.created_on)" + + " FROM #{Journal.table_name}, #{JournalDetail.table_name}" + + " WHERE #{Journal.table_name}.id = #{JournalDetail.table_name}.journal_id" + + " AND #{Journal.table_name}.journalized_type = 'Issue' AND #{Journal.table_name}.journalized_id = #{Issue.table_name}.id" + + " AND #{JournalDetail.table_name}.property = 'attr' AND #{JournalDetail.table_name}.prop_key = 'status_id'" + + " AND #{JournalDetail.table_name}.old_value NOT IN (#{closed_status_values})" + + " AND #{JournalDetail.table_name}.value IN (#{closed_status_values})" + Issue.update_all "closed_on = (#{subselect})" + + # Then set closed_on for closed issues that weren't up updated by the above UPDATE + # No journal was found so we assume that they were closed on creation + Issue.update_all "closed_on = created_on", {:status_id => closed_status_ids, :closed_on => nil} + end + end + + def down + Issue.update_all :closed_on => nil + end +end diff -r 0a574315af3e -r 4f746d8966dd db/migrate/20130217094251_remove_issues_default_fk_values.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/migrate/20130217094251_remove_issues_default_fk_values.rb Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,19 @@ +class RemoveIssuesDefaultFkValues < ActiveRecord::Migration + def up + change_column_default :issues, :tracker_id, nil + change_column_default :issues, :project_id, nil + change_column_default :issues, :status_id, nil + change_column_default :issues, :assigned_to_id, nil + change_column_default :issues, :priority_id, nil + change_column_default :issues, :author_id, nil + end + + def down + change_column_default :issues, :tracker_id, 0 + change_column_default :issues, :project_id, 0 + change_column_default :issues, :status_id, 0 + change_column_default :issues, :assigned_to_id, 0 + change_column_default :issues, :priority_id, 0 + change_column_default :issues, :author_id, 0 + end +end diff -r 0a574315af3e -r 4f746d8966dd doc/CHANGELOG --- a/doc/CHANGELOG Fri Jun 14 09:07:32 2013 +0100 +++ b/doc/CHANGELOG Fri Jun 14 09:28:30 2013 +0100 @@ -1,9 +1,143 @@ == Redmine changelog Redmine - project management software -Copyright (C) 2006-2012 Jean-Philippe Lang +Copyright (C) 2006-2013 Jean-Philippe Lang http://www.redmine.org/ +== 2013-05-01 v2.3.1 + +* Defect #12650: Lost text after selection in issue list with IE +* Defect #12684: Hotkey for Issue-Edit doesn't work as expected +* Defect #13405: Commit link title is escaped twice when using "commit:" prefix +* Defect #13541: Can't access SCM when log/production.scm.stderr.log is not writable +* Defect #13579: Datepicker uses Simplified Chinese in Traditional Chinese locale +* Defect #13584: Missing Portuguese jQuery UI date picker +* Defect #13586: Circular loop testing prevents precedes/follows relation between subtasks +* Defect #13618: CSV export of spent time ignores filters and columns selection +* Defect #13630: PDF export generates the issue id twice +* Defect #13644: Diff - Internal Error +* Defect #13712: Fix email rake tasks to also support no_account_notice and default_group options +* Defect #13811: Broken javascript in IE7 ; recurrence of #12195 +* Defect #13823: Trailing comma in javascript files +* Patch #13531: Traditional Chinese translation for 2.3-stable +* Patch #13552: Dutch translations for 2.3-stable +* Patch #13678: Lithuanian translation for 2.3-stable + +== 2013-03-19 v2.3.0 + +* Defect #3107: Issue with two digit year on Logtime +* Defect #3371: Autologin does not work when using openid +* Defect #3676: www. generates broken link in formatted text +* Defect #4700: Adding news does not send notification to all project members +* Defect #5329: Time entries report broken on first week of year +* Defect #8794: Circular loop when using relations and subtasks +* Defect #9475: German Translation "My custom queries" and "Custom queries" +* Defect #9549: Only 100 users are displayed when adding new project members +* Defect #10277: Redmine wikitext URL-into-link creation with hyphen is wrong +* Defect #10364: Custom field float separator in CSV export +* Defect #10930: rake redmine:load_default_data error in 2.0 with SQLServer +* Defect #10977: Redmine shouldn't require all database gems +* Defect #12528: Handle temporary failures gracefully in the external mail handler script +* Defect #12629: Wrong German "label_issues_by" translation +* Defect #12641: Diff outputs become ??? in some non ASCII words. +* Defect #12707: Typo in app/models/tracker.rb +* Defect #12716: Attachment description lost when issue validation fails +* Defect #12735: Negative duration allowed +* Defect #12736: Negative start/due dates allowed +* Defect #12968: Subtasks don't resepect following/precedes +* Defect #13006: Filter "Assignee's group" doesn't work with group assignments +* Defect #13022: Image pointing towards /logout signs out user +* Defect #13059: Custom fields are listed two times in workflow/Fields permission +* Defect #13076: Project overview page shows trackers from subprojects with disabled issue module +* Defect #13119: custom_field_values are not reloaded on #reload +* Defect #13154: After upgrade to 2.2.2 ticket list on some projects fails +* Defect #13188: Forms are not updated after changing the status field without "Add issues" permission +* Defect #13251: Adding a "follows" relation may not refresh relations list +* Defect #13272: translation missing: setting_default_projects_tracker_ids +* Defect #13328: Copying an issue as a child of itself creates an extra issue +* Defect #13335: Autologin does not work with custom autologin cookie name +* Defect #13350: Japanese mistranslation fix +* Feature #824: Add "closed_on" issue field (storing time of last closing) & add it as a column and filter on the issue list. +* Feature #1766: Custom fields should become addable to Spent Time list/report +* Feature #3436: Show relations in Gantt diagram +* Feature #3957: Ajax file upload with progress bar +* Feature #5298: Store attachments in sub directories +* Feature #5605: Subprojects should (optionally) inherit Members from their parent +* Feature #6727: Add/remove issue watchers via REST API +* Feature #7159: Bulk watch/unwatch issues from the context menu +* Feature #8529: Get the API key of the user through REST API +* Feature #8579: Multiple file upload with HTML5 / Drag-and-Drop +* Feature #10191: Add Filters For Spent time's Details and Report +* Feature #10286: Auto-populate fields while creating a new user with LDAP +* Feature #10352: Preview should already display the freshly attached images +* Feature #11498: Add --no-account-notice option for the mail handler script +* Feature #12122: Gantt progress lines (html only) +* Feature #12228: JRuby 1.7.2 support +* Feature #12251: Custom fields: 'Multiple values' should be able to be checked and then unchecked +* Feature #12401: Split "Manage documents" permission into create, edit and delete permissions +* Feature #12542: Group events in the activity view +* Feature #12665: Link to a file in a repository branch +* Feature #12713: Microsoft SQLServer support +* Feature #12787: Remove "Warning - iconv will be deprecated in the future, use String#encode instead." +* Feature #12843: Add links to projects in Group projects list +* Feature #12898: Handle GET /issues/context_menu parameters nicely to prevent returning error 500 to crawlers +* Feature #12992: Make JSONP support optional and disabled by default +* Feature #13174: Raise group name maximum length to 255 characters +* Feature #13175: Possibility to define the default enable trackers when creating a project +* Feature #13329: Ruby 2.0 support +* Feature #13337: Split translation "label_total" +* Feature #13340: Mail handler: option to add created user to default group +* Feature #13341: Mail handler: --no-notification option to disable notifications to the created user +* Patch #7202: Polish translation for v1.0.4 +* Patch #7851: Italian translation for 'issue' +* Patch #9225: Generate project identifier automatically with JavaScript +* Patch #10916: Optimisation in issues relations display +* Patch #12485: Don't force english language for default admin account +* Patch #12499: Use lambda in model scopes +* Patch #12611: Login link unexpected logs you out +* Patch #12626: Updated Japanese translations for button_view and permission_commit_access +* Patch #12640: Russian "about_x_hours" translation change +* Patch #12645: Russian numeric translation +* Patch #12660: Consistent German translation for my page +* Patch #12708: Restructured german translation (Cleanup) +* Patch #12721: Optimize MenuManager a bit +* Patch #12725: Change pourcent to percent (#12724) +* Patch #12754: Updated Japanese translation for notice_account_register_done +* Patch #12788: Copyright for 2013 +* Patch #12806: Serbian translation change +* Patch #12810: Swedish Translation change +* Patch #12910: Plugin settings div should perhaps have 'settings' CSS class +* Patch #12911: Fix 500 error for requests to the settings path for non-configurable plugins +* Patch #12926: Bulgarian translation (r11218) +* Patch #12927: Swedish Translation for r11244 +* Patch #12967: Change Spanish login/logout translations +* Patch #12988: Russian translation for trunk +* Patch #13080: German translation of label_in +* Patch #13098: Small datepicker improvements +* Patch #13152: Locale file for Azerbaijanian language +* Patch #13155: Add login to /users/:id API for current user +* Patch #13173: Put source :rubygems url HTTP secure +* Patch #13190: Bulgarian translation (r11404) +* Patch #13198: Traditional Chinese language file (to r11426) +* Patch #13203: German translation change for follow and precedes is inconsitent +* Patch #13206: Portuguese translation file +* Patch #13246: Some german translation patches +* Patch #13280: German translation (r11478) +* Patch #13301: Performance: avoid querying all memberships in User#roles_for_project +* Patch #13309: Add "tracker-[id]" CSS class to issues +* Patch #13324: fixing some pt-br locales +* Patch #13339: Complete language Vietnamese file +* Patch #13391: Czech translation update +* Patch #13399: Fixed some wrong or confusing translation in Korean locale +* Patch #13414: Bulgarian translation (r11567) +* Patch #13420: Korean translation for 2.3 (r11583) +* Patch #13437: German translation of setting_emails_header +* Patch #13438: English translation +* Patch #13447: German translation - some patches +* Patch #13450: Czech translation +* Patch #13475: fixing some pt-br locales +* Patch #13514: fixing some pt-br locales + == 2013-03-19 v2.2.4 * Upgrade to Rails 3.2.13 diff -r 0a574315af3e -r 4f746d8966dd doc/INSTALL --- a/doc/INSTALL Fri Jun 14 09:07:32 2013 +0100 +++ b/doc/INSTALL Fri Jun 14 09:28:30 2013 +0100 @@ -1,20 +1,21 @@ == Redmine installation Redmine - project management software -Copyright (C) 2006-2012 Jean-Philippe Lang +Copyright (C) 2006-2013 Jean-Philippe Lang http://www.redmine.org/ == Requirements -* Ruby 1.8.7, 1.9.2 or 1.9.3 +* Ruby 1.8.7, 1.9.2, 1.9.3 or 2.0.0 * RubyGems * Bundler >= 1.0.21 * A database: * MySQL (tested with MySQL 5.1) * PostgreSQL (tested with PostgreSQL 9.1) - * SQLite3 (tested with SQLite 3.6) + * SQLite3 (tested with SQLite 3.7) + * SQLServer (tested with SQLServer 2012) Optional: * SCM binaries (e.g. svn, git...), for repository browsing (must be available in PATH) @@ -24,25 +25,31 @@ 1. Uncompress the program archive -2. Install the required gems by running: +2. Create an empty utf8 encoded database: "redmine" for example + +3. Configure the database parameters in config/database.yml + for the "production" environment (default database is MySQL and ruby1.9) + + If you're running Redmine with MySQL and ruby1.8, replace the adapter name + with `mysql` + +4. Install the required gems by running: bundle install --without development test If ImageMagick is not installed on your system, you should skip the installation of the rmagick gem using: bundle install --without development test rmagick + Only the gems that are needed by the adapters you've specified in your database + configuration file are actually installed (eg. if your config/database.yml + uses the 'mysql2' adapter, then only the mysql2 gem will be installed). Don't + forget to re-run `bundle install` when you change config/database.yml for using + other database adapters. + If you need to load some gems that are not required by Redmine core (eg. fcgi), you can create a file named Gemfile.local at the root of your redmine directory. It will be loaded automatically when running `bundle install`. -3. Create an empty utf8 encoded database: "redmine" for example - -4. Configure the database parameters in config/database.yml - for the "production" environment (default database is MySQL and ruby1.8) - - If you're running Redmine with MySQL and ruby1.9, replace the adapter name - with `mysql2` - 5. Generate a session store secret Redmine stores session data in cookies by default, which requires diff -r 0a574315af3e -r 4f746d8966dd doc/README_FOR_APP --- a/doc/README_FOR_APP Fri Jun 14 09:07:32 2013 +0100 +++ b/doc/README_FOR_APP Fri Jun 14 09:28:30 2013 +0100 @@ -6,7 +6,7 @@ = License -Copyright (C) 2006-2012 Jean-Philippe Lang +Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd doc/RUNNING_TESTS --- a/doc/RUNNING_TESTS Fri Jun 14 09:07:32 2013 +0100 +++ b/doc/RUNNING_TESTS Fri Jun 14 09:28:30 2013 +0100 @@ -9,7 +9,7 @@ Run `rake --tasks test` to see available tests. Run `rake test` to run the entire test suite (except the tests for the -Apache perl module Redmine.pm, see below). +Apache perl module Redmine.pm and Capybara tests, see below). You can run `ruby test/unit/issue_test.rb` for running a single test case. @@ -58,3 +58,12 @@ If you svn server is not running on localhost, you can use the REDMINE_TEST_DAV_SERVER environment variable to specify another host. + +Running Capybara tests +====================== + +You need to have PhantomJS WebDriver listening on port 4444: +`phantomjs --webdriver 4444` + +Capybara tests can be run with: +`rake test:ui` diff -r 0a574315af3e -r 4f746d8966dd doc/UPGRADING --- a/doc/UPGRADING Fri Jun 14 09:07:32 2013 +0100 +++ b/doc/UPGRADING Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ == Redmine upgrade Redmine - project management software -Copyright (C) 2006-2012 Jean-Philippe Lang +Copyright (C) 2006-2013 Jean-Philippe Lang http://www.redmine.org/ @@ -32,6 +32,16 @@ of the rmagick gem using: bundle install --without development test rmagick + Only the gems that are needed by the adapters you've specified in your database + configuration file are actually installed (eg. if your config/database.yml + uses the 'mysql2' adapter, then only the mysql2 gem will be installed). Don't + forget to re-run `bundle install` when you change config/database.yml for using + other database adapters. + + If you need to load some gems that are not required by Redmine core (eg. fcgi), + you can create a file named Gemfile.local at the root of your redmine directory. + It will be loaded automatically when running `bundle install`. + 6. Generate a session store secret Redmine stores session data in cookies by default, which requires diff -r 0a574315af3e -r 4f746d8966dd extra/mail_handler/rdm-mailhandler.rb --- a/extra/mail_handler/rdm-mailhandler.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/extra/mail_handler/rdm-mailhandler.rb Fri Jun 14 09:28:30 2013 +0100 @@ -23,9 +23,10 @@ end class RedmineMailHandler - VERSION = '0.2.1' + VERSION = '0.2.3' - attr_accessor :verbose, :issue_attributes, :allow_override, :unknown_user, :no_permission_check, :url, :key, :no_check_certificate + attr_accessor :verbose, :issue_attributes, :allow_override, :unknown_user, :default_group, :no_permission_check, + :url, :key, :no_check_certificate, :no_account_notice, :no_notification def initialize self.issue_attributes = {} @@ -40,11 +41,6 @@ opts.on("-k", "--key KEY", "Redmine API key") {|v| self.key = v} opts.separator("") opts.separator("General options:") - opts.on("--unknown-user ACTION", "how to handle emails from an unknown user", - "ACTION can be one of the following values:", - "* ignore: email is ignored (default)", - "* accept: accept as anonymous user", - "* create: create a user account") {|v| self.unknown_user = v} opts.on("--no-permission-check", "disable permission checking when receiving", "the email") {self.no_permission_check = '1'} opts.on("--key-file FILE", "path to a file that contains the Redmine", @@ -56,6 +52,19 @@ opts.on("-v", "--verbose", "show extra information") {self.verbose = true} opts.on("-V", "--version", "show version information and exit") {puts VERSION; exit} opts.separator("") + opts.separator("User creation options:") + opts.on("--unknown-user ACTION", "how to handle emails from an unknown user", + "ACTION can be one of the following values:", + "* ignore: email is ignored (default)", + "* accept: accept as anonymous user", + "* create: create a user account") {|v| self.unknown_user = v} + opts.on("--default-group GROUP", "add created user to GROUP (none by default)", + "GROUP can be a comma separated list of groups") { |v| self.default_group = v} + opts.on("--no-account-notice", "don't send account information to the newly", + "created user") { |v| self.no_account_notice = '1'} + opts.on("--no-notification", "disable email notifications for the created", + "user") { |v| self.no_notification = '1'} + opts.separator("") opts.separator("Issue attributes control options:") opts.on("-p", "--project PROJECT", "identifier of the target project") {|v| self.issue_attributes['project'] = v} opts.on("-s", "--status STATUS", "name of the target status") {|v| self.issue_attributes['status'] = v} @@ -95,11 +104,19 @@ data = { 'key' => key, 'email' => email, 'allow_override' => allow_override, 'unknown_user' => unknown_user, + 'default_group' => default_group, + 'no_account_notice' => no_account_notice, + 'no_notification' => no_notification, 'no_permission_check' => no_permission_check} issue_attributes.each { |attr, value| data["issue[#{attr}]"] = value } debug "Posting to #{uri}..." - response = Net::HTTPS.post_form(URI.parse(uri), data, headers, :no_check_certificate => no_check_certificate) + begin + response = Net::HTTPS.post_form(URI.parse(uri), data, headers, :no_check_certificate => no_check_certificate) + rescue SystemCallError => e # connection refused, etc. + warn "An error occured while contacting your Redmine server: #{e.message}" + return 75 # temporary failure + end debug "Response received: #{response.code}" case response.code.to_i diff -r 0a574315af3e -r 4f746d8966dd extra/sample_plugin/test/integration/routing_test.rb --- a/extra/sample_plugin/test/integration/routing_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/extra/sample_plugin/test/integration/routing_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ require File.expand_path(File.dirname(__FILE__) + '../../../../../test/test_helper') -class SamplePluginRoutingTest < ActionController::IntegrationTest +class SamplePluginRoutingTest < ActionDispatch::IntegrationTest def test_example assert_routing( { :method => 'get', :path => "/projects/1234/hello" }, diff -r 0a574315af3e -r 4f746d8966dd extra/svn/Redmine.pm --- a/extra/svn/Redmine.pm Fri Jun 14 09:07:32 2013 +0100 +++ b/extra/svn/Redmine.pm Fri Jun 14 09:28:30 2013 +0100 @@ -388,9 +388,9 @@ $sth->execute($project_id); my $ret = 0; if (my @row = $sth->fetchrow_array) { - if ($row[0] eq "1" || $row[0] eq "t") { - $ret = 1; - } + if ($row[0] eq "1" || $row[0] eq "t") { + $ret = 1; + } } $sth->finish(); undef $sth; @@ -467,9 +467,9 @@ } unless ($auth_source_id) { - my $method = $r->method; + my $method = $r->method; my $salted_password = Digest::SHA::sha1_hex($salt.$pass_digest); - if ($hashed_password eq $salted_password && (($access_mode eq "R" && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/) ) { + if ($hashed_password eq $salted_password && (($access_mode eq "R" && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/) ) { $ret = 1; last; } diff -r 0a574315af3e -r 4f746d8966dd lib/generators/redmine_plugin/templates/en_rails_i18n.yml --- a/lib/generators/redmine_plugin/templates/en_rails_i18n.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/generators/redmine_plugin/templates/en_rails_i18n.yml Fri Jun 14 09:28:30 2013 +0100 @@ -1,3 +1,3 @@ # English strings go here for Rails i18n en: - my_label: "My label" + # my_label: "My label" diff -r 0a574315af3e -r 4f746d8966dd lib/plugins/acts_as_activity_provider/lib/acts_as_activity_provider.rb --- a/lib/plugins/acts_as_activity_provider/lib/acts_as_activity_provider.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/plugins/acts_as_activity_provider/lib/acts_as_activity_provider.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/plugins/acts_as_attachable/lib/acts_as_attachable.rb --- a/lib/plugins/acts_as_attachable/lib/acts_as_attachable.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/plugins/acts_as_attachable/lib/acts_as_attachable.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -78,6 +78,7 @@ end if attachments.is_a?(Array) attachments.each do |attachment| + next unless attachment.is_a?(Hash) a = nil if file = attachment['file'] next unless file.size > 0 diff -r 0a574315af3e -r 4f746d8966dd lib/plugins/acts_as_customizable/lib/acts_as_customizable.rb --- a/lib/plugins/acts_as_customizable/lib/acts_as_customizable.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/plugins/acts_as_customizable/lib/acts_as_customizable.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -32,6 +32,7 @@ :order => "#{CustomField.table_name}.position", :dependent => :delete_all, :validate => false + send :include, Redmine::Acts::Customizable::InstanceMethods validate :validate_custom_field_values after_save :save_custom_field_values @@ -41,11 +42,11 @@ module InstanceMethods def self.included(base) base.extend ClassMethods + base.send :alias_method_chain, :reload, :custom_fields end def available_custom_fields - CustomField.find(:all, :conditions => "type = '#{self.class.name}CustomField'", - :order => 'position') + CustomField.where("type = '#{self.class.name}CustomField'").sorted.all end # Sets the values of the object's custom fields @@ -153,6 +154,12 @@ @custom_field_values_changed = true end + def reload_with_custom_fields(*args) + @custom_field_values = nil + @custom_field_values_changed = false + reload_without_custom_fields(*args) + end + module ClassMethods end end diff -r 0a574315af3e -r 4f746d8966dd lib/plugins/acts_as_event/lib/acts_as_event.rb --- a/lib/plugins/acts_as_event/lib/acts_as_event.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/plugins/acts_as_event/lib/acts_as_event.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -63,6 +63,11 @@ event_datetime.to_date end + def event_group + group = event_options[:group] ? send(event_options[:group]) : self + group || self + end + def event_url(options = {}) option = event_options[:url] if option.is_a?(Proc) diff -r 0a574315af3e -r 4f746d8966dd lib/plugins/acts_as_list/lib/active_record/acts/list.rb --- a/lib/plugins/acts_as_list/lib/active_record/acts/list.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/plugins/acts_as_list/lib/active_record/acts/list.rb Fri Jun 14 09:28:30 2013 +0100 @@ -177,17 +177,17 @@ # Return the next higher item in the list. def higher_item return nil unless in_list? - acts_as_list_class.find(:first, :conditions => + acts_as_list_class.where( "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i - 1).to_s}" - ) + ).first end # Return the next lower item in the list. def lower_item return nil unless in_list? - acts_as_list_class.find(:first, :conditions => + acts_as_list_class.where( "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i + 1).to_s}" - ) + ).first end # Test if this record is in a list diff -r 0a574315af3e -r 4f746d8966dd lib/plugins/acts_as_searchable/lib/acts_as_searchable.rb --- a/lib/plugins/acts_as_searchable/lib/acts_as_searchable.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/plugins/acts_as_searchable/lib/acts_as_searchable.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -72,14 +72,8 @@ tokens = [] << tokens unless tokens.is_a?(Array) projects = [] << projects unless projects.nil? || projects.is_a?(Array) - find_options = {:include => searchable_options[:include]} - find_options[:order] = "#{searchable_options[:order_column]} " + (options[:before] ? 'DESC' : 'ASC') - limit_options = {} limit_options[:limit] = options[:limit] if options[:limit] - if options[:offset] - limit_options[:conditions] = "(#{searchable_options[:date_column]} " + (options[:before] ? '<' : '>') + "'#{connection.quoted_date(options[:offset])}')" - end columns = searchable_options[:columns] columns = columns[0..0] if options[:titles_only] @@ -87,10 +81,7 @@ token_clauses = columns.collect {|column| "(LOWER(#{column}) LIKE ?)"} if !options[:titles_only] && searchable_options[:search_custom_fields] - searchable_custom_field_ids = CustomField.find(:all, - :select => 'id', - :conditions => { :type => "#{self.name}CustomField", - :searchable => true }).collect(&:id) + searchable_custom_field_ids = CustomField.where(:type => "#{self.name}CustomField", :searchable => true).pluck(:id) if searchable_custom_field_ids.any? custom_field_sql = "#{table_name}.id IN (SELECT customized_id FROM #{CustomValue.table_name}" + " WHERE customized_type='#{self.name}' AND customized_id=#{table_name}.id AND LOWER(value) LIKE ?" + @@ -101,9 +92,9 @@ sql = (['(' + token_clauses.join(' OR ') + ')'] * tokens.size).join(options[:all_words] ? ' AND ' : ' OR ') - find_options[:conditions] = [sql, * (tokens.collect {|w| "%#{w.downcase}%"} * token_clauses.size).sort] + tokens_conditions = [sql, * (tokens.collect {|w| "%#{w.downcase}%"} * token_clauses.size).sort] - scope = self + scope = self.scoped project_conditions = [] if searchable_options.has_key?(:permission) project_conditions << Project.allowed_to_condition(user, searchable_options[:permission] || :view_project) @@ -120,9 +111,19 @@ results = [] results_count = 0 - scope = scope.scoped({:conditions => project_conditions}).scoped(find_options) - results_count = scope.count(:all) - results = scope.find(:all, limit_options) + scope = scope. + includes(searchable_options[:include]). + order("#{searchable_options[:order_column]} " + (options[:before] ? 'DESC' : 'ASC')). + where(project_conditions). + where(tokens_conditions) + + results_count = scope.count + + scope_with_limit = scope.limit(options[:limit]) + if options[:offset] + scope_with_limit = scope_with_limit.where("#{searchable_options[:date_column]} #{options[:before] ? '<' : '>'} ?", options[:offset]) + end + results = scope_with_limit.all [results, results_count] end diff -r 0a574315af3e -r 4f746d8966dd lib/plugins/acts_as_tree/lib/active_record/acts/tree.rb --- a/lib/plugins/acts_as_tree/lib/active_record/acts/tree.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/plugins/acts_as_tree/lib/active_record/acts/tree.rb Fri Jun 14 09:28:30 2013 +0100 @@ -46,17 +46,9 @@ belongs_to :parent, :class_name => name, :foreign_key => configuration[:foreign_key], :counter_cache => configuration[:counter_cache] has_many :children, :class_name => name, :foreign_key => configuration[:foreign_key], :order => configuration[:order], :dependent => configuration[:dependent] - class_eval <<-EOV - include ActiveRecord::Acts::Tree::InstanceMethods + scope :roots, where("#{configuration[:foreign_key]} IS NULL").order(configuration[:order]) - def self.roots - find(:all, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}}) - end - - def self.root - find(:first, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}}) - end - EOV + send :include, ActiveRecord::Acts::Tree::InstanceMethods end end diff -r 0a574315af3e -r 4f746d8966dd lib/plugins/acts_as_versioned/lib/acts_as_versioned.rb --- a/lib/plugins/acts_as_versioned/lib/acts_as_versioned.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/plugins/acts_as_versioned/lib/acts_as_versioned.rb Fri Jun 14 09:28:30 2013 +0100 @@ -216,12 +216,12 @@ has_many :versions, version_association_options do # finds earliest version of this record def earliest - @earliest ||= find(:first, :order => 'version') + @earliest ||= order('version').first end # find latest version of this record def latest - @latest ||= find(:first, :order => 'version desc') + @latest ||= order('version desc').first end end before_save :set_new_version @@ -248,14 +248,16 @@ def self.reloadable? ; false ; end # find first version before the given version def self.before(version) - find :first, :order => 'version desc', - :conditions => ["#{original_class.versioned_foreign_key} = ? and version < ?", version.send(original_class.versioned_foreign_key), version.version] + order('version desc'). + where("#{original_class.versioned_foreign_key} = ? and version < ?", version.send(original_class.versioned_foreign_key), version.version). + first end # find first version after the given version. def self.after(version) - find :first, :order => 'version', - :conditions => ["#{original_class.versioned_foreign_key} = ? and version > ?", version.send(original_class.versioned_foreign_key), version.version] + order('version'). + where("#{original_class.versioned_foreign_key} = ? and version > ?", version.send(original_class.versioned_foreign_key), version.version). + first end def previous @@ -467,9 +469,9 @@ # Finds versions of a specific model. Takes an options hash like find def find_versions(id, options = {}) - versioned_class.find :all, { + versioned_class.all({ :conditions => ["#{versioned_foreign_key} = ?", id], - :order => 'version' }.merge(options) + :order => 'version' }.merge(options)) end # Returns an array of columns that are versioned. See non_versioned_columns diff -r 0a574315af3e -r 4f746d8966dd lib/plugins/awesome_nested_set/lib/awesome_nested_set/awesome_nested_set.rb --- a/lib/plugins/awesome_nested_set/lib/awesome_nested_set/awesome_nested_set.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/plugins/awesome_nested_set/lib/awesome_nested_set/awesome_nested_set.rb Fri Jun 14 09:28:30 2013 +0100 @@ -413,7 +413,7 @@ # on creation, set automatically lft and rgt to the end of the tree def set_default_left_and_right - highest_right_row = nested_set_scope(:order => "#{quoted_right_column_name} desc").find(:first, :limit => 1,:lock => true ) + highest_right_row = nested_set_scope(:order => "#{quoted_right_column_name} desc").limit(1).lock(true).first maxright = highest_right_row ? (highest_right_row[right_column_name] || 0) : 0 # adds the new node to the right of all existing nodes self[left_column_name] = maxright + 1 @@ -443,11 +443,11 @@ in_tenacious_transaction do reload_nested_set # select the rows in the model that extend past the deletion point and apply a lock - self.class.base_class.find(:all, - :select => "id", - :conditions => ["#{quoted_left_column_name} >= ?", left], - :lock => true - ) + self.class.base_class. + select("id"). + where("#{quoted_left_column_name} >= ?", left). + lock(true). + all if acts_as_nested_set_options[:dependent] == :destroy descendants.each do |model| diff -r 0a574315af3e -r 4f746d8966dd lib/plugins/classic_pagination/CHANGELOG --- a/lib/plugins/classic_pagination/CHANGELOG Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,152 +0,0 @@ -* Exported the changelog of Pagination code for historical reference. - -* Imported some patches from Rails Trac (others closed as "wontfix"): - #8176, #7325, #7028, #4113. Documentation is much cleaner now and there - are some new unobtrusive features! - -* Extracted Pagination from Rails trunk (r6795) - -# -# ChangeLog for /trunk/actionpack/lib/action_controller/pagination.rb -# -# Generated by Trac 0.10.3 -# 05/20/07 23:48:02 -# - -09/03/06 23:28:54 david [4953] - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - Docs and deprecation - -08/07/06 12:40:14 bitsweat [4715] - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - Deprecate direct usage of @params. Update ActionView::Base for - instance var deprecation. - -06/21/06 02:16:11 rick [4476] - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - Fix indent in pagination documentation. Closes #4990. [Kevin Clark] - -04/25/06 17:42:48 marcel [4268] - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - Remove all remaining references to @params in the documentation. - -03/16/06 06:38:08 rick [3899] - * trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (modified) - trivial documentation patch for #pagination_links [Francois - Beausoleil] closes #4258 - -02/20/06 03:15:22 david [3620] - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - * trunk/actionpack/test/activerecord/pagination_test.rb (modified) - * trunk/activerecord/CHANGELOG (modified) - * trunk/activerecord/lib/active_record/base.rb (modified) - * trunk/activerecord/test/base_test.rb (modified) - Added :count option to pagination that'll make it possible for the - ActiveRecord::Base.count call to using something else than * for the - count. Especially important for count queries using DISTINCT #3839 - [skaes]. Added :select option to Base.count that'll allow you to - select something else than * to be counted on. Especially important - for count queries using DISTINCT (closes #3839) [skaes]. - -02/09/06 09:17:40 nzkoz [3553] - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - * trunk/actionpack/test/active_record_unit.rb (added) - * trunk/actionpack/test/activerecord (added) - * trunk/actionpack/test/activerecord/active_record_assertions_test.rb (added) - * trunk/actionpack/test/activerecord/pagination_test.rb (added) - * trunk/actionpack/test/controller/active_record_assertions_test.rb (deleted) - * trunk/actionpack/test/fixtures/companies.yml (added) - * trunk/actionpack/test/fixtures/company.rb (added) - * trunk/actionpack/test/fixtures/db_definitions (added) - * trunk/actionpack/test/fixtures/db_definitions/sqlite.sql (added) - * trunk/actionpack/test/fixtures/developer.rb (added) - * trunk/actionpack/test/fixtures/developers_projects.yml (added) - * trunk/actionpack/test/fixtures/developers.yml (added) - * trunk/actionpack/test/fixtures/project.rb (added) - * trunk/actionpack/test/fixtures/projects.yml (added) - * trunk/actionpack/test/fixtures/replies.yml (added) - * trunk/actionpack/test/fixtures/reply.rb (added) - * trunk/actionpack/test/fixtures/topic.rb (added) - * trunk/actionpack/test/fixtures/topics.yml (added) - * Fix pagination problems when using include - * Introduce Unit Tests for pagination - * Allow count to work with :include by using count distinct. - - [Kevin Clark & Jeremy Hopple] - -11/05/05 02:10:29 bitsweat [2878] - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - Update paginator docs. Closes #2744. - -10/16/05 15:42:03 minam [2649] - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - Update/clean up AP documentation (rdoc) - -08/31/05 00:13:10 ulysses [2078] - * trunk/actionpack/CHANGELOG (modified) - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - Add option to specify the singular name used by pagination. Closes - #1960 - -08/23/05 14:24:15 minam [2041] - * trunk/actionpack/CHANGELOG (modified) - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - Add support for :include with pagination (subject to existing - constraints for :include with :limit and :offset) #1478 - [michael@schubert.cx] - -07/15/05 20:27:38 david [1839] - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - * trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (modified) - More pagination speed #1334 [Stefan Kaes] - -07/14/05 08:02:01 david [1832] - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - * trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (modified) - * trunk/actionpack/test/controller/addresses_render_test.rb (modified) - Made pagination faster #1334 [Stefan Kaes] - -04/13/05 05:40:22 david [1159] - * trunk/actionpack/CHANGELOG (modified) - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - * trunk/activerecord/lib/active_record/base.rb (modified) - Fixed pagination to work with joins #1034 [scott@sigkill.org] - -04/02/05 09:11:17 david [1067] - * trunk/actionpack/CHANGELOG (modified) - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - * trunk/actionpack/lib/action_controller/scaffolding.rb (modified) - * trunk/actionpack/lib/action_controller/templates/scaffolds/list.rhtml (modified) - * trunk/railties/lib/rails_generator/generators/components/scaffold/templates/controller.rb (modified) - * trunk/railties/lib/rails_generator/generators/components/scaffold/templates/view_list.rhtml (modified) - Added pagination for scaffolding (10 items per page) #964 - [mortonda@dgrmm.net] - -03/31/05 14:46:11 david [1048] - * trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (modified) - Improved the message display on the exception handler pages #963 - [Johan Sorensen] - -03/27/05 00:04:07 david [1017] - * trunk/actionpack/CHANGELOG (modified) - * trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (modified) - Fixed that pagination_helper would ignore :params #947 [Sebastian - Kanthak] - -03/22/05 13:09:44 david [976] - * trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (modified) - Fixed documentation and prepared for 0.11.0 release - -03/21/05 14:35:36 david [967] - * trunk/actionpack/lib/action_controller/pagination.rb (modified) - * trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (modified) - Tweaked the documentation - -03/20/05 23:12:05 david [949] - * trunk/actionpack/CHANGELOG (modified) - * trunk/actionpack/lib/action_controller.rb (modified) - * trunk/actionpack/lib/action_controller/pagination.rb (added) - * trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (added) - * trunk/activesupport/lib/active_support/core_ext/kernel.rb (added) - Added pagination support through both a controller and helper add-on - #817 [Sam Stephenson] diff -r 0a574315af3e -r 4f746d8966dd lib/plugins/classic_pagination/README --- a/lib/plugins/classic_pagination/README Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,18 +0,0 @@ -Pagination -========== - -To install: - - script/plugin install svn://errtheblog.com/svn/plugins/classic_pagination - -This code was extracted from Rails trunk after the release 1.2.3. -WARNING: this code is dead. It is unmaintained, untested and full of cruft. - -There is a much better pagination plugin called will_paginate. -Install it like this and glance through the README: - - script/plugin install svn://errtheblog.com/svn/plugins/will_paginate - -It doesn't have the same API, but is in fact much nicer. You can -have both plugins installed until you change your controller/view code that -handles pagination. Then, simply uninstall classic_pagination. diff -r 0a574315af3e -r 4f746d8966dd lib/plugins/classic_pagination/Rakefile --- a/lib/plugins/classic_pagination/Rakefile Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -require 'rake' -require 'rake/testtask' -require 'rake/rdoctask' - -desc 'Default: run unit tests.' -task :default => :test - -desc 'Test the classic_pagination plugin.' -Rake::TestTask.new(:test) do |t| - t.libs << 'lib' - t.pattern = 'test/**/*_test.rb' - t.verbose = true -end - -desc 'Generate documentation for the classic_pagination plugin.' -Rake::RDocTask.new(:rdoc) do |rdoc| - rdoc.rdoc_dir = 'rdoc' - rdoc.title = 'Pagination' - rdoc.options << '--line-numbers' << '--inline-source' - rdoc.rdoc_files.include('README') - rdoc.rdoc_files.include('lib/**/*.rb') -end diff -r 0a574315af3e -r 4f746d8966dd lib/plugins/classic_pagination/init.rb --- a/lib/plugins/classic_pagination/init.rb Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +0,0 @@ -#-- -# Copyright (c) 2004-2006 David Heinemeier Hansson -# -# 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. -#++ - -require 'pagination' -require 'pagination_helper' - -ActionController::Base.class_eval do - include ActionController::Pagination -end - -ActionView::Base.class_eval do - include ActionView::Helpers::PaginationHelper -end diff -r 0a574315af3e -r 4f746d8966dd lib/plugins/classic_pagination/install.rb --- a/lib/plugins/classic_pagination/install.rb Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -puts "\n\n" + File.read(File.dirname(__FILE__) + '/README') diff -r 0a574315af3e -r 4f746d8966dd lib/plugins/classic_pagination/lib/pagination.rb --- a/lib/plugins/classic_pagination/lib/pagination.rb Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,405 +0,0 @@ -module ActionController - # === Action Pack pagination for Active Record collections - # - # The Pagination module aids in the process of paging large collections of - # Active Record objects. It offers macro-style automatic fetching of your - # model for multiple views, or explicit fetching for single actions. And if - # the magic isn't flexible enough for your needs, you can create your own - # paginators with a minimal amount of code. - # - # The Pagination module can handle as much or as little as you wish. In the - # controller, have it automatically query your model for pagination; or, - # if you prefer, create Paginator objects yourself. - # - # Pagination is included automatically for all controllers. - # - # For help rendering pagination links, see - # ActionView::Helpers::PaginationHelper. - # - # ==== Automatic pagination for every action in a controller - # - # class PersonController < ApplicationController - # model :person - # - # paginate :people, :order => 'last_name, first_name', - # :per_page => 20 - # - # # ... - # end - # - # Each action in this controller now has access to a @people - # instance variable, which is an ordered collection of model objects for the - # current page (at most 20, sorted by last name and first name), and a - # @person_pages Paginator instance. The current page is determined - # by the params[:page] variable. - # - # ==== Pagination for a single action - # - # def list - # @person_pages, @people = - # paginate :people, :order => 'last_name, first_name' - # end - # - # Like the previous example, but explicitly creates @person_pages - # and @people for a single action, and uses the default of 10 items - # per page. - # - # ==== Custom/"classic" pagination - # - # def list - # @person_pages = Paginator.new self, Person.count, 10, params[:page] - # @people = Person.find :all, :order => 'last_name, first_name', - # :limit => @person_pages.items_per_page, - # :offset => @person_pages.current.offset - # end - # - # Explicitly creates the paginator from the previous example and uses - # Paginator#to_sql to retrieve @people from the model. - # - module Pagination - unless const_defined?(:OPTIONS) - # A hash holding options for controllers using macro-style pagination - OPTIONS = Hash.new - - # The default options for pagination - DEFAULT_OPTIONS = { - :class_name => nil, - :singular_name => nil, - :per_page => 10, - :conditions => nil, - :order_by => nil, - :order => nil, - :join => nil, - :joins => nil, - :count => nil, - :include => nil, - :select => nil, - :group => nil, - :parameter => 'page' - } - else - DEFAULT_OPTIONS[:group] = nil - end - - def self.included(base) #:nodoc: - super - base.extend(ClassMethods) - end - - def self.validate_options!(collection_id, options, in_action) #:nodoc: - options.merge!(DEFAULT_OPTIONS) {|key, old, new| old} - - valid_options = DEFAULT_OPTIONS.keys - valid_options << :actions unless in_action - - unknown_option_keys = options.keys - valid_options - raise ActionController::ActionControllerError, - "Unknown options: #{unknown_option_keys.join(', ')}" unless - unknown_option_keys.empty? - - 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 - # for the paginator's current page. This is designed to be used in a - # single action; to automatically paginate multiple actions, consider - # ClassMethods#paginate. - # - # +options+ are: - # :singular_name:: the singular name to use, if it can't be inferred by singularizing the collection name - # :class_name:: the class name to use, if it can't be inferred by - # camelizing the singular name - # :per_page:: the maximum number of items to include in a - # single page. Defaults to 10 - # :conditions:: optional conditions passed to Model.find(:all, *params) and - # Model.count - # :order:: optional order parameter passed to Model.find(:all, *params) - # :order_by:: (deprecated, used :order) optional order parameter passed to Model.find(:all, *params) - # :joins:: optional joins parameter passed to Model.find(:all, *params) - # and Model.count - # :join:: (deprecated, used :joins or :include) optional join parameter passed to Model.find(:all, *params) - # and Model.count - # :include:: optional eager loading parameter passed to Model.find(:all, *params) - # and Model.count - # :select:: :select parameter passed to Model.find(:all, *params) - # - # :count:: parameter passed as :select option to Model.count(*params) - # - # :group:: :group parameter passed to Model.find(:all, *params). It forces the use of DISTINCT instead of plain COUNT to come up with the total number of records - # - def paginate(collection_id, options={}) - Pagination.validate_options!(collection_id, options, true) - paginator_and_collection_for(collection_id, options) - end - - # These methods become class methods on any controller - module ClassMethods - # Creates a +before_filter+ which automatically paginates an Active - # Record model for all actions in a controller (or certain actions if - # specified with the :actions option). - # - # +options+ are the same as PaginationHelper#paginate, with the addition - # of: - # :actions:: an array of actions for which the pagination is - # active. Defaults to +nil+ (i.e., every action) - def paginate(collection_id, options={}) - Pagination.validate_options!(collection_id, options, false) - module_eval do - before_filter :create_paginators_and_retrieve_collections - OPTIONS[self] ||= Hash.new - OPTIONS[self][collection_id] = options - end - end - end - - def create_paginators_and_retrieve_collections #:nodoc: - Pagination::OPTIONS[self.class].each do |collection_id, options| - next unless options[:actions].include? action_name if - options[:actions] - - paginator, collection = - paginator_and_collection_for(collection_id, options) - - paginator_name = "@#{options[:singular_name]}_pages" - self.instance_variable_set(paginator_name, paginator) - - collection_name = "@#{collection_id.to_s}" - self.instance_variable_set(collection_name, collection) - end - end - - # Returns the total number of items in the collection to be paginated for - # the +model+ and given +conditions+. Override this method to implement a - # custom counter. - def count_collection_for_pagination(model, options) - model.count(:conditions => options[:conditions], - :joins => options[:join] || options[:joins], - :include => options[:include], - :select => (options[:group] ? "DISTINCT #{options[:group]}" : options[:count])) - end - - # Returns a collection of items for the given +model+ and +options[conditions]+, - # ordered by +options[order]+, for the current page in the given +paginator+. - # Override this method to implement a custom finder. - def find_collection_for_pagination(model, options, paginator) - model.find(:all, :conditions => options[:conditions], - :order => options[:order_by] || options[:order], - :joins => options[:join] || options[:joins], :include => options[:include], - :select => options[:select], :limit => options[:per_page], - :group => options[:group], :offset => paginator.current.offset) - end - - protected :create_paginators_and_retrieve_collections, - :count_collection_for_pagination, - :find_collection_for_pagination - - def paginator_and_collection_for(collection_id, options) #:nodoc: - klass = options[:class_name].constantize - page = params[options[:parameter]] - count = count_collection_for_pagination(klass, options) - paginator = Paginator.new(self, count, options[:per_page], page) - collection = find_collection_for_pagination(klass, options, paginator) - - return paginator, collection - end - - private :paginator_and_collection_for - - # A class representing a paginator for an Active Record collection. - class Paginator - include Enumerable - - # Creates a new Paginator on the given +controller+ for a set of items - # of size +item_count+ and having +items_per_page+ items per page. - # Raises ArgumentError if items_per_page is out of bounds (i.e., less - # than or equal to zero). The page CGI parameter for links defaults to - # "page" and can be overridden with +page_parameter+. - def initialize(controller, item_count, items_per_page, current_page=1) - raise ArgumentError, 'must have at least one item per page' if - items_per_page <= 0 - - @controller = controller - @item_count = item_count || 0 - @items_per_page = items_per_page - @pages = {} - - self.current_page = current_page - end - attr_reader :controller, :item_count, :items_per_page - - # Sets the current page number of this paginator. If +page+ is a Page - # object, its +number+ attribute is used as the value; if the page does - # not belong to this Paginator, an ArgumentError is raised. - def current_page=(page) - if page.is_a? Page - raise ArgumentError, 'Page/Paginator mismatch' unless - page.paginator == self - end - page = page.to_i - @current_page_number = has_page_number?(page) ? page : 1 - end - - # Returns a Page object representing this paginator's current page. - def current_page - @current_page ||= self[@current_page_number] - end - alias current :current_page - - # Returns a new Page representing the first page in this paginator. - def first_page - @first_page ||= self[1] - end - alias first :first_page - - # Returns a new Page representing the last page in this paginator. - def last_page - @last_page ||= self[page_count] - end - alias last :last_page - - # Returns the number of pages in this paginator. - def page_count - @page_count ||= @item_count.zero? ? 1 : - (q,r=@item_count.divmod(@items_per_page); r==0? q : q+1) - end - - alias length :page_count - - # Returns true if this paginator contains the page of index +number+. - def has_page_number?(number) - number >= 1 and number <= page_count - end - - # Returns a new Page representing the page with the given index - # +number+. - def [](number) - @pages[number] ||= Page.new(self, number) - end - - # Successively yields all the paginator's pages to the given block. - def each(&block) - page_count.times do |n| - yield self[n+1] - end - end - - # A class representing a single page in a paginator. - class Page - include Comparable - - # Creates a new Page for the given +paginator+ with the index - # +number+. If +number+ is not in the range of valid page numbers or - # is not a number at all, it defaults to 1. - def initialize(paginator, number) - @paginator = paginator - @number = number.to_i - @number = 1 unless @paginator.has_page_number? @number - end - attr_reader :paginator, :number - alias to_i :number - - # Compares two Page objects and returns true when they represent the - # same page (i.e., their paginators are the same and they have the - # same page number). - def ==(page) - return false if page.nil? - @paginator == page.paginator and - @number == page.number - end - - # Compares two Page objects and returns -1 if the left-hand page comes - # before the right-hand page, 0 if the pages are equal, and 1 if the - # left-hand page comes after the right-hand page. Raises ArgumentError - # if the pages do not belong to the same Paginator object. - def <=>(page) - raise ArgumentError unless @paginator == page.paginator - @number <=> page.number - end - - # Returns the item offset for the first item in this page. - def offset - @paginator.items_per_page * (@number - 1) - end - - # Returns the number of the first item displayed. - def first_item - offset + 1 - end - - # Returns the number of the last item displayed. - def last_item - [@paginator.items_per_page * @number, @paginator.item_count].min - end - - # Returns true if this page is the first page in the paginator. - def first? - self == @paginator.first - end - - # Returns true if this page is the last page in the paginator. - def last? - self == @paginator.last - end - - # Returns a new Page object representing the page just before this - # page, or nil if this is the first page. - def previous - if first? then nil else @paginator[@number - 1] end - end - - # Returns a new Page object representing the page just after this - # page, or nil if this is the last page. - def next - if last? then nil else @paginator[@number + 1] end - end - - # Returns a new Window object for this page with the specified - # +padding+. - def window(padding=2) - Window.new(self, padding) - end - - # Returns the limit/offset array for this page. - def to_sql - [@paginator.items_per_page, offset] - end - - def to_param #:nodoc: - @number.to_s - end - end - - # A class for representing ranges around a given page. - class Window - # Creates a new Window object for the given +page+ with the specified - # +padding+. - def initialize(page, padding=2) - @paginator = page.paginator - @page = page - self.padding = padding - end - attr_reader :paginator, :page - - # Sets the window's padding (the number of pages on either side of the - # window page). - def padding=(padding) - @padding = padding < 0 ? 0 : padding - # Find the beginning and end pages of the window - @first = @paginator.has_page_number?(@page.number - @padding) ? - @paginator[@page.number - @padding] : @paginator.first - @last = @paginator.has_page_number?(@page.number + @padding) ? - @paginator[@page.number + @padding] : @paginator.last - end - attr_reader :padding, :first, :last - - # Returns an array of Page objects in the current window. - def pages - (@first.number..@last.number).to_a.collect! {|n| @paginator[n]} - end - alias to_a :pages - end - end - - end -end diff -r 0a574315af3e -r 4f746d8966dd lib/plugins/classic_pagination/lib/pagination_helper.rb --- a/lib/plugins/classic_pagination/lib/pagination_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,135 +0,0 @@ -module ActionView - module Helpers - # Provides methods for linking to ActionController::Pagination objects using a simple generator API. You can optionally - # also build your links manually using ActionView::Helpers::AssetHelper#link_to like so: - # - # <%= link_to "Previous page", { :page => paginator.current.previous } if paginator.current.previous %> - # <%= link_to "Next page", { :page => paginator.current.next } if paginator.current.next %> - module PaginationHelper - unless const_defined?(:DEFAULT_OPTIONS) - DEFAULT_OPTIONS = { - :name => :page, - :window_size => 2, - :always_show_anchors => true, - :link_to_current_page => false, - :params => {} - } - end - - # Creates a basic HTML link bar for the given +paginator+. Links will be created - # for the next and/or previous page and for a number of other pages around the current - # pages position. The +html_options+ hash is passed to +link_to+ when the links are created. - # - # ==== Options - # :name:: the routing name for this paginator - # (defaults to +page+) - # :prefix:: prefix for pagination links - # (i.e. Older Pages: 1 2 3 4) - # :suffix:: suffix for pagination links - # (i.e. 1 2 3 4 <- Older Pages) - # :window_size:: the number of pages to show around - # the current page (defaults to 2) - # :always_show_anchors:: whether or not the first and last - # pages should always be shown - # (defaults to +true+) - # :link_to_current_page:: whether or not the current page - # should be linked to (defaults to - # +false+) - # :params:: any additional routing parameters - # for page URLs - # - # ==== Examples - # # We'll assume we have a paginator setup in @person_pages... - # - # pagination_links(@person_pages) - # # => 1 2 3 ... 10 - # - # pagination_links(@person_pages, :link_to_current_page => true) - # # => 1 2 3 ... 10 - # - # pagination_links(@person_pages, :always_show_anchors => false) - # # => 1 2 3 - # - # pagination_links(@person_pages, :window_size => 1) - # # => 1 2 ... 10 - # - # pagination_links(@person_pages, :params => { :viewer => "flash" }) - # # => 1 2 3 ... - # # 10 - def pagination_links(paginator, options={}, html_options={}) - name = options[:name] || DEFAULT_OPTIONS[:name] - params = (options[:params] || DEFAULT_OPTIONS[:params]).clone - - prefix = options[:prefix] || '' - suffix = options[:suffix] || '' - - pagination_links_each(paginator, options, prefix, suffix) do |n| - params[name] = n - link_to(n.to_s, params, html_options) - end - end - - # Iterate through the pages of a given +paginator+, invoking a - # block for each page number that needs to be rendered as a link. - # - # ==== Options - # :window_size:: the number of pages to show around - # the current page (defaults to +2+) - # :always_show_anchors:: whether or not the first and last - # pages should always be shown - # (defaults to +true+) - # :link_to_current_page:: whether or not the current page - # should be linked to (defaults to - # +false+) - # - # ==== Example - # # Turn paginated links into an Ajax call - # pagination_links_each(paginator, page_options) do |link| - # options = { :url => {:action => 'list'}, :update => 'results' } - # html_options = { :href => url_for(:action => 'list') } - # - # link_to_remote(link.to_s, options, html_options) - # end - def pagination_links_each(paginator, options, prefix = nil, suffix = nil) - options = DEFAULT_OPTIONS.merge(options) - link_to_current_page = options[:link_to_current_page] - always_show_anchors = options[:always_show_anchors] - - current_page = paginator.current_page - window_pages = current_page.window(options[:window_size]).pages - return if window_pages.length <= 1 unless link_to_current_page - - first, last = paginator.first, paginator.last - - html = '' - - html << prefix if prefix - - if always_show_anchors and not (wp_first = window_pages[0]).first? - html << yield(first.number) - html << ' ... ' if wp_first.number - first.number > 1 - html << ' ' - end - - window_pages.each do |page| - if current_page == page && !link_to_current_page - html << page.number.to_s - else - html << yield(page.number) - end - html << ' ' - end - - if always_show_anchors and not (wp_last = window_pages[-1]).last? - html << ' ... ' if last.number - wp_last.number > 1 - html << yield(last.number) - end - - html << suffix if suffix - - html - end - - end # PaginationHelper - end # Helpers -end # ActionView diff -r 0a574315af3e -r 4f746d8966dd lib/plugins/classic_pagination/test/fixtures/companies.yml --- a/lib/plugins/classic_pagination/test/fixtures/companies.yml Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,24 +0,0 @@ -thirty_seven_signals: - id: 1 - name: 37Signals - rating: 4 - -TextDrive: - id: 2 - name: TextDrive - rating: 4 - -PlanetArgon: - id: 3 - name: Planet Argon - rating: 4 - -Google: - id: 4 - name: Google - rating: 4 - -Ionist: - id: 5 - name: Ioni.st - rating: 4 \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd lib/plugins/classic_pagination/test/fixtures/company.rb --- a/lib/plugins/classic_pagination/test/fixtures/company.rb Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -class Company < ActiveRecord::Base - attr_protected :rating - set_sequence_name :companies_nonstd_seq - - validates_presence_of :name - def validate - errors.add('rating', 'rating should not be 2') if rating == 2 - end -end \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd lib/plugins/classic_pagination/test/fixtures/developer.rb --- a/lib/plugins/classic_pagination/test/fixtures/developer.rb Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -class Developer < ActiveRecord::Base - has_and_belongs_to_many :projects -end - -class DeVeLoPeR < ActiveRecord::Base - self.table_name = "developers" -end diff -r 0a574315af3e -r 4f746d8966dd lib/plugins/classic_pagination/test/fixtures/developers.yml --- a/lib/plugins/classic_pagination/test/fixtures/developers.yml Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,21 +0,0 @@ -david: - id: 1 - name: David - salary: 80000 - -jamis: - id: 2 - name: Jamis - salary: 150000 - -<% for digit in 3..10 %> -dev_<%= digit %>: - id: <%= digit %> - name: fixture_<%= digit %> - salary: 100000 -<% end %> - -poor_jamis: - id: 11 - name: Jamis - salary: 9000 \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd lib/plugins/classic_pagination/test/fixtures/developers_projects.yml --- a/lib/plugins/classic_pagination/test/fixtures/developers_projects.yml Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,13 +0,0 @@ -david_action_controller: - developer_id: 1 - project_id: 2 - joined_on: 2004-10-10 - -david_active_record: - developer_id: 1 - project_id: 1 - joined_on: 2004-10-10 - -jamis_active_record: - developer_id: 2 - project_id: 1 \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd lib/plugins/classic_pagination/test/fixtures/project.rb --- a/lib/plugins/classic_pagination/test/fixtures/project.rb Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +0,0 @@ -class Project < ActiveRecord::Base - has_and_belongs_to_many :developers, :uniq => true -end diff -r 0a574315af3e -r 4f746d8966dd lib/plugins/classic_pagination/test/fixtures/projects.yml --- a/lib/plugins/classic_pagination/test/fixtures/projects.yml Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -action_controller: - id: 2 - name: Active Controller - -active_record: - id: 1 - name: Active Record diff -r 0a574315af3e -r 4f746d8966dd lib/plugins/classic_pagination/test/fixtures/replies.yml --- a/lib/plugins/classic_pagination/test/fixtures/replies.yml Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,13 +0,0 @@ -witty_retort: - id: 1 - topic_id: 1 - content: Birdman is better! - created_at: <%= 6.hours.ago.to_s(:db) %> - updated_at: nil - -another: - id: 2 - topic_id: 2 - content: Nuh uh! - created_at: <%= 1.hour.ago.to_s(:db) %> - updated_at: nil \ No newline at end of file diff -r 0a574315af3e -r 4f746d8966dd lib/plugins/classic_pagination/test/fixtures/reply.rb --- a/lib/plugins/classic_pagination/test/fixtures/reply.rb Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -class Reply < ActiveRecord::Base - belongs_to :topic, :include => [:replies] - - validates_presence_of :content -end diff -r 0a574315af3e -r 4f746d8966dd lib/plugins/classic_pagination/test/fixtures/schema.sql --- a/lib/plugins/classic_pagination/test/fixtures/schema.sql Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +0,0 @@ -CREATE TABLE 'companies' ( - 'id' INTEGER PRIMARY KEY NOT NULL, - 'name' TEXT DEFAULT NULL, - 'rating' INTEGER DEFAULT 1 -); - -CREATE TABLE 'replies' ( - 'id' INTEGER PRIMARY KEY NOT NULL, - 'content' text, - 'created_at' datetime, - 'updated_at' datetime, - 'topic_id' integer -); - -CREATE TABLE 'topics' ( - 'id' INTEGER PRIMARY KEY NOT NULL, - 'title' varchar(255), - 'subtitle' varchar(255), - 'content' text, - 'created_at' datetime, - 'updated_at' datetime -); - -CREATE TABLE 'developers' ( - 'id' INTEGER PRIMARY KEY NOT NULL, - 'name' TEXT DEFAULT NULL, - 'salary' INTEGER DEFAULT 70000, - 'created_at' DATETIME DEFAULT NULL, - 'updated_at' DATETIME DEFAULT NULL -); - -CREATE TABLE 'projects' ( - 'id' INTEGER PRIMARY KEY NOT NULL, - 'name' TEXT DEFAULT NULL -); - -CREATE TABLE 'developers_projects' ( - 'developer_id' INTEGER NOT NULL, - 'project_id' INTEGER NOT NULL, - 'joined_on' DATE DEFAULT NULL, - 'access_level' INTEGER DEFAULT 1 -); diff -r 0a574315af3e -r 4f746d8966dd lib/plugins/classic_pagination/test/fixtures/topic.rb --- a/lib/plugins/classic_pagination/test/fixtures/topic.rb Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +0,0 @@ -class Topic < ActiveRecord::Base - has_many :replies, :include => [:user], :dependent => :destroy -end diff -r 0a574315af3e -r 4f746d8966dd lib/plugins/classic_pagination/test/fixtures/topics.yml --- a/lib/plugins/classic_pagination/test/fixtures/topics.yml Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -futurama: - id: 1 - title: Isnt futurama awesome? - subtitle: It really is, isnt it. - content: I like futurama - created_at: <%= 1.day.ago.to_s(:db) %> - updated_at: - -harvey_birdman: - id: 2 - title: Harvey Birdman is the king of all men - subtitle: yup - content: It really is - created_at: <%= 2.hours.ago.to_s(:db) %> - updated_at: - -rails: - id: 3 - title: Rails is nice - subtitle: It makes me happy - content: except when I have to hack internals to fix pagination. even then really. - created_at: <%= 20.minutes.ago.to_s(:db) %> diff -r 0a574315af3e -r 4f746d8966dd lib/plugins/classic_pagination/test/helper.rb --- a/lib/plugins/classic_pagination/test/helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,117 +0,0 @@ -require 'test/unit' - -unless defined?(ActiveRecord) - plugin_root = File.join(File.dirname(__FILE__), '..') - - # first look for a symlink to a copy of the framework - if framework_root = ["#{plugin_root}/rails", "#{plugin_root}/../../rails"].find { |p| File.directory? p } - puts "found framework root: #{framework_root}" - # this allows for a plugin to be tested outside an app - $:.unshift "#{framework_root}/activesupport/lib", "#{framework_root}/activerecord/lib", "#{framework_root}/actionpack/lib" - else - # is the plugin installed in an application? - app_root = plugin_root + '/../../..' - - if File.directory? app_root + '/config' - puts 'using config/boot.rb' - ENV['RAILS_ENV'] = 'test' - require File.expand_path(app_root + '/config/boot') - else - # simply use installed gems if available - puts 'using rubygems' - require 'rubygems' - gem 'actionpack'; gem 'activerecord' - end - end - - %w(action_pack active_record action_controller active_record/fixtures action_controller/test_process).each {|f| require f} - - Dependencies.load_paths.unshift "#{plugin_root}/lib" -end - -# Define the connector -class ActiveRecordTestConnector - cattr_accessor :able_to_connect - cattr_accessor :connected - - # Set our defaults - self.connected = false - self.able_to_connect = true - - class << self - def setup - unless self.connected || !self.able_to_connect - setup_connection - load_schema - require_fixture_models - self.connected = true - end - rescue Exception => e # errors from ActiveRecord setup - $stderr.puts "\nSkipping ActiveRecord assertion tests: #{e}" - #$stderr.puts " #{e.backtrace.join("\n ")}\n" - self.able_to_connect = false - end - - private - - def setup_connection - if Object.const_defined?(:ActiveRecord) - defaults = { :database => ':memory:' } - begin - options = defaults.merge :adapter => 'sqlite3', :timeout => 500 - ActiveRecord::Base.establish_connection(options) - ActiveRecord::Base.configurations = { 'sqlite3_ar_integration' => options } - ActiveRecord::Base.connection - rescue Exception # errors from establishing a connection - $stderr.puts 'SQLite 3 unavailable; trying SQLite 2.' - options = defaults.merge :adapter => 'sqlite' - ActiveRecord::Base.establish_connection(options) - ActiveRecord::Base.configurations = { 'sqlite2_ar_integration' => options } - ActiveRecord::Base.connection - end - - Object.send(:const_set, :QUOTED_TYPE, ActiveRecord::Base.connection.quote_column_name('type')) unless Object.const_defined?(:QUOTED_TYPE) - else - raise "Can't setup connection since ActiveRecord isn't loaded." - end - end - - # Load actionpack sqlite tables - def load_schema - File.read(File.dirname(__FILE__) + "/fixtures/schema.sql").split(';').each do |sql| - ActiveRecord::Base.connection.execute(sql) unless sql.blank? - end - end - - def require_fixture_models - Dir.glob(File.dirname(__FILE__) + "/fixtures/*.rb").each {|f| require f} - end - end -end - -# Test case for inheritance -class ActiveRecordTestCase < Test::Unit::TestCase - # Set our fixture path - if ActiveRecordTestConnector.able_to_connect - self.fixture_path = "#{File.dirname(__FILE__)}/fixtures/" - self.use_transactional_fixtures = false - end - - def self.fixtures(*args) - super if ActiveRecordTestConnector.connected - end - - def run(*args) - super if ActiveRecordTestConnector.connected - end - - # Default so Test::Unit::TestCase doesn't complain - def test_truth - end -end - -ActiveRecordTestConnector.setup -ActionController::Routing::Routes.reload rescue nil -ActionController::Routing::Routes.draw do |map| - map.connect ':controller/:action/:id' -end diff -r 0a574315af3e -r 4f746d8966dd lib/plugins/classic_pagination/test/pagination_helper_test.rb --- a/lib/plugins/classic_pagination/test/pagination_helper_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +0,0 @@ -require File.dirname(__FILE__) + '/helper' -require File.dirname(__FILE__) + '/../init' - -class PaginationHelperTest < Test::Unit::TestCase - include ActionController::Pagination - include ActionView::Helpers::PaginationHelper - include ActionView::Helpers::UrlHelper - include ActionView::Helpers::TagHelper - - def setup - @controller = Class.new do - attr_accessor :url, :request - def url_for(options, *parameters_for_method_reference) - url - end - end - @controller = @controller.new - @controller.url = "http://www.example.com" - end - - def test_pagination_links - total, per_page, page = 30, 10, 1 - output = pagination_links Paginator.new(@controller, total, per_page, page) - assert_equal "1 2 3 ", output - end - - def test_pagination_links_with_prefix - total, per_page, page = 30, 10, 1 - output = pagination_links Paginator.new(@controller, total, per_page, page), :prefix => 'Newer ' - assert_equal "Newer 1 2 3 ", output - end - - def test_pagination_links_with_suffix - total, per_page, page = 30, 10, 1 - output = pagination_links Paginator.new(@controller, total, per_page, page), :suffix => 'Older' - assert_equal "1 2 3 Older", output - end -end diff -r 0a574315af3e -r 4f746d8966dd lib/plugins/classic_pagination/test/pagination_test.rb --- a/lib/plugins/classic_pagination/test/pagination_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,177 +0,0 @@ -require File.dirname(__FILE__) + '/helper' -require File.dirname(__FILE__) + '/../init' - -class PaginationTest < ActiveRecordTestCase - fixtures :topics, :replies, :developers, :projects, :developers_projects - - class PaginationController < ActionController::Base - if respond_to? :view_paths= - self.view_paths = [ "#{File.dirname(__FILE__)}/../fixtures/" ] - else - self.template_root = [ "#{File.dirname(__FILE__)}/../fixtures/" ] - end - - def simple_paginate - @topic_pages, @topics = paginate(:topics) - render :nothing => true - end - - def paginate_with_per_page - @topic_pages, @topics = paginate(:topics, :per_page => 1) - render :nothing => true - end - - def paginate_with_order - @topic_pages, @topics = paginate(:topics, :order => 'created_at asc') - render :nothing => true - end - - def paginate_with_order_by - @topic_pages, @topics = paginate(:topics, :order_by => 'created_at asc') - render :nothing => true - end - - def paginate_with_include_and_order - @topic_pages, @topics = paginate(:topics, :include => :replies, :order => 'replies.created_at asc, topics.created_at asc') - render :nothing => true - end - - def paginate_with_conditions - @topic_pages, @topics = paginate(:topics, :conditions => ["created_at > ?", 30.minutes.ago]) - render :nothing => true - end - - def paginate_with_class_name - @developer_pages, @developers = paginate(:developers, :class_name => "DeVeLoPeR") - render :nothing => true - end - - def paginate_with_singular_name - @developer_pages, @developers = paginate() - render :nothing => true - end - - def paginate_with_joins - @developer_pages, @developers = paginate(:developers, - :joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id', - :conditions => 'project_id=1') - render :nothing => true - end - - def paginate_with_join - @developer_pages, @developers = paginate(:developers, - :join => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id', - :conditions => 'project_id=1') - render :nothing => true - end - - def paginate_with_join_and_count - @developer_pages, @developers = paginate(:developers, - :join => 'd LEFT JOIN developers_projects ON d.id = developers_projects.developer_id', - :conditions => 'project_id=1', - :count => "d.id") - render :nothing => true - end - - def paginate_with_join_and_group - @developer_pages, @developers = paginate(:developers, - :join => 'INNER JOIN developers_projects ON developers.id = developers_projects.developer_id', - :group => 'developers.id') - render :nothing => true - end - - def rescue_errors(e) raise e end - - def rescue_action(e) raise end - - end - - def setup - @controller = PaginationController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - super - end - - # Single Action Pagination Tests - - def test_simple_paginate - get :simple_paginate - assert_equal 1, assigns(:topic_pages).page_count - assert_equal 3, assigns(:topics).size - end - - def test_paginate_with_per_page - get :paginate_with_per_page - assert_equal 1, assigns(:topics).size - assert_equal 3, assigns(:topic_pages).page_count - end - - def test_paginate_with_order - get :paginate_with_order - expected = [topics(:futurama), - topics(:harvey_birdman), - topics(:rails)] - assert_equal expected, assigns(:topics) - assert_equal 1, assigns(:topic_pages).page_count - end - - def test_paginate_with_order_by - get :paginate_with_order - expected = assigns(:topics) - get :paginate_with_order_by - assert_equal expected, assigns(:topics) - assert_equal 1, assigns(:topic_pages).page_count - end - - def test_paginate_with_conditions - get :paginate_with_conditions - expected = [topics(:rails)] - assert_equal expected, assigns(:topics) - assert_equal 1, assigns(:topic_pages).page_count - end - - def test_paginate_with_class_name - get :paginate_with_class_name - - assert assigns(:developers).size > 0 - assert_equal DeVeLoPeR, assigns(:developers).first.class - end - - def test_paginate_with_joins - get :paginate_with_joins - assert_equal 2, assigns(:developers).size - developer_names = assigns(:developers).map { |d| d.name } - assert developer_names.include?('David') - assert developer_names.include?('Jamis') - end - - def test_paginate_with_join_and_conditions - get :paginate_with_joins - expected = assigns(:developers) - get :paginate_with_join - assert_equal expected, assigns(:developers) - end - - def test_paginate_with_join_and_count - get :paginate_with_joins - expected = assigns(:developers) - get :paginate_with_join_and_count - assert_equal expected, assigns(:developers) - end - - def test_paginate_with_include_and_order - get :paginate_with_include_and_order - expected = Topic.find(:all, :include => 'replies', :order => 'replies.created_at asc, topics.created_at asc', :limit => 10) - assert_equal expected, assigns(:topics) - end - - def test_paginate_with_join_and_group - get :paginate_with_join_and_group - assert_equal 2, assigns(:developers).size - assert_equal 2, assigns(:developer_pages).item_count - developer_names = assigns(:developers).map { |d| d.name } - assert developer_names.include?('David') - assert developer_names.include?('Jamis') - end -end diff -r 0a574315af3e -r 4f746d8966dd lib/plugins/rfpdf/lib/tcpdf.rb --- a/lib/plugins/rfpdf/lib/tcpdf.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/plugins/rfpdf/lib/tcpdf.rb Fri Jun 14 09:28:30 2013 +0100 @@ -39,9 +39,6 @@ # @package com.tecnick.tcpdf # -@@version = "1.53.0.TC031" -@@fpdf_charwidths = {} - PDF_PRODUCER = 'TCPDF via RFPDF 1.53.0.TC031 (http://tcpdf.sourceforge.net)' module TCPDFFontDescriptor @@ -79,6 +76,9 @@ Rails.logger end + @@version = "1.53.0.TC031" + @@fpdf_charwidths = {} + cattr_accessor :k_cell_height_ratio @@k_cell_height_ratio = 1.25 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine.rb --- a/lib/redmine.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,16 +1,21 @@ -require 'redmine/access_control' -require 'redmine/menu_manager' -require 'redmine/activity' -require 'redmine/search' -require 'redmine/custom_field_format' -require 'redmine/mime_type' +# Redmine - project management software +# Copyright (C) 2006-2013 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + require 'redmine/core_ext' -require 'redmine/themes' -require 'redmine/hook' -require 'redmine/plugin' -require 'redmine/notifiable' -require 'redmine/wiki_formatting' -require 'redmine/scm/base' begin require 'RMagick' unless Object.const_defined?(:Magick) @@ -18,6 +23,41 @@ # RMagick is not available end +require 'redmine/scm/base' +require 'redmine/access_control' +require 'redmine/access_keys' +require 'redmine/activity' +require 'redmine/activity/fetcher' +require 'redmine/ciphering' +require 'redmine/codeset_util' +require 'redmine/custom_field_format' +require 'redmine/i18n' +require 'redmine/menu_manager' +require 'redmine/notifiable' +require 'redmine/platform' +require 'redmine/mime_type' +require 'redmine/notifiable' +require 'redmine/search' +require 'redmine/syntax_highlighting' +require 'redmine/thumbnail' +require 'redmine/unified_diff' +require 'redmine/utils' +require 'redmine/version' +require 'redmine/wiki_formatting' + +require 'redmine/default_data/loader' +require 'redmine/helpers/calendar' +require 'redmine/helpers/diff' +require 'redmine/helpers/gantt' +require 'redmine/helpers/time_report' +require 'redmine/views/other_formats_builder' +require 'redmine/views/labelled_form_builder' +require 'redmine/views/builders' + +require 'redmine/themes' +require 'redmine/hook' +require 'redmine/plugin' + if RUBY_VERSION < '1.9' require 'fastercsv' else @@ -75,7 +115,7 @@ map.permission :manage_subtasks, {} map.permission :set_issues_private, {} map.permission :set_own_issues_private, {}, :require => :loggedin - map.permission :add_issue_notes, {:issues => [:edit, :update], :journals => [:new], :attachments => :upload} + map.permission :add_issue_notes, {:issues => [:edit, :update, :update_form], :journals => [:new], :attachments => :upload} map.permission :edit_issue_notes, {:journals => :edit}, :require => :loggedin map.permission :edit_own_issue_notes, {:journals => :edit}, :require => :loggedin map.permission :view_private_notes, {}, :read => true, :require => :member @@ -87,7 +127,7 @@ map.permission :save_queries, {:queries => [:new, :create, :edit, :update, :destroy]}, :require => :loggedin # Watchers map.permission :view_issue_watchers, {}, :read => true - map.permission :add_issue_watchers, {:watchers => :new} + map.permission :add_issue_watchers, {:watchers => [:new, :create, :append, :autocomplete_for_user]} map.permission :delete_issue_watchers, {:watchers => :destroy} end @@ -106,7 +146,9 @@ end map.project_module :documents do |map| - map.permission :manage_documents, {:documents => [:new, :create, :edit, :update, :destroy, :add_attachment]}, :require => :loggedin + map.permission :add_documents, {:documents => [:new, :create, :add_attachment]}, :require => :loggedin + map.permission :edit_documents, {:documents => [:edit, :update, :add_attachment]}, :require => :loggedin + map.permission :delete_documents, {:documents => [:destroy]}, :require => :loggedin map.permission :view_documents, {:documents => [:index, :show, :download]}, :read => true end @@ -166,7 +208,7 @@ menu.push :login, :signin_path, :if => Proc.new { !User.current.logged? } menu.push :register, :register_path, :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? } + menu.push :logout, :signout_path, :html => {:method => 'post'}, :if => Proc.new { User.current.logged? } end Redmine::MenuManager.map :application_menu do |menu| diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/about.rb --- a/lib/redmine/about.rb Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,16 +0,0 @@ -module Redmine - class About - def self.print_plugin_info - plugins = Redmine::Plugin.registered_plugins - - if !plugins.empty? - column_with = plugins.map {|internal_name, plugin| plugin.name.length}.max - puts "\nAbout your Redmine plugins" - - plugins.each do |internal_name, plugin| - puts sprintf("%-#{column_with}s %s", plugin.name, plugin.version) - end - end - end - end -end diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/access_control.rb --- a/lib/redmine/access_control.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/access_control.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/access_keys.rb --- a/lib/redmine/access_keys.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/access_keys.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/activity.rb --- a/lib/redmine/activity.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/activity.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/activity/fetcher.rb --- a/lib/redmine/activity/fetcher.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/activity/fetcher.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/ciphering.rb --- a/lib/redmine/ciphering.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/ciphering.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/codeset_util.rb --- a/lib/redmine/codeset_util.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/codeset_util.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,4 +1,6 @@ -require 'iconv' +if RUBY_VERSION < '1.9' + require 'iconv' +end module Redmine module CodesetUtil @@ -100,10 +102,20 @@ end encodings = Setting.repositories_encodings.split(',').collect(&:strip) encodings.each do |encoding| - begin - return Iconv.conv('UTF-8', encoding, str) - rescue Iconv::Failure - # do nothing here and try the next encoding + if str.respond_to?(:force_encoding) + begin + str.force_encoding(encoding) + utf8 = str.encode('UTF-8') + return utf8 if utf8.valid_encoding? + rescue + # do nothing here and try the next encoding + end + else + begin + return Iconv.conv('UTF-8', encoding, str) + rescue Iconv::Failure + # do nothing here and try the next encoding + end end end str = self.replace_invalid_utf8(str) diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/configuration.rb --- a/lib/redmine/configuration.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/configuration.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -20,7 +20,8 @@ # Configuration default values @defaults = { - 'email_delivery' => nil + 'email_delivery' => nil, + 'max_concurrent_ajax_uploads' => 2 } @config = nil diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/core_ext/active_record.rb --- a/lib/redmine/core_ext/active_record.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/core_ext/active_record.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -38,3 +38,15 @@ end end end + +class DateValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + before_type_cast = record.attributes_before_type_cast[attribute.to_s] + if before_type_cast.is_a?(String) && before_type_cast.present? + # TODO: #*_date_before_type_cast returns a Mysql::Time with ruby1.8+mysql gem + unless before_type_cast =~ /\A\d{4}-\d{2}-\d{2}( 00:00:00)?\z/ && value + record.errors.add attribute, :not_a_date + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/core_ext/date/calculations.rb --- a/lib/redmine/core_ext/date/calculations.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/core_ext/date/calculations.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/core_ext/string/conversions.rb --- a/lib/redmine/core_ext/string/conversions.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/core_ext/string/conversions.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/core_ext/string/inflections.rb --- a/lib/redmine/core_ext/string/inflections.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/core_ext/string/inflections.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/custom_field_format.rb --- a/lib/redmine/custom_field_format.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/custom_field_format.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/default_data/loader.rb --- a/lib/redmine/default_data/loader.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/default_data/loader.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,10 @@ # Returns true if no data is already loaded in the database # otherwise false def no_data? - !Role.find(:first, :conditions => {:builtin => 0}) && - !Tracker.find(:first) && - !IssueStatus.find(:first) && - !Enumeration.find(:first) + !Role.where(:builtin => 0).exists? && + !Tracker.exists? && + !IssueStatus.exists? && + !Enumeration.exists? end # Loads the default data @@ -139,15 +139,15 @@ rejected = IssueStatus.create!(:name => l(:default_issue_status_rejected), :is_closed => true, :is_default => false, :position => 6) # Workflow - Tracker.find(:all).each { |t| - IssueStatus.find(:all).each { |os| - IssueStatus.find(:all).each { |ns| + Tracker.all.each { |t| + IssueStatus.all.each { |os| + IssueStatus.all.each { |ns| WorkflowTransition.create!(:tracker_id => t.id, :role_id => manager.id, :old_status_id => os.id, :new_status_id => ns.id) unless os == ns } } } - Tracker.find(:all).each { |t| + Tracker.all.each { |t| [new, in_progress, resolved, feedback].each { |os| [in_progress, resolved, feedback, closed].each { |ns| WorkflowTransition.create!(:tracker_id => t.id, :role_id => developer.id, :old_status_id => os.id, :new_status_id => ns.id) unless os == ns @@ -155,7 +155,7 @@ } } - Tracker.find(:all).each { |t| + Tracker.all.each { |t| [new, in_progress, resolved, feedback].each { |os| [closed].each { |ns| WorkflowTransition.create!(:tracker_id => t.id, :role_id => reporter.id, :old_status_id => os.id, :new_status_id => ns.id) unless os == ns diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/export/pdf.rb --- a/lib/redmine/export/pdf.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/export/pdf.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,12 +17,15 @@ # 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 'tcpdf' require 'fpdf/chinese' require 'fpdf/japanese' require 'fpdf/korean' +if RUBY_VERSION < '1.9' + require 'iconv' +end + module Redmine module Export module PDF @@ -86,7 +89,7 @@ def SetTitle(txt) txt = begin - utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt) + utf16txt = to_utf16(txt) hextxt = "" @@ -116,6 +119,15 @@ html end + # Encodes an UTF-8 string to UTF-16BE + def to_utf16(str) + if str.respond_to?(:encode) + str.encode('UTF-16BE') + else + Iconv.conv('UTF-16BE', 'UTF-8', str) + end + end + def RDMCell(w ,h=0, txt='', border=0, ln=0, align='', fill=0, link='') Cell(w, h, fix_text_encoding(txt), border, ln, align, fill, link) end @@ -160,7 +172,7 @@ def bookmark_title(txt) txt = begin - utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt) + utf16txt = to_utf16(txt) hextxt = "" @@ -368,7 +380,7 @@ col_width end - def render_table_header(pdf, query, col_width, row_height, col_id_width, table_width) + def render_table_header(pdf, query, col_width, row_height, table_width) # headers pdf.SetFontStyle('B',8) pdf.SetFillColor(230, 230, 230) @@ -377,13 +389,12 @@ base_x = pdf.GetX base_y = pdf.GetY max_height = issues_to_pdf_write_cells(pdf, query.inline_columns, col_width, row_height, true) - pdf.Rect(base_x, base_y, table_width + col_id_width, max_height, 'FD'); + pdf.Rect(base_x, base_y, table_width, max_height, 'FD'); pdf.SetXY(base_x, base_y); # write the cells on page - pdf.RDMCell(col_id_width, row_height, "#", "T", 0, 'C', 1) issues_to_pdf_write_cells(pdf, query.inline_columns, col_width, row_height, true) - issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width) + issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, 0, col_width) pdf.SetY(base_y + max_height); # rows @@ -405,22 +416,22 @@ # Landscape A4 = 210 x 297 mm page_height = 210 page_width = 297 + left_margin = 10 right_margin = 10 bottom_margin = 20 - col_id_width = 10 row_height = 4 # column widths - table_width = page_width - right_margin - 10 # fixed left margin + table_width = page_width - right_margin - left_margin col_width = [] unless query.inline_columns.empty? - col_width = calc_col_width(issues, query, table_width - col_id_width, pdf) + col_width = calc_col_width(issues, query, table_width, pdf) table_width = col_width.inject(0) {|s,v| s += v} end - # use full width if the description is displayed + # use full width if the description is displayed if table_width > 0 && query.has_column?(:description) - col_width = col_width.map {|w| w = w * (page_width - right_margin - 10 - col_id_width) / table_width} + col_width = col_width.map {|w| w * (page_width - right_margin - left_margin) / table_width} table_width = col_width.inject(0) {|s,v| s += v} end @@ -428,7 +439,7 @@ pdf.SetFontStyle('B',11) pdf.RDMCell(190,10, title) pdf.Ln - render_table_header(pdf, query, col_width, row_height, col_id_width, table_width) + render_table_header(pdf, query, col_width, row_height, table_width) previous_group = false issue_list(issues) do |issue, level| if query.grouped? && @@ -437,7 +448,7 @@ group_label = group.blank? ? 'None' : group.to_s.dup group_label << " (#{query.issue_count_by_group[group]})" pdf.Bookmark group_label, 0, -1 - pdf.RDMCell(table_width + col_id_width, row_height * 2, group_label, 1, 1, 'L') + pdf.RDMCell(table_width, row_height * 2, group_label, 1, 1, 'L') pdf.SetFontStyle('',8) previous_group = group end @@ -456,15 +467,14 @@ space_left = page_height - base_y - bottom_margin if max_height > space_left pdf.AddPage("L") - render_table_header(pdf, query, col_width, row_height, col_id_width, table_width) + render_table_header(pdf, query, col_width, row_height, table_width) base_x = pdf.GetX base_y = pdf.GetY end # write the cells on page - pdf.RDMCell(col_id_width, row_height, issue.id.to_s, "T", 0, 'C', 1) issues_to_pdf_write_cells(pdf, col_values, col_width, row_height) - issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width) + issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, 0, col_width) pdf.SetY(base_y + max_height); if query.has_column?(:description) && issue.description? @@ -501,9 +511,11 @@ end # Draw lines to close the row (MultiCell border drawing in not uniform) + # + # parameter "col_id_width" is not used. it is kept for compatibility. def issues_to_pdf_draw_borders(pdf, top_x, top_y, lower_y, - id_width, col_widths) - col_x = top_x + id_width + col_id_width, col_widths) + col_x = top_x pdf.Line(col_x, top_y, col_x, lower_y) # id right border col_widths.each do |width| col_x += width diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/helpers/calendar.rb --- a/lib/redmine/helpers/calendar.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/helpers/calendar.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/helpers/diff.rb --- a/lib/redmine/helpers/diff.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/helpers/diff.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/helpers/gantt.rb --- a/lib/redmine/helpers/gantt.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/helpers/gantt.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -23,6 +23,12 @@ include Redmine::I18n include Redmine::Utils::DateCalculation + # Relation types that are rendered + DRAW_TYPES = { + IssueRelation::TYPE_BLOCKS => { :landscape_margin => 16, :color => '#F34F4F' }, + IssueRelation::TYPE_PRECEDES => { :landscape_margin => 20, :color => '#628FEA' } + }.freeze + # :nodoc: # Some utility methods for the PDF export class PDF @@ -136,6 +142,20 @@ ) end + # Returns a hash of the relations between the issues that are present on the gantt + # and that should be displayed, grouped by issue ids. + def relations + return @relations if @relations + if issues.any? + issue_ids = issues.map(&:id) + @relations = IssueRelation. + where(:issue_from_id => issue_ids, :issue_to_id => issue_ids, :relation_type => DRAW_TYPES.keys). + group_by(&:issue_from_id) + else + @relations = {} + end + end + # Return all the project nodes that will be displayed def projects return @projects if @projects @@ -277,7 +297,6 @@ pdf_task(options, coords, :label => label, :markers => true, :height => 0.8) end else - ActiveRecord::Base.logger.debug "Gantt#line_for_project was not given a project with a start_date" '' end end @@ -289,10 +308,18 @@ html_class << 'icon icon-package ' html_class << (version.behind_schedule? ? 'version-behind-schedule' : '') << " " html_class << (version.overdue? ? 'version-overdue' : '') + html_class << ' version-closed' unless version.open? + if version.start_date && version.due_date && version.completed_pourcent + progress_date = calc_progress_date(version.start_date, + version.due_date, version.completed_pourcent) + html_class << ' behind-start-date' if progress_date < self.date_from + html_class << ' over-end-date' if progress_date > self.date_to + end s = view.link_to_version(version).html_safe subject = view.content_tag(:span, s, :class => html_class).html_safe - html_subject(options, subject, :css => "version-name") + html_subject(options, subject, :css => "version-name", + :id => "version-#{version.id}") when :image image_subject(options, version.to_s_with_project) when :pdf @@ -303,24 +330,24 @@ def line_for_version(version, options) # Skip versions that don't have a start_date - if version.is_a?(Version) && version.start_date && version.due_date + if version.is_a?(Version) && version.due_date && version.start_date options[:zoom] ||= 1 options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom] coords = coordinates(version.start_date, - version.due_date, version.completed_pourcent, + version.due_date, version.completed_percent, options[:zoom]) - label = "#{h version} #{h version.completed_pourcent.to_i.to_s}%" + label = "#{h version} #{h version.completed_percent.to_i.to_s}%" label = h("#{version.project} -") + label unless @project && @project == version.project case options[:format] when :html - html_task(options, coords, :css => "version task", :label => label, :markers => true) + html_task(options, coords, :css => "version task", + :label => label, :markers => true, :version => version) when :image image_task(options, coords, :label => label, :markers => true, :height => 3) when :pdf pdf_task(options, coords, :label => label, :markers => true, :height => 0.8) end else - ActiveRecord::Base.logger.debug "Gantt#line_for_version was not given a version with a start_date" '' end end @@ -336,6 +363,13 @@ css_classes << ' issue-overdue' if issue.overdue? css_classes << ' issue-behind-schedule' if issue.behind_schedule? css_classes << ' icon icon-issue' unless Setting.gravatar_enabled? && issue.assigned_to + css_classes << ' issue-closed' if issue.closed? + if issue.start_date && issue.due_before && issue.done_ratio + progress_date = calc_progress_date(issue.start_date, + issue.due_before, issue.done_ratio) + css_classes << ' behind-start-date' if progress_date < self.date_from + css_classes << ' over-end-date' if progress_date > self.date_to + end s = "".html_safe if issue.assigned_to.present? assigned_string = l(:field_assigned_to) + ": " + issue.assigned_to.name @@ -347,7 +381,7 @@ s << view.link_to_issue(issue).html_safe subject = view.content_tag(:span, s, :class => css_classes).html_safe html_subject(options, subject, :css => "issue-subject", - :title => issue.subject) + "\n" + :title => issue.subject, :id => "issue-#{issue.id}") + "\n" when :image image_subject(options, issue.subject) when :pdf @@ -378,7 +412,6 @@ pdf_task(options, coords, :label => label) end else - ActiveRecord::Base.logger.debug "GanttHelper#line_for_issue was not given an issue with a due_before" '' end end @@ -611,7 +644,7 @@ coords[:bar_end] = self.date_to - self.date_from + 1 end if progress - progress_date = start_date + (end_date - start_date + 1) * (progress / 100.0) + progress_date = calc_progress_date(start_date, end_date, progress) if progress_date > self.date_from && progress_date > start_date if progress_date < self.date_to coords[:bar_progress_end] = progress_date - self.date_from @@ -638,7 +671,11 @@ coords end - # Sorts a collection of issues by start_date, due_date, id for gantt rendering + def calc_progress_date(start_date, end_date, progress) + start_date + (end_date - start_date + 1) * (progress / 100.0) + end + + # TODO: Sorts a collection of issues by start_date, due_date, id for gantt rendering def sort_issues!(issues) issues.sort! { |a, b| gantt_issue_compare(a, b) } end @@ -678,9 +715,10 @@ def html_subject(params, subject, options={}) style = "position: absolute;top:#{params[:top]}px;left:#{params[:indent]}px;" style << "width:#{params[:subject_width] - params[:indent]}px;" if params[:subject_width] - output = view.content_tag('div', subject, + output = view.content_tag(:div, subject, :class => options[:css], :style => style, - :title => options[:title]) + :title => options[:title], + :id => options[:id]) @subjects << output output end @@ -705,6 +743,16 @@ params[:image].text(params[:indent], params[:top] + 2, subject) end + def issue_relations(issue) + rels = {} + if relations[issue.id] + relations[issue.id].each do |relation| + (rels[relation.relation_type] ||= []) << relation.issue_to_id + end + end + rels + end + def html_task(params, coords, options={}) output = '' # Renders the task bar, with progress and late @@ -714,9 +762,18 @@ style << "top:#{params[:top]}px;" style << "left:#{coords[:bar_start]}px;" style << "width:#{width}px;" - output << view.content_tag(:div, ' '.html_safe, - :style => style, - :class => "#{options[:css]} task_todo") + html_id = "task-todo-issue-#{options[:issue].id}" if options[:issue] + html_id = "task-todo-version-#{options[:version].id}" if options[:version] + content_opt = {:style => style, + :class => "#{options[:css]} task_todo", + :id => html_id} + if options[:issue] + rels = issue_relations(options[:issue]) + if rels.present? + content_opt[:data] = {"rels" => rels.to_json} + end + end + output << view.content_tag(:div, ' '.html_safe, content_opt) if coords[:bar_late_end] width = coords[:bar_late_end] - coords[:bar_start] - 2 style = "" @@ -733,9 +790,12 @@ style << "top:#{params[:top]}px;" style << "left:#{coords[:bar_start]}px;" style << "width:#{width}px;" + html_id = "task-done-issue-#{options[:issue].id}" if options[:issue] + html_id = "task-done-version-#{options[:version].id}" if options[:version] output << view.content_tag(:div, ' '.html_safe, :style => style, - :class => "#{options[:css]} task_done") + :class => "#{options[:css]} task_done", + :id => html_id) end end # Renders the markers diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/helpers/time_report.rb --- a/lib/redmine/helpers/time_report.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/helpers/time_report.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -18,9 +18,9 @@ module Redmine module Helpers class TimeReport - attr_reader :criteria, :columns, :from, :to, :hours, :total_hours, :periods + attr_reader :criteria, :columns, :hours, :total_hours, :periods - def initialize(project, issue, criteria, columns, from, to) + def initialize(project, issue, criteria, columns, time_entry_scope) @project = project @issue = issue @@ -30,8 +30,7 @@ @criteria = @criteria[0,3] @columns = (columns && %w(year month week day).include?(columns)) ? columns : 'month' - @from = from - @to = to + @scope = time_entry_scope run end @@ -44,15 +43,12 @@ def run unless @criteria.empty? - scope = TimeEntry.visible.spent_between(@from, @to) - if @issue - scope = scope.on_issue(@issue) - elsif @project - scope = scope.on_project(@project, Setting.display_subprojects_issues?) - end time_columns = %w(tyear tmonth tweek spent_on) @hours = [] - scope.sum(:hours, :include => :issue, :group => @criteria.collect{|criteria| @available_criteria[criteria][:sql]} + time_columns).each do |hash, hours| + @scope.sum(:hours, + :include => [:issue, :activity], + :group => @criteria.collect{|criteria| @available_criteria[criteria][:sql]} + time_columns, + :joins => @criteria.collect{|criteria| @available_criteria[criteria][:joins]}.compact).each do |hash, hours| h = {'hours' => hours} (@criteria + time_columns).each_with_index do |name, i| h[name] = hash[i] @@ -67,21 +63,17 @@ when 'month' row['month'] = "#{row['tyear']}-#{row['tmonth']}" when 'week' - row['week'] = "#{row['tyear']}-#{row['tweek']}" + row['week'] = "#{row['spent_on'].cwyear}-#{row['tweek']}" when 'day' row['day'] = "#{row['spent_on']}" end end - if @from.nil? - min = @hours.collect {|row| row['spent_on']}.min - @from = min ? min.to_date : Date.today - end + min = @hours.collect {|row| row['spent_on']}.min + @from = min ? min.to_date : Date.today - if @to.nil? - max = @hours.collect {|row| row['spent_on']}.max - @to = max ? max.to_date : Date.today - end + max = @hours.collect {|row| row['spent_on']}.max + @to = max ? max.to_date : Date.today @total_hours = @hours.inject(0) {|s,k| s = s + k['hours'].to_f} @@ -98,7 +90,7 @@ @periods << "#{date_from.year}-#{date_from.month}" date_from = (date_from + 1.month).at_beginning_of_month when 'week' - @periods << "#{date_from.year}-#{date_from.to_date.cweek}" + @periods << "#{date_from.to_date.cwyear}-#{date_from.to_date.cweek}" date_from = (date_from + 7.day).at_beginning_of_week when 'day' @periods << "#{date_from.to_date}" @@ -121,9 +113,9 @@ 'category' => {:sql => "#{Issue.table_name}.category_id", :klass => IssueCategory, :label => :field_category}, - 'member' => {:sql => "#{TimeEntry.table_name}.user_id", + 'user' => {:sql => "#{TimeEntry.table_name}.user_id", :klass => User, - :label => :label_member}, + :label => :label_user}, 'tracker' => {:sql => "#{Issue.table_name}.tracker_id", :klass => Tracker, :label => :label_tracker}, @@ -135,24 +127,19 @@ :label => :label_issue} } + # Add time entry custom fields + custom_fields = TimeEntryCustomField.all + # Add project custom fields + custom_fields += ProjectCustomField.all + # Add issue custom fields + custom_fields += (@project.nil? ? IssueCustomField.for_all : @project.all_issue_custom_fields) + # Add time entry activity custom fields + custom_fields += TimeEntryActivityCustomField.all + # Add list and boolean custom fields as available criteria - custom_fields = (@project.nil? ? IssueCustomField.for_all : @project.all_issue_custom_fields) custom_fields.select {|cf| %w(list bool).include? cf.field_format }.each do |cf| - @available_criteria["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Issue' AND c.customized_id = #{Issue.table_name}.id ORDER BY c.value LIMIT 1)", - :format => cf.field_format, - :label => cf.name} - end if @project - - # Add list and boolean time entry custom fields - TimeEntryCustomField.find(:all).select {|cf| %w(list bool).include? cf.field_format }.each do |cf| - @available_criteria["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'TimeEntry' AND c.customized_id = #{TimeEntry.table_name}.id ORDER BY c.value LIMIT 1)", - :format => cf.field_format, - :label => cf.name} - end - - # Add list and boolean time entry activity custom fields - TimeEntryActivityCustomField.find(:all).select {|cf| %w(list bool).include? cf.field_format }.each do |cf| - @available_criteria["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM #{CustomValue.table_name} c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Enumeration' AND c.customized_id = #{TimeEntry.table_name}.activity_id ORDER BY c.value LIMIT 1)", + @available_criteria["cf_#{cf.id}"] = {:sql => "#{cf.join_alias}.value", + :joins => cf.join_for_order_statement, :format => cf.field_format, :label => cf.name} end diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/hook.rb --- a/lib/redmine/hook.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/hook.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/i18n.rb --- a/lib/redmine/i18n.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/i18n.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/imap.rb --- a/lib/redmine/imap.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/imap.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/menu_manager.rb --- a/lib/redmine/menu_manager.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/menu_manager.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -190,20 +190,17 @@ # Checks if a user is allowed to access the menu item by: # + # * Checking the url target (project only) # * Checking the conditions of the item - # * Checking the url target (project only) def allowed_node?(node, user, project) + if project && user && !user.allowed_to?(node.url, project) + return false + end if node.condition && !node.condition.call(project) # Condition that doesn't pass return false end - - if project - return user && user.allowed_to?(node.url, project) - else - # outside a project, all menu items allowed - return true - end + return true end end diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/mime_type.rb --- a/lib/redmine/mime_type.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/mime_type.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/pagination.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/redmine/pagination.rb Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,244 @@ +# encoding: utf-8 +# +# Redmine - project management software +# Copyright (C) 2006-2013 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 Pagination + class Paginator + attr_reader :item_count, :per_page, :page, :page_param + + def initialize(*args) + if args.first.is_a?(ActionController::Base) + args.shift + ActiveSupport::Deprecation.warn "Paginator no longer takes a controller instance as the first argument. Remove it from #new arguments." + end + item_count, per_page, page, page_param = *args + + @item_count = item_count + @per_page = per_page + page = (page || 1).to_i + if page < 1 + page = 1 + end + @page = page + @page_param = page_param || :page + end + + def offset + (page - 1) * per_page + end + + def first_page + if item_count > 0 + 1 + end + end + + def previous_page + if page > 1 + page - 1 + end + end + + def next_page + if last_item < item_count + page + 1 + end + end + + def last_page + if item_count > 0 + (item_count - 1) / per_page + 1 + end + end + + def first_item + item_count == 0 ? 0 : (offset + 1) + end + + def last_item + l = first_item + per_page - 1 + l > item_count ? item_count : l + end + + def linked_pages + pages = [] + if item_count > 0 + pages += [first_page, page, last_page] + pages += ((page-2)..(page+2)).to_a.select {|p| p > first_page && p < last_page} + end + pages = pages.compact.uniq.sort + if pages.size > 1 + pages + else + [] + end + end + + def items_per_page + ActiveSupport::Deprecation.warn "Paginator#items_per_page will be removed. Use #per_page instead." + per_page + end + + def current + ActiveSupport::Deprecation.warn "Paginator#current will be removed. Use .offset instead of .current.offset." + self + end + end + + # Paginates the given scope or model. Returns a Paginator instance and + # the collection of objects for the current page. + # + # Options: + # :parameter name of the page parameter + # + # Examples: + # @user_pages, @users = paginate User.where(:status => 1) + # + def paginate(scope, options={}) + options = options.dup + finder_options = options.extract!( + :conditions, + :order, + :joins, + :include, + :select + ) + if scope.is_a?(Symbol) || finder_options.values.compact.any? + return deprecated_paginate(scope, finder_options, options) + end + + paginator = paginator(scope.count, options) + collection = scope.limit(paginator.per_page).offset(paginator.offset).to_a + + return paginator, collection + end + + def deprecated_paginate(arg, finder_options, options={}) + ActiveSupport::Deprecation.warn "#paginate with a Symbol and/or find options is depreceted and will be removed. Use a scope instead." + klass = arg.is_a?(Symbol) ? arg.to_s.classify.constantize : arg + scope = klass.scoped(finder_options) + paginate(scope, options) + end + + def paginator(item_count, options={}) + options.assert_valid_keys :parameter, :per_page + + page_param = options[:parameter] || :page + page = (params[page_param] || 1).to_i + per_page = options[:per_page] || per_page_option + Paginator.new(item_count, per_page, page, page_param) + end + + module Helper + include Redmine::I18n + + # Renders the pagination links for the given paginator. + # + # Options: + # :per_page_links if set to false, the "Per page" links are not rendered + # + def pagination_links_full(*args) + pagination_links_each(*args) do |text, parameters, options| + if block_given? + yield text, parameters, options + else + link_to text, params.merge(parameters), options + end + end + end + + # Yields the given block with the text and parameters + # for each pagination link and returns a string that represents the links + def pagination_links_each(paginator, count=nil, options={}, &block) + options.assert_valid_keys :per_page_links + + per_page_links = options.delete(:per_page_links) + per_page_links = false if count.nil? + page_param = paginator.page_param + + html = '' + if paginator.previous_page + # \xc2\xab(utf-8) = « + text = "\xc2\xab " + l(:label_previous) + html << yield(text, {page_param => paginator.previous_page}, :class => 'previous') + ' ' + end + + previous = nil + paginator.linked_pages.each do |page| + if previous && previous != page - 1 + html << content_tag('span', '...', :class => 'spacer') + ' ' + end + if page == paginator.page + html << content_tag('span', page.to_s, :class => 'current page') + else + html << yield(page.to_s, {page_param => page}, :class => 'page') + end + html << ' ' + previous = page + end + + if paginator.next_page + # \xc2\xbb(utf-8) = » + text = l(:label_next) + " \xc2\xbb" + html << yield(text, {page_param => paginator.next_page}, :class => 'next') + ' ' + end + + html << content_tag('span', "(#{paginator.first_item}-#{paginator.last_item}/#{paginator.item_count})", :class => 'items') + ' ' + + if per_page_links != false && links = per_page_links(paginator, &block) + html << content_tag('span', links.to_s, :class => 'per-page') + end + + html.html_safe + end + + # Renders the "Per page" links. + def per_page_links(paginator, &block) + values = per_page_options(paginator.per_page, paginator.item_count) + if values.any? + links = values.collect do |n| + if n == paginator.per_page + content_tag('span', n.to_s) + else + yield(n, :per_page => n, paginator.page_param => nil) + end + end + l(:label_display_per_page, links.join(', ')).html_safe + end + end + + def per_page_options(selected=nil, item_count=nil) + options = Setting.per_page_options_array + if item_count && options.any? + if item_count > options.first + max = options.detect {|value| value >= item_count} || item_count + else + max = item_count + end + options = options.select {|value| value <= max || value == selected} + end + if options.empty? || (options.size == 1 && options.first == selected) + [] + else + options + end + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/platform.rb --- a/lib/redmine/platform.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/platform.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/plugin.rb --- a/lib/redmine/plugin.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/plugin.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -145,6 +145,10 @@ File.join(self.class.public_directory, id.to_s) end + def to_param + id + end + def assets_directory File.join(directory, 'assets') end diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/pop3.rb --- a/lib/redmine/pop3.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/pop3.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/safe_attributes.rb --- a/lib/redmine/safe_attributes.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/safe_attributes.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/scm/adapters/abstract_adapter.rb --- a/lib/redmine/scm/adapters/abstract_adapter.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/scm/adapters/abstract_adapter.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,6 +17,10 @@ require 'cgi' +if RUBY_VERSION < '1.9' + require 'iconv' +end + module Redmine module Scm module Adapters @@ -214,13 +218,39 @@ Rails.logger end + # Path to the file where scm stderr output is logged + # Returns nil if the log file is not writable + def self.stderr_log_file + if @stderr_log_file.nil? + writable = false + path = Redmine::Configuration['scm_stderr_log_file'].presence + path ||= Rails.root.join("log/#{Rails.env}.scm.stderr.log").to_s + if File.exists?(path) + if File.file?(path) && File.writable?(path) + writable = true + else + logger.warn("SCM log file (#{path}) is not writable") + end + else + begin + File.open(path, "w") {} + writable = true + rescue => e + logger.warn("SCM log file (#{path}) cannot be created: #{e.message}") + end + end + @stderr_log_file = writable ? path : false + end + @stderr_log_file || nil + end + def self.shellout(cmd, options = {}, &block) if logger && logger.debug? logger.debug "Shelling out: #{strip_credential(cmd)}" - end - if Rails.env == 'development' - # Capture stderr when running in dev environment - cmd = "#{cmd} 2>>#{shell_quote(Rails.root.join('log/scm.stderr.log').to_s)}" + # Capture stderr in a log file + if stderr_log_file + cmd = "#{cmd} 2>>#{shell_quote(stderr_log_file)}" + end end begin mode = "r+" diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/scm/adapters/bazaar_adapter.rb --- a/lib/redmine/scm/adapters/bazaar_adapter.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/scm/adapters/bazaar_adapter.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -104,13 +104,13 @@ re = %r{^V\s+(#{Regexp.escape(prefix)})?(\/?)([^\/]+)(\/?)\s+(\S+)\r?$} io.each_line do |line| next unless line =~ re - name_locale = $3.strip + name_locale, slash, revision = $3.strip, $4, $5.strip name = scm_iconv('UTF-8', @path_encoding, name_locale) entries << Entry.new({:name => name, :path => ((path.empty? ? "" : "#{path}/") + name), - :kind => ($4.blank? ? 'file' : 'dir'), + :kind => (slash.blank? ? 'file' : 'dir'), :size => nil, - :lastrev => Revision.new(:revision => $5.strip) + :lastrev => Revision.new(:revision => revision) }) end end diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/scm/adapters/darcs_adapter.rb --- a/lib/redmine/scm/adapters/darcs_adapter.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/scm/adapters/darcs_adapter.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/scm/adapters/filesystem_adapter.rb --- a/lib/redmine/scm/adapters/filesystem_adapter.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/scm/adapters/filesystem_adapter.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 Jean-Philippe Lang # # FileSystem adapter # File written by Paul Rivier, at Demotera. diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/scm/adapters/git_adapter.rb --- a/lib/redmine/scm/adapters/git_adapter.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/scm/adapters/git_adapter.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/scm/adapters/mercurial_adapter.rb --- a/lib/redmine/scm/adapters/mercurial_adapter.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/scm/adapters/mercurial_adapter.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/scm/adapters/subversion_adapter.rb --- a/lib/redmine/scm/adapters/subversion_adapter.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/scm/adapters/subversion_adapter.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/scm/base.rb --- a/lib/redmine/scm/base.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/scm/base.rb Fri Jun 14 09:28:30 2013 +0100 @@ -4,7 +4,7 @@ class << self def all - @scms + @scms || [] end # Add a new SCM adapter and repository diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/search.rb --- a/lib/redmine/search.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/search.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/subclass_factory.rb --- a/lib/redmine/subclass_factory.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/subclass_factory.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/syntax_highlighting.rb --- a/lib/redmine/syntax_highlighting.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/syntax_highlighting.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -33,7 +33,6 @@ module CodeRay require 'coderay' - require 'coderay/helpers/file_type' class << self # Highlights +text+ as the content of +filename+ diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/themes.rb --- a/lib/redmine/themes.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/themes.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/thumbnail.rb --- a/lib/redmine/thumbnail.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/thumbnail.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/unified_diff.rb --- a/lib/redmine/unified_diff.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/unified_diff.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -28,17 +28,9 @@ lines = 0 @truncated = false diff_table = DiffTable.new(diff_type, diff_style) - diff.each do |line| - line_encoding = nil - if line.respond_to?(:force_encoding) - line_encoding = line.encoding - # TODO: UTF-16 and Japanese CP932 which is imcompatible with ASCII - # In Japan, diffrence between file path encoding - # and file contents encoding is popular. - line.force_encoding('ASCII-8BIT') - end - unless diff_table.add_line line - line.force_encoding(line_encoding) if line_encoding + diff.each do |line_raw| + line = Redmine::CodesetUtil.to_utf8_by_setting(line_raw) + unless diff_table.add_line(line) self << diff_table if diff_table.length > 0 diff_table = DiffTable.new(diff_type, diff_style) end @@ -83,7 +75,7 @@ @parsing = true end else - if line =~ /^[^\+\-\s@\\]/ + if line =~ %r{^[^\+\-\s@\\]} @parsing = false return false elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/ @@ -207,10 +199,20 @@ while starting < max && line_left[starting] == line_right[starting] starting += 1 end + if (! "".respond_to?(:force_encoding)) && starting < line_left.size + while line_left[starting].ord.between?(128, 191) && starting > 0 + starting -= 1 + end + end ending = -1 while ending >= -(max - starting) && line_left[ending] == line_right[ending] ending -= 1 end + if (! "".respond_to?(:force_encoding)) && ending > (-1 * line_left.size) + while line_left[ending].ord.between?(128, 191) && ending > -1 + ending -= 1 + end + end unless starting == 0 && ending == -1 [starting, ending] end @@ -268,6 +270,12 @@ private def line_to_html(line, offsets) + html = line_to_html_raw(line, offsets) + html.force_encoding('UTF-8') if html.respond_to?(:force_encoding) + html + end + + def line_to_html_raw(line, offsets) if offsets s = '' unless offsets.first == 0 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/utils.rb --- a/lib/redmine/utils.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/utils.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/version.rb --- a/lib/redmine/version.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/version.rb Fri Jun 14 09:28:30 2013 +0100 @@ -3,8 +3,8 @@ module Redmine module VERSION #:nodoc: MAJOR = 2 - MINOR = 2 - TINY = 4 + MINOR = 3 + TINY = 1 # Branch values: # * official release: nil diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/views/api_template_handler.rb --- a/lib/redmine/views/api_template_handler.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/views/api_template_handler.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/views/builders.rb --- a/lib/redmine/views/builders.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/views/builders.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,6 +15,9 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +require 'redmine/views/builders/json' +require 'redmine/views/builders/xml' + module Redmine module Views module Builders diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/views/builders/json.rb --- a/lib/redmine/views/builders/json.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/views/builders/json.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,7 +15,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -require 'blankslate' +require 'redmine/views/builders/structure' module Redmine module Views @@ -25,7 +25,10 @@ def initialize(request, response) super - self.jsonp = (request.params[:callback] || request.params[:jsonp]).to_s.gsub(/[^a-zA-Z0-9_]/, '') + callback = request.params[:callback] || request.params[:jsonp] + if callback && Setting.jsonp_enabled? + self.jsonp = callback.to_s.gsub(/[^a-zA-Z0-9_]/, '') + end end def output diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/views/builders/structure.rb --- a/lib/redmine/views/builders/structure.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/views/builders/structure.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/views/builders/xml.rb --- a/lib/redmine/views/builders/xml.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/views/builders/xml.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/views/labelled_form_builder.rb --- a/lib/redmine/views/labelled_form_builder.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/views/labelled_form_builder.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/views/my_page/block.rb --- a/lib/redmine/views/my_page/block.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/views/my_page/block.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/views/other_formats_builder.rb --- a/lib/redmine/views/other_formats_builder.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/views/other_formats_builder.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/wiki_formatting.rb --- a/lib/redmine/wiki_formatting.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/wiki_formatting.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -86,7 +86,7 @@ AUTO_LINK_RE = %r{ ( # leading text <\w+.*?>| # leading HTML tag, or - [^=<>!:'"/]| # leading punctuation, or + [\s\(\[,;]| # leading punctuation, or ^ # beginning of line ) ( @@ -95,7 +95,7 @@ (?:www\.) # www.* ) ( - (\S+?) # url + ([^<]\S*?) # url (\/)? # slash ) ((?:>)?|[^[:alnum:]_\=\/;\(\)]*?) # post diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/wiki_formatting/macros.rb --- a/lib/redmine/wiki_formatting/macros.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/wiki_formatting/macros.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/wiki_formatting/textile/formatter.rb --- a/lib/redmine/wiki_formatting/textile/formatter.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/wiki_formatting/textile/formatter.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/redmine/wiki_formatting/textile/helper.rb --- a/lib/redmine/wiki_formatting/textile/helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/redmine/wiki_formatting/textile/helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -23,10 +23,7 @@ heads_for_wiki_formatter # Is there a simple way to link to a public resource? url = "#{Redmine::Utils.relative_url_root}/help/wiki_syntax.html" - help_link = link_to(l(:setting_text_formatting), url, - :onclick => "window.open(\"#{ url }\", \"\", \"resizable=yes, location=no, width=300, height=640, menubar=no, status=no, scrollbars=yes\"); return false;") - - javascript_tag("var wikiToolbar = new jsToolBar(document.getElementById('#{field_id}')); wikiToolbar.setHelpLink('#{escape_javascript help_link}'); wikiToolbar.draw();") + javascript_tag("var wikiToolbar = new jsToolBar(document.getElementById('#{field_id}')); wikiToolbar.setHelpLink('#{escape_javascript url}'); wikiToolbar.draw();") end def initial_page_content(page) diff -r 0a574315af3e -r 4f746d8966dd lib/tasks/ci.rake --- a/lib/tasks/ci.rake Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/tasks/ci.rake Fri Jun 14 09:28:30 2013 +0100 @@ -8,72 +8,56 @@ Rake::Task["ci:teardown"].invoke end -# Tasks can be hooked into by redefining them in a plugin namespace :ci do - desc "Setup Redmine for a new build." + desc "Setup Redmine for a new build" task :setup do - Rake::Task["ci:dump_environment"].invoke Rake::Task["tmp:clear"].invoke - Rake::Task["db:create"].invoke + Rake::Task["log:clear"].invoke + Rake::Task["db:create:all"].invoke Rake::Task["db:migrate"].invoke Rake::Task["db:schema:dump"].invoke + Rake::Task["test:scm:setup:all"].invoke Rake::Task["test:scm:update"].invoke end desc "Build Redmine" task :build do Rake::Task["test"].invoke + #Rake::Task["test:ui"].invoke unless RUBY_VERSION < '1.9' end - # Use this to cleanup after building or run post-build analysis. desc "Finish the build" task :teardown do end +end - desc "Creates and configures the databases for the CI server" - task :database do - path = 'config/database.yml' - unless File.exists?(path) - database = ENV['DATABASE_ADAPTER'] - ruby = ENV['RUBY_VER'].gsub('.', '').gsub('-', '') - branch = ENV['BRANCH'].gsub('.', '').gsub('-', '') - dev_db_name = "ci_#{branch}_#{ruby}_dev" - test_db_name = "ci_#{branch}_#{ruby}_test" +desc "Creates database.yml for the CI server" +file 'config/database.yml' do + require 'yaml' + database = ENV['DATABASE_ADAPTER'] + ruby = ENV['RUBY_VER'].gsub('.', '').gsub('-', '') + branch = ENV['BRANCH'].gsub('.', '').gsub('-', '') + dev_db_name = "ci_#{branch}_#{ruby}_dev" + test_db_name = "ci_#{branch}_#{ruby}_test" - case database - when 'mysql' - raise "Error creating databases" unless - system(%|mysql -u jenkins --password=jenkins -e 'create database #{dev_db_name} character set utf8;'|) && - system(%|mysql -u jenkins --password=jenkins -e 'create database #{test_db_name} character set utf8;'|) - dev_conf = { 'adapter' => (RUBY_VERSION >= '1.9' ? 'mysql2' : 'mysql'), 'database' => dev_db_name, 'host' => 'localhost', 'username' => 'jenkins', 'password' => 'jenkins', 'encoding' => 'utf8' } - test_conf = { 'adapter' => (RUBY_VERSION >= '1.9' ? 'mysql2' : 'mysql'), 'database' => test_db_name, 'host' => 'localhost', 'username' => 'jenkins', 'password' => 'jenkins', 'encoding' => 'utf8' } - when 'postgresql' - raise "Error creating databases" unless - system(%|psql -U jenkins -d postgres -c "create database #{dev_db_name} owner jenkins encoding 'UTF8';"|) && - system(%|psql -U jenkins -d postgres -c "create database #{test_db_name} owner jenkins encoding 'UTF8';"|) - dev_conf = { 'adapter' => 'postgresql', 'database' => dev_db_name, 'host' => 'localhost', 'username' => 'jenkins', 'password' => 'jenkins' } - test_conf = { 'adapter' => 'postgresql', 'database' => test_db_name, 'host' => 'localhost', 'username' => 'jenkins', 'password' => 'jenkins' } - when 'sqlite3' - dev_conf = { 'adapter' => 'sqlite3', 'database' => "db/#{dev_db_name}.sqlite3" } - test_conf = { 'adapter' => 'sqlite3', 'database' => "db/#{test_db_name}.sqlite3" } - else - raise "Unknown database" - end - - File.open(path, 'w') do |f| - f.write YAML.dump({'development' => dev_conf, 'test' => test_conf}) - end - end + case database + when 'mysql' + dev_conf = {'adapter' => (RUBY_VERSION >= '1.9' ? 'mysql2' : 'mysql'), 'database' => dev_db_name, 'host' => 'localhost', 'username' => 'jenkins', 'password' => 'jenkins', 'encoding' => 'utf8'} + test_conf = dev_conf.merge('database' => test_db_name) + when 'postgresql' + dev_conf = {'adapter' => 'postgresql', 'database' => dev_db_name, 'host' => 'localhost', 'username' => 'jenkins', 'password' => 'jenkins'} + test_conf = dev_conf.merge('database' => test_db_name) + when 'sqlite3' + dev_conf = {'adapter' => 'sqlite3', 'database' => "db/#{dev_db_name}.sqlite3"} + test_conf = dev_conf.merge('database' => "db/#{test_db_name}.sqlite3") + when 'sqlserver' + dev_conf = {'adapter' => 'sqlserver', 'database' => dev_db_name, 'host' => 'mssqlserver', 'port' => 1433, 'username' => 'jenkins', 'password' => 'jenkins'} + test_conf = dev_conf.merge('database' => test_db_name) + else + abort "Unknown database" end - desc "Dump the environment information to a BUILD_ENVIRONMENT ENV variable for debugging" - task :dump_environment do - - ENV['BUILD_ENVIRONMENT'] = ['ruby -v', 'gem -v', 'gem list'].collect do |command| - result = `#{command}` - "$ #{command}\n#{result}" - end.join("\n") - + File.open('config/database.yml', 'w') do |f| + f.write YAML.dump({'development' => dev_conf, 'test' => test_conf}) end end - diff -r 0a574315af3e -r 4f746d8966dd lib/tasks/ciphering.rake --- a/lib/tasks/ciphering.rake Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/tasks/ciphering.rake Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/tasks/email.rake --- a/lib/tasks/email.rake Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/tasks/email.rake Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -29,6 +29,8 @@ create: create a user account no_permission_check=1 disable permission checking when receiving the email + no_account_notice=1 disable new user account notification + default_group=foo,bar adds created user to foo and bar groups Issue attributes control options: project=PROJECT identifier of the target project @@ -58,6 +60,8 @@ options[:allow_override] = ENV['allow_override'] if ENV['allow_override'] options[:unknown_user] = ENV['unknown_user'] if ENV['unknown_user'] options[:no_permission_check] = ENV['no_permission_check'] if ENV['no_permission_check'] + options[:no_account_notice] = ENV['no_account_notice'] if ENV['no_account_notice'] + options[:default_group] = ENV['default_group'] if ENV['default_group'] MailHandler.receive(STDIN.read, options) end @@ -73,6 +77,8 @@ create: create a user account no_permission_check=1 disable permission checking when receiving the email + no_account_notice=1 disable new user account notification + default_group=foo,bar adds created user to foo and bar groups Available IMAP options: host=HOST IMAP server host (default: 127.0.0.1) @@ -129,6 +135,8 @@ options[:allow_override] = ENV['allow_override'] if ENV['allow_override'] options[:unknown_user] = ENV['unknown_user'] if ENV['unknown_user'] options[:no_permission_check] = ENV['no_permission_check'] if ENV['no_permission_check'] + options[:no_account_notice] = ENV['no_account_notice'] if ENV['no_account_notice'] + options[:default_group] = ENV['default_group'] if ENV['default_group'] Redmine::IMAP.check(imap_options, options) end @@ -162,6 +170,8 @@ options[:allow_override] = ENV['allow_override'] if ENV['allow_override'] options[:unknown_user] = ENV['unknown_user'] if ENV['unknown_user'] options[:no_permission_check] = ENV['no_permission_check'] if ENV['no_permission_check'] + options[:no_account_notice] = ENV['no_account_notice'] if ENV['no_account_notice'] + options[:default_group] = ENV['default_group'] if ENV['default_group'] Redmine::POP3.check(pop_options, options) end diff -r 0a574315af3e -r 4f746d8966dd lib/tasks/locales.rake --- a/lib/tasks/locales.rake Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/tasks/locales.rake Fri Jun 14 09:28:30 2013 +0100 @@ -43,7 +43,15 @@ files = Dir.glob(File.join(dir,'*.{yaml,yml}')) files.sort.each do |file| puts "parsing #{file}..." - file_strings = YAML.load(File.read(file)) + file_strings = YAML.load_file(file) + unless file_strings.is_a?(Hash) + puts "#{file}: content is not a Hash (#{file_strings.class.name})" + next + end + unless file_strings.keys.size == 1 + puts "#{file}: content has multiple keys (#{file_strings.keys.size})" + next + end file_strings = file_strings[file_strings.keys.first] file_strings.each do |key, string| diff -r 0a574315af3e -r 4f746d8966dd lib/tasks/migrate_from_mantis.rake --- a/lib/tasks/migrate_from_mantis.rake Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/tasks/migrate_from_mantis.rake Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -18,7 +18,7 @@ desc 'Mantis migration script' require 'active_record' -require 'iconv' +require 'iconv' if RUBY_VERSION < '1.9' require 'pp' namespace :redmine do @@ -30,7 +30,7 @@ assigned_status = IssueStatus.find_by_position(2) resolved_status = IssueStatus.find_by_position(3) feedback_status = IssueStatus.find_by_position(4) - closed_status = IssueStatus.find :first, :conditions => { :is_closed => true } + closed_status = IssueStatus.where(:is_closed => true).first STATUS_MAPPING = {10 => DEFAULT_STATUS, # new 20 => feedback_status, # feedback 30 => DEFAULT_STATUS, # acknowledged @@ -53,7 +53,7 @@ TRACKER_BUG = Tracker.find_by_position(1) TRACKER_FEATURE = Tracker.find_by_position(2) - roles = Role.find(:all, :conditions => {:builtin => 0}, :order => 'position ASC') + roles = Role.where(:builtin => 0).order('position ASC').all manager_role = roles[0] developer_role = roles[1] DEFAULT_ROLE = roles.last @@ -241,7 +241,7 @@ User.delete_all "login <> 'admin'" users_map = {} users_migrated = 0 - MantisUser.find(:all).each do |user| + MantisUser.all.each do |user| u = User.new :firstname => encode(user.firstname), :lastname => encode(user.lastname), :mail => user.email, @@ -263,7 +263,7 @@ projects_map = {} versions_map = {} categories_map = {} - MantisProject.find(:all).each do |project| + MantisProject.all.each do |project| p = Project.new :name => encode(project.name), :description => encode(project.description) p.identifier = project.identifier @@ -347,7 +347,7 @@ bug.bug_files.each do |file| a = Attachment.new :created_on => file.date_added a.file = file - a.author = User.find :first + a.author = User.first a.container = i a.save end @@ -365,7 +365,7 @@ # Bug relationships print "Migrating bug relations" - MantisBugRelationship.find(:all).each do |relation| + MantisBugRelationship.all.each do |relation| next unless issues_map[relation.source_bug_id] && issues_map[relation.destination_bug_id] r = IssueRelation.new :relation_type => RELATION_TYPE_MAPPING[relation.relationship_type] r.issue_from = Issue.find_by_id(issues_map[relation.source_bug_id]) @@ -379,7 +379,7 @@ # News print "Migrating news" News.destroy_all - MantisNews.find(:all, :conditions => 'project_id > 0').each do |news| + MantisNews.where('project_id > 0').all.each do |news| next unless projects_map[news.project_id] n = News.new :project_id => projects_map[news.project_id], :title => encode(news.headline[0..59]), @@ -395,7 +395,7 @@ # Custom fields print "Migrating custom fields" IssueCustomField.destroy_all - MantisCustomField.find(:all).each do |field| + MantisCustomField.all.each do |field| f = IssueCustomField.new :name => field.name[0..29], :field_format => CUSTOM_FIELD_TYPE_MAPPING[field.format], :min_length => field.length_min, @@ -407,7 +407,7 @@ print '.' STDOUT.flush # Trackers association - f.trackers = Tracker.find :all + f.trackers = Tracker.all # Projects association field.projects.each do |project| @@ -440,9 +440,7 @@ end def self.encoding(charset) - @ic = Iconv.new('UTF-8', charset) - rescue Iconv::InvalidEncoding - return false + @charset = charset end def self.establish_connection(params) @@ -454,9 +452,12 @@ end def self.encode(text) - @ic.iconv text - rescue - text + if RUBY_VERSION < '1.9' + @ic ||= Iconv.new('UTF-8', @charset) + @ic.iconv text + else + text.to_s.force_encoding(@charset).encode('UTF-8') + end end end diff -r 0a574315af3e -r 4f746d8966dd lib/tasks/migrate_from_trac.rake --- a/lib/tasks/migrate_from_trac.rake Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/tasks/migrate_from_trac.rake Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,7 +16,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require 'active_record' -require 'iconv' +require 'iconv' if RUBY_VERSION < '1.9' require 'pp' namespace :redmine do @@ -30,7 +30,7 @@ assigned_status = IssueStatus.find_by_position(2) resolved_status = IssueStatus.find_by_position(3) feedback_status = IssueStatus.find_by_position(4) - closed_status = IssueStatus.find :first, :conditions => { :is_closed => true } + closed_status = IssueStatus.where(:is_closed => true).first STATUS_MAPPING = {'new' => DEFAULT_STATUS, 'reopened' => feedback_status, 'assigned' => assigned_status, @@ -61,7 +61,7 @@ 'patch' =>TRACKER_FEATURE } - roles = Role.find(:all, :conditions => {:builtin => 0}, :order => 'position ASC') + roles = Role.where(:builtin => 0).order('position ASC').all manager_role = roles[0] developer_role = roles[1] DEFAULT_ROLE = roles.last @@ -257,7 +257,7 @@ u.password = 'trac' u.admin = true if TracPermission.find_by_username_and_action(username, 'admin') # finally, a default user is used if the new user is not valid - u = User.find(:first) unless u.save + u = User.first unless u.save end # Make sure he is a member of the project if project_member && !u.member_of?(@target_project) @@ -390,7 +390,7 @@ # Components print "Migrating components" issues_category_map = {} - TracComponent.find(:all).each do |component| + TracComponent.all.each do |component| print '.' STDOUT.flush c = IssueCategory.new :project => @target_project, @@ -404,7 +404,7 @@ # Milestones print "Migrating milestones" version_map = {} - TracMilestone.find(:all).each do |milestone| + TracMilestone.all.each do |milestone| print '.' STDOUT.flush # First we try to find the wiki page... @@ -443,18 +443,18 @@ :field_format => 'string') next if f.new_record? - f.trackers = Tracker.find(:all) + f.trackers = Tracker.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.where(:name => "Resolution").first r = IssueCustomField.new(:name => 'Resolution', :field_format => 'list', :is_filter => true) if r.nil? - r.trackers = Tracker.find(:all) + r.trackers = Tracker.all r.projects << @target_project r.possible_values = (r.possible_values + %w(fixed invalid wontfix duplicate worksforme)).flatten.compact.uniq r.save! @@ -549,7 +549,7 @@ # Wiki print "Migrating wiki" if wiki.save - TracWikiPage.find(:all, :order => 'name, version').each do |page| + TracWikiPage.order('name, version').all.each do |page| # Do not migrate Trac manual wiki pages next if TRAC_WIKI_PAGES.include?(page.name) wiki_edit_count += 1 @@ -603,10 +603,7 @@ end def self.encoding(charset) - @ic = Iconv.new('UTF-8', charset) - rescue Iconv::InvalidEncoding - puts "Invalid encoding!" - return false + @charset = charset end def self.set_trac_directory(path) @@ -713,11 +710,13 @@ end end - private def self.encode(text) - @ic.iconv text - rescue - text + if RUBY_VERSION < '1.9' + @ic ||= Iconv.new('UTF-8', @charset) + @ic.iconv text + else + text.to_s.force_encoding(@charset).encode('UTF-8') + end end end diff -r 0a574315af3e -r 4f746d8966dd lib/tasks/redmine.rake --- a/lib/tasks/redmine.rake Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/tasks/redmine.rake Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -21,6 +21,11 @@ task :prune => :environment do Attachment.prune end + + desc 'Moves attachments stored at the root of the file directory (ie. created before Redmine 2.3) to their subdirectories' + task :move_to_subdirectories => :environment do + Attachment.move_from_root_to_target_directory + end end namespace :tokens do diff -r 0a574315af3e -r 4f746d8966dd lib/tasks/reminder.rake --- a/lib/tasks/reminder.rake Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/tasks/reminder.rake Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd lib/tasks/testing.rake --- a/lib/tasks/testing.rake Fri Jun 14 09:07:32 2013 +0100 +++ b/lib/tasks/testing.rake Fri Jun 14 09:28:30 2013 +0100 @@ -100,4 +100,11 @@ t.test_files = FileList['test/integration/routing/*_test.rb'] end Rake::Task['test:rdm_routing'].comment = "Run the routing tests" + + Rake::TestTask.new(:ui => "db:test:prepare") do |t| + t.libs << "test" + t.verbose = true + t.test_files = FileList['test/ui/**/*_test.rb'] + end + Rake::Task['test:ui'].comment = "Run the UI tests with Capybara (PhantomJS listening on port 4444 is required)" end diff -r 0a574315af3e -r 4f746d8966dd public/images/hourglass.png Binary file public/images/hourglass.png has changed diff -r 0a574315af3e -r 4f746d8966dd public/javascripts/application.js --- a/public/javascripts/application.js Fri Jun 14 09:07:32 2013 +0100 +++ b/public/javascripts/application.js Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ /* Redmine - project management software - Copyright (C) 2006-2012 Jean-Philippe Lang */ + Copyright (C) 2006-2013 Jean-Philippe Lang */ function checkAll(id, checked) { if (checked) { @@ -14,12 +14,12 @@ $(selector).each(function(index) { if (!$(this).is(':checked')) { all_checked = false; } }); - $(selector).attr('checked', !all_checked) + $(selector).attr('checked', !all_checked); } function showAndScrollTo(id, focus) { $('#'+id).show(); - if (focus!=null) { + if (focus !== null) { $('#'+focus).focus(); } $('html, body').animate({scrollTop: $('#'+id).offset().top}, 100); @@ -131,10 +131,10 @@ select = tr.find('td.operator select'); for (i=0;i').val(operators[i]).text(operatorLabels[operators[i]]); - if (operators[i] == operator) {option.attr('selected', true)}; + if (operators[i] == operator) { option.attr('selected', true); } select.append(option); } - select.change(function(){toggleOperator(field)}); + select.change(function(){ toggleOperator(field); }); switch (filterOptions['type']){ case "list": @@ -146,7 +146,7 @@ '  ' ); select = tr.find('td.values select'); - if (values.length > 1) {select.attr('multiple', true)}; + if (values.length > 1) { select.attr('multiple', true); } for (i=0;i'); @@ -189,7 +189,7 @@ var filterValue = allProjects[i]; var option = $('

      ').insertAfter(fileSpan.find('input.filename')); + progressSpan.progressbar(); + fileSpan.addClass('ajax-waiting'); + + var maxSyncUpload = $(inputEl).data('max-concurrent-uploads'); + + if(maxSyncUpload == null || maxSyncUpload <= 0 || ajaxUpload.uploading < maxSyncUpload) + actualUpload(file, attachmentId, fileSpan, inputEl); + else + $(inputEl).parents('form').queue('upload', actualUpload.bind(this, file, attachmentId, fileSpan, inputEl)); +} + +ajaxUpload.uploading = 0; + +function removeFile() { + $(this).parent('span').remove(); + return false; +} + +function uploadBlob(blob, uploadUrl, attachmentId, options) { + + var actualOptions = $.extend({ + loadstartEventHandler: $.noop, + progressEventHandler: $.noop + }, options); + + uploadUrl = uploadUrl + '?attachment_id=' + attachmentId; + if (blob instanceof window.File) { + uploadUrl += '&filename=' + encodeURIComponent(blob.name); + } + + return $.ajax(uploadUrl, { + type: 'POST', + contentType: 'application/octet-stream', + beforeSend: function(jqXhr) { + jqXhr.setRequestHeader('Accept', 'application/js'); + }, + xhr: function() { + var xhr = $.ajaxSettings.xhr(); + xhr.upload.onloadstart = actualOptions.loadstartEventHandler; + xhr.upload.onprogress = actualOptions.progressEventHandler; + return xhr; + }, + data: blob, + cache: false, + processData: false + }); +} + +function addInputFiles(inputEl) { + var clearedFileInput = $(inputEl).clone().val(''); + + if (inputEl.files) { + // upload files using ajax + uploadAndAttachFiles(inputEl.files, inputEl); + $(inputEl).remove(); + } else { + // browser not supporting the file API, upload on form submission + var attachmentId; + var aFilename = inputEl.value.split(/\/|\\/); + attachmentId = addFile(inputEl, { name: aFilename[ aFilename.length - 1 ] }, false); + if (attachmentId) { + $(inputEl).attr({ name: 'attachments[' + attachmentId + '][file]', style: 'display:none;' }).appendTo('#attachments_' + attachmentId); + } + } + + clearedFileInput.insertAfter('#attachments_fields'); +} + +function uploadAndAttachFiles(files, inputEl) { + + var maxFileSize = $(inputEl).data('max-file-size'); + var maxFileSizeExceeded = $(inputEl).data('max-file-size-message'); + + var sizeExceeded = false; + $.each(files, function() { + if (this.size && maxFileSize && this.size > parseInt(maxFileSize)) {sizeExceeded=true;} + }); + if (sizeExceeded) { + window.alert(maxFileSizeExceeded); + } else { + $.each(files, function() {addFile(inputEl, this, true);}); + } +} + +function handleFileDropEvent(e) { + + $(this).removeClass('fileover'); + blockEventPropagation(e); + + if ($.inArray('Files', e.dataTransfer.types) > -1) { + uploadAndAttachFiles(e.dataTransfer.files, $('input:file.file_selector')); + } +} + +function dragOverHandler(e) { + $(this).addClass('fileover'); + blockEventPropagation(e); +} + +function dragOutHandler(e) { + $(this).removeClass('fileover'); + blockEventPropagation(e); +} + +function setupFileDrop() { + if (window.File && window.FileList && window.ProgressEvent && window.FormData) { + + $.event.fixHooks.drop = { props: [ 'dataTransfer' ] }; + + $('form div.box').has('input:file').each(function() { + $(this).on({ + dragover: dragOverHandler, + dragleave: dragOutHandler, + drop: handleFileDropEvent + }); + }); + } +} + +$(document).ready(setupFileDrop); diff -r 0a574315af3e -r 4f746d8966dd public/javascripts/context_menu.js --- a/public/javascripts/context_menu.js Fri Jun 14 09:07:32 2013 +0100 +++ b/public/javascripts/context_menu.js Fri Jun 14 09:28:30 2013 +0100 @@ -186,7 +186,7 @@ function contextMenuClearDocumentSelection() { // TODO if (document.selection) { - document.selection.clear(); // IE + document.selection.empty(); // IE } else { window.getSelection().removeAllRanges(); } diff -r 0a574315af3e -r 4f746d8966dd public/javascripts/gantt.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/javascripts/gantt.js Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,172 @@ +var draw_gantt = null; +var draw_top; +var draw_right; +var draw_left; + +var rels_stroke_width = 2; + +function setDrawArea() { + draw_top = $("#gantt_draw_area").position().top; + draw_right = $("#gantt_draw_area").width(); + draw_left = $("#gantt_area").scrollLeft(); +} + +function getRelationsArray() { + var arr = new Array(); + $.each($('div.task_todo[data-rels]'), function(index_div, element) { + var element_id = $(element).attr("id"); + if (element_id != null) { + var issue_id = element_id.replace("task-todo-issue-", ""); + var data_rels = $(element).data("rels"); + for (rel_type_key in data_rels) { + $.each(data_rels[rel_type_key], function(index_issue, element_issue) { + arr.push({issue_from: issue_id, issue_to: element_issue, + rel_type: rel_type_key}); + }); + } + } + }); + return arr; +} + +function drawRelations() { + var arr = getRelationsArray(); + $.each(arr, function(index_issue, element_issue) { + var issue_from = $("#task-todo-issue-" + element_issue["issue_from"]); + var issue_to = $("#task-todo-issue-" + element_issue["issue_to"]); + if (issue_from.size() == 0 || issue_to.size() == 0) { + return; + } + var issue_height = issue_from.height(); + var issue_from_top = issue_from.position().top + (issue_height / 2) - draw_top; + var issue_from_right = issue_from.position().left + issue_from.width(); + var issue_to_top = issue_to.position().top + (issue_height / 2) - draw_top; + var issue_to_left = issue_to.position().left; + var color = issue_relation_type[element_issue["rel_type"]]["color"]; + var landscape_margin = issue_relation_type[element_issue["rel_type"]]["landscape_margin"]; + var issue_from_right_rel = issue_from_right + landscape_margin; + var issue_to_left_rel = issue_to_left - landscape_margin; + draw_gantt.path(["M", issue_from_right + draw_left, issue_from_top, + "L", issue_from_right_rel + draw_left, issue_from_top]) + .attr({stroke: color, + "stroke-width": rels_stroke_width + }); + if (issue_from_right_rel < issue_to_left_rel) { + draw_gantt.path(["M", issue_from_right_rel + draw_left, issue_from_top, + "L", issue_from_right_rel + draw_left, issue_to_top]) + .attr({stroke: color, + "stroke-width": rels_stroke_width + }); + draw_gantt.path(["M", issue_from_right_rel + draw_left, issue_to_top, + "L", issue_to_left + draw_left, issue_to_top]) + .attr({stroke: color, + "stroke-width": rels_stroke_width + }); + } else { + var issue_middle_top = issue_to_top + + (issue_height * + ((issue_from_top > issue_to_top) ? 1 : -1)); + draw_gantt.path(["M", issue_from_right_rel + draw_left, issue_from_top, + "L", issue_from_right_rel + draw_left, issue_middle_top]) + .attr({stroke: color, + "stroke-width": rels_stroke_width + }); + draw_gantt.path(["M", issue_from_right_rel + draw_left, issue_middle_top, + "L", issue_to_left_rel + draw_left, issue_middle_top]) + .attr({stroke: color, + "stroke-width": rels_stroke_width + }); + draw_gantt.path(["M", issue_to_left_rel + draw_left, issue_middle_top, + "L", issue_to_left_rel + draw_left, issue_to_top]) + .attr({stroke: color, + "stroke-width": rels_stroke_width + }); + draw_gantt.path(["M", issue_to_left_rel + draw_left, issue_to_top, + "L", issue_to_left + draw_left, issue_to_top]) + .attr({stroke: color, + "stroke-width": rels_stroke_width + }); + } + draw_gantt.path(["M", issue_to_left + draw_left, issue_to_top, + "l", -4 * rels_stroke_width, -2 * rels_stroke_width, + "l", 0, 4 * rels_stroke_width, "z"]) + .attr({stroke: "none", + fill: color, + "stroke-linecap": "butt", + "stroke-linejoin": "miter" + }); + }); +} + +function getProgressLinesArray() { + var arr = new Array(); + var today_left = $('#today_line').position().left; + arr.push({left: today_left, top: 0}); + $.each($('div.issue-subject, div.version-name'), function(index, element) { + var t = $(element).position().top - draw_top ; + var h = ($(element).height() / 9); + var element_top_upper = t - h; + var element_top_center = t + (h * 3); + var element_top_lower = t + (h * 8); + var issue_closed = $(element).children('span').hasClass('issue-closed'); + var version_closed = $(element).children('span').hasClass('version-closed'); + if (issue_closed || version_closed) { + arr.push({left: today_left, top: element_top_center}); + } else { + var issue_done = $("#task-done-" + $(element).attr("id")); + var is_behind_start = $(element).children('span').hasClass('behind-start-date'); + var is_over_end = $(element).children('span').hasClass('over-end-date'); + if (is_over_end) { + arr.push({left: draw_right, top: element_top_upper, is_right_edge: true}); + arr.push({left: draw_right, top: element_top_lower, is_right_edge: true, none_stroke: true}); + } else if (issue_done.size() > 0) { + var done_left = issue_done.first().position().left + + issue_done.first().width(); + arr.push({left: done_left, top: element_top_center}); + } else if (is_behind_start) { + arr.push({left: 0 , top: element_top_upper, is_left_edge: true}); + arr.push({left: 0 , top: element_top_lower, is_left_edge: true, none_stroke: true}); + } else { + var todo_left = today_left; + var issue_todo = $("#task-todo-" + $(element).attr("id")); + if (issue_todo.size() > 0){ + todo_left = issue_todo.first().position().left; + } + arr.push({left: Math.min(today_left, todo_left), top: element_top_center}); + } + } + }); + return arr; +} + +function drawGanttProgressLines() { + var arr = getProgressLinesArray(); + var color = $("#today_line") + .css("border-left-color"); + var i; + for(i = 1 ; i < arr.length ; i++) { + if (!("none_stroke" in arr[i]) && + (!("is_right_edge" in arr[i - 1] && "is_right_edge" in arr[i]) && + !("is_left_edge" in arr[i - 1] && "is_left_edge" in arr[i])) + ) { + var x1 = (arr[i - 1].left == 0) ? 0 : arr[i - 1].left + draw_left; + var x2 = (arr[i].left == 0) ? 0 : arr[i].left + draw_left; + draw_gantt.path(["M", x1, arr[i - 1].top, + "L", x2, arr[i].top]) + .attr({stroke: color, "stroke-width": 2}); + } + } +} + +function drawGanttHandler() { + var folder = document.getElementById('gantt_draw_area'); + if(draw_gantt != null) + draw_gantt.clear(); + else + draw_gantt = Raphael(folder); + setDrawArea(); + if ($("#draw_progress_line").attr('checked')) + drawGanttProgressLines(); + if ($("#draw_rels").attr('checked')) + drawRelations(); +} diff -r 0a574315af3e -r 4f746d8966dd public/javascripts/i18n/jquery.ui.datepicker-az.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/javascripts/i18n/jquery.ui.datepicker-az.js Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,23 @@ +/* Azerbaijani (UTF-8) initialisation for the jQuery UI date picker plugin. */ +/* Written by Jamil Najafov (necefov33@gmail.com). */ +jQuery(function($) { + $.datepicker.regional['az'] = { + closeText: 'Bağla', + prevText: '<Geri', + nextText: 'İrəli>', + currentText: 'Bugün', + monthNames: ['Yanvar','Fevral','Mart','Aprel','May','İyun', + 'İyul','Avqust','Sentyabr','Oktyabr','Noyabr','Dekabr'], + monthNamesShort: ['Yan','Fev','Mar','Apr','May','İyun', + 'İyul','Avq','Sen','Okt','Noy','Dek'], + dayNames: ['Bazar','Bazar ertəsi','Çərşənbə axşamı','Çərşənbə','Cümə axşamı','Cümə','Şənbə'], + dayNamesShort: ['B','Be','Ça','Ç','Ca','C','Ş'], + dayNamesMin: ['B','B','Ç','С','Ç','C','Ş'], + weekHeader: 'Hf', + dateFormat: 'dd.mm.yy', + firstDay: 1, + isRTL: false, + showMonthAfterYear: false, + yearSuffix: ''}; + $.datepicker.setDefaults($.datepicker.regional['az']); +}); diff -r 0a574315af3e -r 4f746d8966dd public/javascripts/i18n/jquery.ui.datepicker-pt.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/javascripts/i18n/jquery.ui.datepicker-pt.js Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,22 @@ +/* Portuguese initialisation for the jQuery UI date picker plugin. */ +jQuery(function($){ + $.datepicker.regional['pt'] = { + closeText: 'Fechar', + prevText: '<Anterior', + nextText: 'Seguinte', + currentText: 'Hoje', + monthNames: ['Janeiro','Fevereiro','Março','Abril','Maio','Junho', + 'Julho','Agosto','Setembro','Outubro','Novembro','Dezembro'], + monthNamesShort: ['Jan','Fev','Mar','Abr','Mai','Jun', + 'Jul','Ago','Set','Out','Nov','Dez'], + dayNames: ['Domingo','Segunda-feira','Terça-feira','Quarta-feira','Quinta-feira','Sexta-feira','Sábado'], + dayNamesShort: ['Dom','Seg','Ter','Qua','Qui','Sex','Sáb'], + dayNamesMin: ['Dom','Seg','Ter','Qua','Qui','Sex','Sáb'], + weekHeader: 'Sem', + dateFormat: 'dd/mm/yy', + firstDay: 0, + isRTL: false, + showMonthAfterYear: false, + yearSuffix: ''}; + $.datepicker.setDefaults($.datepicker.regional['pt']); +}); diff -r 0a574315af3e -r 4f746d8966dd public/javascripts/jquery-1.7.2-ui-1.8.21-ujs-2.0.3.js --- a/public/javascripts/jquery-1.7.2-ui-1.8.21-ujs-2.0.3.js Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,133 +0,0 @@ -/*! jQuery v1.7.2 jquery.com | jquery.org/license */ -(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cu(a){if(!cj[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ck||(ck=c.createElement("iframe"),ck.frameBorder=ck.width=ck.height=0),b.appendChild(ck);if(!cl||!ck.createElement)cl=(ck.contentWindow||ck.contentDocument).document,cl.write((f.support.boxModel?"":"")+""),cl.close();d=cl.createElement(a),cl.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ck)}cj[a]=e}return cj[a]}function ct(a,b){var c={};f.each(cp.concat.apply([],cp.slice(0,b)),function(){c[this]=a});return c}function cs(){cq=b}function cr(){setTimeout(cs,0);return cq=f.now()}function ci(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ch(){try{return new a.XMLHttpRequest}catch(b){}}function cb(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){if(c!=="border")for(;e=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?+d:j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){if(typeof c!="string"||!c)return null;var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c
      <%= link_to h(tracker.name), edit_tracker_path(tracker) %><% unless tracker.workflow_rules.count > 0 %><%= l(:text_tracker_no_workflow) %> (<%= link_to l(:button_edit), {:controller => 'workflows', :action => 'edit', :tracker_id => tracker} %>)<% end %><%= reorder_links('tracker', {:action => 'update', :id => tracker}, :put) %> + <% unless tracker.workflow_rules.count > 0 %> + + <%= l(:text_tracker_no_workflow) %> (<%= link_to l(:button_edit), workflows_edit_path(:tracker_id => tracker) %>) + + <% end %> + + <%= reorder_links('tracker', {:action => 'update', :id => tracker}, :put) %> + <%= delete_link tracker_path(tracker) %>
      a",d=p.getElementsByTagName("*"),e=p.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=p.getElementsByTagName("input")[0],b={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:p.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,pixelMargin:!0},f.boxModel=b.boxModel=c.compatMode==="CSS1Compat",i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete p.test}catch(r){b.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",function(){b.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),i.setAttribute("name","t"),p.appendChild(i),j=c.createDocumentFragment(),j.appendChild(p.lastChild),b.checkClone=j.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,j.removeChild(i),j.appendChild(p);if(p.attachEvent)for(n in{submit:1,change:1,focusin:1})m="on"+n,o=m in p,o||(p.setAttribute(m,"return;"),o=typeof p[m]=="function"),b[n+"Bubbles"]=o;j.removeChild(p),j=g=h=p=i=null,f(function(){var d,e,g,h,i,j,l,m,n,q,r,s,t,u=c.getElementsByTagName("body")[0];!u||(m=1,t="padding:0;margin:0;border:",r="position:absolute;top:0;left:0;width:1px;height:1px;",s=t+"0;visibility:hidden;",n="style='"+r+t+"5px solid #000;",q="
      "+""+"
      ",d=c.createElement("div"),d.style.cssText=s+"width:0;height:0;position:static;top:0;margin-top:"+m+"px",u.insertBefore(d,u.firstChild),p=c.createElement("div"),d.appendChild(p),p.innerHTML="
      t
      ",k=p.getElementsByTagName("td"),o=k[0].offsetHeight===0,k[0].style.display="",k[1].style.display="none",b.reliableHiddenOffsets=o&&k[0].offsetHeight===0,a.getComputedStyle&&(p.innerHTML="",l=c.createElement("div"),l.style.width="0",l.style.marginRight="0",p.style.width="2px",p.appendChild(l),b.reliableMarginRight=(parseInt((a.getComputedStyle(l,null)||{marginRight:0}).marginRight,10)||0)===0),typeof p.style.zoom!="undefined"&&(p.innerHTML="",p.style.width=p.style.padding="1px",p.style.border=0,p.style.overflow="hidden",p.style.display="inline",p.style.zoom=1,b.inlineBlockNeedsLayout=p.offsetWidth===3,p.style.display="block",p.style.overflow="visible",p.innerHTML="
      ",b.shrinkWrapBlocks=p.offsetWidth!==3),p.style.cssText=r+s,p.innerHTML=q,e=p.firstChild,g=e.firstChild,i=e.nextSibling.firstChild.firstChild,j={doesNotAddBorder:g.offsetTop!==5,doesAddBorderForTableAndCells:i.offsetTop===5},g.style.position="fixed",g.style.top="20px",j.fixedPosition=g.offsetTop===20||g.offsetTop===15,g.style.position=g.style.top="",e.style.overflow="hidden",e.style.position="relative",j.subtractsBorderForOverflowNotVisible=g.offsetTop===-5,j.doesNotIncludeMarginInBodyOffset=u.offsetTop!==m,a.getComputedStyle&&(p.style.marginTop="1%",b.pixelMargin=(a.getComputedStyle(p,null)||{marginTop:0}).marginTop!=="1%"),typeof d.style.zoom!="undefined"&&(d.style.zoom=1),u.removeChild(d),l=p=d=null,f.extend(b,j))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e1,null,!1)},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,b){a&&(b=(b||"fx")+"mark",f._data(a,b,(f._data(a,b)||0)+1))},_unmark:function(a,b,c){a!==!0&&(c=b,b=a,a=!1);if(b){c=c||"fx";var d=c+"mark",e=a?0:(f._data(b,d)||1)-1;e?f._data(b,d,e):(f.removeData(b,d,!0),n(b,c,"mark"))}},queue:function(a,b,c){var d;if(a){b=(b||"fx")+"queue",d=f._data(a,b),c&&(!d||f.isArray(c)?d=f._data(a,b,f.makeArray(c)):d.push(c));return d||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e={};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),f._data(a,b+".run",e),d.call(a,function(){f.dequeue(a,b)},e)),c.length||(f.removeData(a,b+"queue "+b+".run",!0),n(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){var d=2;typeof a!="string"&&(c=a,a="fx",d--);if(arguments.length1)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,f.prop,a,b,arguments.length>1)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(p);for(c=0,d=this.length;c-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.type]||f.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.type]||f.valHooks[g.nodeName.toLowerCase()];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h,i=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;i=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/(?:^|\s)hover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function( -a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")};f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler,g=p.selector),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;le&&j.push({elem:this,matches:d.slice(e)});for(k=0;k0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));o.match.globalPOS=p;var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

      ";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
      ";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h0)for(h=g;h=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/]","i"),bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*",""],legend:[1,"
      ","
      "],thead:[1,"","
      "],tr:[2,"","
      "],td:[3,"","
      "],col:[2,"","
      "],area:[1,"",""],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div
      ","
      "]),f.fn.extend({text:function(a){return f.access(this,function(a){return a===b?f.text(this):this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f -.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){return f.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1>");try{for(;d1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||f.isXMLDoc(a)||!bc.test("<"+a.nodeName+">")?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g,h,i,j=[];b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);for(var k=0,l;(l=a[k])!=null;k++){typeof l=="number"&&(l+="");if(!l)continue;if(typeof l=="string")if(!_.test(l))l=b.createTextNode(l);else{l=l.replace(Y,"<$1>");var m=(Z.exec(l)||["",""])[1].toLowerCase(),n=bg[m]||bg._default,o=n[0],p=b.createElement("div"),q=bh.childNodes,r;b===c?bh.appendChild(p):U(b).appendChild(p),p.innerHTML=n[1]+l+n[2];while(o--)p=p.lastChild;if(!f.support.tbody){var s=$.test(l),t=m==="table"&&!s?p.firstChild&&p.firstChild.childNodes:n[1]===""&&!s?p.childNodes:[];for(i=t.length-1;i>=0;--i)f.nodeName(t[i],"tbody")&&!t[i].childNodes.length&&t[i].parentNode.removeChild(t[i])}!f.support.leadingWhitespace&&X.test(l)&&p.insertBefore(b.createTextNode(X.exec(l)[0]),p.firstChild),l=p.childNodes,p&&(p.parentNode.removeChild(p),q.length>0&&(r=q[q.length-1],r&&r.parentNode&&r.parentNode.removeChild(r)))}var u;if(!f.support.appendChecked)if(l[0]&&typeof (u=l.length)=="number")for(i=0;i1)},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=by(a,"opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d,h==="string"&&(g=bu.exec(d))&&(d=+(g[1]+1)*+g[2]+parseFloat(f.css(a,c)),h="number");if(d==null||h==="number"&&isNaN(d))return;h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(by)return by(a,c)},swap:function(a,b,c){var d={},e,f;for(f in b)d[f]=a.style[f],a.style[f]=b[f];e=c.call(a);for(f in b)a.style[f]=d[f];return e}}),f.curCSS=f.css,c.defaultView&&c.defaultView.getComputedStyle&&(bz=function(a,b){var c,d,e,g,h=a.style;b=b.replace(br,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b))),!f.support.pixelMargin&&e&&bv.test(b)&&bt.test(c)&&(g=h.width,h.width=c,c=e.width,h.width=g);return c}),c.documentElement.currentStyle&&(bA=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f==null&&g&&(e=g[b])&&(f=e),bt.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),by=bz||bA,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth!==0?bB(a,b,d):f.swap(a,bw,function(){return bB(a,b,d)})},set:function(a,b){return bs.test(b)?b+"px":b}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bq.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bp,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bp.test(g)?g.replace(bp,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){return f.swap(a,{display:"inline-block"},function(){return b?by(a,"margin-right"):a.style.marginRight})}})}),f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)}),f.each({margin:"",padding:"",border:"Width"},function(a,b){f.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bx[d]+b]=e[d]||e[d-2]||e[0];return f}}});var bC=/%20/g,bD=/\[\]$/,bE=/\r?\n/g,bF=/#.*$/,bG=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bH=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bI=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bJ=/^(?:GET|HEAD)$/,bK=/^\/\//,bL=/\?/,bM=/)<[^<]*)*<\/script>/gi,bN=/^(?:select|textarea)/i,bO=/\s+/,bP=/([?&])_=[^&]*/,bQ=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bR=f.fn.load,bS={},bT={},bU,bV,bW=["*/"]+["*"];try{bU=e.href}catch(bX){bU=c.createElement("a"),bU.href="",bU=bU.href}bV=bQ.exec(bU.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bR)return bR.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
      ").append(c.replace(bM,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bN.test(this.nodeName)||bH.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bE,"\r\n")}}):{name:b.name,value:c.replace(bE,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b$(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b$(a,b);return a},ajaxSettings:{url:bU,isLocal:bI.test(bV[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bW},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bY(bS),ajaxTransport:bY(bT),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?ca(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cb(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bG.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bF,"").replace(bK,bV[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bO),d.crossDomain==null&&(r=bQ.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bV[1]&&r[2]==bV[2]&&(r[3]||(r[1]==="http:"?80:443))==(bV[3]||(bV[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bZ(bS,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bJ.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bL.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bP,"$1_="+x);d.url=y+(y===d.url?(bL.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bW+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bZ(bT,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)b_(g,a[g],c,e);return d.join("&").replace(bC,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cc=f.now(),cd=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cc++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=typeof b.data=="string"&&/^application\/x\-www\-form\-urlencoded/.test(b.contentType);if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(cd.test(b.url)||e&&cd.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(cd,l),b.url===j&&(e&&(k=k.replace(cd,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var ce=a.ActiveXObject?function(){for(var a in cg)cg[a](0,1)}:!1,cf=0,cg;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ch()||ci()}:ch,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,ce&&delete cg[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n);try{m.text=h.responseText}catch(a){}try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cf,ce&&(cg||(cg={},f(a).unload(ce)),cg[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cj={},ck,cl,cm=/^(?:toggle|show|hide)$/,cn=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,co,cp=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cq;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(ct("show",3),a,b,c);for(var g=0,h=this.length;g=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);f.fn[a]=function(e){return f.access(this,function(a,e,g){var h=cy(a);if(g===b)return h?c in h?h[c]:f.support.boxModel&&h.document.documentElement[e]||h.document.body[e]:a[e];h?h.scrollTo(d?f(h).scrollLeft():g,d?g:f(h).scrollTop()):a[e]=g},a,e,arguments.length,null)}}),f.each({Height:"height",Width:"width"},function(a,c){var d="client"+a,e="scroll"+a,g="offset"+a;f.fn["inner"+a]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,c,"padding")):this[c]():null},f.fn["outer"+a]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,c,a?"margin":"border")):this[c]():null},f.fn[c]=function(a){return f.access(this,function(a,c,h){var i,j,k,l;if(f.isWindow(a)){i=a.document,j=i.documentElement[d];return f.support.boxModel&&j||i.body&&i.body[d]||j}if(a.nodeType===9){i=a.documentElement;if(i[d]>=i[e])return i[d];return Math.max(a.body[e],i[e],a.body[g],i[g])}if(h===b){k=f.css(a,c),l=parseFloat(k);return f.isNumeric(l)?l:k}f(a).css(c,h)},c,a,arguments.length,null)}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window); - -/*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.ui.core.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function(a,b){function c(b,c){var e=b.nodeName.toLowerCase();if("area"===e){var f=b.parentNode,g=f.name,h;return!b.href||!g||f.nodeName.toLowerCase()!=="map"?!1:(h=a("img[usemap=#"+g+"]")[0],!!h&&d(h))}return(/input|select|textarea|button|object/.test(e)?!b.disabled:"a"==e?b.href||c:c)&&d(b)}function d(b){return!a(b).parents().andSelf().filter(function(){return a.curCSS(this,"visibility")==="hidden"||a.expr.filters.hidden(this)}).length}a.ui=a.ui||{};if(a.ui.version)return;a.extend(a.ui,{version:"1.8.21",keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}}),a.fn.extend({propAttr:a.fn.prop||a.fn.attr,_focus:a.fn.focus,focus:function(b,c){return typeof b=="number"?this.each(function(){var d=this;setTimeout(function(){a(d).focus(),c&&c.call(d)},b)}):this._focus.apply(this,arguments)},scrollParent:function(){var b;return a.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?b=this.parents().filter(function(){return/(relative|absolute|fixed)/.test(a.curCSS(this,"position",1))&&/(auto|scroll)/.test(a.curCSS(this,"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0):b=this.parents().filter(function(){return/(auto|scroll)/.test(a.curCSS(this,"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0),/fixed/.test(this.css("position"))||!b.length?a(document):b},zIndex:function(c){if(c!==b)return this.css("zIndex",c);if(this.length){var d=a(this[0]),e,f;while(d.length&&d[0]!==document){e=d.css("position");if(e==="absolute"||e==="relative"||e==="fixed"){f=parseInt(d.css("zIndex"),10);if(!isNaN(f)&&f!==0)return f}d=d.parent()}}return 0},disableSelection:function(){return this.bind((a.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),a.each(["Width","Height"],function(c,d){function h(b,c,d,f){return a.each(e,function(){c-=parseFloat(a.curCSS(b,"padding"+this,!0))||0,d&&(c-=parseFloat(a.curCSS(b,"border"+this+"Width",!0))||0),f&&(c-=parseFloat(a.curCSS(b,"margin"+this,!0))||0)}),c}var e=d==="Width"?["Left","Right"]:["Top","Bottom"],f=d.toLowerCase(),g={innerWidth:a.fn.innerWidth,innerHeight:a.fn.innerHeight,outerWidth:a.fn.outerWidth,outerHeight:a.fn.outerHeight};a.fn["inner"+d]=function(c){return c===b?g["inner"+d].call(this):this.each(function(){a(this).css(f,h(this,c)+"px")})},a.fn["outer"+d]=function(b,c){return typeof b!="number"?g["outer"+d].call(this,b):this.each(function(){a(this).css(f,h(this,b,!0,c)+"px")})}}),a.extend(a.expr[":"],{data:function(b,c,d){return!!a.data(b,d[3])},focusable:function(b){return c(b,!isNaN(a.attr(b,"tabindex")))},tabbable:function(b){var d=a.attr(b,"tabindex"),e=isNaN(d);return(e||d>=0)&&c(b,!e)}}),a(function(){var b=document.body,c=b.appendChild(c=document.createElement("div"));c.offsetHeight,a.extend(c.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0}),a.support.minHeight=c.offsetHeight===100,a.support.selectstart="onselectstart"in c,b.removeChild(c).style.display="none"}),a.extend(a.ui,{plugin:{add:function(b,c,d){var e=a.ui[b].prototype;for(var f in d)e.plugins[f]=e.plugins[f]||[],e.plugins[f].push([c,d[f]])},call:function(a,b,c){var d=a.plugins[b];if(!d||!a.element[0].parentNode)return;for(var e=0;e0?!0:(b[d]=1,e=b[d]>0,b[d]=0,e)},isOverAxis:function(a,b,c){return a>b&&a=9||!!b.button?this._mouseStarted?(this._mouseDrag(b),b.preventDefault()):(this._mouseDistanceMet(b)&&this._mouseDelayMet(b)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,b)!==!1,this._mouseStarted?this._mouseDrag(b):this._mouseUp(b)),!this._mouseStarted):this._mouseUp(b)},_mouseUp:function(b){return a(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,b.target==this._mouseDownEvent.target&&a.data(b.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(b)),!1},_mouseDistanceMet:function(a){return Math.max(Math.abs(this._mouseDownEvent.pageX-a.pageX),Math.abs(this._mouseDownEvent.pageY-a.pageY))>=this.options.distance},_mouseDelayMet:function(a){return this.mouseDelayMet},_mouseStart:function(a){},_mouseDrag:function(a){},_mouseStop:function(a){},_mouseCapture:function(a){return!0}})})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.ui.position.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function(a,b){a.ui=a.ui||{};var c=/left|center|right/,d=/top|center|bottom/,e="center",f={},g=a.fn.position,h=a.fn.offset;a.fn.position=function(b){if(!b||!b.of)return g.apply(this,arguments);b=a.extend({},b);var h=a(b.of),i=h[0],j=(b.collision||"flip").split(" "),k=b.offset?b.offset.split(" "):[0,0],l,m,n;return i.nodeType===9?(l=h.width(),m=h.height(),n={top:0,left:0}):i.setTimeout?(l=h.width(),m=h.height(),n={top:h.scrollTop(),left:h.scrollLeft()}):i.preventDefault?(b.at="left top",l=m=0,n={top:b.of.pageY,left:b.of.pageX}):(l=h.outerWidth(),m=h.outerHeight(),n=h.offset()),a.each(["my","at"],function(){var a=(b[this]||"").split(" ");a.length===1&&(a=c.test(a[0])?a.concat([e]):d.test(a[0])?[e].concat(a):[e,e]),a[0]=c.test(a[0])?a[0]:e,a[1]=d.test(a[1])?a[1]:e,b[this]=a}),j.length===1&&(j[1]=j[0]),k[0]=parseInt(k[0],10)||0,k.length===1&&(k[1]=k[0]),k[1]=parseInt(k[1],10)||0,b.at[0]==="right"?n.left+=l:b.at[0]===e&&(n.left+=l/2),b.at[1]==="bottom"?n.top+=m:b.at[1]===e&&(n.top+=m/2),n.left+=k[0],n.top+=k[1],this.each(function(){var c=a(this),d=c.outerWidth(),g=c.outerHeight(),h=parseInt(a.curCSS(this,"marginLeft",!0))||0,i=parseInt(a.curCSS(this,"marginTop",!0))||0,o=d+h+(parseInt(a.curCSS(this,"marginRight",!0))||0),p=g+i+(parseInt(a.curCSS(this,"marginBottom",!0))||0),q=a.extend({},n),r;b.my[0]==="right"?q.left-=d:b.my[0]===e&&(q.left-=d/2),b.my[1]==="bottom"?q.top-=g:b.my[1]===e&&(q.top-=g/2),f.fractions||(q.left=Math.round(q.left),q.top=Math.round(q.top)),r={left:q.left-h,top:q.top-i},a.each(["left","top"],function(c,e){a.ui.position[j[c]]&&a.ui.position[j[c]][e](q,{targetWidth:l,targetHeight:m,elemWidth:d,elemHeight:g,collisionPosition:r,collisionWidth:o,collisionHeight:p,offset:k,my:b.my,at:b.at})}),a.fn.bgiframe&&c.bgiframe(),c.offset(a.extend(q,{using:b.using}))})},a.ui.position={fit:{left:function(b,c){var d=a(window),e=c.collisionPosition.left+c.collisionWidth-d.width()-d.scrollLeft();b.left=e>0?b.left-e:Math.max(b.left-c.collisionPosition.left,b.left)},top:function(b,c){var d=a(window),e=c.collisionPosition.top+c.collisionHeight-d.height()-d.scrollTop();b.top=e>0?b.top-e:Math.max(b.top-c.collisionPosition.top,b.top)}},flip:{left:function(b,c){if(c.at[0]===e)return;var d=a(window),f=c.collisionPosition.left+c.collisionWidth-d.width()-d.scrollLeft(),g=c.my[0]==="left"?-c.elemWidth:c.my[0]==="right"?c.elemWidth:0,h=c.at[0]==="left"?c.targetWidth:-c.targetWidth,i=-2*c.offset[0];b.left+=c.collisionPosition.left<0?g+h+i:f>0?g+h+i:0},top:function(b,c){if(c.at[1]===e)return;var d=a(window),f=c.collisionPosition.top+c.collisionHeight-d.height()-d.scrollTop(),g=c.my[1]==="top"?-c.elemHeight:c.my[1]==="bottom"?c.elemHeight:0,h=c.at[1]==="top"?c.targetHeight:-c.targetHeight,i=-2*c.offset[1];b.top+=c.collisionPosition.top<0?g+h+i:f>0?g+h+i:0}}},a.offset.setOffset||(a.offset.setOffset=function(b,c){/static/.test(a.curCSS(b,"position"))&&(b.style.position="relative");var d=a(b),e=d.offset(),f=parseInt(a.curCSS(b,"top",!0),10)||0,g=parseInt(a.curCSS(b,"left",!0),10)||0,h={top:c.top-e.top+f,left:c.left-e.left+g};"using"in c?c.using.call(b,h):d.css(h)},a.fn.offset=function(b){var c=this[0];return!c||!c.ownerDocument?null:b?a.isFunction(b)?this.each(function(c){a(this).offset(b.call(this,c,a(this).offset()))}):this.each(function(){a.offset.setOffset(this,b)}):h.call(this)}),function(){var b=document.getElementsByTagName("body")[0],c=document.createElement("div"),d,e,g,h,i;d=document.createElement(b?"div":"body"),g={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},b&&a.extend(g,{position:"absolute",left:"-1000px",top:"-1000px"});for(var j in g)d.style[j]=g[j];d.appendChild(c),e=b||document.documentElement,e.insertBefore(d,e.firstChild),c.style.cssText="position: absolute; left: 10.7432222px; top: 10.432325px; height: 30px; width: 201px;",h=a(c).offset(function(a,b){return b}).offset(),d.innerHTML="",e.removeChild(d),i=h.top+h.left+(b?2e3:0),f.fractions=i>21&&i<22}()})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.ui.draggable.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function(a,b){a.widget("ui.draggable",a.ui.mouse,{widgetEventPrefix:"drag",options:{addClasses:!0,appendTo:"parent",axis:!1,connectToSortable:!1,containment:!1,cursor:"auto",cursorAt:!1,grid:!1,handle:!1,helper:"original",iframeFix:!1,opacity:!1,refreshPositions:!1,revert:!1,revertDuration:500,scope:"default",scroll:!0,scrollSensitivity:20,scrollSpeed:20,snap:!1,snapMode:"both",snapTolerance:20,stack:!1,zIndex:!1},_create:function(){this.options.helper=="original"&&!/^(?:r|a|f)/.test(this.element.css("position"))&&(this.element[0].style.position="relative"),this.options.addClasses&&this.element.addClass("ui-draggable"),this.options.disabled&&this.element.addClass("ui-draggable-disabled"),this._mouseInit()},destroy:function(){if(!this.element.data("draggable"))return;return this.element.removeData("draggable").unbind(".draggable").removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled"),this._mouseDestroy(),this},_mouseCapture:function(b){var c=this.options;return this.helper||c.disabled||a(b.target).is(".ui-resizable-handle")?!1:(this.handle=this._getHandle(b),this.handle?(c.iframeFix&&a(c.iframeFix===!0?"iframe":c.iframeFix).each(function(){a('
      ').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1e3}).css(a(this).offset()).appendTo("body")}),!0):!1)},_mouseStart:function(b){var c=this.options;return this.helper=this._createHelper(b),this.helper.addClass("ui-draggable-dragging"),this._cacheHelperProportions(),a.ui.ddmanager&&(a.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(),this.offset=this.positionAbs=this.element.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},a.extend(this.offset,{click:{left:b.pageX-this.offset.left,top:b.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.originalPosition=this.position=this._generatePosition(b),this.originalPageX=b.pageX,this.originalPageY=b.pageY,c.cursorAt&&this._adjustOffsetFromHelper(c.cursorAt),c.containment&&this._setContainment(),this._trigger("start",b)===!1?(this._clear(),!1):(this._cacheHelperProportions(),a.ui.ddmanager&&!c.dropBehaviour&&a.ui.ddmanager.prepareOffsets(this,b),this._mouseDrag(b,!0),a.ui.ddmanager&&a.ui.ddmanager.dragStart(this,b),!0)},_mouseDrag:function(b,c){this.position=this._generatePosition(b),this.positionAbs=this._convertPositionTo("absolute");if(!c){var d=this._uiHash();if(this._trigger("drag",b,d)===!1)return this._mouseUp({}),!1;this.position=d.position}if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";return a.ui.ddmanager&&a.ui.ddmanager.drag(this,b),!1},_mouseStop:function(b){var c=!1;a.ui.ddmanager&&!this.options.dropBehaviour&&(c=a.ui.ddmanager.drop(this,b)),this.dropped&&(c=this.dropped,this.dropped=!1);var d=this.element[0],e=!1;while(d&&(d=d.parentNode))d==document&&(e=!0);if(!e&&this.options.helper==="original")return!1;if(this.options.revert=="invalid"&&!c||this.options.revert=="valid"&&c||this.options.revert===!0||a.isFunction(this.options.revert)&&this.options.revert.call(this.element,c)){var f=this;a(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){f._trigger("stop",b)!==!1&&f._clear()})}else this._trigger("stop",b)!==!1&&this._clear();return!1},_mouseUp:function(b){return this.options.iframeFix===!0&&a("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)}),a.ui.ddmanager&&a.ui.ddmanager.dragStop(this,b),a.ui.mouse.prototype._mouseUp.call(this,b)},cancel:function(){return this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear(),this},_getHandle:function(b){var c=!this.options.handle||!a(this.options.handle,this.element).length?!0:!1;return a(this.options.handle,this.element).find("*").andSelf().each(function(){this==b.target&&(c=!0)}),c},_createHelper:function(b){var c=this.options,d=a.isFunction(c.helper)?a(c.helper.apply(this.element[0],[b])):c.helper=="clone"?this.element.clone().removeAttr("id"):this.element;return d.parents("body").length||d.appendTo(c.appendTo=="parent"?this.element[0].parentNode:c.appendTo),d[0]!=this.element[0]&&!/(fixed|absolute)/.test(d.css("position"))&&d.css("position","absolute"),d},_adjustOffsetFromHelper:function(b){typeof b=="string"&&(b=b.split(" ")),a.isArray(b)&&(b={left:+b[0],top:+b[1]||0}),"left"in b&&(this.offset.click.left=b.left+this.margins.left),"right"in b&&(this.offset.click.left=this.helperProportions.width-b.right+this.margins.left),"top"in b&&(this.offset.click.top=b.top+this.margins.top),"bottom"in b&&(this.offset.click.top=this.helperProportions.height-b.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var b=this.offsetParent.offset();this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0])&&(b.left+=this.scrollParent.scrollLeft(),b.top+=this.scrollParent.scrollTop());if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&a.browser.msie)b={top:0,left:0};return{top:b.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:b.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var a=this.element.position();return{top:a.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:a.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var b=this.options;b.containment=="parent"&&(b.containment=this.helper[0].parentNode);if(b.containment=="document"||b.containment=="window")this.containment=[b.containment=="document"?0:a(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,b.containment=="document"?0:a(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,(b.containment=="document"?0:a(window).scrollLeft())+a(b.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(b.containment=="document"?0:a(window).scrollTop())+(a(b.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(b.containment)&&b.containment.constructor!=Array){var c=a(b.containment),d=c[0];if(!d)return;var e=c.offset(),f=a(d).css("overflow")!="hidden";this.containment=[(parseInt(a(d).css("borderLeftWidth"),10)||0)+(parseInt(a(d).css("paddingLeft"),10)||0),(parseInt(a(d).css("borderTopWidth"),10)||0)+(parseInt(a(d).css("paddingTop"),10)||0),(f?Math.max(d.scrollWidth,d.offsetWidth):d.offsetWidth)-(parseInt(a(d).css("borderLeftWidth"),10)||0)-(parseInt(a(d).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(f?Math.max(d.scrollHeight,d.offsetHeight):d.offsetHeight)-(parseInt(a(d).css("borderTopWidth"),10)||0)-(parseInt(a(d).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom],this.relative_container=c}else b.containment.constructor==Array&&(this.containment=b.containment)},_convertPositionTo:function(b,c){c||(c=this.position);var d=b=="absolute"?1:-1,e=this.options,f=this.cssPosition=="absolute"&&(this.scrollParent[0]==document||!a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,g=/(html|body)/i.test(f[0].tagName);return{top:c.top+this.offset.relative.top*d+this.offset.parent.top*d-(a.browser.safari&&a.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():g?0:f.scrollTop())*d),left:c.left+this.offset.relative.left*d+this.offset.parent.left*d-(a.browser.safari&&a.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():g?0:f.scrollLeft())*d)}},_generatePosition:function(b){var c=this.options,d=this.cssPosition=="absolute"&&(this.scrollParent[0]==document||!a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,e=/(html|body)/i.test(d[0].tagName),f=b.pageX,g=b.pageY;if(this.originalPosition){var h;if(this.containment){if(this.relative_container){var i=this.relative_container.offset();h=[this.containment[0]+i.left,this.containment[1]+i.top,this.containment[2]+i.left,this.containment[3]+i.top]}else h=this.containment;b.pageX-this.offset.click.lefth[2]&&(f=h[2]+this.offset.click.left),b.pageY-this.offset.click.top>h[3]&&(g=h[3]+this.offset.click.top)}if(c.grid){var j=c.grid[1]?this.originalPageY+Math.round((g-this.originalPageY)/c.grid[1])*c.grid[1]:this.originalPageY;g=h?j-this.offset.click.toph[3]?j-this.offset.click.toph[2]?k-this.offset.click.left=0;k--){var l=d.snapElements[k].left,m=l+d.snapElements[k].width,n=d.snapElements[k].top,o=n+d.snapElements[k].height;if(!(l-f=k&&g<=l||h>=k&&h<=l||gl)&&(e>=i&&e<=j||f>=i&&f<=j||ej);default:return!1}},a.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(b,c){var d=a.ui.ddmanager.droppables[b.options.scope]||[],e=c?c.type:null,f=(b.currentItem||b.element).find(":data(droppable)").andSelf();g:for(var h=0;h
      ').css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("resizable",this.element.data("resizable")),this.elementIsWrapper=!0,this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")}),this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0}),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css({margin:this.originalElement.css("margin")}),this._proportionallyResize()),this.handles=c.handles||(a(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se");if(this.handles.constructor==String){this.handles=="all"&&(this.handles="n,e,s,w,se,sw,ne,nw");var d=this.handles.split(",");this.handles={};for(var e=0;e');h.css({zIndex:c.zIndex}),"se"==f&&h.addClass("ui-icon ui-icon-gripsmall-diagonal-se"),this.handles[f]=".ui-resizable-"+f,this.element.append(h)}}this._renderAxis=function(b){b=b||this.element;for(var c in this.handles){this.handles[c].constructor==String&&(this.handles[c]=a(this.handles[c],this.element).show());if(this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)){var d=a(this.handles[c],this.element),e=0;e=/sw|ne|nw|se|n|s/.test(c)?d.outerHeight():d.outerWidth();var f=["padding",/ne|nw|n/.test(c)?"Top":/se|sw|s/.test(c)?"Bottom":/^e$/.test(c)?"Right":"Left"].join("");b.css(f,e),this._proportionallyResize()}if(!a(this.handles[c]).length)continue}},this._renderAxis(this.element),this._handles=a(".ui-resizable-handle",this.element).disableSelection(),this._handles.mouseover(function(){if(!b.resizing){if(this.className)var a=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);b.axis=a&&a[1]?a[1]:"se"}}),c.autoHide&&(this._handles.hide(),a(this.element).addClass("ui-resizable-autohide").hover(function(){if(c.disabled)return;a(this).removeClass("ui-resizable-autohide"),b._handles.show()},function(){if(c.disabled)return;b.resizing||(a(this).addClass("ui-resizable-autohide"),b._handles.hide())})),this._mouseInit()},destroy:function(){this._mouseDestroy();var b=function(b){a(b).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};if(this.elementIsWrapper){b(this.element);var c=this.element;c.after(this.originalElement.css({position:c.css("position"),width:c.outerWidth(),height:c.outerHeight(),top:c.css("top"),left:c.css("left")})).remove()}return this.originalElement.css("resize",this.originalResizeStyle),b(this.originalElement),this},_mouseCapture:function(b){var c=!1;for(var d in this.handles)a(this.handles[d])[0]==b.target&&(c=!0);return!this.options.disabled&&c},_mouseStart:function(b){var d=this.options,e=this.element.position(),f=this.element;this.resizing=!0,this.documentScroll={top:a(document).scrollTop(),left:a(document).scrollLeft()},(f.is(".ui-draggable")||/absolute/.test(f.css("position")))&&f.css({position:"absolute",top:e.top,left:e.left}),this._renderProxy();var g=c(this.helper.css("left")),h=c(this.helper.css("top"));d.containment&&(g+=a(d.containment).scrollLeft()||0,h+=a(d.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:g,top:h},this.size=this._helper?{width:f.outerWidth(),height:f.outerHeight()}:{width:f.width(),height:f.height()},this.originalSize=this._helper?{width:f.outerWidth(),height:f.outerHeight()}:{width:f.width(),height:f.height()},this.originalPosition={left:g,top:h},this.sizeDiff={width:f.outerWidth()-f.width(),height:f.outerHeight()-f.height()},this.originalMousePosition={left:b.pageX,top:b.pageY},this.aspectRatio=typeof d.aspectRatio=="number"?d.aspectRatio:this.originalSize.width/this.originalSize.height||1;var i=a(".ui-resizable-"+this.axis).css("cursor");return a("body").css("cursor",i=="auto"?this.axis+"-resize":i),f.addClass("ui-resizable-resizing"),this._propagate("start",b),!0},_mouseDrag:function(b){var c=this.helper,d=this.options,e={},f=this,g=this.originalMousePosition,h=this.axis,i=b.pageX-g.left||0,j=b.pageY-g.top||0,k=this._change[h];if(!k)return!1;var l=k.apply(this,[b,i,j]),m=a.browser.msie&&a.browser.version<7,n=this.sizeDiff;this._updateVirtualBoundaries(b.shiftKey);if(this._aspectRatio||b.shiftKey)l=this._updateRatio(l,b);return l=this._respectSize(l,b),this._propagate("resize",b),c.css({top:this.position.top+"px",left:this.position.left+"px",width:this.size.width+"px",height:this.size.height+"px"}),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),this._updateCache(l),this._trigger("resize",b,this.ui()),!1},_mouseStop:function(b){this.resizing=!1;var c=this.options,d=this;if(this._helper){var e=this._proportionallyResizeElements,f=e.length&&/textarea/i.test(e[0].nodeName),g=f&&a.ui.hasScroll(e[0],"left")?0:d.sizeDiff.height,h=f?0:d.sizeDiff.width,i={width:d.helper.width()-h,height:d.helper.height()-g},j=parseInt(d.element.css("left"),10)+(d.position.left-d.originalPosition.left)||null,k=parseInt(d.element.css("top"),10)+(d.position.top-d.originalPosition.top)||null;c.animate||this.element.css(a.extend(i,{top:k,left:j})),d.helper.height(d.size.height),d.helper.width(d.size.width),this._helper&&!c.animate&&this._proportionallyResize()}return a("body").css("cursor","auto"),this.element.removeClass("ui-resizable-resizing"),this._propagate("stop",b),this._helper&&this.helper.remove(),!1},_updateVirtualBoundaries:function(a){var b=this.options,c,e,f,g,h;h={minWidth:d(b.minWidth)?b.minWidth:0,maxWidth:d(b.maxWidth)?b.maxWidth:Infinity,minHeight:d(b.minHeight)?b.minHeight:0,maxHeight:d(b.maxHeight)?b.maxHeight:Infinity};if(this._aspectRatio||a)c=h.minHeight*this.aspectRatio,f=h.minWidth/this.aspectRatio,e=h.maxHeight*this.aspectRatio,g=h.maxWidth/this.aspectRatio,c>h.minWidth&&(h.minWidth=c),f>h.minHeight&&(h.minHeight=f),ea.width,k=d(a.height)&&e.minHeight&&e.minHeight>a.height;j&&(a.width=e.minWidth),k&&(a.height=e.minHeight),h&&(a.width=e.maxWidth),i&&(a.height=e.maxHeight);var l=this.originalPosition.left+this.originalSize.width,m=this.position.top+this.size.height,n=/sw|nw|w/.test(g),o=/nw|ne|n/.test(g);j&&n&&(a.left=l-e.minWidth),h&&n&&(a.left=l-e.maxWidth),k&&o&&(a.top=m-e.minHeight),i&&o&&(a.top=m-e.maxHeight);var p=!a.width&&!a.height;return p&&!a.left&&a.top?a.top=null:p&&!a.top&&a.left&&(a.left=null),a},_proportionallyResize:function(){var b=this.options;if(!this._proportionallyResizeElements.length)return;var c=this.helper||this.element;for(var d=0;d');var d=a.browser.msie&&a.browser.version<7,e=d?1:0,f=d?2:-1;this.helper.addClass(this._helper).css({width:this.element.outerWidth()+f,height:this.element.outerHeight()+f,position:"absolute",left:this.elementOffset.left-e+"px",top:this.elementOffset.top-e+"px",zIndex:++c.zIndex}),this.helper.appendTo("body").disableSelection()}else this.helper=this.element},_change:{e:function(a,b,c){return{width:this.originalSize.width+b}},w:function(a,b,c){var d=this.options,e=this.originalSize,f=this.originalPosition;return{left:f.left+b,width:e.width-b}},n:function(a,b,c){var d=this.options,e=this.originalSize,f=this.originalPosition;return{top:f.top+c,height:e.height-c}},s:function(a,b,c){return{height:this.originalSize.height+c}},se:function(b,c,d){return a.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[b,c,d]))},sw:function(b,c,d){return a.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[b,c,d]))},ne:function(b,c,d){return a.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[b,c,d]))},nw:function(b,c,d){return a.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[b,c,d]))}},_propagate:function(b,c){a.ui.plugin.call(this,b,[c,this.ui()]),b!="resize"&&this._trigger(b,c,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),a.extend(a.ui.resizable,{version:"1.8.21"}),a.ui.plugin.add("resizable","alsoResize",{start:function(b,c){var d=a(this).data("resizable"),e=d.options,f=function(b){a(b).each(function(){var b=a(this);b.data("resizable-alsoresize",{width:parseInt(b.width(),10),height:parseInt(b.height(),10),left:parseInt(b.css("left"),10),top:parseInt(b.css("top"),10)})})};typeof e.alsoResize=="object"&&!e.alsoResize.parentNode?e.alsoResize.length?(e.alsoResize=e.alsoResize[0],f(e.alsoResize)):a.each(e.alsoResize,function(a){f(a)}):f(e.alsoResize)},resize:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d.originalSize,g=d.originalPosition,h={height:d.size.height-f.height||0,width:d.size.width-f.width||0,top:d.position.top-g.top||0,left:d.position.left-g.left||0},i=function(b,d){a(b).each(function(){var b=a(this),e=a(this).data("resizable-alsoresize"),f={},g=d&&d.length?d:b.parents(c.originalElement[0]).length?["width","height"]:["width","height","top","left"];a.each(g,function(a,b){var c=(e[b]||0)+(h[b]||0);c&&c>=0&&(f[b]=c||null)}),b.css(f)})};typeof e.alsoResize=="object"&&!e.alsoResize.nodeType?a.each(e.alsoResize,function(a,b){i(a,b)}):i(e.alsoResize)},stop:function(b,c){a(this).removeData("resizable-alsoresize")}}),a.ui.plugin.add("resizable","animate",{stop:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d._proportionallyResizeElements,g=f.length&&/textarea/i.test(f[0].nodeName),h=g&&a.ui.hasScroll(f[0],"left")?0:d.sizeDiff.height,i=g?0:d.sizeDiff.width,j={width:d.size.width-i,height:d.size.height-h},k=parseInt(d.element.css("left"),10)+(d.position.left-d.originalPosition.left)||null,l=parseInt(d.element.css("top"),10)+(d.position.top-d.originalPosition.top)||null;d.element.animate(a.extend(j,l&&k?{top:l,left:k}:{}),{duration:e.animateDuration,easing:e.animateEasing,step:function(){var c={width:parseInt(d.element.css("width"),10),height:parseInt(d.element.css("height"),10),top:parseInt(d.element.css("top"),10),left:parseInt(d.element.css("left"),10)};f&&f.length&&a(f[0]).css({width:c.width,height:c.height}),d._updateCache(c),d._propagate("resize",b)}})}}),a.ui.plugin.add("resizable","containment",{start:function(b,d){var e=a(this).data("resizable"),f=e.options,g=e.element,h=f.containment,i=h instanceof a?h.get(0):/parent/.test(h)?g.parent().get(0):h;if(!i)return;e.containerElement=a(i);if(/document/.test(h)||h==document)e.containerOffset={left:0,top:0},e.containerPosition={left:0,top:0},e.parentData={element:a(document),left:0,top:0,width:a(document).width(),height:a(document).height()||document.body.parentNode.scrollHeight};else{var j=a(i),k=[];a(["Top","Right","Left","Bottom"]).each(function(a,b){k[a]=c(j.css("padding"+b))}),e.containerOffset=j.offset(),e.containerPosition=j.position(),e.containerSize={height:j.innerHeight()-k[3],width:j.innerWidth()-k[1]};var l=e.containerOffset,m=e.containerSize.height,n=e.containerSize.width,o=a.ui.hasScroll(i,"left")?i.scrollWidth:n,p=a.ui.hasScroll(i)?i.scrollHeight:m;e.parentData={element:i,left:l.left,top:l.top,width:o,height:p}}},resize:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d.containerSize,g=d.containerOffset,h=d.size,i=d.position,j=d._aspectRatio||b.shiftKey,k={top:0,left:0},l=d.containerElement;l[0]!=document&&/static/.test(l.css("position"))&&(k=g),i.left<(d._helper?g.left:0)&&(d.size.width=d.size.width+(d._helper?d.position.left-g.left:d.position.left-k.left),j&&(d.size.height=d.size.width/d.aspectRatio),d.position.left=e.helper?g.left:0),i.top<(d._helper?g.top:0)&&(d.size.height=d.size.height+(d._helper?d.position.top-g.top:d.position.top),j&&(d.size.width=d.size.height*d.aspectRatio),d.position.top=d._helper?g.top:0),d.offset.left=d.parentData.left+d.position.left,d.offset.top=d.parentData.top+d.position.top;var m=Math.abs((d._helper?d.offset.left-k.left:d.offset.left-k.left)+d.sizeDiff.width),n=Math.abs((d._helper?d.offset.top-k.top:d.offset.top-g.top)+d.sizeDiff.height),o=d.containerElement.get(0)==d.element.parent().get(0),p=/relative|absolute/.test(d.containerElement.css("position"));o&&p&&(m-=d.parentData.left),m+d.size.width>=d.parentData.width&&(d.size.width=d.parentData.width-m,j&&(d.size.height=d.size.width/d.aspectRatio)),n+d.size.height>=d.parentData.height&&(d.size.height=d.parentData.height-n,j&&(d.size.width=d.size.height*d.aspectRatio))},stop:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d.position,g=d.containerOffset,h=d.containerPosition,i=d.containerElement,j=a(d.helper),k=j.offset(),l=j.outerWidth()-d.sizeDiff.width,m=j.outerHeight()-d.sizeDiff.height;d._helper&&!e.animate&&/relative/.test(i.css("position"))&&a(this).css({left:k.left-h.left-g.left,width:l,height:m}),d._helper&&!e.animate&&/static/.test(i.css("position"))&&a(this).css({left:k.left-h.left-g.left,width:l,height:m})}}),a.ui.plugin.add("resizable","ghost",{start:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d.size;d.ghost=d.originalElement.clone(),d.ghost.css({opacity:.25,display:"block",position:"relative",height:f.height,width:f.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass(typeof e.ghost=="string"?e.ghost:""),d.ghost.appendTo(d.helper)},resize:function(b,c){var d=a(this).data("resizable"),e=d.options;d.ghost&&d.ghost.css({position:"relative",height:d.size.height,width:d.size.width})},stop:function(b,c){var d=a(this).data("resizable"),e=d.options;d.ghost&&d.helper&&d.helper.get(0).removeChild(d.ghost.get(0))}}),a.ui.plugin.add("resizable","grid",{resize:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d.size,g=d.originalSize,h=d.originalPosition,i=d.axis,j=e._aspectRatio||b.shiftKey;e.grid=typeof e.grid=="number"?[e.grid,e.grid]:e.grid;var k=Math.round((f.width-g.width)/(e.grid[0]||1))*(e.grid[0]||1),l=Math.round((f.height-g.height)/(e.grid[1]||1))*(e.grid[1]||1);/^(se|s|e)$/.test(i)?(d.size.width=g.width+k,d.size.height=g.height+l):/^(ne)$/.test(i)?(d.size.width=g.width+k,d.size.height=g.height+l,d.position.top=h.top-l):/^(sw)$/.test(i)?(d.size.width=g.width+k,d.size.height=g.height+l,d.position.left=h.left-k):(d.size.width=g.width+k,d.size.height=g.height+l,d.position.top=h.top-l,d.position.left=h.left-k)}});var c=function(a){return parseInt(a,10)||0},d=function(a){return!isNaN(parseInt(a,10))}})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.ui.selectable.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function(a,b){a.widget("ui.selectable",a.ui.mouse,{options:{appendTo:"body",autoRefresh:!0,distance:0,filter:"*",tolerance:"touch"},_create:function(){var b=this;this.element.addClass("ui-selectable"),this.dragged=!1;var c;this.refresh=function(){c=a(b.options.filter,b.element[0]),c.addClass("ui-selectee"),c.each(function(){var b=a(this),c=b.offset();a.data(this,"selectable-item",{element:this,$element:b,left:c.left,top:c.top,right:c.left+b.outerWidth(),bottom:c.top+b.outerHeight(),startselected:!1,selected:b.hasClass("ui-selected"),selecting:b.hasClass("ui-selecting"),unselecting:b.hasClass("ui-unselecting")})})},this.refresh(),this.selectees=c.addClass("ui-selectee"),this._mouseInit(),this.helper=a("
      ")},destroy:function(){return this.selectees.removeClass("ui-selectee").removeData("selectable-item"),this.element.removeClass("ui-selectable ui-selectable-disabled").removeData("selectable").unbind(".selectable"),this._mouseDestroy(),this},_mouseStart:function(b){var c=this;this.opos=[b.pageX,b.pageY];if(this.options.disabled)return;var d=this.options;this.selectees=a(d.filter,this.element[0]),this._trigger("start",b),a(d.appendTo).append(this.helper),this.helper.css({left:b.clientX,top:b.clientY,width:0,height:0}),d.autoRefresh&&this.refresh(),this.selectees.filter(".ui-selected").each(function(){var d=a.data(this,"selectable-item");d.startselected=!0,!b.metaKey&&!b.ctrlKey&&(d.$element.removeClass("ui-selected"),d.selected=!1,d.$element.addClass("ui-unselecting"),d.unselecting=!0,c._trigger("unselecting",b,{unselecting:d.element}))}),a(b.target).parents().andSelf().each(function(){var d=a.data(this,"selectable-item");if(d){var e=!b.metaKey&&!b.ctrlKey||!d.$element.hasClass("ui-selected");return d.$element.removeClass(e?"ui-unselecting":"ui-selected").addClass(e?"ui-selecting":"ui-unselecting"),d.unselecting=!e,d.selecting=e,d.selected=e,e?c._trigger("selecting",b,{selecting:d.element}):c._trigger("unselecting",b,{unselecting:d.element}),!1}})},_mouseDrag:function(b){var c=this;this.dragged=!0;if(this.options.disabled)return;var d=this.options,e=this.opos[0],f=this.opos[1],g=b.pageX,h=b.pageY;if(e>g){var i=g;g=e,e=i}if(f>h){var i=h;h=f,f=i}return this.helper.css({left:e,top:f,width:g-e,height:h-f}),this.selectees.each(function(){var i=a.data(this,"selectable-item");if(!i||i.element==c.element[0])return;var j=!1;d.tolerance=="touch"?j=!(i.left>g||i.righth||i.bottome&&i.rightf&&i.bottom *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1e3},_create:function(){var a=this.options;this.containerCache={},this.element.addClass("ui-sortable"),this.refresh(),this.floating=this.items.length?a.axis==="x"||/left|right/.test(this.items[0].item.css("float"))||/inline|table-cell/.test(this.items[0].item.css("display")):!1,this.offset=this.element.offset(),this._mouseInit(),this.ready=!0},destroy:function(){a.Widget.prototype.destroy.call(this),this.element.removeClass("ui-sortable ui-sortable-disabled"),this._mouseDestroy();for(var b=this.items.length-1;b>=0;b--)this.items[b].item.removeData(this.widgetName+"-item");return this},_setOption:function(b,c){b==="disabled"?(this.options[b]=c,this.widget()[c?"addClass":"removeClass"]("ui-sortable-disabled")):a.Widget.prototype._setOption.apply(this,arguments)},_mouseCapture:function(b,c){var d=this;if(this.reverting)return!1;if(this.options.disabled||this.options.type=="static")return!1;this._refreshItems(b);var e=null,f=this,g=a(b.target).parents().each(function(){if(a.data(this,d.widgetName+"-item")==f)return e=a(this),!1});a.data(b.target,d.widgetName+"-item")==f&&(e=a(b.target));if(!e)return!1;if(this.options.handle&&!c){var h=!1;a(this.options.handle,e).find("*").andSelf().each(function(){this==b.target&&(h=!0)});if(!h)return!1}return this.currentItem=e,this._removeCurrentsFromItems(),!0},_mouseStart:function(b,c,d){var e=this.options,f=this;this.currentContainer=this,this.refreshPositions(),this.helper=this._createHelper(b),this._cacheHelperProportions(),this._cacheMargins(),this.scrollParent=this.helper.scrollParent(),this.offset=this.currentItem.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},a.extend(this.offset,{click:{left:b.pageX-this.offset.left,top:b.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.helper.css("position","absolute"),this.cssPosition=this.helper.css("position"),this.originalPosition=this._generatePosition(b),this.originalPageX=b.pageX,this.originalPageY=b.pageY,e.cursorAt&&this._adjustOffsetFromHelper(e.cursorAt),this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]},this.helper[0]!=this.currentItem[0]&&this.currentItem.hide(),this._createPlaceholder(),e.containment&&this._setContainment(),e.cursor&&(a("body").css("cursor")&&(this._storedCursor=a("body").css("cursor")),a("body").css("cursor",e.cursor)),e.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",e.opacity)),e.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",e.zIndex)),this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"&&(this.overflowOffset=this.scrollParent.offset()),this._trigger("start",b,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions();if(!d)for(var g=this.containers.length-1;g>=0;g--)this.containers[g]._trigger("activate",b,f._uiHash(this));return a.ui.ddmanager&&(a.ui.ddmanager.current=this),a.ui.ddmanager&&!e.dropBehaviour&&a.ui.ddmanager.prepareOffsets(this,b),this.dragging=!0,this.helper.addClass("ui-sortable-helper"),this._mouseDrag(b),!0},_mouseDrag:function(b){this.position=this._generatePosition(b),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs);if(this.options.scroll){var c=this.options,d=!1;this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-b.pageY=0;e--){var f=this.items[e],g=f.item[0],h=this._intersectsWithPointer(f);if(!h)continue;if(g!=this.currentItem[0]&&this.placeholder[h==1?"next":"prev"]()[0]!=g&&!a.ui.contains(this.placeholder[0],g)&&(this.options.type=="semi-dynamic"?!a.ui.contains(this.element[0],g):!0)){this.direction=h==1?"down":"up";if(this.options.tolerance=="pointer"||this._intersectsWithSides(f))this._rearrange(b,f);else break;this._trigger("change",b,this._uiHash());break}}return this._contactContainers(b),a.ui.ddmanager&&a.ui.ddmanager.drag(this,b),this._trigger("sort",b,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(b,c){if(!b)return;a.ui.ddmanager&&!this.options.dropBehaviour&&a.ui.ddmanager.drop(this,b);if(this.options.revert){var d=this,e=d.placeholder.offset();d.reverting=!0,a(this.helper).animate({left:e.left-this.offset.parent.left-d.margins.left+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollLeft),top:e.top-this.offset.parent.top-d.margins.top+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollTop)},parseInt(this.options.revert,10)||500,function(){d._clear(b)})}else this._clear(b,c);return!1},cancel:function(){var b=this;if(this.dragging){this._mouseUp({target:null}),this.options.helper=="original"?this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"):this.currentItem.show();for(var c=this.containers.length-1;c>=0;c--)this.containers[c]._trigger("deactivate",null,b._uiHash(this)),this.containers[c].containerCache.over&&(this.containers[c]._trigger("out",null,b._uiHash(this)),this.containers[c].containerCache.over=0)}return this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.options.helper!="original"&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),a.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?a(this.domPosition.prev).after(this.currentItem):a(this.domPosition.parent).prepend(this.currentItem)),this},serialize:function(b){var c=this._getItemsAsjQuery(b&&b.connected),d=[];return b=b||{},a(c).each(function(){var c=(a(b.item||this).attr(b.attribute||"id")||"").match(b.expression||/(.+)[-=_](.+)/);c&&d.push((b.key||c[1]+"[]")+"="+(b.key&&b.expression?c[1]:c[2]))}),!d.length&&b.key&&d.push(b.key+"="),d.join("&")},toArray:function(b){var c=this._getItemsAsjQuery(b&&b.connected),d=[];return b=b||{},c.each(function(){d.push(a(b.item||this).attr(b.attribute||"id")||"")}),d},_intersectsWith:function(a){var b=this.positionAbs.left,c=b+this.helperProportions.width,d=this.positionAbs.top,e=d+this.helperProportions.height,f=a.left,g=f+a.width,h=a.top,i=h+a.height,j=this.offset.click.top,k=this.offset.click.left,l=d+j>h&&d+jf&&b+ka[this.floating?"width":"height"]?l:f0?"down":"up")},_getDragHorizontalDirection:function(){var a=this.positionAbs.left-this.lastPositionAbs.left;return a!=0&&(a>0?"right":"left")},refresh:function(a){return this._refreshItems(a),this.refreshPositions(),this},_connectWith:function(){var a=this.options;return a.connectWith.constructor==String?[a.connectWith]:a.connectWith},_getItemsAsjQuery:function(b){var c=this,d=[],e=[],f=this._connectWith();if(f&&b)for(var g=f.length-1;g>=0;g--){var h=a(f[g]);for(var i=h.length-1;i>=0;i--){var j=a.data(h[i],this.widgetName);j&&j!=this&&!j.options.disabled&&e.push([a.isFunction(j.options.items)?j.options.items.call(j.element):a(j.options.items,j.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),j])}}e.push([a.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):a(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]);for(var g=e.length-1;g>=0;g--)e[g][0].each(function(){d.push(this)});return a(d)},_removeCurrentsFromItems:function(){var a=this.currentItem.find(":data("+this.widgetName+"-item)");for(var b=0;b=0;g--){var h=a(f[g]);for(var i=h.length-1;i>=0;i--){var j=a.data(h[i],this.widgetName);j&&j!=this&&!j.options.disabled&&(e.push([a.isFunction(j.options.items)?j.options.items.call(j.element[0],b,{item:this.currentItem}):a(j.options.items,j.element),j]),this.containers.push(j))}}for(var g=e.length-1;g>=0;g--){var k=e[g][1],l=e[g][0];for(var i=0,m=l.length;i=0;c--){var d=this.items[c];if(d.instance!=this.currentContainer&&this.currentContainer&&d.item[0]!=this.currentItem[0])continue;var e=this.options.toleranceElement?a(this.options.toleranceElement,d.item):d.item;b||(d.width=e.outerWidth(),d.height=e.outerHeight());var f=e.offset();d.left=f.left,d.top=f.top}if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(var c=this.containers.length-1;c>=0;c--){var f=this.containers[c].element.offset();this.containers[c].containerCache.left=f.left,this.containers[c].containerCache.top=f.top,this.containers[c].containerCache.width=this.containers[c].element.outerWidth(),this.containers[c].containerCache.height=this.containers[c].element.outerHeight()}return this},_createPlaceholder:function(b){var c=b||this,d=c.options;if(!d.placeholder||d.placeholder.constructor==String){var e=d.placeholder;d.placeholder={element:function(){var b=a(document.createElement(c.currentItem[0].nodeName)).addClass(e||c.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper")[0];return e||(b.style.visibility="hidden"),b},update:function(a,b){if(e&&!d.forcePlaceholderSize)return;b.height()||b.height(c.currentItem.innerHeight()-parseInt(c.currentItem.css("paddingTop")||0,10)-parseInt(c.currentItem.css("paddingBottom")||0,10)),b.width()||b.width(c.currentItem.innerWidth()-parseInt(c.currentItem.css("paddingLeft")||0,10)-parseInt(c.currentItem.css("paddingRight")||0,10))}}}c.placeholder=a(d.placeholder.element.call(c.element,c.currentItem)),c.currentItem.after(c.placeholder),d.placeholder.update(c,c.placeholder)},_contactContainers:function(b){var c=null,d=null;for(var e=this.containers.length-1;e>=0;e--){if(a.ui.contains(this.currentItem[0],this.containers[e].element[0]))continue;if(this._intersectsWith(this.containers[e].containerCache)){if(c&&a.ui.contains(this.containers[e].element[0],c.element[0]))continue;c=this.containers[e],d=e}else this.containers[e].containerCache.over&&(this.containers[e]._trigger("out",b,this._uiHash(this)),this.containers[e].containerCache.over=0)}if(!c)return;if(this.containers.length===1)this.containers[d]._trigger("over",b,this._uiHash(this)),this.containers[d].containerCache.over=1;else if(this.currentContainer!=this.containers[d]){var f=1e4,g=null,h=this.positionAbs[this.containers[d].floating?"left":"top"];for(var i=this.items.length-1;i>=0;i--){if(!a.ui.contains(this.containers[d].element[0],this.items[i].item[0]))continue;var j=this.containers[d].floating?this.items[i].item.offset().left:this.items[i].item.offset().top;Math.abs(j-h)0?"down":"up")}if(!g&&!this.options.dropOnEmpty)return;this.currentContainer=this.containers[d],g?this._rearrange(b,g,null,!0):this._rearrange(b,null,this.containers[d].element,!0),this._trigger("change",b,this._uiHash()),this.containers[d]._trigger("change",b,this._uiHash(this)),this.options.placeholder.update(this.currentContainer,this.placeholder),this.containers[d]._trigger("over",b,this._uiHash(this)),this.containers[d].containerCache.over=1}},_createHelper:function(b){var c=this.options,d=a.isFunction(c.helper)?a(c.helper.apply(this.element[0],[b,this.currentItem])):c.helper=="clone"?this.currentItem.clone():this.currentItem;return d.parents("body").length||a(c.appendTo!="parent"?c.appendTo:this.currentItem[0].parentNode)[0].appendChild(d[0]),d[0]==this.currentItem[0]&&(this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")}),(d[0].style.width==""||c.forceHelperSize)&&d.width(this.currentItem.width()),(d[0].style.height==""||c.forceHelperSize)&&d.height(this.currentItem.height()),d},_adjustOffsetFromHelper:function(b){typeof b=="string"&&(b=b.split(" ")),a.isArray(b)&&(b={left:+b[0],top:+b[1]||0}),"left"in b&&(this.offset.click.left=b.left+this.margins.left),"right"in b&&(this.offset.click.left=this.helperProportions.width-b.right+this.margins.left),"top"in b&&(this.offset.click.top=b.top+this.margins.top),"bottom"in b&&(this.offset.click.top=this.helperProportions.height-b.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var b=this.offsetParent.offset();this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0])&&(b.left+=this.scrollParent.scrollLeft(),b.top+=this.scrollParent.scrollTop());if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&a.browser.msie)b={top:0,left:0};return{top:b.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:b.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var a=this.currentItem.position();return{top:a.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:a.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css("marginLeft"),10)||0,top:parseInt(this.currentItem.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var b=this.options;b.containment=="parent"&&(b.containment=this.helper[0].parentNode);if(b.containment=="document"||b.containment=="window")this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,a(b.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(a(b.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(b.containment)){var c=a(b.containment)[0],d=a(b.containment).offset(),e=a(c).css("overflow")!="hidden";this.containment=[d.left+(parseInt(a(c).css("borderLeftWidth"),10)||0)+(parseInt(a(c).css("paddingLeft"),10)||0)-this.margins.left,d.top+(parseInt(a(c).css("borderTopWidth"),10)||0)+(parseInt(a(c).css("paddingTop"),10)||0)-this.margins.top,d.left+(e?Math.max(c.scrollWidth,c.offsetWidth):c.offsetWidth)-(parseInt(a(c).css("borderLeftWidth"),10)||0)-(parseInt(a(c).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,d.top+(e?Math.max(c.scrollHeight,c.offsetHeight):c.offsetHeight)-(parseInt(a(c).css("borderTopWidth"),10)||0)-(parseInt(a(c).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top]}},_convertPositionTo:function(b,c){c||(c=this.position);var d=b=="absolute"?1:-1,e=this.options,f=this.cssPosition=="absolute"&&(this.scrollParent[0]==document||!a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,g=/(html|body)/i.test(f[0].tagName);return{top:c.top+this.offset.relative.top*d+this.offset.parent.top*d-(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():g?0:f.scrollTop())*d),left:c.left+this.offset.relative.left*d+this.offset.parent.left*d-(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():g?0:f.scrollLeft())*d)}},_generatePosition:function(b){var c=this.options,d=this.cssPosition=="absolute"&&(this.scrollParent[0]==document||!a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,e=/(html|body)/i.test(d[0].tagName);this.cssPosition=="relative"&&(this.scrollParent[0]==document||this.scrollParent[0]==this.offsetParent[0])&&(this.offset.relative=this._getRelativeOffset());var f=b.pageX,g=b.pageY;if(this.originalPosition){this.containment&&(b.pageX-this.offset.click.leftthis.containment[2]&&(f=this.containment[2]+this.offset.click.left),b.pageY-this.offset.click.top>this.containment[3]&&(g=this.containment[3]+this.offset.click.top));if(c.grid){var h=this.originalPageY+Math.round((g-this.originalPageY)/c.grid[1])*c.grid[1];g=this.containment?h-this.offset.click.topthis.containment[3]?h-this.offset.click.topthis.containment[2]?i-this.offset.click.left=0;f--)a.ui.contains(this.containers[f].element[0],this.currentItem[0])&&!c&&(d.push(function(a){return function(b){a._trigger("receive",b,this._uiHash(this))}}.call(this,this.containers[f])),d.push(function(a){return function(b){a._trigger("update",b,this._uiHash(this))}}.call(this,this.containers[f])))}for(var f=this.containers.length-1;f>=0;f--)c||d.push(function(a){return function(b){a._trigger("deactivate",b,this._uiHash(this))}}.call(this,this.containers[f])),this.containers[f].containerCache.over&&(d.push(function(a){return function(b){a._trigger("out",b,this._uiHash(this))}}.call(this,this.containers[f])),this.containers[f].containerCache.over=0);this._storedCursor&&a("body").css("cursor",this._storedCursor),this._storedOpacity&&this.helper.css("opacity",this._storedOpacity),this._storedZIndex&&this.helper.css("zIndex",this._storedZIndex=="auto"?"":this._storedZIndex),this.dragging=!1;if(this.cancelHelperRemoval){if(!c){this._trigger("beforeStop",b,this._uiHash());for(var f=0;f li > :first-child,> :not(li):even",icons:{header:"ui-icon-triangle-1-e",headerSelected:"ui-icon-triangle-1-s"},navigation:!1,navigationFilter:function(){return this.href.toLowerCase()===location.href.toLowerCase()}},_create:function(){var b=this,c=b.options;b.running=0,b.element.addClass("ui-accordion ui-widget ui-helper-reset").children("li").addClass("ui-accordion-li-fix"),b.headers=b.element.find(c.header).addClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all").bind("mouseenter.accordion",function(){if(c.disabled)return;a(this).addClass("ui-state-hover")}).bind("mouseleave.accordion",function(){if(c.disabled)return;a(this).removeClass("ui-state-hover")}).bind("focus.accordion",function(){if(c.disabled)return;a(this).addClass("ui-state-focus")}).bind("blur.accordion",function(){if(c.disabled)return;a(this).removeClass("ui-state-focus")}),b.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom");if(c.navigation){var d=b.element.find("a").filter(c.navigationFilter).eq(0);if(d.length){var e=d.closest(".ui-accordion-header");e.length?b.active=e:b.active=d.closest(".ui-accordion-content").prev()}}b.active=b._findActive(b.active||c.active).addClass("ui-state-default ui-state-active").toggleClass("ui-corner-all").toggleClass("ui-corner-top"),b.active.next().addClass("ui-accordion-content-active"),b._createIcons(),b.resize(),b.element.attr("role","tablist"),b.headers.attr("role","tab").bind("keydown.accordion",function(a){return b._keydown(a)}).next().attr("role","tabpanel"),b.headers.not(b.active||"").attr({"aria-expanded":"false","aria-selected":"false",tabIndex:-1}).next().hide(),b.active.length?b.active.attr({"aria-expanded":"true","aria-selected":"true",tabIndex:0}):b.headers.eq(0).attr("tabIndex",0),a.browser.safari||b.headers.find("a").attr("tabIndex",-1),c.event&&b.headers.bind(c.event.split(" ").join(".accordion ")+".accordion",function(a){b._clickHandler.call(b,a,this),a.preventDefault()})},_createIcons:function(){var b=this.options;b.icons&&(a("").addClass("ui-icon "+b.icons.header).prependTo(this.headers),this.active.children(".ui-icon").toggleClass(b.icons.header).toggleClass(b.icons.headerSelected),this.element.addClass("ui-accordion-icons"))},_destroyIcons:function(){this.headers.children(".ui-icon").remove(),this.element.removeClass("ui-accordion-icons")},destroy:function(){var b=this.options;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role"),this.headers.unbind(".accordion").removeClass("ui-accordion-header ui-accordion-disabled ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top").removeAttr("role").removeAttr("aria-expanded").removeAttr("aria-selected").removeAttr("tabIndex"),this.headers.find("a").removeAttr("tabIndex"),this._destroyIcons();var c=this.headers.next().css("display","").removeAttr("role").removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-accordion-disabled ui-state-disabled");return(b.autoHeight||b.fillHeight)&&c.css("height",""),a.Widget.prototype.destroy.call(this)},_setOption:function(b,c){a.Widget.prototype._setOption.apply(this,arguments),b=="active"&&this.activate(c),b=="icons"&&(this._destroyIcons(),c&&this._createIcons()),b=="disabled"&&this.headers.add(this.headers.next())[c?"addClass":"removeClass"]("ui-accordion-disabled ui-state-disabled")},_keydown:function(b){if(this.options.disabled||b.altKey||b.ctrlKey)return;var c=a.ui.keyCode,d=this.headers.length,e=this.headers.index(b.target),f=!1;switch(b.keyCode){case c.RIGHT:case c.DOWN:f=this.headers[(e+1)%d];break;case c.LEFT:case c.UP:f=this.headers[(e-1+d)%d];break;case c.SPACE:case c.ENTER:this._clickHandler({target:b.target},b.target),b.preventDefault()}return f?(a(b.target).attr("tabIndex",-1),a(f).attr("tabIndex",0),f.focus(),!1):!0},resize:function(){var b=this.options,c;if(b.fillSpace){if(a.browser.msie){var d=this.element.parent().css("overflow");this.element.parent().css("overflow","hidden")}c=this.element.parent().height(),a.browser.msie&&this.element.parent().css("overflow",d),this.headers.each(function(){c-=a(this).outerHeight(!0)}),this.headers.next().each(function(){a(this).height(Math.max(0,c-a(this).innerHeight()+a(this).height()))}).css("overflow","auto")}else b.autoHeight&&(c=0,this.headers.next().each(function(){c=Math.max(c,a(this).height("").height())}).height(c));return this},activate:function(a){this.options.active=a;var b=this._findActive(a)[0];return this._clickHandler({target:b},b),this},_findActive:function(b){return b?typeof b=="number"?this.headers.filter(":eq("+b+")"):this.headers.not(this.headers.not(b)):b===!1?a([]):this.headers.filter(":eq(0)")},_clickHandler:function(b,c){var d=this.options;if(d.disabled)return;if(!b.target){if(!d.collapsible)return;this.active.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").children(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header),this.active.next().addClass("ui-accordion-content-active");var e=this.active.next(),f={options:d,newHeader:a([]),oldHeader:d.active,newContent:a([]),oldContent:e},g=this.active=a([]);this._toggle(g,e,f);return}var h=a(b.currentTarget||c),i=h[0]===this.active[0];d.active=d.collapsible&&i?!1:this.headers.index(h);if(this.running||!d.collapsible&&i)return;var j=this.active,g=h.next(),e=this.active.next(),f={options:d,newHeader:i&&d.collapsible?a([]):h,oldHeader:this.active,newContent:i&&d.collapsible?a([]):g,oldContent:e},k=this.headers.index(this.active[0])>this.headers.index(h[0]);this.active=i?a([]):h,this._toggle(g,e,f,i,k),j.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").children(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header),i||(h.removeClass("ui-state-default ui-corner-all").addClass("ui-state-active ui-corner-top").children(".ui-icon").removeClass(d.icons.header).addClass(d.icons.headerSelected),h.next().addClass("ui-accordion-content-active"));return},_toggle:function(b,c,d,e,f){var g=this,h=g.options;g.toShow=b,g.toHide=c,g.data=d;var i=function(){if(!g)return;return g._completed.apply(g,arguments)};g._trigger("changestart",null,g.data),g.running=c.size()===0?b.size():c.size();if(h.animated){var j={};h.collapsible&&e?j={toShow:a([]),toHide:c,complete:i,down:f,autoHeight:h.autoHeight||h.fillSpace}:j={toShow:b,toHide:c,complete:i,down:f,autoHeight:h.autoHeight||h.fillSpace},h.proxied||(h.proxied=h.animated),h.proxiedDuration||(h.proxiedDuration=h.duration),h.animated=a.isFunction(h.proxied)?h.proxied(j):h.proxied,h.duration=a.isFunction(h.proxiedDuration)?h.proxiedDuration(j):h.proxiedDuration;var k=a.ui.accordion.animations,l=h.duration,m=h.animated;m&&!k[m]&&!a.easing[m]&&(m="slide"),k[m]||(k[m]=function(a){this.slide(a,{easing:m,duration:l||700})}),k[m](j)}else h.collapsible&&e?b.toggle():(c.hide(),b.show()),i(!0);c.prev().attr({"aria-expanded":"false","aria-selected":"false",tabIndex:-1}).blur(),b.prev().attr({"aria-expanded":"true","aria-selected":"true",tabIndex:0}).focus()},_completed:function(a){this.running=a?0:--this.running;if(this.running)return;this.options.clearStyle&&this.toShow.add(this.toHide).css({height:"",overflow:""}),this.toHide.removeClass("ui-accordion-content-active"),this.toHide.length&&(this.toHide.parent()[0].className=this.toHide.parent()[0].className),this._trigger("change",null,this.data)}}),a.extend(a.ui.accordion,{version:"1.8.21",animations:{slide:function(b,c){b=a.extend({easing:"swing",duration:300},b,c);if(!b.toHide.size()){b.toShow.animate({height:"show",paddingTop:"show",paddingBottom:"show"},b);return}if(!b.toShow.size()){b.toHide.animate({height:"hide",paddingTop:"hide",paddingBottom:"hide"},b);return}var d=b.toShow.css("overflow"),e=0,f={},g={},h=["height","paddingTop","paddingBottom"],i,j=b.toShow;i=j[0].style.width,j.width(j.parent().width()-parseFloat(j.css("paddingLeft"))-parseFloat(j.css("paddingRight"))-(parseFloat(j.css("borderLeftWidth"))||0)-(parseFloat(j.css("borderRightWidth"))||0)),a.each(h,function(c,d){g[d]="hide";var e=(""+a.css(b.toShow[0],d)).match(/^([\d+-.]+)(.*)$/);f[d]={value:e[1],unit:e[2]||"px"}}),b.toShow.css({height:0,overflow:"hidden"}).show(),b.toHide.filter(":hidden").each(b.complete).end().filter(":visible").animate(g,{step:function(a,c){c.prop=="height"&&(e=c.end-c.start===0?0:(c.now-c.start)/(c.end-c.start)),b.toShow[0].style[c.prop]=e*f[c.prop].value+f[c.prop].unit},duration:b.duration,easing:b.easing,complete:function(){b.autoHeight||b.toShow.css("height",""),b.toShow.css({width:i,overflow:d}),b.complete()}})},bounceslide:function(a){this.slide(a,{easing:a.down?"easeOutBounce":"swing",duration:a.down?1e3:200})}}})})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.ui.autocomplete.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function(a,b){var c=0;a.widget("ui.autocomplete",{options:{appendTo:"body",autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null},pending:0,_create:function(){var b=this,c=this.element[0].ownerDocument,d;this.isMultiLine=this.element.is("textarea"),this.element.addClass("ui-autocomplete-input").attr("autocomplete","off").attr({role:"textbox","aria-autocomplete":"list","aria-haspopup":"true"}).bind("keydown.autocomplete",function(c){if(b.options.disabled||b.element.propAttr("readOnly"))return;d=!1;var e=a.ui.keyCode;switch(c.keyCode){case e.PAGE_UP:b._move("previousPage",c);break;case e.PAGE_DOWN:b._move("nextPage",c);break;case e.UP:b._keyEvent("previous",c);break;case e.DOWN:b._keyEvent("next",c);break;case e.ENTER:case e.NUMPAD_ENTER:b.menu.active&&(d=!0,c.preventDefault());case e.TAB:if(!b.menu.active)return;b.menu.select(c);break;case e.ESCAPE:b.element.val(b.term),b.close(c);break;default:clearTimeout(b.searching),b.searching=setTimeout(function(){b.term!=b.element.val()&&(b.selectedItem=null,b.search(null,c))},b.options.delay)}}).bind("keypress.autocomplete",function(a){d&&(d=!1,a.preventDefault())}).bind("focus.autocomplete",function(){if(b.options.disabled)return;b.selectedItem=null,b.previous=b.element.val()}).bind("blur.autocomplete",function(a){if(b.options.disabled)return;clearTimeout(b.searching),b.closing=setTimeout(function(){b.close(a),b._change(a)},150)}),this._initSource(),this.menu=a("
        ").addClass("ui-autocomplete").appendTo(a(this.options.appendTo||"body",c)[0]).mousedown(function(c){var d=b.menu.element[0];a(c.target).closest(".ui-menu-item").length||setTimeout(function(){a(document).one("mousedown",function(c){c.target!==b.element[0]&&c.target!==d&&!a.ui.contains(d,c.target)&&b.close()})},1),setTimeout(function(){clearTimeout(b.closing)},13)}).menu({focus:function(a,c){var d=c.item.data("item.autocomplete");!1!==b._trigger("focus",a,{item:d})&&/^key/.test(a.originalEvent.type)&&b.element.val(d.value)},selected:function(a,d){var e=d.item.data("item.autocomplete"),f=b.previous;b.element[0]!==c.activeElement&&(b.element.focus(),b.previous=f,setTimeout(function(){b.previous=f,b.selectedItem=e},1)),!1!==b._trigger("select",a,{item:e})&&b.element.val(e.value),b.term=b.element.val(),b.close(a),b.selectedItem=e},blur:function(a,c){b.menu.element.is(":visible")&&b.element.val()!==b.term&&b.element.val(b.term)}}).zIndex(this.element.zIndex()+1).css({top:0,left:0}).hide().data("menu"),a.fn.bgiframe&&this.menu.element.bgiframe(),b.beforeunloadHandler=function(){b.element.removeAttr("autocomplete")},a(window).bind("beforeunload",b.beforeunloadHandler)},destroy:function(){this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete").removeAttr("role").removeAttr("aria-autocomplete").removeAttr("aria-haspopup"),this.menu.element.remove(),a(window).unbind("beforeunload",this.beforeunloadHandler),a.Widget.prototype.destroy.call(this)},_setOption:function(b,c){a.Widget.prototype._setOption.apply(this,arguments),b==="source"&&this._initSource(),b==="appendTo"&&this.menu.element.appendTo(a(c||"body",this.element[0].ownerDocument)[0]),b==="disabled"&&c&&this.xhr&&this.xhr.abort()},_initSource:function(){var b=this,c,d;a.isArray(this.options.source)?(c=this.options.source,this.source=function(b,d){d(a.ui.autocomplete.filter(c,b.term))}):typeof this.options.source=="string"?(d=this.options.source,this.source=function(c,e){b.xhr&&b.xhr.abort(),b.xhr=a.ajax({url:d,data:c,dataType:"json",success:function(a,b){e(a)},error:function(){e([])}})}):this.source=this.options.source},search:function(a,b){a=a!=null?a:this.element.val(),this.term=this.element.val();if(a.length").data("item.autocomplete",c).append(a("").text(c.label)).appendTo(b)},_move:function(a,b){if(!this.menu.element.is(":visible")){this.search(null,b);return}if(this.menu.first()&&/^previous/.test(a)||this.menu.last()&&/^next/.test(a)){this.element.val(this.term),this.menu.deactivate();return}this.menu[a](b)},widget:function(){return this.menu.element},_keyEvent:function(a,b){if(!this.isMultiLine||this.menu.element.is(":visible"))this._move(a,b),b.preventDefault()}}),a.extend(a.ui.autocomplete,{escapeRegex:function(a){return a.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&")},filter:function(b,c){var d=new RegExp(a.ui.autocomplete.escapeRegex(c),"i");return a.grep(b,function(a){return d.test(a.label||a.value||a)})}})})(jQuery),function(a){a.widget("ui.menu",{_create:function(){var b=this;this.element.addClass("ui-menu ui-widget ui-widget-content ui-corner-all").attr({role:"listbox","aria-activedescendant":"ui-active-menuitem"}).click(function(c){if(!a(c.target).closest(".ui-menu-item a").length)return;c.preventDefault(),b.select(c)}),this.refresh()},refresh:function(){var b=this,c=this.element.children("li:not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","menuitem");c.children("a").addClass("ui-corner-all").attr("tabindex",-1).mouseenter(function(c){b.activate(c,a(this).parent())}).mouseleave(function(){b.deactivate()})},activate:function(a,b){this.deactivate();if(this.hasScroll()){var c=b.offset().top-this.element.offset().top,d=this.element.scrollTop(),e=this.element.height();c<0?this.element.scrollTop(d+c):c>=e&&this.element.scrollTop(d+c-e+b.height())}this.active=b.eq(0).children("a").addClass("ui-state-hover").attr("id","ui-active-menuitem").end(),this._trigger("focus",a,{item:b})},deactivate:function(){if(!this.active)return;this.active.children("a").removeClass("ui-state-hover").removeAttr("id"),this._trigger("blur"),this.active=null},next:function(a){this.move("next",".ui-menu-item:first",a)},previous:function(a){this.move("prev",".ui-menu-item:last",a)},first:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},last:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},move:function(a,b,c){if(!this.active){this.activate(c,this.element.children(b));return}var d=this.active[a+"All"](".ui-menu-item").eq(0);d.length?this.activate(c,d):this.activate(c,this.element.children(b))},nextPage:function(b){if(this.hasScroll()){if(!this.active||this.last()){this.activate(b,this.element.children(".ui-menu-item:first"));return}var c=this.active.offset().top,d=this.element.height(),e=this.element.children(".ui-menu-item").filter(function(){var b=a(this).offset().top-c-d+a(this).height();return b<10&&b>-10});e.length||(e=this.element.children(".ui-menu-item:last")),this.activate(b,e)}else this.activate(b,this.element.children(".ui-menu-item").filter(!this.active||this.last()?":first":":last"))},previousPage:function(b){if(this.hasScroll()){if(!this.active||this.first()){this.activate(b,this.element.children(".ui-menu-item:last"));return}var c=this.active.offset().top,d=this.element.height(),e=this.element.children(".ui-menu-item").filter(function(){var b=a(this).offset().top-c+d-a(this).height();return b<10&&b>-10});e.length||(e=this.element.children(".ui-menu-item:first")),this.activate(b,e)}else this.activate(b,this.element.children(".ui-menu-item").filter(!this.active||this.first()?":last":":first"))},hasScroll:function(){return this.element.height()",this.element[0].ownerDocument).addClass("ui-button-text").html(this.options.label).appendTo(b.empty()).text(),d=this.options.icons,e=d.primary&&d.secondary,f=[];d.primary||d.secondary?(this.options.text&&f.push("ui-button-text-icon"+(e?"s":d.primary?"-primary":"-secondary")),d.primary&&b.prepend(""),d.secondary&&b.append(""),this.options.text||(f.push(e?"ui-button-icons-only":"ui-button-icon-only"),this.hasTitle||b.attr("title",c))):f.push("ui-button-text-only"),b.addClass(f.join(" "))}}),a.widget("ui.buttonset",{options:{items:":button, :submit, :reset, :checkbox, :radio, a, :data(button)"},_create:function(){this.element.addClass("ui-buttonset")},_init:function(){this.refresh()},_setOption:function(b,c){b==="disabled"&&this.buttons.button("option",b,c),a.Widget.prototype._setOption.apply(this,arguments)},refresh:function(){var b=this.element.css("direction")==="rtl";this.buttons=this.element.find(this.options.items).filter(":ui-button").button("refresh").end().not(":ui-button").button().end().map(function(){return a(this).button("widget")[0]}).removeClass("ui-corner-all ui-corner-left ui-corner-right").filter(":first").addClass(b?"ui-corner-right":"ui-corner-left").end().filter(":last").addClass(b?"ui-corner-left":"ui-corner-right").end().end()},destroy:function(){this.element.removeClass("ui-buttonset"),this.buttons.map(function(){return a(this).button("widget")[0]}).removeClass("ui-corner-left ui-corner-right").end().button("destroy"),a.Widget.prototype.destroy.call(this)}})})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.ui.dialog.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function(a,b){var c="ui-dialog ui-widget ui-widget-content ui-corner-all ",d={buttons:!0,height:!0,maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0,width:!0},e={maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0},f=a.attrFn||{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0,click:!0};a.widget("ui.dialog",{options:{autoOpen:!0,buttons:{},closeOnEscape:!0,closeText:"close",dialogClass:"",draggable:!0,hide:null,height:"auto",maxHeight:!1,maxWidth:!1,minHeight:150,minWidth:150,modal:!1,position:{my:"center",at:"center",collision:"fit",using:function(b){var c=a(this).css(b).offset().top;c<0&&a(this).css("top",b.top-c)}},resizable:!0,show:null,stack:!0,title:"",width:300,zIndex:1e3},_create:function(){this.originalTitle=this.element.attr("title"),typeof this.originalTitle!="string"&&(this.originalTitle=""),this.options.title=this.options.title||this.originalTitle;var b=this,d=b.options,e=d.title||" ",f=a.ui.dialog.getTitleId(b.element),g=(b.uiDialog=a("
        ")).appendTo(document.body).hide().addClass(c+d.dialogClass).css({zIndex:d.zIndex}).attr("tabIndex",-1).css("outline",0).keydown(function(c){d.closeOnEscape&&!c.isDefaultPrevented()&&c.keyCode&&c.keyCode===a.ui.keyCode.ESCAPE&&(b.close(c),c.preventDefault())}).attr({role:"dialog","aria-labelledby":f}).mousedown(function(a){b.moveToTop(!1,a)}),h=b.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(g),i=(b.uiDialogTitlebar=a("
        ")).addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").prependTo(g),j=a('').addClass("ui-dialog-titlebar-close ui-corner-all").attr("role","button").hover(function(){j.addClass("ui-state-hover")},function(){j.removeClass("ui-state-hover")}).focus(function(){j.addClass("ui-state-focus")}).blur(function(){j.removeClass("ui-state-focus")}).click(function(a){return b.close(a),!1}).appendTo(i),k=(b.uiDialogTitlebarCloseText=a("")).addClass("ui-icon ui-icon-closethick").text(d.closeText).appendTo(j),l=a("").addClass("ui-dialog-title").attr("id",f).html(e).prependTo(i);a.isFunction(d.beforeclose)&&!a.isFunction(d.beforeClose)&&(d.beforeClose=d.beforeclose),i.find("*").add(i).disableSelection(),d.draggable&&a.fn.draggable&&b._makeDraggable(),d.resizable&&a.fn.resizable&&b._makeResizable(),b._createButtons(d.buttons),b._isOpen=!1,a.fn.bgiframe&&g.bgiframe()},_init:function(){this.options.autoOpen&&this.open()},destroy:function(){var a=this;return a.overlay&&a.overlay.destroy(),a.uiDialog.hide(),a.element.unbind(".dialog").removeData("dialog").removeClass("ui-dialog-content ui-widget-content").hide().appendTo("body"),a.uiDialog.remove(),a.originalTitle&&a.element.attr("title",a.originalTitle),a},widget:function(){return this.uiDialog},close:function(b){var c=this,d,e;if(!1===c._trigger("beforeClose",b))return;return c.overlay&&c.overlay.destroy(),c.uiDialog.unbind("keypress.ui-dialog"),c._isOpen=!1,c.options.hide?c.uiDialog.hide(c.options.hide,function(){c._trigger("close",b)}):(c.uiDialog.hide(),c._trigger("close",b)),a.ui.dialog.overlay.resize(),c.options.modal&&(d=0,a(".ui-dialog").each(function(){this!==c.uiDialog[0]&&(e=a(this).css("z-index"),isNaN(e)||(d=Math.max(d,e)))}),a.ui.dialog.maxZ=d),c},isOpen:function(){return this._isOpen},moveToTop:function(b,c){var d=this,e=d.options,f;return e.modal&&!b||!e.stack&&!e.modal?d._trigger("focus",c):(e.zIndex>a.ui.dialog.maxZ&&(a.ui.dialog.maxZ=e.zIndex),d.overlay&&(a.ui.dialog.maxZ+=1,d.overlay.$el.css("z-index",a.ui.dialog.overlay.maxZ=a.ui.dialog.maxZ)),f={scrollTop:d.element.scrollTop(),scrollLeft:d.element.scrollLeft()},a.ui.dialog.maxZ+=1,d.uiDialog.css("z-index",a.ui.dialog.maxZ),d.element.attr(f),d._trigger("focus",c),d)},open:function(){if(this._isOpen)return;var b=this,c=b.options,d=b.uiDialog;return b.overlay=c.modal?new a.ui.dialog.overlay(b):null,b._size(),b._position(c.position),d.show(c.show),b.moveToTop(!0),c.modal&&d.bind("keydown.ui-dialog",function(b){if(b.keyCode!==a.ui.keyCode.TAB)return;var c=a(":tabbable",this),d=c.filter(":first"),e=c.filter(":last");if(b.target===e[0]&&!b.shiftKey)return d.focus(1),!1;if(b.target===d[0]&&b.shiftKey)return e.focus(1),!1}),a(b.element.find(":tabbable").get().concat(d.find(".ui-dialog-buttonpane :tabbable").get().concat(d.get()))).eq(0).focus(),b._isOpen=!0,b._trigger("open"),b},_createButtons:function(b){var c=this,d=!1,e=a("
        ").addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"),g=a("
        ").addClass("ui-dialog-buttonset").appendTo(e);c.uiDialog.find(".ui-dialog-buttonpane").remove(),typeof b=="object"&&b!==null&&a.each(b,function(){return!(d=!0)}),d&&(a.each(b,function(b,d){d=a.isFunction(d)?{click:d,text:b}:d;var e=a('').click(function(){d.click.apply(c.element[0],arguments)}).appendTo(g);a.each(d,function(a,b){if(a==="click")return;a in f?e[a](b):e.attr(a,b)}),a.fn.button&&e.button()}),e.appendTo(c.uiDialog))},_makeDraggable:function(){function f(a){return{position:a.position,offset:a.offset}}var b=this,c=b.options,d=a(document),e;b.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",handle:".ui-dialog-titlebar",containment:"document",start:function(d,g){e=c.height==="auto"?"auto":a(this).height(),a(this).height(a(this).height()).addClass("ui-dialog-dragging"),b._trigger("dragStart",d,f(g))},drag:function(a,c){b._trigger("drag",a,f(c))},stop:function(g,h){c.position=[h.position.left-d.scrollLeft(),h.position.top-d.scrollTop()],a(this).removeClass("ui-dialog-dragging").height(e),b._trigger("dragStop",g,f(h)),a.ui.dialog.overlay.resize()}})},_makeResizable:function(c){function h(a){return{originalPosition:a.originalPosition,originalSize:a.originalSize,position:a.position,size:a.size}}c=c===b?this.options.resizable:c;var d=this,e=d.options,f=d.uiDialog.css("position"),g=typeof c=="string"?c:"n,e,s,w,se,sw,ne,nw";d.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:d.element,maxWidth:e.maxWidth,maxHeight:e.maxHeight,minWidth:e.minWidth,minHeight:d._minHeight(),handles:g,start:function(b,c){a(this).addClass("ui-dialog-resizing"),d._trigger("resizeStart",b,h(c))},resize:function(a,b){d._trigger("resize",a,h(b))},stop:function(b,c){a(this).removeClass("ui-dialog-resizing"),e.height=a(this).height(),e.width=a(this).width(),d._trigger("resizeStop",b,h(c)),a.ui.dialog.overlay.resize()}}).css("position",f).find(".ui-resizable-se").addClass("ui-icon ui-icon-grip-diagonal-se")},_minHeight:function(){var a=this.options;return a.height==="auto"?a.minHeight:Math.min(a.minHeight,a.height)},_position:function(b){var c=[],d=[0,0],e;if(b){if(typeof b=="string"||typeof b=="object"&&"0"in b)c=b.split?b.split(" "):[b[0],b[1]],c.length===1&&(c[1]=c[0]),a.each(["left","top"],function(a,b){+c[a]===c[a]&&(d[a]=c[a],c[a]=b)}),b={my:c.join(" "),at:c.join(" "),offset:d.join(" ")};b=a.extend({},a.ui.dialog.prototype.options.position,b)}else b=a.ui.dialog.prototype.options.position;e=this.uiDialog.is(":visible"),e||this.uiDialog.show(),this.uiDialog.css({top:0,left:0}).position(a.extend({of:window},b)),e||this.uiDialog.hide()},_setOptions:function(b){var c=this,f={},g=!1;a.each(b,function(a,b){c._setOption(a,b),a in d&&(g=!0),a in e&&(f[a]=b)}),g&&this._size(),this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option",f)},_setOption:function(b,d){var e=this,f=e.uiDialog;switch(b){case"beforeclose":b="beforeClose";break;case"buttons":e._createButtons(d);break;case"closeText":e.uiDialogTitlebarCloseText.text(""+d);break;case"dialogClass":f.removeClass(e.options.dialogClass).addClass(c+d);break;case"disabled":d?f.addClass("ui-dialog-disabled"):f.removeClass("ui-dialog-disabled");break;case"draggable":var g=f.is(":data(draggable)");g&&!d&&f.draggable("destroy"),!g&&d&&e._makeDraggable();break;case"position":e._position(d);break;case"resizable":var h=f.is(":data(resizable)");h&&!d&&f.resizable("destroy"),h&&typeof d=="string"&&f.resizable("option","handles",d),!h&&d!==!1&&e._makeResizable(d);break;case"title":a(".ui-dialog-title",e.uiDialogTitlebar).html(""+(d||" "))}a.Widget.prototype._setOption.apply(e,arguments)},_size:function(){var b=this.options,c,d,e=this.uiDialog.is(":visible");this.element.show().css({width:"auto",minHeight:0,height:0}),b.minWidth>b.width&&(b.width=b.minWidth),c=this.uiDialog.css({height:"auto",width:b.width}).height(),d=Math.max(0,b.minHeight-c);if(b.height==="auto")if(a.support.minHeight)this.element.css({minHeight:d,height:"auto"});else{this.uiDialog.show();var f=this.element.css("height","auto").height();e||this.uiDialog.hide(),this.element.height(Math.max(f,d))}else this.element.height(Math.max(b.height-c,0));this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option","minHeight",this._minHeight())}}),a.extend(a.ui.dialog,{version:"1.8.21",uuid:0,maxZ:0,getTitleId:function(a){var b=a.attr("id");return b||(this.uuid+=1,b=this.uuid),"ui-dialog-title-"+b},overlay:function(b){this.$el=a.ui.dialog.overlay.create(b)}}),a.extend(a.ui.dialog.overlay,{instances:[],oldInstances:[],maxZ:0,events:a.map("focus,mousedown,mouseup,keydown,keypress,click".split(","),function(a){return a+".dialog-overlay"}).join(" "),create:function(b){this.instances.length===0&&(setTimeout(function(){a.ui.dialog.overlay.instances.length&&a(document).bind(a.ui.dialog.overlay.events,function(b){if(a(b.target).zIndex()").addClass("ui-widget-overlay")).appendTo(document.body).css({width:this.width(),height:this.height()});return a.fn.bgiframe&&c.bgiframe(),this.instances.push(c),c},destroy:function(b){var c=a.inArray(b,this.instances);c!=-1&&this.oldInstances.push(this.instances.splice(c,1)[0]),this.instances.length===0&&a([document,window]).unbind(".dialog-overlay"),b.remove();var d=0;a.each(this.instances,function(){d=Math.max(d,this.css("z-index"))}),this.maxZ=d},height:function(){var b,c;return a.browser.msie&&a.browser.version<7?(b=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight),c=Math.max(document.documentElement.offsetHeight,document.body.offsetHeight),b").appendTo(this.element).addClass("ui-slider-range ui-widget-header"+(d.range==="min"||d.range==="max"?" ui-slider-range-"+d.range:"")));for(var i=e.length;ic&&(f=c,g=a(this),i=b)}),c.range===!0&&this.values(1)===c.min&&(i+=1,g=a(this.handles[i])),j=this._start(b,i),j===!1?!1:(this._mouseSliding=!0,h._handleIndex=i,g.addClass("ui-state-active").focus(),k=g.offset(),l=!a(b.target).parents().andSelf().is(".ui-slider-handle"),this._clickOffset=l?{left:0,top:0}:{left:b.pageX-k.left-g.width()/2,top:b.pageY-k.top-g.height()/2-(parseInt(g.css("borderTopWidth"),10)||0)-(parseInt(g.css("borderBottomWidth"),10)||0)+(parseInt(g.css("marginTop"),10)||0)},this.handles.hasClass("ui-state-hover")||this._slide(b,i,e),this._animateOff=!0,!0))},_mouseStart:function(a){return!0},_mouseDrag:function(a){var b={x:a.pageX,y:a.pageY},c=this._normValueFromMouse(b);return this._slide(a,this._handleIndex,c),!1},_mouseStop:function(a){return this.handles.removeClass("ui-state-active"),this._mouseSliding=!1,this._stop(a,this._handleIndex),this._change(a,this._handleIndex),this._handleIndex=null,this._clickOffset=null,this._animateOff=!1,!1},_detectOrientation:function(){this.orientation=this.options.orientation==="vertical"?"vertical":"horizontal"},_normValueFromMouse:function(a){var b,c,d,e,f;return this.orientation==="horizontal"?(b=this.elementSize.width,c=a.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)):(b=this.elementSize.height,c=a.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)),d=c/b,d>1&&(d=1),d<0&&(d=0),this.orientation==="vertical"&&(d=1-d),e=this._valueMax()-this._valueMin(),f=this._valueMin()+d*e,this._trimAlignValue(f)},_start:function(a,b){var c={handle:this.handles[b],value:this.value()};return this.options.values&&this.options.values.length&&(c.value=this.values(b),c.values=this.values()),this._trigger("start",a,c)},_slide:function(a,b,c){var d,e,f;this.options.values&&this.options.values.length?(d=this.values(b?0:1),this.options.values.length===2&&this.options.range===!0&&(b===0&&c>d||b===1&&c1){this.options.values[b]=this._trimAlignValue(c),this._refreshValue(),this._change(null,b);return}if(!arguments.length)return this._values();if(!a.isArray(arguments[0]))return this.options.values&&this.options.values.length?this._values(b):this.value();d=this.options.values,e=arguments[0];for(f=0;f=this._valueMax())return this._valueMax();var b=this.options.step>0?this.options.step:1,c=(a-this._valueMin())%b,d=a-c;return Math.abs(c)*2>=b&&(d+=c>0?b:-b),parseFloat(d.toFixed(5))},_valueMin:function(){return this.options.min},_valueMax:function(){return this.options.max},_refreshValue:function(){var b=this.options.range,c=this.options,d=this,e=this._animateOff?!1:c.animate,f,g={},h,i,j,k;this.options.values&&this.options.values.length?this.handles.each(function(b,i){f=(d.values(b)-d._valueMin())/(d._valueMax()-d._valueMin())*100,g[d.orientation==="horizontal"?"left":"bottom"]=f+"%",a(this).stop(1,1)[e?"animate":"css"](g,c.animate),d.options.range===!0&&(d.orientation==="horizontal"?(b===0&&d.range.stop(1,1)[e?"animate":"css"]({left:f+"%"},c.animate),b===1&&d.range[e?"animate":"css"]({width:f-h+"%"},{queue:!1,duration:c.animate})):(b===0&&d.range.stop(1,1)[e?"animate":"css"]({bottom:f+"%"},c.animate),b===1&&d.range[e?"animate":"css"]({height:f-h+"%"},{queue:!1,duration:c.animate}))),h=f}):(i=this.value(),j=this._valueMin(),k=this._valueMax(),f=k!==j?(i-j)/(k-j)*100:0,g[d.orientation==="horizontal"?"left":"bottom"]=f+"%",this.handle.stop(1,1)[e?"animate":"css"](g,c.animate),b==="min"&&this.orientation==="horizontal"&&this.range.stop(1,1)[e?"animate":"css"]({width:f+"%"},c.animate),b==="max"&&this.orientation==="horizontal"&&this.range[e?"animate":"css"]({width:100-f+"%"},{queue:!1,duration:c.animate}),b==="min"&&this.orientation==="vertical"&&this.range.stop(1,1)[e?"animate":"css"]({height:f+"%"},c.animate),b==="max"&&this.orientation==="vertical"&&this.range[e?"animate":"css"]({height:100-f+"%"},{queue:!1,duration:c.animate}))}}),a.extend(a.ui.slider,{version:"1.8.21"})})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.ui.tabs.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function(a,b){function e(){return++c}function f(){return++d}var c=0,d=0;a.widget("ui.tabs",{options:{add:null,ajaxOptions:null,cache:!1,cookie:null,collapsible:!1,disable:null,disabled:[],enable:null,event:"click",fx:null,idPrefix:"ui-tabs-",load:null,panelTemplate:"
        ",remove:null,select:null,show:null,spinner:"Loading…",tabTemplate:"
      • #{label}
      • "},_create:function(){this._tabify(!0)},_setOption:function(a,b){if(a=="selected"){if(this.options.collapsible&&b==this.options.selected)return;this.select(b)}else this.options[a]=b,this._tabify()},_tabId:function(a){return a.title&&a.title.replace(/\s/g,"_").replace(/[^\w\u00c0-\uFFFF-]/g,"")||this.options.idPrefix+e()},_sanitizeSelector:function(a){return a.replace(/:/g,"\\:")},_cookie:function(){var b=this.cookie||(this.cookie=this.options.cookie.name||"ui-tabs-"+f());return a.cookie.apply(null,[b].concat(a.makeArray(arguments)))},_ui:function(a,b){return{tab:a,panel:b,index:this.anchors.index(a)}},_cleanup:function(){this.lis.filter(".ui-state-processing").removeClass("ui-state-processing").find("span:data(label.tabs)").each(function(){var b=a(this);b.html(b.data("label.tabs")).removeData("label.tabs")})},_tabify:function(c){function m(b,c){b.css("display",""),!a.support.opacity&&c.opacity&&b[0].style.removeAttribute("filter")}var d=this,e=this.options,f=/^#.+/;this.list=this.element.find("ol,ul").eq(0),this.lis=a(" > li:has(a[href])",this.list),this.anchors=this.lis.map(function(){return a("a",this)[0]}),this.panels=a([]),this.anchors.each(function(b,c){var g=a(c).attr("href"),h=g.split("#")[0],i;h&&(h===location.toString().split("#")[0]||(i=a("base")[0])&&h===i.href)&&(g=c.hash,c.href=g);if(f.test(g))d.panels=d.panels.add(d.element.find(d._sanitizeSelector(g)));else if(g&&g!=="#"){a.data(c,"href.tabs",g),a.data(c,"load.tabs",g.replace(/#.*$/,""));var j=d._tabId(c);c.href="#"+j;var k=d.element.find("#"+j);k.length||(k=a(e.panelTemplate).attr("id",j).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").insertAfter(d.panels[b-1]||d.list),k.data("destroy.tabs",!0)),d.panels=d.panels.add(k)}else e.disabled.push(b)}),c?(this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all"),this.list.addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all"),this.lis.addClass("ui-state-default ui-corner-top"),this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom"),e.selected===b?(location.hash&&this.anchors.each(function(a,b){if(b.hash==location.hash)return e.selected=a,!1}),typeof e.selected!="number"&&e.cookie&&(e.selected=parseInt(d._cookie(),10)),typeof e.selected!="number"&&this.lis.filter(".ui-tabs-selected").length&&(e.selected=this.lis.index(this.lis.filter(".ui-tabs-selected"))),e.selected=e.selected||(this.lis.length?0:-1)):e.selected===null&&(e.selected=-1),e.selected=e.selected>=0&&this.anchors[e.selected]||e.selected<0?e.selected:0,e.disabled=a.unique(e.disabled.concat(a.map(this.lis.filter(".ui-state-disabled"),function(a,b){return d.lis.index(a)}))).sort(),a.inArray(e.selected,e.disabled)!=-1&&e.disabled.splice(a.inArray(e.selected,e.disabled),1),this.panels.addClass("ui-tabs-hide"),this.lis.removeClass("ui-tabs-selected ui-state-active"),e.selected>=0&&this.anchors.length&&(d.element.find(d._sanitizeSelector(d.anchors[e.selected].hash)).removeClass("ui-tabs-hide"),this.lis.eq(e.selected).addClass("ui-tabs-selected ui-state-active"),d.element.queue("tabs",function(){d._trigger("show",null,d._ui(d.anchors[e.selected],d.element.find(d._sanitizeSelector(d.anchors[e.selected].hash))[0]))}),this.load(e.selected)),a(window).bind("unload",function(){d.lis.add(d.anchors).unbind(".tabs"),d.lis=d.anchors=d.panels=null})):e.selected=this.lis.index(this.lis.filter(".ui-tabs-selected")),this.element[e.collapsible?"addClass":"removeClass"]("ui-tabs-collapsible"),e.cookie&&this._cookie(e.selected,e.cookie);for(var g=0,h;h=this.lis[g];g++)a(h)[a.inArray(g,e.disabled)!=-1&&!a(h).hasClass("ui-tabs-selected")?"addClass":"removeClass"]("ui-state-disabled");e.cache===!1&&this.anchors.removeData("cache.tabs"),this.lis.add(this.anchors).unbind(".tabs");if(e.event!=="mouseover"){var i=function(a,b){b.is(":not(.ui-state-disabled)")&&b.addClass("ui-state-"+a)},j=function(a,b){b.removeClass("ui-state-"+a)};this.lis.bind("mouseover.tabs",function(){i("hover",a(this))}),this.lis.bind("mouseout.tabs",function(){j("hover",a(this))}),this.anchors.bind("focus.tabs",function(){i("focus",a(this).closest("li"))}),this.anchors.bind("blur.tabs",function(){j("focus",a(this).closest("li"))})}var k,l;e.fx&&(a.isArray(e.fx)?(k=e.fx[0],l=e.fx[1]):k=l=e.fx);var n=l?function(b,c){a(b).closest("li").addClass("ui-tabs-selected ui-state-active"),c.hide().removeClass("ui-tabs-hide").animate(l,l.duration||"normal",function(){m(c,l),d._trigger("show",null,d._ui(b,c[0]))})}:function(b,c){a(b).closest("li").addClass("ui-tabs-selected ui-state-active"),c.removeClass("ui-tabs-hide"),d._trigger("show",null,d._ui(b,c[0]))},o=k?function(a,b){b.animate(k,k.duration||"normal",function(){d.lis.removeClass("ui-tabs-selected ui-state-active"),b.addClass("ui-tabs-hide"),m(b,k),d.element.dequeue("tabs")})}:function(a,b,c){d.lis.removeClass("ui-tabs-selected ui-state-active"),b.addClass("ui-tabs-hide"),d.element.dequeue("tabs")};this.anchors.bind(e.event+".tabs",function(){var b=this,c=a(b).closest("li"),f=d.panels.filter(":not(.ui-tabs-hide)"),g=d.element.find(d._sanitizeSelector(b.hash));if(c.hasClass("ui-tabs-selected")&&!e.collapsible||c.hasClass("ui-state-disabled")||c.hasClass("ui-state-processing")||d.panels.filter(":animated").length||d._trigger("select",null,d._ui(this,g[0]))===!1)return this.blur(),!1;e.selected=d.anchors.index(this),d.abort();if(e.collapsible){if(c.hasClass("ui-tabs-selected"))return e.selected=-1,e.cookie&&d._cookie(e.selected,e.cookie),d.element.queue("tabs",function(){o(b,f)}).dequeue("tabs"),this.blur(),!1;if(!f.length)return e.cookie&&d._cookie(e.selected,e.cookie),d.element.queue("tabs",function(){n(b,g)}),d.load(d.anchors.index(this)),this.blur(),!1}e.cookie&&d._cookie(e.selected,e.cookie);if(g.length)f.length&&d.element.queue("tabs",function(){o(b,f)}),d.element.queue("tabs",function(){n(b,g)}),d.load(d.anchors.index(this));else throw"jQuery UI Tabs: Mismatching fragment identifier.";a.browser.msie&&this.blur()}),this.anchors.bind("click.tabs",function(){return!1})},_getIndex:function(a){return typeof a=="string"&&(a=this.anchors.index(this.anchors.filter("[href$='"+a+"']"))),a},destroy:function(){var b=this.options;return this.abort(),this.element.unbind(".tabs").removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible").removeData("tabs"),this.list.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all"),this.anchors.each(function(){var b=a.data(this,"href.tabs");b&&(this.href=b);var c=a(this).unbind(".tabs");a.each(["href","load","cache"],function(a,b){c.removeData(b+".tabs")})}),this.lis.unbind(".tabs").add(this.panels).each(function(){a.data(this,"destroy.tabs")?a(this).remove():a(this).removeClass(["ui-state-default","ui-corner-top","ui-tabs-selected","ui-state-active","ui-state-hover","ui-state-focus","ui-state-disabled","ui-tabs-panel","ui-widget-content","ui-corner-bottom","ui-tabs-hide"].join(" "))}),b.cookie&&this._cookie(null,b.cookie),this},add:function(c,d,e){e===b&&(e=this.anchors.length);var f=this,g=this.options,h=a(g.tabTemplate.replace(/#\{href\}/g,c).replace(/#\{label\}/g,d)),i=c.indexOf("#")?this._tabId(a("a",h)[0]):c.replace("#","");h.addClass("ui-state-default ui-corner-top").data("destroy.tabs",!0);var j=f.element.find("#"+i);return j.length||(j=a(g.panelTemplate).attr("id",i).data("destroy.tabs",!0)),j.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide"),e>=this.lis.length?(h.appendTo(this.list),j.appendTo(this.list[0].parentNode)):(h.insertBefore(this.lis[e]),j.insertBefore(this.panels[e])),g.disabled=a.map(g.disabled,function(a,b){return a>=e?++a:a}),this._tabify(),this.anchors.length==1&&(g.selected=0,h.addClass("ui-tabs-selected ui-state-active"),j.removeClass("ui-tabs-hide"),this.element.queue("tabs",function(){f._trigger("show",null,f._ui(f.anchors[0],f.panels[0]))}),this.load(0)),this._trigger("add",null,this._ui(this.anchors[e],this.panels[e])),this},remove:function(b){b=this._getIndex(b);var c=this.options,d=this.lis.eq(b).remove(),e=this.panels.eq(b).remove();return d.hasClass("ui-tabs-selected")&&this.anchors.length>1&&this.select(b+(b+1=b?--a:a}),this._tabify(),this._trigger("remove",null,this._ui(d.find("a")[0],e[0])),this},enable:function(b){b=this._getIndex(b);var c=this.options;if(a.inArray(b,c.disabled)==-1)return;return this.lis.eq(b).removeClass("ui-state-disabled"),c.disabled=a.grep(c.disabled,function(a,c){return a!=b}),this._trigger("enable",null,this._ui(this.anchors[b],this.panels[b])),this},disable:function(a){a=this._getIndex(a);var b=this,c=this.options;return a!=c.selected&&(this.lis.eq(a).addClass("ui-state-disabled"),c.disabled.push(a),c.disabled.sort(),this._trigger("disable",null,this._ui(this.anchors[a],this.panels[a]))),this},select:function(a){a=this._getIndex(a);if(a==-1)if(this.options.collapsible&&this.options.selected!=-1)a=this.options.selected;else return this;return this.anchors.eq(a).trigger(this.options.event+".tabs"),this},load:function(b){b=this._getIndex(b);var c=this,d=this.options,e=this.anchors.eq(b)[0],f=a.data(e,"load.tabs");this.abort();if(!f||this.element.queue("tabs").length!==0&&a.data(e,"cache.tabs")){this.element.dequeue("tabs");return}this.lis.eq(b).addClass("ui-state-processing");if(d.spinner){var g=a("span",e);g.data("label.tabs",g.html()).html(d.spinner)}return this.xhr=a.ajax(a.extend({},d.ajaxOptions,{url:f,success:function(f,g){c.element.find(c._sanitizeSelector(e.hash)).html(f),c._cleanup(),d.cache&&a.data(e,"cache.tabs",!0),c._trigger("load",null,c._ui(c.anchors[b],c.panels[b]));try{d.ajaxOptions.success(f,g)}catch(h){}},error:function(a,f,g){c._cleanup(),c._trigger("load",null,c._ui(c.anchors[b],c.panels[b]));try{d.ajaxOptions.error(a,f,b,e)}catch(g){}}})),c.element.dequeue("tabs"),this},abort:function(){return this.element.queue([]),this.panels.stop(!1,!0),this.element.queue("tabs",this.element.queue("tabs").splice(-2,2)),this.xhr&&(this.xhr.abort(),delete this.xhr),this._cleanup(),this},url:function(a,b){return this.anchors.eq(a).removeData("cache.tabs").data("load.tabs",b),this},length:function(){return this.anchors.length}}),a.extend(a.ui.tabs,{version:"1.8.21"}),a.extend(a.ui.tabs.prototype,{rotation:null,rotate:function(a,b){var c=this,d=this.options,e=c._rotate||(c._rotate=function(b){clearTimeout(c.rotation),c.rotation=setTimeout(function(){var a=d.selected;c.select(++a'))}function bindHover(a){var b="button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";return a.bind("mouseout",function(a){var c=$(a.target).closest(b);if(!c.length)return;c.removeClass("ui-state-hover ui-datepicker-prev-hover ui-datepicker-next-hover")}).bind("mouseover",function(c){var d=$(c.target).closest(b);if($.datepicker._isDisabledDatepicker(instActive.inline?a.parent()[0]:instActive.input[0])||!d.length)return;d.parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"),d.addClass("ui-state-hover"),d.hasClass("ui-datepicker-prev")&&d.addClass("ui-datepicker-prev-hover"),d.hasClass("ui-datepicker-next")&&d.addClass("ui-datepicker-next-hover")})}function extendRemove(a,b){$.extend(a,b);for(var c in b)if(b[c]==null||b[c]==undefined)a[c]=b[c];return a}function isArray(a){return a&&($.browser.safari&&typeof a=="object"&&a.length||a.constructor&&a.constructor.toString().match(/\Array\(\)/))}$.extend($.ui,{datepicker:{version:"1.8.21"}});var PROP_NAME="datepicker",dpuuid=(new Date).getTime(),instActive;$.extend(Datepicker.prototype,{markerClassName:"hasDatepicker",maxRows:4,log:function(){this.debug&&console.log.apply("",arguments)},_widgetDatepicker:function(){return this.dpDiv},setDefaults:function(a){return extendRemove(this._defaults,a||{}),this},_attachDatepicker:function(target,settings){var inlineSettings=null;for(var attrName in this._defaults){var attrValue=target.getAttribute("date:"+attrName);if(attrValue){inlineSettings=inlineSettings||{};try{inlineSettings[attrName]=eval(attrValue)}catch(err){inlineSettings[attrName]=attrValue}}}var nodeName=target.nodeName.toLowerCase(),inline=nodeName=="div"||nodeName=="span";target.id||(this.uuid+=1,target.id="dp"+this.uuid);var inst=this._newInst($(target),inline);inst.settings=$.extend({},settings||{},inlineSettings||{}),nodeName=="input"?this._connectDatepicker(target,inst):inline&&this._inlineDatepicker(target,inst)},_newInst:function(a,b){var c=a[0].id.replace(/([^A-Za-z0-9_-])/g,"\\\\$1");return{id:c,input:a,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:b,dpDiv:b?bindHover($('
        ')):this.dpDiv}},_connectDatepicker:function(a,b){var c=$(a);b.append=$([]),b.trigger=$([]);if(c.hasClass(this.markerClassName))return;this._attachments(c,b),c.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).keyup(this._doKeyUp).bind("setData.datepicker",function(a,c,d){b.settings[c]=d}).bind("getData.datepicker",function(a,c){return this._get(b,c)}),this._autoSize(b),$.data(a,PROP_NAME,b),b.settings.disabled&&this._disableDatepicker(a)},_attachments:function(a,b){var c=this._get(b,"appendText"),d=this._get(b,"isRTL");b.append&&b.append.remove(),c&&(b.append=$(''+c+""),a[d?"before":"after"](b.append)),a.unbind("focus",this._showDatepicker),b.trigger&&b.trigger.remove();var e=this._get(b,"showOn");(e=="focus"||e=="both")&&a.focus(this._showDatepicker);if(e=="button"||e=="both"){var f=this._get(b,"buttonText"),g=this._get(b,"buttonImage");b.trigger=$(this._get(b,"buttonImageOnly")?$("").addClass(this._triggerClass).attr({src:g,alt:f,title:f}):$('').addClass(this._triggerClass).html(g==""?f:$("").attr({src:g,alt:f,title:f}))),a[d?"before":"after"](b.trigger),b.trigger.click(function(){return $.datepicker._datepickerShowing&&$.datepicker._lastInput==a[0]?$.datepicker._hideDatepicker():$.datepicker._datepickerShowing&&$.datepicker._lastInput!=a[0]?($.datepicker._hideDatepicker(),$.datepicker._showDatepicker(a[0])):$.datepicker._showDatepicker(a[0]),!1})}},_autoSize:function(a){if(this._get(a,"autoSize")&&!a.inline){var b=new Date(2009,11,20),c=this._get(a,"dateFormat");if(c.match(/[DM]/)){var d=function(a){var b=0,c=0;for(var d=0;db&&(b=a[d].length,c=d);return c};b.setMonth(d(this._get(a,c.match(/MM/)?"monthNames":"monthNamesShort"))),b.setDate(d(this._get(a,c.match(/DD/)?"dayNames":"dayNamesShort"))+20-b.getDay())}a.input.attr("size",this._formatDate(a,b).length)}},_inlineDatepicker:function(a,b){var c=$(a);if(c.hasClass(this.markerClassName))return;c.addClass(this.markerClassName).append(b.dpDiv).bind("setData.datepicker",function(a,c,d){b.settings[c]=d}).bind("getData.datepicker",function(a,c){return this._get(b,c)}),$.data(a,PROP_NAME,b),this._setDate(b,this._getDefaultDate(b),!0),this._updateDatepicker(b),this._updateAlternate(b),b.settings.disabled&&this._disableDatepicker(a),b.dpDiv.css("display","block")},_dialogDatepicker:function(a,b,c,d,e){var f=this._dialogInst;if(!f){this.uuid+=1;var g="dp"+this.uuid;this._dialogInput=$(''),this._dialogInput.keydown(this._doKeyDown),$("body").append(this._dialogInput),f=this._dialogInst=this._newInst(this._dialogInput,!1),f.settings={},$.data(this._dialogInput[0],PROP_NAME,f)}extendRemove(f.settings,d||{}),b=b&&b.constructor==Date?this._formatDate(f,b):b,this._dialogInput.val(b),this._pos=e?e.length?e:[e.pageX,e.pageY]:null;if(!this._pos){var h=document.documentElement.clientWidth,i=document.documentElement.clientHeight,j=document.documentElement.scrollLeft||document.body.scrollLeft,k=document.documentElement.scrollTop||document.body.scrollTop;this._pos=[h/2-100+j,i/2-150+k]}return this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px"),f.settings.onSelect=c,this._inDialog=!0,this.dpDiv.addClass(this._dialogClass),this._showDatepicker(this._dialogInput[0]),$.blockUI&&$.blockUI(this.dpDiv),$.data(this._dialogInput[0],PROP_NAME,f),this},_destroyDatepicker:function(a){var b=$(a),c=$.data(a,PROP_NAME);if(!b.hasClass(this.markerClassName))return;var d=a.nodeName.toLowerCase();$.removeData(a,PROP_NAME),d=="input"?(c.append.remove(),c.trigger.remove(),b.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup",this._doKeyUp)):(d=="div"||d=="span")&&b.removeClass(this.markerClassName).empty()},_enableDatepicker:function(a){var b=$(a),c=$.data(a,PROP_NAME);if(!b.hasClass(this.markerClassName))return;var d=a.nodeName.toLowerCase();if(d=="input")a.disabled=!1,c.trigger.filter("button").each(function(){this.disabled=!1}).end().filter("img").css({opacity:"1.0",cursor:""});else if(d=="div"||d=="span"){var e=b.children("."+this._inlineClass);e.children().removeClass("ui-state-disabled"),e.find("select.ui-datepicker-month, select.ui-datepicker-year").removeAttr("disabled")}this._disabledInputs=$.map(this._disabledInputs,function(b){return b==a?null:b})},_disableDatepicker:function(a){var b=$(a),c=$.data(a,PROP_NAME);if(!b.hasClass(this.markerClassName))return;var d=a.nodeName.toLowerCase();if(d=="input")a.disabled=!0,c.trigger.filter("button").each(function(){this.disabled=!0}).end().filter("img").css({opacity:"0.5",cursor:"default"});else if(d=="div"||d=="span"){var e=b.children("."+this._inlineClass);e.children().addClass("ui-state-disabled"),e.find("select.ui-datepicker-month, select.ui-datepicker-year").attr("disabled","disabled")}this._disabledInputs=$.map(this._disabledInputs,function(b){return b==a?null:b}),this._disabledInputs[this._disabledInputs.length]=a},_isDisabledDatepicker:function(a){if(!a)return!1;for(var b=0;b-1}},_doKeyUp:function(a){var b=$.datepicker._getInst(a.target);if(b.input.val()!=b.lastVal)try{var c=$.datepicker.parseDate($.datepicker._get(b,"dateFormat"),b.input?b.input.val():null,$.datepicker._getFormatConfig(b));c&&($.datepicker._setDateFromField(b),$.datepicker._updateAlternate(b),$.datepicker._updateDatepicker(b))}catch(d){$.datepicker.log(d)}return!0},_showDatepicker:function(a){a=a.target||a,a.nodeName.toLowerCase()!="input"&&(a=$("input",a.parentNode)[0]);if($.datepicker._isDisabledDatepicker(a)||$.datepicker._lastInput==a)return;var b=$.datepicker._getInst(a);$.datepicker._curInst&&$.datepicker._curInst!=b&&($.datepicker._curInst.dpDiv.stop(!0,!0),b&&$.datepicker._datepickerShowing&&$.datepicker._hideDatepicker($.datepicker._curInst.input[0]));var c=$.datepicker._get(b,"beforeShow"),d=c?c.apply(a,[a,b]):{};if(d===!1)return;extendRemove(b.settings,d),b.lastVal=null,$.datepicker._lastInput=a,$.datepicker._setDateFromField(b),$.datepicker._inDialog&&(a.value=""),$.datepicker._pos||($.datepicker._pos=$.datepicker._findPos(a),$.datepicker._pos[1]+=a.offsetHeight);var e=!1;$(a).parents().each(function(){return e|=$(this).css("position")=="fixed",!e}),e&&$.browser.opera&&($.datepicker._pos[0]-=document.documentElement.scrollLeft,$.datepicker._pos[1]-=document.documentElement.scrollTop);var f={left:$.datepicker._pos[0],top:$.datepicker._pos[1]};$.datepicker._pos=null,b.dpDiv.empty(),b.dpDiv.css({position:"absolute",display:"block",top:"-1000px"}),$.datepicker._updateDatepicker(b),f=$.datepicker._checkOffset(b,f,e),b.dpDiv.css({position:$.datepicker._inDialog&&$.blockUI?"static":e?"fixed":"absolute",display:"none",left:f.left+"px",top:f.top+"px"});if(!b.inline){var g=$.datepicker._get(b,"showAnim"),h=$.datepicker._get(b,"duration"),i=function(){var a=b.dpDiv.find("iframe.ui-datepicker-cover");if(!!a.length){var c=$.datepicker._getBorders(b.dpDiv);a.css({left:-c[0],top:-c[1],width:b.dpDiv.outerWidth(),height:b.dpDiv.outerHeight()})}};b.dpDiv.zIndex($(a).zIndex()+1),$.datepicker._datepickerShowing=!0,$.effects&&$.effects[g]?b.dpDiv.show(g,$.datepicker._get(b,"showOptions"),h,i):b.dpDiv[g||"show"](g?h:null,i),(!g||!h)&&i(),b.input.is(":visible")&&!b.input.is(":disabled")&&b.input.focus(),$.datepicker._curInst=b}},_updateDatepicker:function(a){var b=this;b.maxRows=4;var c=$.datepicker._getBorders(a.dpDiv);instActive=a,a.dpDiv.empty().append(this._generateHTML(a));var d=a.dpDiv.find("iframe.ui-datepicker-cover");!d.length||d.css({left:-c[0],top:-c[1],width:a.dpDiv.outerWidth(),height:a.dpDiv.outerHeight()}),a.dpDiv.find("."+this._dayOverClass+" a").mouseover();var e=this._getNumberOfMonths(a),f=e[1],g=17;a.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width(""),f>1&&a.dpDiv.addClass("ui-datepicker-multi-"+f).css("width",g*f+"em"),a.dpDiv[(e[0]!=1||e[1]!=1?"add":"remove")+"Class"]("ui-datepicker-multi"),a.dpDiv[(this._get(a,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl"),a==$.datepicker._curInst&&$.datepicker._datepickerShowing&&a.input&&a.input.is(":visible")&&!a.input.is(":disabled")&&a.input[0]!=document.activeElement&&a.input.focus();if(a.yearshtml){var h=a.yearshtml;setTimeout(function(){h===a.yearshtml&&a.yearshtml&&a.dpDiv.find("select.ui-datepicker-year:first").replaceWith(a.yearshtml),h=a.yearshtml=null},0)}},_getBorders:function(a){var b=function(a){return{thin:1,medium:2,thick:3}[a]||a};return[parseFloat(b(a.css("border-left-width"))),parseFloat(b(a.css("border-top-width")))]},_checkOffset:function(a,b,c){var d=a.dpDiv.outerWidth(),e=a.dpDiv.outerHeight(),f=a.input?a.input.outerWidth():0,g=a.input?a.input.outerHeight():0,h=document.documentElement.clientWidth+$(document).scrollLeft(),i=document.documentElement.clientHeight+$(document).scrollTop();return b.left-=this._get(a,"isRTL")?d-f:0,b.left-=c&&b.left==a.input.offset().left?$(document).scrollLeft():0,b.top-=c&&b.top==a.input.offset().top+g?$(document).scrollTop():0,b.left-=Math.min(b.left,b.left+d>h&&h>d?Math.abs(b.left+d-h):0),b.top-=Math.min(b.top,b.top+e>i&&i>e?Math.abs(e+g):0),b},_findPos:function(a){var b=this._getInst(a),c=this._get(b,"isRTL");while(a&&(a.type=="hidden"||a.nodeType!=1||$.expr.filters.hidden(a)))a=a[c?"previousSibling":"nextSibling"];var d=$(a).offset();return[d.left,d.top]},_hideDatepicker:function(a){var b=this._curInst;if(!b||a&&b!=$.data(a,PROP_NAME))return;if(this._datepickerShowing){var c=this._get(b,"showAnim"),d=this._get(b,"duration"),e=function(){$.datepicker._tidyDialog(b)};$.effects&&$.effects[c]?b.dpDiv.hide(c,$.datepicker._get(b,"showOptions"),d,e):b.dpDiv[c=="slideDown"?"slideUp":c=="fadeIn"?"fadeOut":"hide"](c?d:null,e),c||e(),this._datepickerShowing=!1;var f=this._get(b,"onClose");f&&f.apply(b.input?b.input[0]:null,[b.input?b.input.val():"",b]),this._lastInput=null,this._inDialog&&(this._dialogInput.css({position:"absolute",left:"0",top:"-100px"}),$.blockUI&&($.unblockUI(),$("body").append(this.dpDiv))),this._inDialog=!1}},_tidyDialog:function(a){a.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")},_checkExternalClick:function(a){if(!$.datepicker._curInst)return;var b=$(a.target),c=$.datepicker._getInst(b[0]);(b[0].id!=$.datepicker._mainDivId&&b.parents("#"+$.datepicker._mainDivId).length==0&&!b.hasClass($.datepicker.markerClassName)&&!b.closest("."+$.datepicker._triggerClass).length&&$.datepicker._datepickerShowing&&(!$.datepicker._inDialog||!$.blockUI)||b.hasClass($.datepicker.markerClassName)&&$.datepicker._curInst!=c)&&$.datepicker._hideDatepicker()},_adjustDate:function(a,b,c){var d=$(a),e=this._getInst(d[0]);if(this._isDisabledDatepicker(d[0]))return;this._adjustInstDate(e,b+(c=="M"?this._get(e,"showCurrentAtPos"):0),c),this._updateDatepicker(e)},_gotoToday:function(a){var b=$(a),c=this._getInst(b[0]);if(this._get(c,"gotoCurrent")&&c.currentDay)c.selectedDay=c.currentDay,c.drawMonth=c.selectedMonth=c.currentMonth,c.drawYear=c.selectedYear=c.currentYear;else{var d=new Date;c.selectedDay=d.getDate(),c.drawMonth=c.selectedMonth=d.getMonth(),c.drawYear=c.selectedYear=d.getFullYear()}this._notifyChange(c),this._adjustDate(b)},_selectMonthYear:function(a,b,c){var d=$(a),e=this._getInst(d[0]);e["selected"+(c=="M"?"Month":"Year")]=e["draw"+(c=="M"?"Month":"Year")]=parseInt(b.options[b.selectedIndex].value,10),this._notifyChange(e),this._adjustDate(d)},_selectDay:function(a,b,c,d){var e=$(a);if($(d).hasClass(this._unselectableClass)||this._isDisabledDatepicker(e[0]))return;var f=this._getInst(e[0]);f.selectedDay=f.currentDay=$("a",d).html(),f.selectedMonth=f.currentMonth=b,f.selectedYear=f.currentYear=c,this._selectDate(a,this._formatDate(f,f.currentDay,f.currentMonth,f.currentYear))},_clearDate:function(a){var b=$(a),c=this._getInst(b[0]);this._selectDate(b,"")},_selectDate:function(a,b){var c=$(a),d=this._getInst(c[0]);b=b!=null?b:this._formatDate(d),d.input&&d.input.val(b),this._updateAlternate(d);var e=this._get(d,"onSelect");e?e.apply(d.input?d.input[0]:null,[b,d]):d.input&&d.input.trigger("change"),d.inline?this._updateDatepicker(d):(this._hideDatepicker(),this._lastInput=d.input[0],typeof d.input[0]!="object"&&d.input.focus(),this._lastInput=null)},_updateAlternate:function(a){var b=this._get(a,"altField");if(b){var c=this._get(a,"altFormat")||this._get(a,"dateFormat"),d=this._getDate(a),e=this.formatDate(c,d,this._getFormatConfig(a));$(b).each(function(){$(this).val(e)})}},noWeekends:function(a){var b=a.getDay();return[b>0&&b<6,""]},iso8601Week:function(a){var b=new Date(a.getTime());b.setDate(b.getDate()+4-(b.getDay()||7));var c=b.getTime();return b.setMonth(0),b.setDate(1),Math.floor(Math.round((c-b)/864e5)/7)+1},parseDate:function(a,b,c){if(a==null||b==null)throw"Invalid arguments";b=typeof b=="object"?b.toString():b+"";if(b=="")return null;var d=(c?c.shortYearCutoff:null)||this._defaults.shortYearCutoff;d=typeof d!="string"?d:(new Date).getFullYear()%100+parseInt(d,10);var e=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,f=(c?c.dayNames:null)||this._defaults.dayNames,g=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort,h=(c?c.monthNames:null)||this._defaults.monthNames,i=-1,j=-1,k=-1,l=-1,m=!1,n=function(b){var c=s+1-1){j=1,k=l;do{var u=this._getDaysInMonth(i,j-1);if(k<=u)break;j++,k-=u}while(!0)}var t=this._daylightSavingAdjust(new Date(i,j-1,k));if(t.getFullYear()!=i||t.getMonth()+1!=j||t.getDate()!=k)throw"Invalid date";return t},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925))*24*60*60*1e7,formatDate:function(a,b,c){if(!b)return"";var d=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,e=(c?c.dayNames:null)||this._defaults.dayNames,f=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort,g=(c?c.monthNames:null)||this._defaults.monthNames,h=function(b){var c=m+112?a.getHours()+2:0),a):null},_setDate:function(a,b,c){var d=!b,e=a.selectedMonth,f=a.selectedYear,g=this._restrictMinMax(a,this._determineDate(a,b,new Date));a.selectedDay=a.currentDay=g.getDate(),a.drawMonth=a.selectedMonth=a.currentMonth=g.getMonth(),a.drawYear=a.selectedYear=a.currentYear=g.getFullYear(),(e!=a.selectedMonth||f!=a.selectedYear)&&!c&&this._notifyChange(a),this._adjustInstDate(a),a.input&&a.input.val(d?"":this._formatDate(a))},_getDate:function(a){var b=!a.currentYear||a.input&&a.input.val()==""?null:this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return b},_generateHTML:function(a){var b=new Date;b=this._daylightSavingAdjust(new Date(b.getFullYear(),b.getMonth(),b.getDate()));var c=this._get(a,"isRTL"),d=this._get(a,"showButtonPanel"),e=this._get(a,"hideIfNoPrevNext"),f=this._get(a,"navigationAsDateFormat"),g=this._getNumberOfMonths(a),h=this._get(a,"showCurrentAtPos"),i=this._get(a,"stepMonths"),j=g[0]!=1||g[1]!=1,k=this._daylightSavingAdjust(a.currentDay?new Date(a.currentYear,a.currentMonth,a.currentDay):new Date(9999,9,9)),l=this._getMinMaxDate(a,"min"),m=this._getMinMaxDate(a,"max"),n=a.drawMonth-h,o=a.drawYear;n<0&&(n+=12,o--);if(m){var p=this._daylightSavingAdjust(new Date(m.getFullYear(),m.getMonth()-g[0]*g[1]+1,m.getDate()));p=l&&pp)n--,n<0&&(n=11,o--)}a.drawMonth=n,a.drawYear=o;var q=this._get(a,"prevText");q=f?this.formatDate(q,this._daylightSavingAdjust(new Date(o,n-i,1)),this._getFormatConfig(a)):q;var r=this._canAdjustMonth(a,-1,o,n)?''+q+"":e?"":''+q+"",s=this._get(a,"nextText");s=f?this.formatDate(s,this._daylightSavingAdjust(new Date(o,n+i,1)),this._getFormatConfig(a)):s;var t=this._canAdjustMonth(a,1,o,n)?''+s+"":e?"":''+s+"",u=this._get(a,"currentText"),v=this._get(a,"gotoCurrent")&&a.currentDay?k:b;u=f?this.formatDate(u,v,this._getFormatConfig(a)):u;var w=a.inline?"":'",x=d?'
        '+(c?w:"")+(this._isInRange(a,v)?'":"")+(c?"":w)+"
        ":"",y=parseInt(this._get(a,"firstDay"),10);y=isNaN(y)?0:y;var z=this._get(a,"showWeek"),A=this._get(a,"dayNames"),B=this._get(a,"dayNamesShort"),C=this._get(a,"dayNamesMin"),D=this._get(a,"monthNames"),E=this._get(a,"monthNamesShort"),F=this._get(a,"beforeShowDay"),G=this._get(a,"showOtherMonths"),H=this._get(a,"selectOtherMonths"),I=this._get(a,"calculateWeek")||this.iso8601Week,J=this._getDefaultDate(a),K="";for(var L=0;L1)switch(N){case 0:Q+=" ui-datepicker-group-first",P=" ui-corner-"+(c?"right":"left");break;case g[1]-1:Q+=" ui-datepicker-group-last",P=" ui-corner-"+(c?"left":"right");break;default:Q+=" ui-datepicker-group-middle",P=""}Q+='">'}Q+='
        '+(/all|left/.test(P)&&L==0?c?t:r:"")+(/all|right/.test(P)&&L==0?c?r:t:"")+this._generateMonthYearHeader(a,n,o,l,m,L>0||N>0,D,E)+'
        '+"";var R=z?'":"";for(var S=0;S<7;S++){var T=(S+y)%7;R+="=5?' class="ui-datepicker-week-end"':"")+">"+''+C[T]+""}Q+=R+"";var U=this._getDaysInMonth(o,n);o==a.selectedYear&&n==a.selectedMonth&&(a.selectedDay=Math.min(a.selectedDay,U));var V=(this._getFirstDayOfMonth(o,n)-y+7)%7,W=Math.ceil((V+U)/7),X=j?this.maxRows>W?this.maxRows:W:W;this.maxRows=X;var Y=this._daylightSavingAdjust(new Date(o,n,1-V));for(var Z=0;Z";var _=z?'":"";for(var S=0;S<7;S++){var ba=F?F.apply(a.input?a.input[0]:null,[Y]):[!0,""],bb=Y.getMonth()!=n,bc=bb&&!H||!ba[0]||l&&Ym;_+='",Y.setDate(Y.getDate()+1),Y=this._daylightSavingAdjust(Y)}Q+=_+""}n++,n>11&&(n=0,o++),Q+="
        '+this._get(a,"weekHeader")+"
        '+this._get(a,"calculateWeek")(Y)+""+(bb&&!G?" ":bc?''+Y.getDate()+"":''+Y.getDate()+"")+"
        "+(j?"
        "+(g[0]>0&&N==g[1]-1?'
        ':""):""),M+=Q}K+=M}return K+=x+($.browser.msie&&parseInt($.browser.version,10)<7&&!a.inline?'':""),a._keyEvent=!1,K},_generateMonthYearHeader:function(a,b,c,d,e,f,g,h){var i=this._get(a,"changeMonth"),j=this._get(a,"changeYear"),k=this._get(a,"showMonthAfterYear"),l='
        ',m="";if(f||!i)m+=''+g[b]+"";else{var n=d&&d.getFullYear()==c,o=e&&e.getFullYear()==c;m+='"}k||(l+=m+(f||!i||!j?" ":""));if(!a.yearshtml){a.yearshtml="";if(f||!j)l+=''+c+"";else{var q=this._get(a,"yearRange").split(":"),r=(new Date).getFullYear(),s=function(a){var b=a.match(/c[+-].*/)?c+parseInt(a.substring(1),10):a.match(/[+-].*/)?r+parseInt(a,10):parseInt(a,10);return isNaN(b)?r:b},t=s(q[0]),u=Math.max(t,s(q[1]||""));t=d?Math.max(t,d.getFullYear()):t,u=e?Math.min(u,e.getFullYear()):u,a.yearshtml+='",l+=a.yearshtml,a.yearshtml=null}}return l+=this._get(a,"yearSuffix"),k&&(l+=(f||!i||!j?" ":"")+m),l+="
        ",l},_adjustInstDate:function(a,b,c){var d=a.drawYear+(c=="Y"?b:0),e=a.drawMonth+(c=="M"?b:0),f=Math.min(a.selectedDay,this._getDaysInMonth(d,e))+(c=="D"?b:0),g=this._restrictMinMax(a,this._daylightSavingAdjust(new Date(d,e,f)));a.selectedDay=g.getDate(),a.drawMonth=a.selectedMonth=g.getMonth(),a.drawYear=a.selectedYear=g.getFullYear(),(c=="M"||c=="Y")&&this._notifyChange(a)},_restrictMinMax:function(a,b){var c=this._getMinMaxDate(a,"min"),d=this._getMinMaxDate(a,"max"),e=c&&bd?d:e,e},_notifyChange:function(a){var b=this._get(a,"onChangeMonthYear");b&&b.apply(a.input?a.input[0]:null,[a.selectedYear,a.selectedMonth+1,a])},_getNumberOfMonths:function(a){var b=this._get(a,"numberOfMonths");return b==null?[1,1]:typeof b=="number"?[1,b]:b},_getMinMaxDate:function(a,b){return this._determineDate(a,this._get(a,b+"Date"),null)},_getDaysInMonth:function(a,b){return 32-this._daylightSavingAdjust(new Date(a,b,32)).getDate()},_getFirstDayOfMonth:function(a,b){return(new Date(a,b,1)).getDay()},_canAdjustMonth:function(a,b,c,d){var e=this._getNumberOfMonths(a),f=this._daylightSavingAdjust(new Date(c,d+(b<0?b:e[0]*e[1]),1));return b<0&&f.setDate(this._getDaysInMonth(f.getFullYear(),f.getMonth())),this._isInRange(a,f)},_isInRange:function(a,b){var c=this._getMinMaxDate(a,"min"),d=this._getMinMaxDate(a,"max");return(!c||b.getTime()>=c.getTime())&&(!d||b.getTime()<=d.getTime())},_getFormatConfig:function(a){var b=this._get(a,"shortYearCutoff");return b=typeof b!="string"?b:(new Date).getFullYear()%100+parseInt(b,10),{shortYearCutoff:b,dayNamesShort:this._get(a,"dayNamesShort"),dayNames:this._get(a,"dayNames"),monthNamesShort:this._get(a,"monthNamesShort"),monthNames:this._get(a,"monthNames")}},_formatDate:function(a,b,c,d){b||(a.currentDay=a.selectedDay,a.currentMonth=a.selectedMonth,a.currentYear=a.selectedYear);var e=b?typeof b=="object"?b:this._daylightSavingAdjust(new Date(d,c,b)):this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return this.formatDate(this._get(a,"dateFormat"),e,this._getFormatConfig(a))}}),$.fn.datepicker=function(a){if(!this.length)return this;$.datepicker.initialized||($(document).mousedown($.datepicker._checkExternalClick).find("body").append($.datepicker.dpDiv),$.datepicker.initialized=!0);var b=Array.prototype.slice.call(arguments,1);return typeof a!="string"||a!="isDisabled"&&a!="getDate"&&a!="widget"?a=="option"&&arguments.length==2&&typeof arguments[1]=="string"?$.datepicker["_"+a+"Datepicker"].apply($.datepicker,[this[0]].concat(b)):this.each(function(){typeof a=="string"?$.datepicker["_"+a+"Datepicker"].apply($.datepicker,[this].concat(b)):$.datepicker._attachDatepicker(this,a)}):$.datepicker["_"+a+"Datepicker"].apply($.datepicker,[this[0]].concat(b))},$.datepicker=new Datepicker,$.datepicker.initialized=!1,$.datepicker.uuid=(new Date).getTime(),$.datepicker.version="1.8.21",window["DP_jQuery_"+dpuuid]=$})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.ui.progressbar.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function(a,b){a.widget("ui.progressbar",{options:{value:0,max:100},min:0,_create:function(){this.element.addClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").attr({role:"progressbar","aria-valuemin":this.min,"aria-valuemax":this.options.max,"aria-valuenow":this._value()}),this.valueDiv=a("
        ").appendTo(this.element),this.oldValue=this._value(),this._refreshValue()},destroy:function(){this.element.removeClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow"),this.valueDiv.remove(),a.Widget.prototype.destroy.apply(this,arguments)},value:function(a){return a===b?this._value():(this._setOption("value",a),this)},_setOption:function(b,c){b==="value"&&(this.options.value=c,this._refreshValue(),this._value()===this.options.max&&this._trigger("complete")),a.Widget.prototype._setOption.apply(this,arguments)},_value:function(){var a=this.options.value;return typeof a!="number"&&(a=0),Math.min(this.options.max,Math.max(this.min,a))},_percentage:function(){return 100*this._value()/this.options.max},_refreshValue:function(){var a=this.value(),b=this._percentage();this.oldValue!==a&&(this.oldValue=a,this._trigger("change")),this.valueDiv.toggle(a>this.min).toggleClass("ui-corner-right",a===this.options.max).width(b.toFixed(0)+"%"),this.element.attr("aria-valuenow",a)}}),a.extend(a.ui.progressbar,{version:"1.8.21"})})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.effects.core.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -jQuery.effects||function(a,b){function c(b){var c;return b&&b.constructor==Array&&b.length==3?b:(c=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(b))?[parseInt(c[1],10),parseInt(c[2],10),parseInt(c[3],10)]:(c=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(b))?[parseFloat(c[1])*2.55,parseFloat(c[2])*2.55,parseFloat(c[3])*2.55]:(c=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(b))?[parseInt(c[1],16),parseInt(c[2],16),parseInt(c[3],16)]:(c=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(b))?[parseInt(c[1]+c[1],16),parseInt(c[2]+c[2],16),parseInt(c[3]+c[3],16)]:(c=/rgba\(0, 0, 0, 0\)/.exec(b))?e.transparent:e[a.trim(b).toLowerCase()]}function d(b,d){var e;do{e=a.curCSS(b,d);if(e!=""&&e!="transparent"||a.nodeName(b,"body"))break;d="backgroundColor"}while(b=b.parentNode);return c(e)}function h(){var a=document.defaultView?document.defaultView.getComputedStyle(this,null):this.currentStyle,b={},c,d;if(a&&a.length&&a[0]&&a[a[0]]){var e=a.length;while(e--)c=a[e],typeof a[c]=="string"&&(d=c.replace(/\-(\w)/g,function(a,b){return b.toUpperCase()}),b[d]=a[c])}else for(c in a)typeof a[c]=="string"&&(b[c]=a[c]);return b}function i(b){var c,d;for(c in b)d=b[c],(d==null||a.isFunction(d)||c in g||/scrollbar/.test(c)||!/color/i.test(c)&&isNaN(parseFloat(d)))&&delete b[c];return b}function j(a,b){var c={_:0},d;for(d in b)a[d]!=b[d]&&(c[d]=b[d]);return c}function k(b,c,d,e){typeof b=="object"&&(e=c,d=null,c=b,b=c.effect),a.isFunction(c)&&(e=c,d=null,c={});if(typeof c=="number"||a.fx.speeds[c])e=d,d=c,c={};return a.isFunction(d)&&(e=d,d=null),c=c||{},d=d||c.duration,d=a.fx.off?0:typeof d=="number"?d:d in a.fx.speeds?a.fx.speeds[d]:a.fx.speeds._default,e=e||c.complete,[b,c,d,e]}function l(b){return!b||typeof b=="number"||a.fx.speeds[b]?!0:typeof b=="string"&&!a.effects[b]?!0:!1}a.effects={},a.each(["backgroundColor","borderBottomColor","borderLeftColor","borderRightColor","borderTopColor","borderColor","color","outlineColor"],function(b,e){a.fx.step[e]=function(a){a.colorInit||(a.start=d(a.elem,e),a.end=c(a.end),a.colorInit=!0),a.elem.style[e]="rgb("+Math.max(Math.min(parseInt(a.pos*(a.end[0]-a.start[0])+a.start[0],10),255),0)+","+Math.max(Math.min(parseInt(a.pos*(a.end[1]-a.start[1])+a.start[1],10),255),0)+","+Math.max(Math.min(parseInt(a.pos*(a.end[2]-a.start[2])+a.start[2],10),255),0)+")"}});var e={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0],transparent:[255,255,255]},f=["add","remove","toggle"],g={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};a.effects.animateClass=function(b,c,d,e){return a.isFunction(d)&&(e=d,d=null),this.queue(function(){var g=a(this),k=g.attr("style")||" ",l=i(h.call(this)),m,n=g.attr("class")||"";a.each(f,function(a,c){b[c]&&g[c+"Class"](b[c])}),m=i(h.call(this)),g.attr("class",n),g.animate(j(l,m),{queue:!1,duration:c,easing:d,complete:function(){a.each(f,function(a,c){b[c]&&g[c+"Class"](b[c])}),typeof g.attr("style")=="object"?(g.attr("style").cssText="",g.attr("style").cssText=k):g.attr("style",k),e&&e.apply(this,arguments),a.dequeue(this)}})})},a.fn.extend({_addClass:a.fn.addClass,addClass:function(b,c,d,e){return c?a.effects.animateClass.apply(this,[{add:b},c,d,e]):this._addClass(b)},_removeClass:a.fn.removeClass,removeClass:function(b,c,d,e){return c?a.effects.animateClass.apply(this,[{remove:b},c,d,e]):this._removeClass(b)},_toggleClass:a.fn.toggleClass,toggleClass:function(c,d,e,f,g){return typeof d=="boolean"||d===b?e?a.effects.animateClass.apply(this,[d?{add:c}:{remove:c},e,f,g]):this._toggleClass(c,d):a.effects.animateClass.apply(this,[{toggle:c},d,e,f])},switchClass:function(b,c,d,e,f){return a.effects.animateClass.apply(this,[{add:c,remove:b},d,e,f])}}),a.extend(a.effects,{version:"1.8.21",save:function(a,b){for(var c=0;c
        ").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),e=document.activeElement;try{e.id}catch(f){e=document.body}return b.wrap(d),(b[0]===e||a.contains(b[0],e))&&a(e).focus(),d=b.parent(),b.css("position")=="static"?(d.css({position:"relative"}),b.css({position:"relative"})):(a.extend(c,{position:b.css("position"),zIndex:b.css("z-index")}),a.each(["top","left","bottom","right"],function(a,d){c[d]=b.css(d),isNaN(parseInt(c[d],10))&&(c[d]="auto")}),b.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),d.css(c).show()},removeWrapper:function(b){var c,d=document.activeElement;return b.parent().is(".ui-effects-wrapper")?(c=b.parent().replaceWith(b),(b[0]===d||a.contains(b[0],d))&&a(d).focus(),c):b},setTransition:function(b,c,d,e){return e=e||{},a.each(c,function(a,c){var f=b.cssUnit(c);f[0]>0&&(e[c]=f[0]*d+f[1])}),e}}),a.fn.extend({effect:function(b,c,d,e){var f=k.apply(this,arguments),g={options:f[1],duration:f[2],callback:f[3]},h=g.options.mode,i=a.effects[b];return a.fx.off||!i?h?this[h](g.duration,g.callback):this.each(function(){g.callback&&g.callback.call(this)}):i.call(this,g)},_show:a.fn.show,show:function(a){if(l(a))return this._show.apply(this,arguments);var b=k.apply(this,arguments);return b[1].mode="show",this.effect.apply(this,b)},_hide:a.fn.hide,hide:function(a){if(l(a))return this._hide.apply(this,arguments);var b=k.apply(this,arguments);return b[1].mode="hide",this.effect.apply(this,b)},__toggle:a.fn.toggle,toggle:function(b){if(l(b)||typeof b=="boolean"||a.isFunction(b))return this.__toggle.apply(this,arguments);var c=k.apply(this,arguments);return c[1].mode="toggle",this.effect.apply(this,c)},cssUnit:function(b){var c=this.css(b),d=[];return a.each(["em","px","%","pt"],function(a,b){c.indexOf(b)>0&&(d=[parseFloat(c),b])}),d}}),a.easing.jswing=a.easing.swing,a.extend(a.easing,{def:"easeOutQuad",swing:function(b,c,d,e,f){return a.easing[a.easing.def](b,c,d,e,f)},easeInQuad:function(a,b,c,d,e){return d*(b/=e)*b+c},easeOutQuad:function(a,b,c,d,e){return-d*(b/=e)*(b-2)+c},easeInOutQuad:function(a,b,c,d,e){return(b/=e/2)<1?d/2*b*b+c:-d/2*(--b*(b-2)-1)+c},easeInCubic:function(a,b,c,d,e){return d*(b/=e)*b*b+c},easeOutCubic:function(a,b,c,d,e){return d*((b=b/e-1)*b*b+1)+c},easeInOutCubic:function(a,b,c,d,e){return(b/=e/2)<1?d/2*b*b*b+c:d/2*((b-=2)*b*b+2)+c},easeInQuart:function(a,b,c,d,e){return d*(b/=e)*b*b*b+c},easeOutQuart:function(a,b,c,d,e){return-d*((b=b/e-1)*b*b*b-1)+c},easeInOutQuart:function(a,b,c,d,e){return(b/=e/2)<1?d/2*b*b*b*b+c:-d/2*((b-=2)*b*b*b-2)+c},easeInQuint:function(a,b,c,d,e){return d*(b/=e)*b*b*b*b+c},easeOutQuint:function(a,b,c,d,e){return d*((b=b/e-1)*b*b*b*b+1)+c},easeInOutQuint:function(a,b,c,d,e){return(b/=e/2)<1?d/2*b*b*b*b*b+c:d/2*((b-=2)*b*b*b*b+2)+c},easeInSine:function(a,b,c,d,e){return-d*Math.cos(b/e*(Math.PI/2))+d+c},easeOutSine:function(a,b,c,d,e){return d*Math.sin(b/e*(Math.PI/2))+c},easeInOutSine:function(a,b,c,d,e){return-d/2*(Math.cos(Math.PI*b/e)-1)+c},easeInExpo:function(a,b,c,d,e){return b==0?c:d*Math.pow(2,10*(b/e-1))+c},easeOutExpo:function(a,b,c,d,e){return b==e?c+d:d*(-Math.pow(2,-10*b/e)+1)+c},easeInOutExpo:function(a,b,c,d,e){return b==0?c:b==e?c+d:(b/=e/2)<1?d/2*Math.pow(2,10*(b-1))+c:d/2*(-Math.pow(2,-10*--b)+2)+c},easeInCirc:function(a,b,c,d,e){return-d*(Math.sqrt(1-(b/=e)*b)-1)+c},easeOutCirc:function(a,b,c,d,e){return d*Math.sqrt(1-(b=b/e-1)*b)+c},easeInOutCirc:function(a,b,c,d,e){return(b/=e/2)<1?-d/2*(Math.sqrt(1-b*b)-1)+c:d/2*(Math.sqrt(1-(b-=2)*b)+1)+c},easeInElastic:function(a,b,c,d,e){var f=1.70158,g=0,h=d;if(b==0)return c;if((b/=e)==1)return c+d;g||(g=e*.3);if(h
        ").css({position:"absolute",visibility:"visible",left:-j*(g/d),top:-i*(h/c)}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:g/d,height:h/c,left:f.left+j*(g/d)+(b.options.mode=="show"?(j-Math.floor(d/2))*(g/d):0),top:f.top+i*(h/c)+(b.options.mode=="show"?(i-Math.floor(c/2))*(h/c):0),opacity:b.options.mode=="show"?0:1}).animate({left:f.left+j*(g/d)+(b.options.mode=="show"?0:(j-Math.floor(d/2))*(g/d)),top:f.top+i*(h/c)+(b.options.mode=="show"?0:(i-Math.floor(c/2))*(h/c)),opacity:b.options.mode=="show"?1:0},b.duration||500);setTimeout(function(){b.options.mode=="show"?e.css({visibility:"visible"}):e.css({visibility:"visible"}).hide(),b.callback&&b.callback.apply(e[0]),e.dequeue(),a("div.ui-effects-explode").remove()},b.duration||500)})}})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.effects.fade.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function(a,b){a.effects.fade=function(b){return this.queue(function(){var c=a(this),d=a.effects.setMode(c,b.options.mode||"hide");c.animate({opacity:d},{queue:!1,duration:b.duration,easing:b.options.easing,complete:function(){b.callback&&b.callback.apply(this,arguments),c.dequeue()}})})}})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.effects.fold.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function(a,b){a.effects.fold=function(b){return this.queue(function(){var c=a(this),d=["position","top","bottom","left","right"],e=a.effects.setMode(c,b.options.mode||"hide"),f=b.options.size||15,g=!!b.options.horizFirst,h=b.duration?b.duration/2:a.fx.speeds._default/2;a.effects.save(c,d),c.show();var i=a.effects.createWrapper(c).css({overflow:"hidden"}),j=e=="show"!=g,k=j?["width","height"]:["height","width"],l=j?[i.width(),i.height()]:[i.height(),i.width()],m=/([0-9]+)%/.exec(f);m&&(f=parseInt(m[1],10)/100*l[e=="hide"?0:1]),e=="show"&&i.css(g?{height:0,width:f}:{height:f,width:0});var n={},p={};n[k[0]]=e=="show"?l[0]:f,p[k[1]]=e=="show"?l[1]:0,i.animate(n,h,b.options.easing).animate(p,h,b.options.easing,function(){e=="hide"&&c.hide(),a.effects.restore(c,d),a.effects.removeWrapper(c),b.callback&&b.callback.apply(c[0],arguments),c.dequeue()})})}})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.effects.highlight.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function(a,b){a.effects.highlight=function(b){return this.queue(function(){var c=a(this),d=["backgroundImage","backgroundColor","opacity"],e=a.effects.setMode(c,b.options.mode||"show"),f={backgroundColor:c.css("backgroundColor")};e=="hide"&&(f.opacity=0),a.effects.save(c,d),c.show().css({backgroundImage:"none",backgroundColor:b.options.color||"#ffff99"}).animate(f,{queue:!1,duration:b.duration,easing:b.options.easing,complete:function(){e=="hide"&&c.hide(),a.effects.restore(c,d),e=="show"&&!a.support.opacity&&this.style.removeAttribute("filter"),b.callback&&b.callback.apply(this,arguments),c.dequeue()}})})}})(jQuery);;/*! jQuery UI - v1.8.21 - 2012-06-05 -* https://github.com/jquery/jquery-ui -* Includes: jquery.effects.pulsate.js -* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ -(function(a,b){a.effects.pulsate=function(b){return this.queue(function(){var c=a(this),d=a.effects.setMode(c,b.options.mode||"show"),e=(b.options.times||5)*2-1,f=b.duration?b.duration/2:a.fx.speeds._default/2,g=c.is(":visible"),h=0;g||(c.css("opacity",0).show(),h=1),(d=="hide"&&g||d=="show"&&!g)&&e--;for(var i=0;i
      ').appendTo(document.body).addClass(b.options.className).css({top:g.top,left:g.left,height:c.innerHeight(),width:c.innerWidth(),position:"absolute"}).animate(f,b.duration,b.options.easing,function(){h.remove(),b.callback&&b.callback.apply(c[0],arguments),c.dequeue()})})}})(jQuery);; - -/* JQuery UJS 2.0.3 */ -(function(a,b){var c=function(){var b=a(document).data("events");return b&&b.click&&a.grep(b.click,function(a){return a.namespace==="rails"}).length};if(c()){a.error("jquery-ujs has already been loaded!")}var d;a.rails=d={linkClickSelector:"a[data-confirm], a[data-method], a[data-remote], a[data-disable-with]",inputChangeSelector:"select[data-remote], input[data-remote], textarea[data-remote]",formSubmitSelector:"form",formInputClickSelector:"form input[type=submit], form input[type=image], form button[type=submit], form button:not([type])",disableSelector:"input[data-disable-with], button[data-disable-with], textarea[data-disable-with]",enableSelector:"input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled",requiredInputSelector:"input[name][required]:not([disabled]),textarea[name][required]:not([disabled])",fileInputSelector:"input:file",linkDisableSelector:"a[data-disable-with]",CSRFProtection:function(b){var c=a('meta[name="csrf-token"]').attr("content");if(c)b.setRequestHeader("X-CSRF-Token",c)},fire:function(b,c,d){var e=a.Event(c);b.trigger(e,d);return e.result!==false},confirm:function(a){return confirm(a)},ajax:function(b){return a.ajax(b)},href:function(a){return a.attr("href")},handleRemote:function(c){var e,f,g,h,i,j,k,l;if(d.fire(c,"ajax:before")){h=c.data("cross-domain");i=h===b?null:h;j=c.data("with-credentials")||null;k=c.data("type")||a.ajaxSettings&&a.ajaxSettings.dataType;if(c.is("form")){e=c.attr("method");f=c.attr("action");g=c.serializeArray();var m=c.data("ujs:submit-button");if(m){g.push(m);c.data("ujs:submit-button",null)}}else if(c.is(d.inputChangeSelector)){e=c.data("method");f=c.data("url");g=c.serialize();if(c.data("params"))g=g+"&"+c.data("params")}else{e=c.data("method");f=d.href(c);g=c.data("params")||null}l={type:e||"GET",data:g,dataType:k,beforeSend:function(a,e){if(e.dataType===b){a.setRequestHeader("accept","*/*;q=0.5, "+e.accepts.script)}return d.fire(c,"ajax:beforeSend",[a,e])},success:function(a,b,d){c.trigger("ajax:success",[a,b,d])},complete:function(a,b){c.trigger("ajax:complete",[a,b])},error:function(a,b,d){c.trigger("ajax:error",[a,b,d])},xhrFields:{withCredentials:j},crossDomain:i};if(f){l.url=f}var n=d.ajax(l);c.trigger("ajax:send",n);return n}else{return false}},handleMethod:function(c){var e=d.href(c),f=c.data("method"),g=c.attr("target"),h=a("meta[name=csrf-token]").attr("content"),i=a("meta[name=csrf-param]").attr("content"),j=a('
      '),k='';if(i!==b&&h!==b){k+=''}if(g){j.attr("target",g)}j.hide().append(k).appendTo("body");j.submit()},disableFormElements:function(b){b.find(d.disableSelector).each(function(){var b=a(this),c=b.is("button")?"html":"val";b.data("ujs:enable-with",b[c]());b[c](b.data("disable-with"));b.prop("disabled",true)})},enableFormElements:function(b){b.find(d.enableSelector).each(function(){var b=a(this),c=b.is("button")?"html":"val";if(b.data("ujs:enable-with"))b[c](b.data("ujs:enable-with"));b.prop("disabled",false)})},allowAction:function(a){var b=a.data("confirm"),c=false,e;if(!b){return true}if(d.fire(a,"confirm")){c=d.confirm(b);e=d.fire(a,"confirm:complete",[c])}return c&&e},blankInputs:function(b,c,d){var e=a(),f,g,h=c||"input,textarea";b.find(h).each(function(){f=a(this);g=f.is(":checkbox,:radio")?f.is(":checked"):f.val();if(g==!!d){e=e.add(f)}});return e.length?e:false},nonBlankInputs:function(a,b){return d.blankInputs(a,b,true)},stopEverything:function(b){a(b.target).trigger("ujs:everythingStopped");b.stopImmediatePropagation();return false},callFormSubmitBindings:function(c,d){var e=c.data("events"),f=true;if(e!==b&&e["submit"]!==b){a.each(e["submit"],function(a,b){if(typeof b.handler==="function")return f=b.handler(d)})}return f},disableElement:function(a){a.data("ujs:enable-with",a.html());a.html(a.data("disable-with"));a.bind("click.railsDisable",function(a){return d.stopEverything(a)})},enableElement:function(a){if(a.data("ujs:enable-with")!==b){a.html(a.data("ujs:enable-with"));a.data("ujs:enable-with",false)}a.unbind("click.railsDisable")}};if(d.fire(a(document),"rails:attachBindings")){a.ajaxPrefilter(function(a,b,c){if(!a.crossDomain){d.CSRFProtection(c)}});a(document).delegate(d.linkDisableSelector,"ajax:complete",function(){d.enableElement(a(this))});a(document).delegate(d.linkClickSelector,"click.rails",function(c){var e=a(this),f=e.data("method"),g=e.data("params");if(!d.allowAction(e))return d.stopEverything(c);if(e.is(d.linkDisableSelector))d.disableElement(e);if(e.data("remote")!==b){if((c.metaKey||c.ctrlKey)&&(!f||f==="GET")&&!g){return true}if(d.handleRemote(e)===false){d.enableElement(e)}return false}else if(e.data("method")){d.handleMethod(e);return false}});a(document).delegate(d.inputChangeSelector,"change.rails",function(b){var c=a(this);if(!d.allowAction(c))return d.stopEverything(b);d.handleRemote(c);return false});a(document).delegate(d.formSubmitSelector,"submit.rails",function(c){var e=a(this),f=e.data("remote")!==b,g=d.blankInputs(e,d.requiredInputSelector),h=d.nonBlankInputs(e,d.fileInputSelector);if(!d.allowAction(e))return d.stopEverything(c);if(g&&e.attr("novalidate")==b&&d.fire(e,"ajax:aborted:required",[g])){return d.stopEverything(c)}if(f){if(h){setTimeout(function(){d.disableFormElements(e)},13);return d.fire(e,"ajax:aborted:file",[h])}if(!a.support.submitBubbles&&a().jquery<"1.7"&&d.callFormSubmitBindings(e,c)===false)return d.stopEverything(c);d.handleRemote(e);return false}else{setTimeout(function(){d.disableFormElements(e)},13)}});a(document).delegate(d.formInputClickSelector,"click.rails",function(b){var c=a(this);if(!d.allowAction(c))return d.stopEverything(b);var e=c.attr("name"),f=e?{name:e,value:c.val()}:null;c.closest("form").data("ujs:submit-button",f)});a(document).delegate(d.formSubmitSelector,"ajax:beforeSend.rails",function(b){if(this==b.target)d.disableFormElements(a(this))});a(document).delegate(d.formSubmitSelector,"ajax:complete.rails",function(b){if(this==b.target)d.enableFormElements(a(this))});a(function(){csrf_token=a("meta[name=csrf-token]").attr("content");csrf_param=a("meta[name=csrf-param]").attr("content");a('form input[name="'+csrf_param+'"]').val(csrf_token)})}})(jQuery) diff -r 0a574315af3e -r 4f746d8966dd public/javascripts/jquery-1.8.3-ui-1.9.2-ujs-2.0.3.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/javascripts/jquery-1.8.3-ui-1.9.2-ujs-2.0.3.js Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,11 @@ +/*! jQuery v1.8.3 jquery.com | jquery.org/license */ +(function(e,t){function _(e){var t=M[e]={};return v.each(e.split(y),function(e,n){t[n]=!0}),t}function H(e,n,r){if(r===t&&e.nodeType===1){var i="data-"+n.replace(P,"-$1").toLowerCase();r=e.getAttribute(i);if(typeof r=="string"){try{r=r==="true"?!0:r==="false"?!1:r==="null"?null:+r+""===r?+r:D.test(r)?v.parseJSON(r):r}catch(s){}v.data(e,n,r)}else r=t}return r}function B(e){var t;for(t in e){if(t==="data"&&v.isEmptyObject(e[t]))continue;if(t!=="toJSON")return!1}return!0}function et(){return!1}function tt(){return!0}function ut(e){return!e||!e.parentNode||e.parentNode.nodeType===11}function at(e,t){do e=e[t];while(e&&e.nodeType!==1);return e}function ft(e,t,n){t=t||0;if(v.isFunction(t))return v.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return v.grep(e,function(e,r){return e===t===n});if(typeof t=="string"){var r=v.grep(e,function(e){return e.nodeType===1});if(it.test(t))return v.filter(t,r,!n);t=v.filter(t,r)}return v.grep(e,function(e,r){return v.inArray(e,t)>=0===n})}function lt(e){var t=ct.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}function Lt(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function At(e,t){if(t.nodeType!==1||!v.hasData(e))return;var n,r,i,s=v._data(e),o=v._data(t,s),u=s.events;if(u){delete o.handle,o.events={};for(n in u)for(r=0,i=u[n].length;r").appendTo(i.body),n=t.css("display");t.remove();if(n==="none"||n===""){Pt=i.body.appendChild(Pt||v.extend(i.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!Ht||!Pt.createElement)Ht=(Pt.contentWindow||Pt.contentDocument).document,Ht.write(""),Ht.close();t=Ht.body.appendChild(Ht.createElement(e)),n=Dt(t,"display"),i.body.removeChild(Pt)}return Wt[e]=n,n}function fn(e,t,n,r){var i;if(v.isArray(t))v.each(t,function(t,i){n||sn.test(e)?r(e,i):fn(e+"["+(typeof i=="object"?t:"")+"]",i,n,r)});else if(!n&&v.type(t)==="object")for(i in t)fn(e+"["+i+"]",t[i],n,r);else r(e,t)}function Cn(e){return function(t,n){typeof t!="string"&&(n=t,t="*");var r,i,s,o=t.toLowerCase().split(y),u=0,a=o.length;if(v.isFunction(n))for(;u)[^>]*$|#([\w\-]*)$)/,E=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,S=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,T=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,N=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,C=/^-ms-/,k=/-([\da-z])/gi,L=function(e,t){return(t+"").toUpperCase()},A=function(){i.addEventListener?(i.removeEventListener("DOMContentLoaded",A,!1),v.ready()):i.readyState==="complete"&&(i.detachEvent("onreadystatechange",A),v.ready())},O={};v.fn=v.prototype={constructor:v,init:function(e,n,r){var s,o,u,a;if(!e)return this;if(e.nodeType)return this.context=this[0]=e,this.length=1,this;if(typeof e=="string"){e.charAt(0)==="<"&&e.charAt(e.length-1)===">"&&e.length>=3?s=[null,e,null]:s=w.exec(e);if(s&&(s[1]||!n)){if(s[1])return n=n instanceof v?n[0]:n,a=n&&n.nodeType?n.ownerDocument||n:i,e=v.parseHTML(s[1],a,!0),E.test(s[1])&&v.isPlainObject(n)&&this.attr.call(e,n,!0),v.merge(this,e);o=i.getElementById(s[2]);if(o&&o.parentNode){if(o.id!==s[2])return r.find(e);this.length=1,this[0]=o}return this.context=i,this.selector=e,this}return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e)}return v.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),v.makeArray(e,this))},selector:"",jquery:"1.8.3",length:0,size:function(){return this.length},toArray:function(){return l.call(this)},get:function(e){return e==null?this.toArray():e<0?this[this.length+e]:this[e]},pushStack:function(e,t,n){var r=v.merge(this.constructor(),e);return r.prevObject=this,r.context=this.context,t==="find"?r.selector=this.selector+(this.selector?" ":"")+n:t&&(r.selector=this.selector+"."+t+"("+n+")"),r},each:function(e,t){return v.each(this,e,t)},ready:function(e){return v.ready.promise().done(e),this},eq:function(e){return e=+e,e===-1?this.slice(e):this.slice(e,e+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(l.apply(this,arguments),"slice",l.call(arguments).join(","))},map:function(e){return this.pushStack(v.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:[].sort,splice:[].splice},v.fn.init.prototype=v.fn,v.extend=v.fn.extend=function(){var e,n,r,i,s,o,u=arguments[0]||{},a=1,f=arguments.length,l=!1;typeof u=="boolean"&&(l=u,u=arguments[1]||{},a=2),typeof u!="object"&&!v.isFunction(u)&&(u={}),f===a&&(u=this,--a);for(;a0)return;r.resolveWith(i,[v]),v.fn.trigger&&v(i).trigger("ready").off("ready")},isFunction:function(e){return v.type(e)==="function"},isArray:Array.isArray||function(e){return v.type(e)==="array"},isWindow:function(e){return e!=null&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return e==null?String(e):O[h.call(e)]||"object"},isPlainObject:function(e){if(!e||v.type(e)!=="object"||e.nodeType||v.isWindow(e))return!1;try{if(e.constructor&&!p.call(e,"constructor")&&!p.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}var r;for(r in e);return r===t||p.call(e,r)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw new Error(e)},parseHTML:function(e,t,n){var r;return!e||typeof e!="string"?null:(typeof t=="boolean"&&(n=t,t=0),t=t||i,(r=E.exec(e))?[t.createElement(r[1])]:(r=v.buildFragment([e],t,n?null:[]),v.merge([],(r.cacheable?v.clone(r.fragment):r.fragment).childNodes)))},parseJSON:function(t){if(!t||typeof t!="string")return null;t=v.trim(t);if(e.JSON&&e.JSON.parse)return e.JSON.parse(t);if(S.test(t.replace(T,"@").replace(N,"]").replace(x,"")))return(new Function("return "+t))();v.error("Invalid JSON: "+t)},parseXML:function(n){var r,i;if(!n||typeof n!="string")return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(s){r=t}return(!r||!r.documentElement||r.getElementsByTagName("parsererror").length)&&v.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&g.test(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(C,"ms-").replace(k,L)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,n,r){var i,s=0,o=e.length,u=o===t||v.isFunction(e);if(r){if(u){for(i in e)if(n.apply(e[i],r)===!1)break}else for(;s0&&e[0]&&e[a-1]||a===0||v.isArray(e));if(f)for(;u-1)a.splice(n,1),i&&(n<=o&&o--,n<=u&&u--)}),this},has:function(e){return v.inArray(e,a)>-1},empty:function(){return a=[],this},disable:function(){return a=f=n=t,this},disabled:function(){return!a},lock:function(){return f=t,n||c.disable(),this},locked:function(){return!f},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],a&&(!r||f)&&(i?f.push(t):l(t)),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!r}};return c},v.extend({Deferred:function(e){var t=[["resolve","done",v.Callbacks("once memory"),"resolved"],["reject","fail",v.Callbacks("once memory"),"rejected"],["notify","progress",v.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return v.Deferred(function(n){v.each(t,function(t,r){var s=r[0],o=e[t];i[r[1]](v.isFunction(o)?function(){var e=o.apply(this,arguments);e&&v.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[s+"With"](this===i?n:this,[e])}:n[s])}),e=null}).promise()},promise:function(e){return e!=null?v.extend(e,r):r}},i={};return r.pipe=r.then,v.each(t,function(e,s){var o=s[2],u=s[3];r[s[1]]=o.add,u&&o.add(function(){n=u},t[e^1][2].disable,t[2][2].lock),i[s[0]]=o.fire,i[s[0]+"With"]=o.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=l.call(arguments),r=n.length,i=r!==1||e&&v.isFunction(e.promise)?r:0,s=i===1?e:v.Deferred(),o=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?l.call(arguments):r,n===u?s.notifyWith(t,n):--i||s.resolveWith(t,n)}},u,a,f;if(r>1){u=new Array(r),a=new Array(r),f=new Array(r);for(;t
      a",n=p.getElementsByTagName("*"),r=p.getElementsByTagName("a")[0];if(!n||!r||!n.length)return{};s=i.createElement("select"),o=s.appendChild(i.createElement("option")),u=p.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(r.getAttribute("style")),hrefNormalized:r.getAttribute("href")==="/a",opacity:/^0.5/.test(r.style.opacity),cssFloat:!!r.style.cssFloat,checkOn:u.value==="on",optSelected:o.selected,getSetAttribute:p.className!=="t",enctype:!!i.createElement("form").enctype,html5Clone:i.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",boxModel:i.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},u.checked=!0,t.noCloneChecked=u.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!o.disabled;try{delete p.test}catch(d){t.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",h=function(){t.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick"),p.detachEvent("onclick",h)),u=i.createElement("input"),u.value="t",u.setAttribute("type","radio"),t.radioValue=u.value==="t",u.setAttribute("checked","checked"),u.setAttribute("name","t"),p.appendChild(u),a=i.createDocumentFragment(),a.appendChild(p.lastChild),t.checkClone=a.cloneNode(!0).cloneNode(!0).lastChild.checked,t.appendChecked=u.checked,a.removeChild(u),a.appendChild(p);if(p.attachEvent)for(l in{submit:!0,change:!0,focusin:!0})f="on"+l,c=f in p,c||(p.setAttribute(f,"return;"),c=typeof p[f]=="function"),t[l+"Bubbles"]=c;return v(function(){var n,r,s,o,u="padding:0;margin:0;border:0;display:block;overflow:hidden;",a=i.getElementsByTagName("body")[0];if(!a)return;n=i.createElement("div"),n.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",a.insertBefore(n,a.firstChild),r=i.createElement("div"),n.appendChild(r),r.innerHTML="
      t
      ",s=r.getElementsByTagName("td"),s[0].style.cssText="padding:0;margin:0;border:0;display:none",c=s[0].offsetHeight===0,s[0].style.display="",s[1].style.display="none",t.reliableHiddenOffsets=c&&s[0].offsetHeight===0,r.innerHTML="",r.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",t.boxSizing=r.offsetWidth===4,t.doesNotIncludeMarginInBodyOffset=a.offsetTop!==1,e.getComputedStyle&&(t.pixelPosition=(e.getComputedStyle(r,null)||{}).top!=="1%",t.boxSizingReliable=(e.getComputedStyle(r,null)||{width:"4px"}).width==="4px",o=i.createElement("div"),o.style.cssText=r.style.cssText=u,o.style.marginRight=o.style.width="0",r.style.width="1px",r.appendChild(o),t.reliableMarginRight=!parseFloat((e.getComputedStyle(o,null)||{}).marginRight)),typeof r.style.zoom!="undefined"&&(r.innerHTML="",r.style.cssText=u+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=r.offsetWidth===3,r.style.display="block",r.style.overflow="visible",r.innerHTML="
      ",r.firstChild.style.width="5px",t.shrinkWrapBlocks=r.offsetWidth!==3,n.style.zoom=1),a.removeChild(n),n=r=s=o=null}),a.removeChild(p),n=r=s=o=u=a=p=null,t}();var D=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;v.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(v.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(e){return e=e.nodeType?v.cache[e[v.expando]]:e[v.expando],!!e&&!B(e)},data:function(e,n,r,i){if(!v.acceptData(e))return;var s,o,u=v.expando,a=typeof n=="string",f=e.nodeType,l=f?v.cache:e,c=f?e[u]:e[u]&&u;if((!c||!l[c]||!i&&!l[c].data)&&a&&r===t)return;c||(f?e[u]=c=v.deletedIds.pop()||v.guid++:c=u),l[c]||(l[c]={},f||(l[c].toJSON=v.noop));if(typeof n=="object"||typeof n=="function")i?l[c]=v.extend(l[c],n):l[c].data=v.extend(l[c].data,n);return s=l[c],i||(s.data||(s.data={}),s=s.data),r!==t&&(s[v.camelCase(n)]=r),a?(o=s[n],o==null&&(o=s[v.camelCase(n)])):o=s,o},removeData:function(e,t,n){if(!v.acceptData(e))return;var r,i,s,o=e.nodeType,u=o?v.cache:e,a=o?e[v.expando]:v.expando;if(!u[a])return;if(t){r=n?u[a]:u[a].data;if(r){v.isArray(t)||(t in r?t=[t]:(t=v.camelCase(t),t in r?t=[t]:t=t.split(" ")));for(i=0,s=t.length;i1,null,!1))},removeData:function(e){return this.each(function(){v.removeData(this,e)})}}),v.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=v._data(e,t),n&&(!r||v.isArray(n)?r=v._data(e,t,v.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=v.queue(e,t),r=n.length,i=n.shift(),s=v._queueHooks(e,t),o=function(){v.dequeue(e,t)};i==="inprogress"&&(i=n.shift(),r--),i&&(t==="fx"&&n.unshift("inprogress"),delete s.stop,i.call(e,o,s)),!r&&s&&s.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return v._data(e,n)||v._data(e,n,{empty:v.Callbacks("once memory").add(function(){v.removeData(e,t+"queue",!0),v.removeData(e,n,!0)})})}}),v.fn.extend({queue:function(e,n){var r=2;return typeof e!="string"&&(n=e,e="fx",r--),arguments.length1)},removeAttr:function(e){return this.each(function(){v.removeAttr(this,e)})},prop:function(e,t){return v.access(this,v.prop,e,t,arguments.length>1)},removeProp:function(e){return e=v.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,s,o,u;if(v.isFunction(e))return this.each(function(t){v(this).addClass(e.call(this,t,this.className))});if(e&&typeof e=="string"){t=e.split(y);for(n=0,r=this.length;n=0)r=r.replace(" "+n[s]+" "," ");i.className=e?v.trim(r):""}}}return this},toggleClass:function(e,t){var n=typeof e,r=typeof t=="boolean";return v.isFunction(e)?this.each(function(n){v(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if(n==="string"){var i,s=0,o=v(this),u=t,a=e.split(y);while(i=a[s++])u=r?u:!o.hasClass(i),o[u?"addClass":"removeClass"](i)}else if(n==="undefined"||n==="boolean")this.className&&v._data(this,"__className__",this.className),this.className=this.className||e===!1?"":v._data(this,"__className__")||""})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;n=0)return!0;return!1},val:function(e){var n,r,i,s=this[0];if(!arguments.length){if(s)return n=v.valHooks[s.type]||v.valHooks[s.nodeName.toLowerCase()],n&&"get"in n&&(r=n.get(s,"value"))!==t?r:(r=s.value,typeof r=="string"?r.replace(R,""):r==null?"":r);return}return i=v.isFunction(e),this.each(function(r){var s,o=v(this);if(this.nodeType!==1)return;i?s=e.call(this,r,o.val()):s=e,s==null?s="":typeof s=="number"?s+="":v.isArray(s)&&(s=v.map(s,function(e){return e==null?"":e+""})),n=v.valHooks[this.type]||v.valHooks[this.nodeName.toLowerCase()];if(!n||!("set"in n)||n.set(this,s,"value")===t)this.value=s})}}),v.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,s=e.type==="select-one"||i<0,o=s?null:[],u=s?i+1:r.length,a=i<0?u:s?i:0;for(;a=0}),n.length||(e.selectedIndex=-1),n}}},attrFn:{},attr:function(e,n,r,i){var s,o,u,a=e.nodeType;if(!e||a===3||a===8||a===2)return;if(i&&v.isFunction(v.fn[n]))return v(e)[n](r);if(typeof e.getAttribute=="undefined")return v.prop(e,n,r);u=a!==1||!v.isXMLDoc(e),u&&(n=n.toLowerCase(),o=v.attrHooks[n]||(X.test(n)?F:j));if(r!==t){if(r===null){v.removeAttr(e,n);return}return o&&"set"in o&&u&&(s=o.set(e,r,n))!==t?s:(e.setAttribute(n,r+""),r)}return o&&"get"in o&&u&&(s=o.get(e,n))!==null?s:(s=e.getAttribute(n),s===null?t:s)},removeAttr:function(e,t){var n,r,i,s,o=0;if(t&&e.nodeType===1){r=t.split(y);for(;o=0}})});var $=/^(?:textarea|input|select)$/i,J=/^([^\.]*|)(?:\.(.+)|)$/,K=/(?:^|\s)hover(\.\S+|)\b/,Q=/^key/,G=/^(?:mouse|contextmenu)|click/,Y=/^(?:focusinfocus|focusoutblur)$/,Z=function(e){return v.event.special.hover?e:e.replace(K,"mouseenter$1 mouseleave$1")};v.event={add:function(e,n,r,i,s){var o,u,a,f,l,c,h,p,d,m,g;if(e.nodeType===3||e.nodeType===8||!n||!r||!(o=v._data(e)))return;r.handler&&(d=r,r=d.handler,s=d.selector),r.guid||(r.guid=v.guid++),a=o.events,a||(o.events=a={}),u=o.handle,u||(o.handle=u=function(e){return typeof v=="undefined"||!!e&&v.event.triggered===e.type?t:v.event.dispatch.apply(u.elem,arguments)},u.elem=e),n=v.trim(Z(n)).split(" ");for(f=0;f=0&&(y=y.slice(0,-1),a=!0),y.indexOf(".")>=0&&(b=y.split("."),y=b.shift(),b.sort());if((!s||v.event.customEvent[y])&&!v.event.global[y])return;n=typeof n=="object"?n[v.expando]?n:new v.Event(y,n):new v.Event(y),n.type=y,n.isTrigger=!0,n.exclusive=a,n.namespace=b.join("."),n.namespace_re=n.namespace?new RegExp("(^|\\.)"+b.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,h=y.indexOf(":")<0?"on"+y:"";if(!s){u=v.cache;for(f in u)u[f].events&&u[f].events[y]&&v.event.trigger(n,r,u[f].handle.elem,!0);return}n.result=t,n.target||(n.target=s),r=r!=null?v.makeArray(r):[],r.unshift(n),p=v.event.special[y]||{};if(p.trigger&&p.trigger.apply(s,r)===!1)return;m=[[s,p.bindType||y]];if(!o&&!p.noBubble&&!v.isWindow(s)){g=p.delegateType||y,l=Y.test(g+y)?s:s.parentNode;for(c=s;l;l=l.parentNode)m.push([l,g]),c=l;c===(s.ownerDocument||i)&&m.push([c.defaultView||c.parentWindow||e,g])}for(f=0;f=0:v.find(h,this,null,[s]).length),u[h]&&f.push(c);f.length&&w.push({elem:s,matches:f})}d.length>m&&w.push({elem:this,matches:d.slice(m)});for(r=0;r0?this.on(t,null,e,n):this.trigger(t)},Q.test(t)&&(v.event.fixHooks[t]=v.event.keyHooks),G.test(t)&&(v.event.fixHooks[t]=v.event.mouseHooks)}),function(e,t){function nt(e,t,n,r){n=n||[],t=t||g;var i,s,a,f,l=t.nodeType;if(!e||typeof e!="string")return n;if(l!==1&&l!==9)return[];a=o(t);if(!a&&!r)if(i=R.exec(e))if(f=i[1]){if(l===9){s=t.getElementById(f);if(!s||!s.parentNode)return n;if(s.id===f)return n.push(s),n}else if(t.ownerDocument&&(s=t.ownerDocument.getElementById(f))&&u(t,s)&&s.id===f)return n.push(s),n}else{if(i[2])return S.apply(n,x.call(t.getElementsByTagName(e),0)),n;if((f=i[3])&&Z&&t.getElementsByClassName)return S.apply(n,x.call(t.getElementsByClassName(f),0)),n}return vt(e.replace(j,"$1"),t,n,r,a)}function rt(e){return function(t){var n=t.nodeName.toLowerCase();return n==="input"&&t.type===e}}function it(e){return function(t){var n=t.nodeName.toLowerCase();return(n==="input"||n==="button")&&t.type===e}}function st(e){return N(function(t){return t=+t,N(function(n,r){var i,s=e([],n.length,t),o=s.length;while(o--)n[i=s[o]]&&(n[i]=!(r[i]=n[i]))})})}function ot(e,t,n){if(e===t)return n;var r=e.nextSibling;while(r){if(r===t)return-1;r=r.nextSibling}return 1}function ut(e,t){var n,r,s,o,u,a,f,l=L[d][e+" "];if(l)return t?0:l.slice(0);u=e,a=[],f=i.preFilter;while(u){if(!n||(r=F.exec(u)))r&&(u=u.slice(r[0].length)||u),a.push(s=[]);n=!1;if(r=I.exec(u))s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=r[0].replace(j," ");for(o in i.filter)(r=J[o].exec(u))&&(!f[o]||(r=f[o](r)))&&(s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=o,n.matches=r);if(!n)break}return t?u.length:u?nt.error(e):L(e,a).slice(0)}function at(e,t,r){var i=t.dir,s=r&&t.dir==="parentNode",o=w++;return t.first?function(t,n,r){while(t=t[i])if(s||t.nodeType===1)return e(t,n,r)}:function(t,r,u){if(!u){var a,f=b+" "+o+" ",l=f+n;while(t=t[i])if(s||t.nodeType===1){if((a=t[d])===l)return t.sizset;if(typeof a=="string"&&a.indexOf(f)===0){if(t.sizset)return t}else{t[d]=l;if(e(t,r,u))return t.sizset=!0,t;t.sizset=!1}}}else while(t=t[i])if(s||t.nodeType===1)if(e(t,r,u))return t}}function ft(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function lt(e,t,n,r,i){var s,o=[],u=0,a=e.length,f=t!=null;for(;u-1&&(s[f]=!(o[f]=c))}}else g=lt(g===o?g.splice(d,g.length):g),i?i(null,o,g,a):S.apply(o,g)})}function ht(e){var t,n,r,s=e.length,o=i.relative[e[0].type],u=o||i.relative[" "],a=o?1:0,f=at(function(e){return e===t},u,!0),l=at(function(e){return T.call(t,e)>-1},u,!0),h=[function(e,n,r){return!o&&(r||n!==c)||((t=n).nodeType?f(e,n,r):l(e,n,r))}];for(;a1&&ft(h),a>1&&e.slice(0,a-1).join("").replace(j,"$1"),n,a0,s=e.length>0,o=function(u,a,f,l,h){var p,d,v,m=[],y=0,w="0",x=u&&[],T=h!=null,N=c,C=u||s&&i.find.TAG("*",h&&a.parentNode||a),k=b+=N==null?1:Math.E;T&&(c=a!==g&&a,n=o.el);for(;(p=C[w])!=null;w++){if(s&&p){for(d=0;v=e[d];d++)if(v(p,a,f)){l.push(p);break}T&&(b=k,n=++o.el)}r&&((p=!v&&p)&&y--,u&&x.push(p))}y+=w;if(r&&w!==y){for(d=0;v=t[d];d++)v(x,m,a,f);if(u){if(y>0)while(w--)!x[w]&&!m[w]&&(m[w]=E.call(l));m=lt(m)}S.apply(l,m),T&&!u&&m.length>0&&y+t.length>1&&nt.uniqueSort(l)}return T&&(b=k,c=N),x};return o.el=0,r?N(o):o}function dt(e,t,n){var r=0,i=t.length;for(;r2&&(f=u[0]).type==="ID"&&t.nodeType===9&&!s&&i.relative[u[1].type]){t=i.find.ID(f.matches[0].replace($,""),t,s)[0];if(!t)return n;e=e.slice(u.shift().length)}for(o=J.POS.test(e)?-1:u.length-1;o>=0;o--){f=u[o];if(i.relative[l=f.type])break;if(c=i.find[l])if(r=c(f.matches[0].replace($,""),z.test(u[0].type)&&t.parentNode||t,s)){u.splice(o,1),e=r.length&&u.join("");if(!e)return S.apply(n,x.call(r,0)),n;break}}}return a(e,h)(r,t,s,n,z.test(e)),n}function mt(){}var n,r,i,s,o,u,a,f,l,c,h=!0,p="undefined",d=("sizcache"+Math.random()).replace(".",""),m=String,g=e.document,y=g.documentElement,b=0,w=0,E=[].pop,S=[].push,x=[].slice,T=[].indexOf||function(e){var t=0,n=this.length;for(;ti.cacheLength&&delete e[t.shift()],e[n+" "]=r},e)},k=C(),L=C(),A=C(),O="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",_=M.replace("w","w#"),D="([*^$|!~]?=)",P="\\["+O+"*("+M+")"+O+"*(?:"+D+O+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+_+")|)|)"+O+"*\\]",H=":("+M+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+P+")|[^:]|\\\\.)*|.*))\\)|)",B=":(even|odd|eq|gt|lt|nth|first|last)(?:\\("+O+"*((?:-\\d)?\\d*)"+O+"*\\)|)(?=[^-]|$)",j=new RegExp("^"+O+"+|((?:^|[^\\\\])(?:\\\\.)*)"+O+"+$","g"),F=new RegExp("^"+O+"*,"+O+"*"),I=new RegExp("^"+O+"*([\\x20\\t\\r\\n\\f>+~])"+O+"*"),q=new RegExp(H),R=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,U=/^:not/,z=/[\x20\t\r\n\f]*[+~]/,W=/:not\($/,X=/h\d/i,V=/input|select|textarea|button/i,$=/\\(?!\\)/g,J={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),NAME:new RegExp("^\\[name=['\"]?("+M+")['\"]?\\]"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+H),POS:new RegExp(B,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+O+"*(even|odd|(([+-]|)(\\d*)n|)"+O+"*(?:([+-]|)"+O+"*(\\d+)|))"+O+"*\\)|)","i"),needsContext:new RegExp("^"+O+"*[>+~]|"+B,"i")},K=function(e){var t=g.createElement("div");try{return e(t)}catch(n){return!1}finally{t=null}},Q=K(function(e){return e.appendChild(g.createComment("")),!e.getElementsByTagName("*").length}),G=K(function(e){return e.innerHTML="",e.firstChild&&typeof e.firstChild.getAttribute!==p&&e.firstChild.getAttribute("href")==="#"}),Y=K(function(e){e.innerHTML="";var t=typeof e.lastChild.getAttribute("multiple");return t!=="boolean"&&t!=="string"}),Z=K(function(e){return e.innerHTML="",!e.getElementsByClassName||!e.getElementsByClassName("e").length?!1:(e.lastChild.className="e",e.getElementsByClassName("e").length===2)}),et=K(function(e){e.id=d+0,e.innerHTML="
      ",y.insertBefore(e,y.firstChild);var t=g.getElementsByName&&g.getElementsByName(d).length===2+g.getElementsByName(d+0).length;return r=!g.getElementById(d),y.removeChild(e),t});try{x.call(y.childNodes,0)[0].nodeType}catch(tt){x=function(e){var t,n=[];for(;t=this[e];e++)n.push(t);return n}}nt.matches=function(e,t){return nt(e,null,null,t)},nt.matchesSelector=function(e,t){return nt(t,null,null,[e]).length>0},s=nt.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(i===1||i===9||i===11){if(typeof e.textContent=="string")return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=s(e)}else if(i===3||i===4)return e.nodeValue}else for(;t=e[r];r++)n+=s(t);return n},o=nt.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?t.nodeName!=="HTML":!1},u=nt.contains=y.contains?function(e,t){var n=e.nodeType===9?e.documentElement:e,r=t&&t.parentNode;return e===r||!!(r&&r.nodeType===1&&n.contains&&n.contains(r))}:y.compareDocumentPosition?function(e,t){return t&&!!(e.compareDocumentPosition(t)&16)}:function(e,t){while(t=t.parentNode)if(t===e)return!0;return!1},nt.attr=function(e,t){var n,r=o(e);return r||(t=t.toLowerCase()),(n=i.attrHandle[t])?n(e):r||Y?e.getAttribute(t):(n=e.getAttributeNode(t),n?typeof e[t]=="boolean"?e[t]?t:null:n.specified?n.value:null:null)},i=nt.selectors={cacheLength:50,createPseudo:N,match:J,attrHandle:G?{}:{href:function(e){return e.getAttribute("href",2)},type:function(e){return e.getAttribute("type")}},find:{ID:r?function(e,t,n){if(typeof t.getElementById!==p&&!n){var r=t.getElementById(e);return r&&r.parentNode?[r]:[]}}:function(e,n,r){if(typeof n.getElementById!==p&&!r){var i=n.getElementById(e);return i?i.id===e||typeof i.getAttributeNode!==p&&i.getAttributeNode("id").value===e?[i]:t:[]}},TAG:Q?function(e,t){if(typeof t.getElementsByTagName!==p)return t.getElementsByTagName(e)}:function(e,t){var n=t.getElementsByTagName(e);if(e==="*"){var r,i=[],s=0;for(;r=n[s];s++)r.nodeType===1&&i.push(r);return i}return n},NAME:et&&function(e,t){if(typeof t.getElementsByName!==p)return t.getElementsByName(name)},CLASS:Z&&function(e,t,n){if(typeof t.getElementsByClassName!==p&&!n)return t.getElementsByClassName(e)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace($,""),e[3]=(e[4]||e[5]||"").replace($,""),e[2]==="~="&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),e[1]==="nth"?(e[2]||nt.error(e[0]),e[3]=+(e[3]?e[4]+(e[5]||1):2*(e[2]==="even"||e[2]==="odd")),e[4]=+(e[6]+e[7]||e[2]==="odd")):e[2]&&nt.error(e[0]),e},PSEUDO:function(e){var t,n;if(J.CHILD.test(e[0]))return null;if(e[3])e[2]=e[3];else if(t=e[4])q.test(t)&&(n=ut(t,!0))&&(n=t.indexOf(")",t.length-n)-t.length)&&(t=t.slice(0,n),e[0]=e[0].slice(0,n)),e[2]=t;return e.slice(0,3)}},filter:{ID:r?function(e){return e=e.replace($,""),function(t){return t.getAttribute("id")===e}}:function(e){return e=e.replace($,""),function(t){var n=typeof t.getAttributeNode!==p&&t.getAttributeNode("id");return n&&n.value===e}},TAG:function(e){return e==="*"?function(){return!0}:(e=e.replace($,"").toLowerCase(),function(t){return t.nodeName&&t.nodeName.toLowerCase()===e})},CLASS:function(e){var t=k[d][e+" "];return t||(t=new RegExp("(^|"+O+")"+e+"("+O+"|$)"))&&k(e,function(e){return t.test(e.className||typeof e.getAttribute!==p&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r,i){var s=nt.attr(r,e);return s==null?t==="!=":t?(s+="",t==="="?s===n:t==="!="?s!==n:t==="^="?n&&s.indexOf(n)===0:t==="*="?n&&s.indexOf(n)>-1:t==="$="?n&&s.substr(s.length-n.length)===n:t==="~="?(" "+s+" ").indexOf(n)>-1:t==="|="?s===n||s.substr(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r){return e==="nth"?function(e){var t,i,s=e.parentNode;if(n===1&&r===0)return!0;if(s){i=0;for(t=s.firstChild;t;t=t.nextSibling)if(t.nodeType===1){i++;if(e===t)break}}return i-=r,i===n||i%n===0&&i/n>=0}:function(t){var n=t;switch(e){case"only":case"first":while(n=n.previousSibling)if(n.nodeType===1)return!1;if(e==="first")return!0;n=t;case"last":while(n=n.nextSibling)if(n.nodeType===1)return!1;return!0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||nt.error("unsupported pseudo: "+e);return r[d]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?N(function(e,n){var i,s=r(e,t),o=s.length;while(o--)i=T.call(e,s[o]),e[i]=!(n[i]=s[o])}):function(e){return r(e,0,n)}):r}},pseudos:{not:N(function(e){var t=[],n=[],r=a(e.replace(j,"$1"));return r[d]?N(function(e,t,n,i){var s,o=r(e,null,i,[]),u=e.length;while(u--)if(s=o[u])e[u]=!(t[u]=s)}):function(e,i,s){return t[0]=e,r(t,null,s,n),!n.pop()}}),has:N(function(e){return function(t){return nt(e,t).length>0}}),contains:N(function(e){return function(t){return(t.textContent||t.innerText||s(t)).indexOf(e)>-1}}),enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&!!e.checked||t==="option"&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},parent:function(e){return!i.pseudos.empty(e)},empty:function(e){var t;e=e.firstChild;while(e){if(e.nodeName>"@"||(t=e.nodeType)===3||t===4)return!1;e=e.nextSibling}return!0},header:function(e){return X.test(e.nodeName)},text:function(e){var t,n;return e.nodeName.toLowerCase()==="input"&&(t=e.type)==="text"&&((n=e.getAttribute("type"))==null||n.toLowerCase()===t)},radio:rt("radio"),checkbox:rt("checkbox"),file:rt("file"),password:rt("password"),image:rt("image"),submit:it("submit"),reset:it("reset"),button:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&e.type==="button"||t==="button"},input:function(e){return V.test(e.nodeName)},focus:function(e){var t=e.ownerDocument;return e===t.activeElement&&(!t.hasFocus||t.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},active:function(e){return e===e.ownerDocument.activeElement},first:st(function(){return[0]}),last:st(function(e,t){return[t-1]}),eq:st(function(e,t,n){return[n<0?n+t:n]}),even:st(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:st(function(e,t,n){for(var r=n<0?n+t:n;++r",e.querySelectorAll("[selected]").length||i.push("\\["+O+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),e.querySelectorAll(":checked").length||i.push(":checked")}),K(function(e){e.innerHTML="

      ",e.querySelectorAll("[test^='']").length&&i.push("[*^$]="+O+"*(?:\"\"|'')"),e.innerHTML="",e.querySelectorAll(":enabled").length||i.push(":enabled",":disabled")}),i=new RegExp(i.join("|")),vt=function(e,r,s,o,u){if(!o&&!u&&!i.test(e)){var a,f,l=!0,c=d,h=r,p=r.nodeType===9&&e;if(r.nodeType===1&&r.nodeName.toLowerCase()!=="object"){a=ut(e),(l=r.getAttribute("id"))?c=l.replace(n,"\\$&"):r.setAttribute("id",c),c="[id='"+c+"'] ",f=a.length;while(f--)a[f]=c+a[f].join("");h=z.test(e)&&r.parentNode||r,p=a.join(",")}if(p)try{return S.apply(s,x.call(h.querySelectorAll(p),0)),s}catch(v){}finally{l||r.removeAttribute("id")}}return t(e,r,s,o,u)},u&&(K(function(t){e=u.call(t,"div");try{u.call(t,"[test!='']:sizzle"),s.push("!=",H)}catch(n){}}),s=new RegExp(s.join("|")),nt.matchesSelector=function(t,n){n=n.replace(r,"='$1']");if(!o(t)&&!s.test(n)&&!i.test(n))try{var a=u.call(t,n);if(a||e||t.document&&t.document.nodeType!==11)return a}catch(f){}return nt(n,null,null,[t]).length>0})}(),i.pseudos.nth=i.pseudos.eq,i.filters=mt.prototype=i.pseudos,i.setFilters=new mt,nt.attr=v.attr,v.find=nt,v.expr=nt.selectors,v.expr[":"]=v.expr.pseudos,v.unique=nt.uniqueSort,v.text=nt.getText,v.isXMLDoc=nt.isXML,v.contains=nt.contains}(e);var nt=/Until$/,rt=/^(?:parents|prev(?:Until|All))/,it=/^.[^:#\[\.,]*$/,st=v.expr.match.needsContext,ot={children:!0,contents:!0,next:!0,prev:!0};v.fn.extend({find:function(e){var t,n,r,i,s,o,u=this;if(typeof e!="string")return v(e).filter(function(){for(t=0,n=u.length;t0)for(i=r;i=0:v.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){var n,r=0,i=this.length,s=[],o=st.test(e)||typeof e!="string"?v(e,t||this.context):0;for(;r-1:v.find.matchesSelector(n,e)){s.push(n);break}n=n.parentNode}}return s=s.length>1?v.unique(s):s,this.pushStack(s,"closest",e)},index:function(e){return e?typeof e=="string"?v.inArray(this[0],v(e)):v.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(e,t){var n=typeof e=="string"?v(e,t):v.makeArray(e&&e.nodeType?[e]:e),r=v.merge(this.get(),n);return this.pushStack(ut(n[0])||ut(r[0])?r:v.unique(r))},addBack:function(e){return this.add(e==null?this.prevObject:this.prevObject.filter(e))}}),v.fn.andSelf=v.fn.addBack,v.each({parent:function(e){var t=e.parentNode;return t&&t.nodeType!==11?t:null},parents:function(e){return v.dir(e,"parentNode")},parentsUntil:function(e,t,n){return v.dir(e,"parentNode",n)},next:function(e){return at(e,"nextSibling")},prev:function(e){return at(e,"previousSibling")},nextAll:function(e){return v.dir(e,"nextSibling")},prevAll:function(e){return v.dir(e,"previousSibling")},nextUntil:function(e,t,n){return v.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return v.dir(e,"previousSibling",n)},siblings:function(e){return v.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return v.sibling(e.firstChild)},contents:function(e){return v.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:v.merge([],e.childNodes)}},function(e,t){v.fn[e]=function(n,r){var i=v.map(this,t,n);return nt.test(e)||(r=n),r&&typeof r=="string"&&(i=v.filter(r,i)),i=this.length>1&&!ot[e]?v.unique(i):i,this.length>1&&rt.test(e)&&(i=i.reverse()),this.pushStack(i,e,l.call(arguments).join(","))}}),v.extend({filter:function(e,t,n){return n&&(e=":not("+e+")"),t.length===1?v.find.matchesSelector(t[0],e)?[t[0]]:[]:v.find.matches(e,t)},dir:function(e,n,r){var i=[],s=e[n];while(s&&s.nodeType!==9&&(r===t||s.nodeType!==1||!v(s).is(r)))s.nodeType===1&&i.push(s),s=s[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)e.nodeType===1&&e!==t&&n.push(e);return n}});var ct="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",ht=/ jQuery\d+="(?:null|\d+)"/g,pt=/^\s+/,dt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,vt=/<([\w:]+)/,mt=/]","i"),Et=/^(?:checkbox|radio)$/,St=/checked\s*(?:[^=]|=\s*.checked.)/i,xt=/\/(java|ecma)script/i,Tt=/^\s*\s*$/g,Nt={option:[1,""],legend:[1,"
      ","
      "],thead:[1,"","
      "],tr:[2,"","
      "],td:[3,"","
      "],col:[2,"","
      "],area:[1,"",""],_default:[0,"",""]},Ct=lt(i),kt=Ct.appendChild(i.createElement("div"));Nt.optgroup=Nt.option,Nt.tbody=Nt.tfoot=Nt.colgroup=Nt.caption=Nt.thead,Nt.th=Nt.td,v.support.htmlSerialize||(Nt._default=[1,"X
      ","
      "]),v.fn.extend({text:function(e){return v.access(this,function(e){return e===t?v.text(this):this.empty().append((this[0]&&this[0].ownerDocument||i).createTextNode(e))},null,e,arguments.length)},wrapAll:function(e){if(v.isFunction(e))return this.each(function(t){v(this).wrapAll(e.call(this,t))});if(this[0]){var t=v(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&e.firstChild.nodeType===1)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return v.isFunction(e)?this.each(function(t){v(this).wrapInner(e.call(this,t))}):this.each(function(){var t=v(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=v.isFunction(e);return this.each(function(n){v(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){v.nodeName(this,"body")||v(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.appendChild(e)})},prepend:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(e,this.firstChild)})},before:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(e,this),"before",this.selector)}},after:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this.nextSibling)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(this,e),"after",this.selector)}},remove:function(e,t){var n,r=0;for(;(n=this[r])!=null;r++)if(!e||v.filter(e,[n]).length)!t&&n.nodeType===1&&(v.cleanData(n.getElementsByTagName("*")),v.cleanData([n])),n.parentNode&&n.parentNode.removeChild(n);return this},empty:function(){var e,t=0;for(;(e=this[t])!=null;t++){e.nodeType===1&&v.cleanData(e.getElementsByTagName("*"));while(e.firstChild)e.removeChild(e.firstChild)}return this},clone:function(e,t){return e=e==null?!1:e,t=t==null?e:t,this.map(function(){return v.clone(this,e,t)})},html:function(e){return v.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return n.nodeType===1?n.innerHTML.replace(ht,""):t;if(typeof e=="string"&&!yt.test(e)&&(v.support.htmlSerialize||!wt.test(e))&&(v.support.leadingWhitespace||!pt.test(e))&&!Nt[(vt.exec(e)||["",""])[1].toLowerCase()]){e=e.replace(dt,"<$1>");try{for(;r1&&typeof f=="string"&&St.test(f))return this.each(function(){v(this).domManip(e,n,r)});if(v.isFunction(f))return this.each(function(i){var s=v(this);e[0]=f.call(this,i,n?s.html():t),s.domManip(e,n,r)});if(this[0]){i=v.buildFragment(e,this,l),o=i.fragment,s=o.firstChild,o.childNodes.length===1&&(o=s);if(s){n=n&&v.nodeName(s,"tr");for(u=i.cacheable||c-1;a0?this.clone(!0):this).get(),v(o[i])[t](r),s=s.concat(r);return this.pushStack(s,e,o.selector)}}),v.extend({clone:function(e,t,n){var r,i,s,o;v.support.html5Clone||v.isXMLDoc(e)||!wt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(kt.innerHTML=e.outerHTML,kt.removeChild(o=kt.firstChild));if((!v.support.noCloneEvent||!v.support.noCloneChecked)&&(e.nodeType===1||e.nodeType===11)&&!v.isXMLDoc(e)){Ot(e,o),r=Mt(e),i=Mt(o);for(s=0;r[s];++s)i[s]&&Ot(r[s],i[s])}if(t){At(e,o);if(n){r=Mt(e),i=Mt(o);for(s=0;r[s];++s)At(r[s],i[s])}}return r=i=null,o},clean:function(e,t,n,r){var s,o,u,a,f,l,c,h,p,d,m,g,y=t===i&&Ct,b=[];if(!t||typeof t.createDocumentFragment=="undefined")t=i;for(s=0;(u=e[s])!=null;s++){typeof u=="number"&&(u+="");if(!u)continue;if(typeof u=="string")if(!gt.test(u))u=t.createTextNode(u);else{y=y||lt(t),c=t.createElement("div"),y.appendChild(c),u=u.replace(dt,"<$1>"),a=(vt.exec(u)||["",""])[1].toLowerCase(),f=Nt[a]||Nt._default,l=f[0],c.innerHTML=f[1]+u+f[2];while(l--)c=c.lastChild;if(!v.support.tbody){h=mt.test(u),p=a==="table"&&!h?c.firstChild&&c.firstChild.childNodes:f[1]===""&&!h?c.childNodes:[];for(o=p.length-1;o>=0;--o)v.nodeName(p[o],"tbody")&&!p[o].childNodes.length&&p[o].parentNode.removeChild(p[o])}!v.support.leadingWhitespace&&pt.test(u)&&c.insertBefore(t.createTextNode(pt.exec(u)[0]),c.firstChild),u=c.childNodes,c.parentNode.removeChild(c)}u.nodeType?b.push(u):v.merge(b,u)}c&&(u=c=y=null);if(!v.support.appendChecked)for(s=0;(u=b[s])!=null;s++)v.nodeName(u,"input")?_t(u):typeof u.getElementsByTagName!="undefined"&&v.grep(u.getElementsByTagName("input"),_t);if(n){m=function(e){if(!e.type||xt.test(e.type))return r?r.push(e.parentNode?e.parentNode.removeChild(e):e):n.appendChild(e)};for(s=0;(u=b[s])!=null;s++)if(!v.nodeName(u,"script")||!m(u))n.appendChild(u),typeof u.getElementsByTagName!="undefined"&&(g=v.grep(v.merge([],u.getElementsByTagName("script")),m),b.splice.apply(b,[s+1,0].concat(g)),s+=g.length)}return b},cleanData:function(e,t){var n,r,i,s,o=0,u=v.expando,a=v.cache,f=v.support.deleteExpando,l=v.event.special;for(;(i=e[o])!=null;o++)if(t||v.acceptData(i)){r=i[u],n=r&&a[r];if(n){if(n.events)for(s in n.events)l[s]?v.event.remove(i,s):v.removeEvent(i,s,n.handle);a[r]&&(delete a[r],f?delete i[u]:i.removeAttribute?i.removeAttribute(u):i[u]=null,v.deletedIds.push(r))}}}}),function(){var e,t;v.uaMatch=function(e){e=e.toLowerCase();var t=/(chrome)[ \/]([\w.]+)/.exec(e)||/(webkit)[ \/]([\w.]+)/.exec(e)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(e)||/(msie) ([\w.]+)/.exec(e)||e.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(e)||[];return{browser:t[1]||"",version:t[2]||"0"}},e=v.uaMatch(o.userAgent),t={},e.browser&&(t[e.browser]=!0,t.version=e.version),t.chrome?t.webkit=!0:t.webkit&&(t.safari=!0),v.browser=t,v.sub=function(){function e(t,n){return new e.fn.init(t,n)}v.extend(!0,e,this),e.superclass=this,e.fn=e.prototype=this(),e.fn.constructor=e,e.sub=this.sub,e.fn.init=function(r,i){return i&&i instanceof v&&!(i instanceof e)&&(i=e(i)),v.fn.init.call(this,r,i,t)},e.fn.init.prototype=e.fn;var t=e(i);return e}}();var Dt,Pt,Ht,Bt=/alpha\([^)]*\)/i,jt=/opacity=([^)]*)/,Ft=/^(top|right|bottom|left)$/,It=/^(none|table(?!-c[ea]).+)/,qt=/^margin/,Rt=new RegExp("^("+m+")(.*)$","i"),Ut=new RegExp("^("+m+")(?!px)[a-z%]+$","i"),zt=new RegExp("^([-+])=("+m+")","i"),Wt={BODY:"block"},Xt={position:"absolute",visibility:"hidden",display:"block"},Vt={letterSpacing:0,fontWeight:400},$t=["Top","Right","Bottom","Left"],Jt=["Webkit","O","Moz","ms"],Kt=v.fn.toggle;v.fn.extend({css:function(e,n){return v.access(this,function(e,n,r){return r!==t?v.style(e,n,r):v.css(e,n)},e,n,arguments.length>1)},show:function(){return Yt(this,!0)},hide:function(){return Yt(this)},toggle:function(e,t){var n=typeof e=="boolean";return v.isFunction(e)&&v.isFunction(t)?Kt.apply(this,arguments):this.each(function(){(n?e:Gt(this))?v(this).show():v(this).hide()})}}),v.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Dt(e,"opacity");return n===""?"1":n}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":v.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(!e||e.nodeType===3||e.nodeType===8||!e.style)return;var s,o,u,a=v.camelCase(n),f=e.style;n=v.cssProps[a]||(v.cssProps[a]=Qt(f,a)),u=v.cssHooks[n]||v.cssHooks[a];if(r===t)return u&&"get"in u&&(s=u.get(e,!1,i))!==t?s:f[n];o=typeof r,o==="string"&&(s=zt.exec(r))&&(r=(s[1]+1)*s[2]+parseFloat(v.css(e,n)),o="number");if(r==null||o==="number"&&isNaN(r))return;o==="number"&&!v.cssNumber[a]&&(r+="px");if(!u||!("set"in u)||(r=u.set(e,r,i))!==t)try{f[n]=r}catch(l){}},css:function(e,n,r,i){var s,o,u,a=v.camelCase(n);return n=v.cssProps[a]||(v.cssProps[a]=Qt(e.style,a)),u=v.cssHooks[n]||v.cssHooks[a],u&&"get"in u&&(s=u.get(e,!0,i)),s===t&&(s=Dt(e,n)),s==="normal"&&n in Vt&&(s=Vt[n]),r||i!==t?(o=parseFloat(s),r||v.isNumeric(o)?o||0:s):s},swap:function(e,t,n){var r,i,s={};for(i in t)s[i]=e.style[i],e.style[i]=t[i];r=n.call(e);for(i in t)e.style[i]=s[i];return r}}),e.getComputedStyle?Dt=function(t,n){var r,i,s,o,u=e.getComputedStyle(t,null),a=t.style;return u&&(r=u.getPropertyValue(n)||u[n],r===""&&!v.contains(t.ownerDocument,t)&&(r=v.style(t,n)),Ut.test(r)&&qt.test(n)&&(i=a.width,s=a.minWidth,o=a.maxWidth,a.minWidth=a.maxWidth=a.width=r,r=u.width,a.width=i,a.minWidth=s,a.maxWidth=o)),r}:i.documentElement.currentStyle&&(Dt=function(e,t){var n,r,i=e.currentStyle&&e.currentStyle[t],s=e.style;return i==null&&s&&s[t]&&(i=s[t]),Ut.test(i)&&!Ft.test(t)&&(n=s.left,r=e.runtimeStyle&&e.runtimeStyle.left,r&&(e.runtimeStyle.left=e.currentStyle.left),s.left=t==="fontSize"?"1em":i,i=s.pixelLeft+"px",s.left=n,r&&(e.runtimeStyle.left=r)),i===""?"auto":i}),v.each(["height","width"],function(e,t){v.cssHooks[t]={get:function(e,n,r){if(n)return e.offsetWidth===0&&It.test(Dt(e,"display"))?v.swap(e,Xt,function(){return tn(e,t,r)}):tn(e,t,r)},set:function(e,n,r){return Zt(e,n,r?en(e,t,r,v.support.boxSizing&&v.css(e,"boxSizing")==="border-box"):0)}}}),v.support.opacity||(v.cssHooks.opacity={get:function(e,t){return jt.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=v.isNumeric(t)?"alpha(opacity="+t*100+")":"",s=r&&r.filter||n.filter||"";n.zoom=1;if(t>=1&&v.trim(s.replace(Bt,""))===""&&n.removeAttribute){n.removeAttribute("filter");if(r&&!r.filter)return}n.filter=Bt.test(s)?s.replace(Bt,i):s+" "+i}}),v(function(){v.support.reliableMarginRight||(v.cssHooks.marginRight={get:function(e,t){return v.swap(e,{display:"inline-block"},function(){if(t)return Dt(e,"marginRight")})}}),!v.support.pixelPosition&&v.fn.position&&v.each(["top","left"],function(e,t){v.cssHooks[t]={get:function(e,n){if(n){var r=Dt(e,t);return Ut.test(r)?v(e).position()[t]+"px":r}}}})}),v.expr&&v.expr.filters&&(v.expr.filters.hidden=function(e){return e.offsetWidth===0&&e.offsetHeight===0||!v.support.reliableHiddenOffsets&&(e.style&&e.style.display||Dt(e,"display"))==="none"},v.expr.filters.visible=function(e){return!v.expr.filters.hidden(e)}),v.each({margin:"",padding:"",border:"Width"},function(e,t){v.cssHooks[e+t]={expand:function(n){var r,i=typeof n=="string"?n.split(" "):[n],s={};for(r=0;r<4;r++)s[e+$t[r]+t]=i[r]||i[r-2]||i[0];return s}},qt.test(e)||(v.cssHooks[e+t].set=Zt)});var rn=/%20/g,sn=/\[\]$/,on=/\r?\n/g,un=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,an=/^(?:select|textarea)/i;v.fn.extend({serialize:function(){return v.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?v.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||an.test(this.nodeName)||un.test(this.type))}).map(function(e,t){var n=v(this).val();return n==null?null:v.isArray(n)?v.map(n,function(e,n){return{name:t.name,value:e.replace(on,"\r\n")}}):{name:t.name,value:n.replace(on,"\r\n")}}).get()}}),v.param=function(e,n){var r,i=[],s=function(e,t){t=v.isFunction(t)?t():t==null?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};n===t&&(n=v.ajaxSettings&&v.ajaxSettings.traditional);if(v.isArray(e)||e.jquery&&!v.isPlainObject(e))v.each(e,function(){s(this.name,this.value)});else for(r in e)fn(r,e[r],n,s);return i.join("&").replace(rn,"+")};var ln,cn,hn=/#.*$/,pn=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,dn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,vn=/^(?:GET|HEAD)$/,mn=/^\/\//,gn=/\?/,yn=/)<[^<]*)*<\/script>/gi,bn=/([?&])_=[^&]*/,wn=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,En=v.fn.load,Sn={},xn={},Tn=["*/"]+["*"];try{cn=s.href}catch(Nn){cn=i.createElement("a"),cn.href="",cn=cn.href}ln=wn.exec(cn.toLowerCase())||[],v.fn.load=function(e,n,r){if(typeof e!="string"&&En)return En.apply(this,arguments);if(!this.length)return this;var i,s,o,u=this,a=e.indexOf(" ");return a>=0&&(i=e.slice(a,e.length),e=e.slice(0,a)),v.isFunction(n)?(r=n,n=t):n&&typeof n=="object"&&(s="POST"),v.ajax({url:e,type:s,dataType:"html",data:n,complete:function(e,t){r&&u.each(r,o||[e.responseText,t,e])}}).done(function(e){o=arguments,u.html(i?v("
      ").append(e.replace(yn,"")).find(i):e)}),this},v.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(e,t){v.fn[t]=function(e){return this.on(t,e)}}),v.each(["get","post"],function(e,n){v[n]=function(e,r,i,s){return v.isFunction(r)&&(s=s||i,i=r,r=t),v.ajax({type:n,url:e,data:r,success:i,dataType:s})}}),v.extend({getScript:function(e,n){return v.get(e,t,n,"script")},getJSON:function(e,t,n){return v.get(e,t,n,"json")},ajaxSetup:function(e,t){return t?Ln(e,v.ajaxSettings):(t=e,e=v.ajaxSettings),Ln(e,t),e},ajaxSettings:{url:cn,isLocal:dn.test(ln[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":Tn},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":e.String,"text html":!0,"text json":v.parseJSON,"text xml":v.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:Cn(Sn),ajaxTransport:Cn(xn),ajax:function(e,n){function T(e,n,s,a){var l,y,b,w,S,T=n;if(E===2)return;E=2,u&&clearTimeout(u),o=t,i=a||"",x.readyState=e>0?4:0,s&&(w=An(c,x,s));if(e>=200&&e<300||e===304)c.ifModified&&(S=x.getResponseHeader("Last-Modified"),S&&(v.lastModified[r]=S),S=x.getResponseHeader("Etag"),S&&(v.etag[r]=S)),e===304?(T="notmodified",l=!0):(l=On(c,w),T=l.state,y=l.data,b=l.error,l=!b);else{b=T;if(!T||e)T="error",e<0&&(e=0)}x.status=e,x.statusText=(n||T)+"",l?d.resolveWith(h,[y,T,x]):d.rejectWith(h,[x,T,b]),x.statusCode(g),g=t,f&&p.trigger("ajax"+(l?"Success":"Error"),[x,c,l?y:b]),m.fireWith(h,[x,T]),f&&(p.trigger("ajaxComplete",[x,c]),--v.active||v.event.trigger("ajaxStop"))}typeof e=="object"&&(n=e,e=t),n=n||{};var r,i,s,o,u,a,f,l,c=v.ajaxSetup({},n),h=c.context||c,p=h!==c&&(h.nodeType||h instanceof v)?v(h):v.event,d=v.Deferred(),m=v.Callbacks("once memory"),g=c.statusCode||{},b={},w={},E=0,S="canceled",x={readyState:0,setRequestHeader:function(e,t){if(!E){var n=e.toLowerCase();e=w[n]=w[n]||e,b[e]=t}return this},getAllResponseHeaders:function(){return E===2?i:null},getResponseHeader:function(e){var n;if(E===2){if(!s){s={};while(n=pn.exec(i))s[n[1].toLowerCase()]=n[2]}n=s[e.toLowerCase()]}return n===t?null:n},overrideMimeType:function(e){return E||(c.mimeType=e),this},abort:function(e){return e=e||S,o&&o.abort(e),T(0,e),this}};d.promise(x),x.success=x.done,x.error=x.fail,x.complete=m.add,x.statusCode=function(e){if(e){var t;if(E<2)for(t in e)g[t]=[g[t],e[t]];else t=e[x.status],x.always(t)}return this},c.url=((e||c.url)+"").replace(hn,"").replace(mn,ln[1]+"//"),c.dataTypes=v.trim(c.dataType||"*").toLowerCase().split(y),c.crossDomain==null&&(a=wn.exec(c.url.toLowerCase()),c.crossDomain=!(!a||a[1]===ln[1]&&a[2]===ln[2]&&(a[3]||(a[1]==="http:"?80:443))==(ln[3]||(ln[1]==="http:"?80:443)))),c.data&&c.processData&&typeof c.data!="string"&&(c.data=v.param(c.data,c.traditional)),kn(Sn,c,n,x);if(E===2)return x;f=c.global,c.type=c.type.toUpperCase(),c.hasContent=!vn.test(c.type),f&&v.active++===0&&v.event.trigger("ajaxStart");if(!c.hasContent){c.data&&(c.url+=(gn.test(c.url)?"&":"?")+c.data,delete c.data),r=c.url;if(c.cache===!1){var N=v.now(),C=c.url.replace(bn,"$1_="+N);c.url=C+(C===c.url?(gn.test(c.url)?"&":"?")+"_="+N:"")}}(c.data&&c.hasContent&&c.contentType!==!1||n.contentType)&&x.setRequestHeader("Content-Type",c.contentType),c.ifModified&&(r=r||c.url,v.lastModified[r]&&x.setRequestHeader("If-Modified-Since",v.lastModified[r]),v.etag[r]&&x.setRequestHeader("If-None-Match",v.etag[r])),x.setRequestHeader("Accept",c.dataTypes[0]&&c.accepts[c.dataTypes[0]]?c.accepts[c.dataTypes[0]]+(c.dataTypes[0]!=="*"?", "+Tn+"; q=0.01":""):c.accepts["*"]);for(l in c.headers)x.setRequestHeader(l,c.headers[l]);if(!c.beforeSend||c.beforeSend.call(h,x,c)!==!1&&E!==2){S="abort";for(l in{success:1,error:1,complete:1})x[l](c[l]);o=kn(xn,c,n,x);if(!o)T(-1,"No Transport");else{x.readyState=1,f&&p.trigger("ajaxSend",[x,c]),c.async&&c.timeout>0&&(u=setTimeout(function(){x.abort("timeout")},c.timeout));try{E=1,o.send(b,T)}catch(k){if(!(E<2))throw k;T(-1,k)}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var Mn=[],_n=/\?/,Dn=/(=)\?(?=&|$)|\?\?/,Pn=v.now();v.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Mn.pop()||v.expando+"_"+Pn++;return this[e]=!0,e}}),v.ajaxPrefilter("json jsonp",function(n,r,i){var s,o,u,a=n.data,f=n.url,l=n.jsonp!==!1,c=l&&Dn.test(f),h=l&&!c&&typeof a=="string"&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Dn.test(a);if(n.dataTypes[0]==="jsonp"||c||h)return s=n.jsonpCallback=v.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,o=e[s],c?n.url=f.replace(Dn,"$1"+s):h?n.data=a.replace(Dn,"$1"+s):l&&(n.url+=(_n.test(f)?"&":"?")+n.jsonp+"="+s),n.converters["script json"]=function(){return u||v.error(s+" was not called"),u[0]},n.dataTypes[0]="json",e[s]=function(){u=arguments},i.always(function(){e[s]=o,n[s]&&(n.jsonpCallback=r.jsonpCallback,Mn.push(s)),u&&v.isFunction(o)&&o(u[0]),u=o=t}),"script"}),v.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(e){return v.globalEval(e),e}}}),v.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),v.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=i.head||i.getElementsByTagName("head")[0]||i.documentElement;return{send:function(s,o){n=i.createElement("script"),n.async="async",e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,i){if(i||!n.readyState||/loaded|complete/.test(n.readyState))n.onload=n.onreadystatechange=null,r&&n.parentNode&&r.removeChild(n),n=t,i||o(200,"success")},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(0,1)}}}});var Hn,Bn=e.ActiveXObject?function(){for(var e in Hn)Hn[e](0,1)}:!1,jn=0;v.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&Fn()||In()}:Fn,function(e){v.extend(v.support,{ajax:!!e,cors:!!e&&"withCredentials"in e})}(v.ajaxSettings.xhr()),v.support.ajax&&v.ajaxTransport(function(n){if(!n.crossDomain||v.support.cors){var r;return{send:function(i,s){var o,u,a=n.xhr();n.username?a.open(n.type,n.url,n.async,n.username,n.password):a.open(n.type,n.url,n.async);if(n.xhrFields)for(u in n.xhrFields)a[u]=n.xhrFields[u];n.mimeType&&a.overrideMimeType&&a.overrideMimeType(n.mimeType),!n.crossDomain&&!i["X-Requested-With"]&&(i["X-Requested-With"]="XMLHttpRequest");try{for(u in i)a.setRequestHeader(u,i[u])}catch(f){}a.send(n.hasContent&&n.data||null),r=function(e,i){var u,f,l,c,h;try{if(r&&(i||a.readyState===4)){r=t,o&&(a.onreadystatechange=v.noop,Bn&&delete Hn[o]);if(i)a.readyState!==4&&a.abort();else{u=a.status,l=a.getAllResponseHeaders(),c={},h=a.responseXML,h&&h.documentElement&&(c.xml=h);try{c.text=a.responseText}catch(p){}try{f=a.statusText}catch(p){f=""}!u&&n.isLocal&&!n.crossDomain?u=c.text?200:404:u===1223&&(u=204)}}}catch(d){i||s(-1,d)}c&&s(u,f,c,l)},n.async?a.readyState===4?setTimeout(r,0):(o=++jn,Bn&&(Hn||(Hn={},v(e).unload(Bn)),Hn[o]=r),a.onreadystatechange=r):r()},abort:function(){r&&r(0,1)}}}});var qn,Rn,Un=/^(?:toggle|show|hide)$/,zn=new RegExp("^(?:([-+])=|)("+m+")([a-z%]*)$","i"),Wn=/queueHooks$/,Xn=[Gn],Vn={"*":[function(e,t){var n,r,i=this.createTween(e,t),s=zn.exec(t),o=i.cur(),u=+o||0,a=1,f=20;if(s){n=+s[2],r=s[3]||(v.cssNumber[e]?"":"px");if(r!=="px"&&u){u=v.css(i.elem,e,!0)||n||1;do a=a||".5",u/=a,v.style(i.elem,e,u+r);while(a!==(a=i.cur()/o)&&a!==1&&--f)}i.unit=r,i.start=u,i.end=s[1]?u+(s[1]+1)*n:n}return i}]};v.Animation=v.extend(Kn,{tweener:function(e,t){v.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;r-1,f={},l={},c,h;a?(l=i.position(),c=l.top,h=l.left):(c=parseFloat(o)||0,h=parseFloat(u)||0),v.isFunction(t)&&(t=t.call(e,n,s)),t.top!=null&&(f.top=t.top-s.top+c),t.left!=null&&(f.left=t.left-s.left+h),"using"in t?t.using.call(e,f):i.css(f)}},v.fn.extend({position:function(){if(!this[0])return;var e=this[0],t=this.offsetParent(),n=this.offset(),r=er.test(t[0].nodeName)?{top:0,left:0}:t.offset();return n.top-=parseFloat(v.css(e,"marginTop"))||0,n.left-=parseFloat(v.css(e,"marginLeft"))||0,r.top+=parseFloat(v.css(t[0],"borderTopWidth"))||0,r.left+=parseFloat(v.css(t[0],"borderLeftWidth"))||0,{top:n.top-r.top,left:n.left-r.left}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||i.body;while(e&&!er.test(e.nodeName)&&v.css(e,"position")==="static")e=e.offsetParent;return e||i.body})}}),v.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);v.fn[e]=function(i){return v.access(this,function(e,i,s){var o=tr(e);if(s===t)return o?n in o?o[n]:o.document.documentElement[i]:e[i];o?o.scrollTo(r?v(o).scrollLeft():s,r?s:v(o).scrollTop()):e[i]=s},e,i,arguments.length,null)}}),v.each({Height:"height",Width:"width"},function(e,n){v.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){v.fn[i]=function(i,s){var o=arguments.length&&(r||typeof i!="boolean"),u=r||(i===!0||s===!0?"margin":"border");return v.access(this,function(n,r,i){var s;return v.isWindow(n)?n.document.documentElement["client"+e]:n.nodeType===9?(s=n.documentElement,Math.max(n.body["scroll"+e],s["scroll"+e],n.body["offset"+e],s["offset"+e],s["client"+e])):i===t?v.css(n,r,i,u):v.style(n,r,i,u)},n,o?i:t,o,null)}})}),e.jQuery=e.$=v,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return v})})(window); + +/*! jQuery UI - v1.9.2 - 2012-12-26 +* http://jqueryui.com +* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.position.js, jquery.ui.draggable.js, jquery.ui.droppable.js, jquery.ui.resizable.js, jquery.ui.selectable.js, jquery.ui.sortable.js, jquery.ui.accordion.js, jquery.ui.autocomplete.js, jquery.ui.button.js, jquery.ui.datepicker.js, jquery.ui.dialog.js, jquery.ui.menu.js, jquery.ui.progressbar.js, jquery.ui.slider.js, jquery.ui.spinner.js, jquery.ui.tabs.js, jquery.ui.tooltip.js, jquery.ui.effect.js, jquery.ui.effect-blind.js, jquery.ui.effect-bounce.js, jquery.ui.effect-clip.js, jquery.ui.effect-drop.js, jquery.ui.effect-explode.js, jquery.ui.effect-fade.js, jquery.ui.effect-fold.js, jquery.ui.effect-highlight.js, jquery.ui.effect-pulsate.js, jquery.ui.effect-scale.js, jquery.ui.effect-shake.js, jquery.ui.effect-slide.js, jquery.ui.effect-transfer.js +* Copyright (c) 2012 jQuery Foundation and other contributors Licensed MIT */ +(function(e,t){function i(t,n){var r,i,o,u=t.nodeName.toLowerCase();return"area"===u?(r=t.parentNode,i=r.name,!t.href||!i||r.nodeName.toLowerCase()!=="map"?!1:(o=e("img[usemap=#"+i+"]")[0],!!o&&s(o))):(/input|select|textarea|button|object/.test(u)?!t.disabled:"a"===u?t.href||n:n)&&s(t)}function s(t){return e.expr.filters.visible(t)&&!e(t).parents().andSelf().filter(function(){return e.css(this,"visibility")==="hidden"}).length}var n=0,r=/^ui-id-\d+$/;e.ui=e.ui||{};if(e.ui.version)return;e.extend(e.ui,{version:"1.9.2",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),e.fn.extend({_focus:e.fn.focus,focus:function(t,n){return typeof t=="number"?this.each(function(){var r=this;setTimeout(function(){e(r).focus(),n&&n.call(r)},t)}):this._focus.apply(this,arguments)},scrollParent:function(){var t;return e.ui.ie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?t=this.parents().filter(function(){return/(relative|absolute|fixed)/.test(e.css(this,"position"))&&/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0):t=this.parents().filter(function(){return/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0),/fixed/.test(this.css("position"))||!t.length?e(document):t},zIndex:function(n){if(n!==t)return this.css("zIndex",n);if(this.length){var r=e(this[0]),i,s;while(r.length&&r[0]!==document){i=r.css("position");if(i==="absolute"||i==="relative"||i==="fixed"){s=parseInt(r.css("zIndex"),10);if(!isNaN(s)&&s!==0)return s}r=r.parent()}}return 0},uniqueId:function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++n)})},removeUniqueId:function(){return this.each(function(){r.test(this.id)&&e(this).removeAttr("id")})}}),e.extend(e.expr[":"],{data:e.expr.createPseudo?e.expr.createPseudo(function(t){return function(n){return!!e.data(n,t)}}):function(t,n,r){return!!e.data(t,r[3])},focusable:function(t){return i(t,!isNaN(e.attr(t,"tabindex")))},tabbable:function(t){var n=e.attr(t,"tabindex"),r=isNaN(n);return(r||n>=0)&&i(t,!r)}}),e(function(){var t=document.body,n=t.appendChild(n=document.createElement("div"));n.offsetHeight,e.extend(n.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0}),e.support.minHeight=n.offsetHeight===100,e.support.selectstart="onselectstart"in n,t.removeChild(n).style.display="none"}),e("").outerWidth(1).jquery||e.each(["Width","Height"],function(n,r){function u(t,n,r,s){return e.each(i,function(){n-=parseFloat(e.css(t,"padding"+this))||0,r&&(n-=parseFloat(e.css(t,"border"+this+"Width"))||0),s&&(n-=parseFloat(e.css(t,"margin"+this))||0)}),n}var i=r==="Width"?["Left","Right"]:["Top","Bottom"],s=r.toLowerCase(),o={innerWidth:e.fn.innerWidth,innerHeight:e.fn.innerHeight,outerWidth:e.fn.outerWidth,outerHeight:e.fn.outerHeight};e.fn["inner"+r]=function(n){return n===t?o["inner"+r].call(this):this.each(function(){e(this).css(s,u(this,n)+"px")})},e.fn["outer"+r]=function(t,n){return typeof t!="number"?o["outer"+r].call(this,t):this.each(function(){e(this).css(s,u(this,t,!0,n)+"px")})}}),e("").data("a-b","a").removeData("a-b").data("a-b")&&(e.fn.removeData=function(t){return function(n){return arguments.length?t.call(this,e.camelCase(n)):t.call(this)}}(e.fn.removeData)),function(){var t=/msie ([\w.]+)/.exec(navigator.userAgent.toLowerCase())||[];e.ui.ie=t.length?!0:!1,e.ui.ie6=parseFloat(t[1],10)===6}(),e.fn.extend({disableSelection:function(){return this.bind((e.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(e){e.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),e.extend(e.ui,{plugin:{add:function(t,n,r){var i,s=e.ui[t].prototype;for(i in r)s.plugins[i]=s.plugins[i]||[],s.plugins[i].push([n,r[i]])},call:function(e,t,n){var r,i=e.plugins[t];if(!i||!e.element[0].parentNode||e.element[0].parentNode.nodeType===11)return;for(r=0;r0?!0:(t[r]=1,i=t[r]>0,t[r]=0,i)},isOverAxis:function(e,t,n){return e>t&&e",options:{disabled:!1,create:null},_createWidget:function(t,r){r=e(r||this.defaultElement||this)[0],this.element=e(r),this.uuid=n++,this.eventNamespace="."+this.widgetName+this.uuid,this.options=e.widget.extend({},this.options,this._getCreateOptions(),t),this.bindings=e(),this.hoverable=e(),this.focusable=e(),r!==this&&(e.data(r,this.widgetName,this),e.data(r,this.widgetFullName,this),this._on(!0,this.element,{remove:function(e){e.target===r&&this.destroy()}}),this.document=e(r.style?r.ownerDocument:r.document||r),this.window=e(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:e.noop,_getCreateEventData:e.noop,_create:e.noop,_init:e.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(e.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled "+"ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:e.noop,widget:function(){return this.element},option:function(n,r){var i=n,s,o,u;if(arguments.length===0)return e.widget.extend({},this.options);if(typeof n=="string"){i={},s=n.split("."),n=s.shift();if(s.length){o=i[n]=e.widget.extend({},this.options[n]);for(u=0;u=9||!!t.button?this._mouseStarted?(this._mouseDrag(t),t.preventDefault()):(this._mouseDistanceMet(t)&&this._mouseDelayMet(t)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,t)!==!1,this._mouseStarted?this._mouseDrag(t):this._mouseUp(t)),!this._mouseStarted):this._mouseUp(t)},_mouseUp:function(t){return e(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,t.target===this._mouseDownEvent.target&&e.data(t.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(t)),!1},_mouseDistanceMet:function(e){return Math.max(Math.abs(this._mouseDownEvent.pageX-e.pageX),Math.abs(this._mouseDownEvent.pageY-e.pageY))>=this.options.distance},_mouseDelayMet:function(e){return this.mouseDelayMet},_mouseStart:function(e){},_mouseDrag:function(e){},_mouseStop:function(e){},_mouseCapture:function(e){return!0}})})(jQuery);(function(e,t){function h(e,t,n){return[parseInt(e[0],10)*(l.test(e[0])?t/100:1),parseInt(e[1],10)*(l.test(e[1])?n/100:1)]}function p(t,n){return parseInt(e.css(t,n),10)||0}e.ui=e.ui||{};var n,r=Math.max,i=Math.abs,s=Math.round,o=/left|center|right/,u=/top|center|bottom/,a=/[\+\-]\d+%?/,f=/^\w+/,l=/%$/,c=e.fn.position;e.position={scrollbarWidth:function(){if(n!==t)return n;var r,i,s=e("
      "),o=s.children()[0];return e("body").append(s),r=o.offsetWidth,s.css("overflow","scroll"),i=o.offsetWidth,r===i&&(i=s[0].clientWidth),s.remove(),n=r-i},getScrollInfo:function(t){var n=t.isWindow?"":t.element.css("overflow-x"),r=t.isWindow?"":t.element.css("overflow-y"),i=n==="scroll"||n==="auto"&&t.width0?"right":"center",vertical:u<0?"top":o>0?"bottom":"middle"};lr(i(o),i(u))?h.important="horizontal":h.important="vertical",t.using.call(this,e,h)}),a.offset(e.extend(C,{using:u}))})},e.ui.position={fit:{left:function(e,t){var n=t.within,i=n.isWindow?n.scrollLeft:n.offset.left,s=n.width,o=e.left-t.collisionPosition.marginLeft,u=i-o,a=o+t.collisionWidth-s-i,f;t.collisionWidth>s?u>0&&a<=0?(f=e.left+u+t.collisionWidth-s-i,e.left+=u-f):a>0&&u<=0?e.left=i:u>a?e.left=i+s-t.collisionWidth:e.left=i:u>0?e.left+=u:a>0?e.left-=a:e.left=r(e.left-o,e.left)},top:function(e,t){var n=t.within,i=n.isWindow?n.scrollTop:n.offset.top,s=t.within.height,o=e.top-t.collisionPosition.marginTop,u=i-o,a=o+t.collisionHeight-s-i,f;t.collisionHeight>s?u>0&&a<=0?(f=e.top+u+t.collisionHeight-s-i,e.top+=u-f):a>0&&u<=0?e.top=i:u>a?e.top=i+s-t.collisionHeight:e.top=i:u>0?e.top+=u:a>0?e.top-=a:e.top=r(e.top-o,e.top)}},flip:{left:function(e,t){var n=t.within,r=n.offset.left+n.scrollLeft,s=n.width,o=n.isWindow?n.scrollLeft:n.offset.left,u=e.left-t.collisionPosition.marginLeft,a=u-o,f=u+t.collisionWidth-s-o,l=t.my[0]==="left"?-t.elemWidth:t.my[0]==="right"?t.elemWidth:0,c=t.at[0]==="left"?t.targetWidth:t.at[0]==="right"?-t.targetWidth:0,h=-2*t.offset[0],p,d;if(a<0){p=e.left+l+c+h+t.collisionWidth-s-r;if(p<0||p0){d=e.left-t.collisionPosition.marginLeft+l+c+h-o;if(d>0||i(d)a&&(v<0||v0&&(d=e.top-t.collisionPosition.marginTop+c+h+p-o,e.top+c+h+p>f&&(d>0||i(d)10&&i<11,t.innerHTML="",n.removeChild(t)}(),e.uiBackCompat!==!1&&function(e){var n=e.fn.position;e.fn.position=function(r){if(!r||!r.offset)return n.call(this,r);var i=r.offset.split(" "),s=r.at.split(" ");return i.length===1&&(i[1]=i[0]),/^\d/.test(i[0])&&(i[0]="+"+i[0]),/^\d/.test(i[1])&&(i[1]="+"+i[1]),s.length===1&&(/left|center|right/.test(s[0])?s[1]="center":(s[1]=s[0],s[0]="center")),n.call(this,e.extend(r,{at:s[0]+i[0]+" "+s[1]+i[1],offset:t}))}}(jQuery)})(jQuery);(function(e,t){e.widget("ui.draggable",e.ui.mouse,{version:"1.9.2",widgetEventPrefix:"drag",options:{addClasses:!0,appendTo:"parent",axis:!1,connectToSortable:!1,containment:!1,cursor:"auto",cursorAt:!1,grid:!1,handle:!1,helper:"original",iframeFix:!1,opacity:!1,refreshPositions:!1,revert:!1,revertDuration:500,scope:"default",scroll:!0,scrollSensitivity:20,scrollSpeed:20,snap:!1,snapMode:"both",snapTolerance:20,stack:!1,zIndex:!1},_create:function(){this.options.helper=="original"&&!/^(?:r|a|f)/.test(this.element.css("position"))&&(this.element[0].style.position="relative"),this.options.addClasses&&this.element.addClass("ui-draggable"),this.options.disabled&&this.element.addClass("ui-draggable-disabled"),this._mouseInit()},_destroy:function(){this.element.removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled"),this._mouseDestroy()},_mouseCapture:function(t){var n=this.options;return this.helper||n.disabled||e(t.target).is(".ui-resizable-handle")?!1:(this.handle=this._getHandle(t),this.handle?(e(n.iframeFix===!0?"iframe":n.iframeFix).each(function(){e('
      ').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1e3}).css(e(this).offset()).appendTo("body")}),!0):!1)},_mouseStart:function(t){var n=this.options;return this.helper=this._createHelper(t),this.helper.addClass("ui-draggable-dragging"),this._cacheHelperProportions(),e.ui.ddmanager&&(e.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(),this.offset=this.positionAbs=this.element.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},e.extend(this.offset,{click:{left:t.pageX-this.offset.left,top:t.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.originalPosition=this.position=this._generatePosition(t),this.originalPageX=t.pageX,this.originalPageY=t.pageY,n.cursorAt&&this._adjustOffsetFromHelper(n.cursorAt),n.containment&&this._setContainment(),this._trigger("start",t)===!1?(this._clear(),!1):(this._cacheHelperProportions(),e.ui.ddmanager&&!n.dropBehaviour&&e.ui.ddmanager.prepareOffsets(this,t),this._mouseDrag(t,!0),e.ui.ddmanager&&e.ui.ddmanager.dragStart(this,t),!0)},_mouseDrag:function(t,n){this.position=this._generatePosition(t),this.positionAbs=this._convertPositionTo("absolute");if(!n){var r=this._uiHash();if(this._trigger("drag",t,r)===!1)return this._mouseUp({}),!1;this.position=r.position}if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";return e.ui.ddmanager&&e.ui.ddmanager.drag(this,t),!1},_mouseStop:function(t){var n=!1;e.ui.ddmanager&&!this.options.dropBehaviour&&(n=e.ui.ddmanager.drop(this,t)),this.dropped&&(n=this.dropped,this.dropped=!1);var r=this.element[0],i=!1;while(r&&(r=r.parentNode))r==document&&(i=!0);if(!i&&this.options.helper==="original")return!1;if(this.options.revert=="invalid"&&!n||this.options.revert=="valid"&&n||this.options.revert===!0||e.isFunction(this.options.revert)&&this.options.revert.call(this.element,n)){var s=this;e(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){s._trigger("stop",t)!==!1&&s._clear()})}else this._trigger("stop",t)!==!1&&this._clear();return!1},_mouseUp:function(t){return e("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)}),e.ui.ddmanager&&e.ui.ddmanager.dragStop(this,t),e.ui.mouse.prototype._mouseUp.call(this,t)},cancel:function(){return this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear(),this},_getHandle:function(t){var n=!this.options.handle||!e(this.options.handle,this.element).length?!0:!1;return e(this.options.handle,this.element).find("*").andSelf().each(function(){this==t.target&&(n=!0)}),n},_createHelper:function(t){var n=this.options,r=e.isFunction(n.helper)?e(n.helper.apply(this.element[0],[t])):n.helper=="clone"?this.element.clone().removeAttr("id"):this.element;return r.parents("body").length||r.appendTo(n.appendTo=="parent"?this.element[0].parentNode:n.appendTo),r[0]!=this.element[0]&&!/(fixed|absolute)/.test(r.css("position"))&&r.css("position","absolute"),r},_adjustOffsetFromHelper:function(t){typeof t=="string"&&(t=t.split(" ")),e.isArray(t)&&(t={left:+t[0],top:+t[1]||0}),"left"in t&&(this.offset.click.left=t.left+this.margins.left),"right"in t&&(this.offset.click.left=this.helperProportions.width-t.right+this.margins.left),"top"in t&&(this.offset.click.top=t.top+this.margins.top),"bottom"in t&&(this.offset.click.top=this.helperProportions.height-t.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var t=this.offsetParent.offset();this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&e.contains(this.scrollParent[0],this.offsetParent[0])&&(t.left+=this.scrollParent.scrollLeft(),t.top+=this.scrollParent.scrollTop());if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&e.ui.ie)t={top:0,left:0};return{top:t.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:t.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var e=this.element.position();return{top:e.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:e.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var t=this.options;t.containment=="parent"&&(t.containment=this.helper[0].parentNode);if(t.containment=="document"||t.containment=="window")this.containment=[t.containment=="document"?0:e(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,t.containment=="document"?0:e(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,(t.containment=="document"?0:e(window).scrollLeft())+e(t.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(t.containment=="document"?0:e(window).scrollTop())+(e(t.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(t.containment)&&t.containment.constructor!=Array){var n=e(t.containment),r=n[0];if(!r)return;var i=n.offset(),s=e(r).css("overflow")!="hidden";this.containment=[(parseInt(e(r).css("borderLeftWidth"),10)||0)+(parseInt(e(r).css("paddingLeft"),10)||0),(parseInt(e(r).css("borderTopWidth"),10)||0)+(parseInt(e(r).css("paddingTop"),10)||0),(s?Math.max(r.scrollWidth,r.offsetWidth):r.offsetWidth)-(parseInt(e(r).css("borderLeftWidth"),10)||0)-(parseInt(e(r).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(s?Math.max(r.scrollHeight,r.offsetHeight):r.offsetHeight)-(parseInt(e(r).css("borderTopWidth"),10)||0)-(parseInt(e(r).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom],this.relative_container=n}else t.containment.constructor==Array&&(this.containment=t.containment)},_convertPositionTo:function(t,n){n||(n=this.position);var r=t=="absolute"?1:-1,i=this.options,s=this.cssPosition!="absolute"||this.scrollParent[0]!=document&&!!e.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,o=/(html|body)/i.test(s[0].tagName);return{top:n.top+this.offset.relative.top*r+this.offset.parent.top*r-(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():o?0:s.scrollTop())*r,left:n.left+this.offset.relative.left*r+this.offset.parent.left*r-(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():o?0:s.scrollLeft())*r}},_generatePosition:function(t){var n=this.options,r=this.cssPosition!="absolute"||this.scrollParent[0]!=document&&!!e.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,i=/(html|body)/i.test(r[0].tagName),s=t.pageX,o=t.pageY;if(this.originalPosition){var u;if(this.containment){if(this.relative_container){var a=this.relative_container.offset();u=[this.containment[0]+a.left,this.containment[1]+a.top,this.containment[2]+a.left,this.containment[3]+a.top]}else u=this.containment;t.pageX-this.offset.click.leftu[2]&&(s=u[2]+this.offset.click.left),t.pageY-this.offset.click.top>u[3]&&(o=u[3]+this.offset.click.top)}if(n.grid){var f=n.grid[1]?this.originalPageY+Math.round((o-this.originalPageY)/n.grid[1])*n.grid[1]:this.originalPageY;o=u?f-this.offset.click.topu[3]?f-this.offset.click.topu[2]?l-this.offset.click.left=0;l--){var c=r.snapElements[l].left,h=c+r.snapElements[l].width,p=r.snapElements[l].top,d=p+r.snapElements[l].height;if(!(c-s=l&&o<=c||u>=l&&u<=c||oc)&&(i>=a&&i<=f||s>=a&&s<=f||if);default:return!1}},e.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(t,n){var r=e.ui.ddmanager.droppables[t.options.scope]||[],i=n?n.type:null,s=(t.currentItem||t.element).find(":data(droppable)").andSelf();e:for(var o=0;o
      ').css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("resizable",this.element.data("resizable")),this.elementIsWrapper=!0,this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")}),this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0}),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css({margin:this.originalElement.css("margin")}),this._proportionallyResize()),this.handles=n.handles||(e(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se");if(this.handles.constructor==String){this.handles=="all"&&(this.handles="n,e,s,w,se,sw,ne,nw");var r=this.handles.split(",");this.handles={};for(var i=0;i');u.css({zIndex:n.zIndex}),"se"==s&&u.addClass("ui-icon ui-icon-gripsmall-diagonal-se"),this.handles[s]=".ui-resizable-"+s,this.element.append(u)}}this._renderAxis=function(t){t=t||this.element;for(var n in this.handles){this.handles[n].constructor==String&&(this.handles[n]=e(this.handles[n],this.element).show());if(this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)){var r=e(this.handles[n],this.element),i=0;i=/sw|ne|nw|se|n|s/.test(n)?r.outerHeight():r.outerWidth();var s=["padding",/ne|nw|n/.test(n)?"Top":/se|sw|s/.test(n)?"Bottom":/^e$/.test(n)?"Right":"Left"].join("");t.css(s,i),this._proportionallyResize()}if(!e(this.handles[n]).length)continue}},this._renderAxis(this.element),this._handles=e(".ui-resizable-handle",this.element).disableSelection(),this._handles.mouseover(function(){if(!t.resizing){if(this.className)var e=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);t.axis=e&&e[1]?e[1]:"se"}}),n.autoHide&&(this._handles.hide(),e(this.element).addClass("ui-resizable-autohide").mouseenter(function(){if(n.disabled)return;e(this).removeClass("ui-resizable-autohide"),t._handles.show()}).mouseleave(function(){if(n.disabled)return;t.resizing||(e(this).addClass("ui-resizable-autohide"),t._handles.hide())})),this._mouseInit()},_destroy:function(){this._mouseDestroy();var t=function(t){e(t).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").removeData("ui-resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};if(this.elementIsWrapper){t(this.element);var n=this.element;this.originalElement.css({position:n.css("position"),width:n.outerWidth(),height:n.outerHeight(),top:n.css("top"),left:n.css("left")}).insertAfter(n),n.remove()}return this.originalElement.css("resize",this.originalResizeStyle),t(this.originalElement),this},_mouseCapture:function(t){var n=!1;for(var r in this.handles)e(this.handles[r])[0]==t.target&&(n=!0);return!this.options.disabled&&n},_mouseStart:function(t){var r=this.options,i=this.element.position(),s=this.element;this.resizing=!0,this.documentScroll={top:e(document).scrollTop(),left:e(document).scrollLeft()},(s.is(".ui-draggable")||/absolute/.test(s.css("position")))&&s.css({position:"absolute",top:i.top,left:i.left}),this._renderProxy();var o=n(this.helper.css("left")),u=n(this.helper.css("top"));r.containment&&(o+=e(r.containment).scrollLeft()||0,u+=e(r.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:o,top:u},this.size=this._helper?{width:s.outerWidth(),height:s.outerHeight()}:{width:s.width(),height:s.height()},this.originalSize=this._helper?{width:s.outerWidth(),height:s.outerHeight()}:{width:s.width(),height:s.height()},this.originalPosition={left:o,top:u},this.sizeDiff={width:s.outerWidth()-s.width(),height:s.outerHeight()-s.height()},this.originalMousePosition={left:t.pageX,top:t.pageY},this.aspectRatio=typeof r.aspectRatio=="number"?r.aspectRatio:this.originalSize.width/this.originalSize.height||1;var a=e(".ui-resizable-"+this.axis).css("cursor");return e("body").css("cursor",a=="auto"?this.axis+"-resize":a),s.addClass("ui-resizable-resizing"),this._propagate("start",t),!0},_mouseDrag:function(e){var t=this.helper,n=this.options,r={},i=this,s=this.originalMousePosition,o=this.axis,u=e.pageX-s.left||0,a=e.pageY-s.top||0,f=this._change[o];if(!f)return!1;var l=f.apply(this,[e,u,a]);this._updateVirtualBoundaries(e.shiftKey);if(this._aspectRatio||e.shiftKey)l=this._updateRatio(l,e);return l=this._respectSize(l,e),this._propagate("resize",e),t.css({top:this.position.top+"px",left:this.position.left+"px",width:this.size.width+"px",height:this.size.height+"px"}),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),this._updateCache(l),this._trigger("resize",e,this.ui()),!1},_mouseStop:function(t){this.resizing=!1;var n=this.options,r=this;if(this._helper){var i=this._proportionallyResizeElements,s=i.length&&/textarea/i.test(i[0].nodeName),o=s&&e.ui.hasScroll(i[0],"left")?0:r.sizeDiff.height,u=s?0:r.sizeDiff.width,a={width:r.helper.width()-u,height:r.helper.height()-o},f=parseInt(r.element.css("left"),10)+(r.position.left-r.originalPosition.left)||null,l=parseInt(r.element.css("top"),10)+(r.position.top-r.originalPosition.top)||null;n.animate||this.element.css(e.extend(a,{top:l,left:f})),r.helper.height(r.size.height),r.helper.width(r.size.width),this._helper&&!n.animate&&this._proportionallyResize()}return e("body").css("cursor","auto"),this.element.removeClass("ui-resizable-resizing"),this._propagate("stop",t),this._helper&&this.helper.remove(),!1},_updateVirtualBoundaries:function(e){var t=this.options,n,i,s,o,u;u={minWidth:r(t.minWidth)?t.minWidth:0,maxWidth:r(t.maxWidth)?t.maxWidth:Infinity,minHeight:r(t.minHeight)?t.minHeight:0,maxHeight:r(t.maxHeight)?t.maxHeight:Infinity};if(this._aspectRatio||e)n=u.minHeight*this.aspectRatio,s=u.minWidth/this.aspectRatio,i=u.maxHeight*this.aspectRatio,o=u.maxWidth/this.aspectRatio,n>u.minWidth&&(u.minWidth=n),s>u.minHeight&&(u.minHeight=s),ie.width,l=r(e.height)&&i.minHeight&&i.minHeight>e.height;f&&(e.width=i.minWidth),l&&(e.height=i.minHeight),u&&(e.width=i.maxWidth),a&&(e.height=i.maxHeight);var c=this.originalPosition.left+this.originalSize.width,h=this.position.top+this.size.height,p=/sw|nw|w/.test(o),d=/nw|ne|n/.test(o);f&&p&&(e.left=c-i.minWidth),u&&p&&(e.left=c-i.maxWidth),l&&d&&(e.top=h-i.minHeight),a&&d&&(e.top=h-i.maxHeight);var v=!e.width&&!e.height;return v&&!e.left&&e.top?e.top=null:v&&!e.top&&e.left&&(e.left=null),e},_proportionallyResize:function(){var t=this.options;if(!this._proportionallyResizeElements.length)return;var n=this.helper||this.element;for(var r=0;r');var r=e.ui.ie6?1:0,i=e.ui.ie6?2:-1;this.helper.addClass(this._helper).css({width:this.element.outerWidth()+i,height:this.element.outerHeight()+i,position:"absolute",left:this.elementOffset.left-r+"px",top:this.elementOffset.top-r+"px",zIndex:++n.zIndex}),this.helper.appendTo("body").disableSelection()}else this.helper=this.element},_change:{e:function(e,t,n){return{width:this.originalSize.width+t}},w:function(e,t,n){var r=this.options,i=this.originalSize,s=this.originalPosition;return{left:s.left+t,width:i.width-t}},n:function(e,t,n){var r=this.options,i=this.originalSize,s=this.originalPosition;return{top:s.top+n,height:i.height-n}},s:function(e,t,n){return{height:this.originalSize.height+n}},se:function(t,n,r){return e.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[t,n,r]))},sw:function(t,n,r){return e.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[t,n,r]))},ne:function(t,n,r){return e.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[t,n,r]))},nw:function(t,n,r){return e.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[t,n,r]))}},_propagate:function(t,n){e.ui.plugin.call(this,t,[n,this.ui()]),t!="resize"&&this._trigger(t,n,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),e.ui.plugin.add("resizable","alsoResize",{start:function(t,n){var r=e(this).data("resizable"),i=r.options,s=function(t){e(t).each(function(){var t=e(this);t.data("resizable-alsoresize",{width:parseInt(t.width(),10),height:parseInt(t.height(),10),left:parseInt(t.css("left"),10),top:parseInt(t.css("top"),10)})})};typeof i.alsoResize=="object"&&!i.alsoResize.parentNode?i.alsoResize.length?(i.alsoResize=i.alsoResize[0],s(i.alsoResize)):e.each(i.alsoResize,function(e){s(e)}):s(i.alsoResize)},resize:function(t,n){var r=e(this).data("resizable"),i=r.options,s=r.originalSize,o=r.originalPosition,u={height:r.size.height-s.height||0,width:r.size.width-s.width||0,top:r.position.top-o.top||0,left:r.position.left-o.left||0},a=function(t,r){e(t).each(function(){var t=e(this),i=e(this).data("resizable-alsoresize"),s={},o=r&&r.length?r:t.parents(n.originalElement[0]).length?["width","height"]:["width","height","top","left"];e.each(o,function(e,t){var n=(i[t]||0)+(u[t]||0);n&&n>=0&&(s[t]=n||null)}),t.css(s)})};typeof i.alsoResize=="object"&&!i.alsoResize.nodeType?e.each(i.alsoResize,function(e,t){a(e,t)}):a(i.alsoResize)},stop:function(t,n){e(this).removeData("resizable-alsoresize")}}),e.ui.plugin.add("resizable","animate",{stop:function(t,n){var r=e(this).data("resizable"),i=r.options,s=r._proportionallyResizeElements,o=s.length&&/textarea/i.test(s[0].nodeName),u=o&&e.ui.hasScroll(s[0],"left")?0:r.sizeDiff.height,a=o?0:r.sizeDiff.width,f={width:r.size.width-a,height:r.size.height-u},l=parseInt(r.element.css("left"),10)+(r.position.left-r.originalPosition.left)||null,c=parseInt(r.element.css("top"),10)+(r.position.top-r.originalPosition.top)||null;r.element.animate(e.extend(f,c&&l?{top:c,left:l}:{}),{duration:i.animateDuration,easing:i.animateEasing,step:function(){var n={width:parseInt(r.element.css("width"),10),height:parseInt(r.element.css("height"),10),top:parseInt(r.element.css("top"),10),left:parseInt(r.element.css("left"),10)};s&&s.length&&e(s[0]).css({width:n.width,height:n.height}),r._updateCache(n),r._propagate("resize",t)}})}}),e.ui.plugin.add("resizable","containment",{start:function(t,r){var i=e(this).data("resizable"),s=i.options,o=i.element,u=s.containment,a=u instanceof e?u.get(0):/parent/.test(u)?o.parent().get(0):u;if(!a)return;i.containerElement=e(a);if(/document/.test(u)||u==document)i.containerOffset={left:0,top:0},i.containerPosition={left:0,top:0},i.parentData={element:e(document),left:0,top:0,width:e(document).width(),height:e(document).height()||document.body.parentNode.scrollHeight};else{var f=e(a),l=[];e(["Top","Right","Left","Bottom"]).each(function(e,t){l[e]=n(f.css("padding"+t))}),i.containerOffset=f.offset(),i.containerPosition=f.position(),i.containerSize={height:f.innerHeight()-l[3],width:f.innerWidth()-l[1]};var c=i.containerOffset,h=i.containerSize.height,p=i.containerSize.width,d=e.ui.hasScroll(a,"left")?a.scrollWidth:p,v=e.ui.hasScroll(a)?a.scrollHeight:h;i.parentData={element:a,left:c.left,top:c.top,width:d,height:v}}},resize:function(t,n){var r=e(this).data("resizable"),i=r.options,s=r.containerSize,o=r.containerOffset,u=r.size,a=r.position,f=r._aspectRatio||t.shiftKey,l={top:0,left:0},c=r.containerElement;c[0]!=document&&/static/.test(c.css("position"))&&(l=o),a.left<(r._helper?o.left:0)&&(r.size.width=r.size.width+(r._helper?r.position.left-o.left:r.position.left-l.left),f&&(r.size.height=r.size.width/r.aspectRatio),r.position.left=i.helper?o.left:0),a.top<(r._helper?o.top:0)&&(r.size.height=r.size.height+(r._helper?r.position.top-o.top:r.position.top),f&&(r.size.width=r.size.height*r.aspectRatio),r.position.top=r._helper?o.top:0),r.offset.left=r.parentData.left+r.position.left,r.offset.top=r.parentData.top+r.position.top;var h=Math.abs((r._helper?r.offset.left-l.left:r.offset.left-l.left)+r.sizeDiff.width),p=Math.abs((r._helper?r.offset.top-l.top:r.offset.top-o.top)+r.sizeDiff.height),d=r.containerElement.get(0)==r.element.parent().get(0),v=/relative|absolute/.test(r.containerElement.css("position"));d&&v&&(h-=r.parentData.left),h+r.size.width>=r.parentData.width&&(r.size.width=r.parentData.width-h,f&&(r.size.height=r.size.width/r.aspectRatio)),p+r.size.height>=r.parentData.height&&(r.size.height=r.parentData.height-p,f&&(r.size.width=r.size.height*r.aspectRatio))},stop:function(t,n){var r=e(this).data("resizable"),i=r.options,s=r.position,o=r.containerOffset,u=r.containerPosition,a=r.containerElement,f=e(r.helper),l=f.offset(),c=f.outerWidth()-r.sizeDiff.width,h=f.outerHeight()-r.sizeDiff.height;r._helper&&!i.animate&&/relative/.test(a.css("position"))&&e(this).css({left:l.left-u.left-o.left,width:c,height:h}),r._helper&&!i.animate&&/static/.test(a.css("position"))&&e(this).css({left:l.left-u.left-o.left,width:c,height:h})}}),e.ui.plugin.add("resizable","ghost",{start:function(t,n){var r=e(this).data("resizable"),i=r.options,s=r.size;r.ghost=r.originalElement.clone(),r.ghost.css({opacity:.25,display:"block",position:"relative",height:s.height,width:s.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass(typeof i.ghost=="string"?i.ghost:""),r.ghost.appendTo(r.helper)},resize:function(t,n){var r=e(this).data("resizable"),i=r.options;r.ghost&&r.ghost.css({position:"relative",height:r.size.height,width:r.size.width})},stop:function(t,n){var r=e(this).data("resizable"),i=r.options;r.ghost&&r.helper&&r.helper.get(0).removeChild(r.ghost.get(0))}}),e.ui.plugin.add("resizable","grid",{resize:function(t,n){var r=e(this).data("resizable"),i=r.options,s=r.size,o=r.originalSize,u=r.originalPosition,a=r.axis,f=i._aspectRatio||t.shiftKey;i.grid=typeof i.grid=="number"?[i.grid,i.grid]:i.grid;var l=Math.round((s.width-o.width)/(i.grid[0]||1))*(i.grid[0]||1),c=Math.round((s.height-o.height)/(i.grid[1]||1))*(i.grid[1]||1);/^(se|s|e)$/.test(a)?(r.size.width=o.width+l,r.size.height=o.height+c):/^(ne)$/.test(a)?(r.size.width=o.width+l,r.size.height=o.height+c,r.position.top=u.top-c):/^(sw)$/.test(a)?(r.size.width=o.width+l,r.size.height=o.height+c,r.position.left=u.left-l):(r.size.width=o.width+l,r.size.height=o.height+c,r.position.top=u.top-c,r.position.left=u.left-l)}});var n=function(e){return parseInt(e,10)||0},r=function(e){return!isNaN(parseInt(e,10))}})(jQuery);(function(e,t){e.widget("ui.selectable",e.ui.mouse,{version:"1.9.2",options:{appendTo:"body",autoRefresh:!0,distance:0,filter:"*",tolerance:"touch"},_create:function(){var t=this;this.element.addClass("ui-selectable"),this.dragged=!1;var n;this.refresh=function(){n=e(t.options.filter,t.element[0]),n.addClass("ui-selectee"),n.each(function(){var t=e(this),n=t.offset();e.data(this,"selectable-item",{element:this,$element:t,left:n.left,top:n.top,right:n.left+t.outerWidth(),bottom:n.top+t.outerHeight(),startselected:!1,selected:t.hasClass("ui-selected"),selecting:t.hasClass("ui-selecting"),unselecting:t.hasClass("ui-unselecting")})})},this.refresh(),this.selectees=n.addClass("ui-selectee"),this._mouseInit(),this.helper=e("
      ")},_destroy:function(){this.selectees.removeClass("ui-selectee").removeData("selectable-item"),this.element.removeClass("ui-selectable ui-selectable-disabled"),this._mouseDestroy()},_mouseStart:function(t){var n=this;this.opos=[t.pageX,t.pageY];if(this.options.disabled)return;var r=this.options;this.selectees=e(r.filter,this.element[0]),this._trigger("start",t),e(r.appendTo).append(this.helper),this.helper.css({left:t.clientX,top:t.clientY,width:0,height:0}),r.autoRefresh&&this.refresh(),this.selectees.filter(".ui-selected").each(function(){var r=e.data(this,"selectable-item");r.startselected=!0,!t.metaKey&&!t.ctrlKey&&(r.$element.removeClass("ui-selected"),r.selected=!1,r.$element.addClass("ui-unselecting"),r.unselecting=!0,n._trigger("unselecting",t,{unselecting:r.element}))}),e(t.target).parents().andSelf().each(function(){var r=e.data(this,"selectable-item");if(r){var i=!t.metaKey&&!t.ctrlKey||!r.$element.hasClass("ui-selected");return r.$element.removeClass(i?"ui-unselecting":"ui-selected").addClass(i?"ui-selecting":"ui-unselecting"),r.unselecting=!i,r.selecting=i,r.selected=i,i?n._trigger("selecting",t,{selecting:r.element}):n._trigger("unselecting",t,{unselecting:r.element}),!1}})},_mouseDrag:function(t){var n=this;this.dragged=!0;if(this.options.disabled)return;var r=this.options,i=this.opos[0],s=this.opos[1],o=t.pageX,u=t.pageY;if(i>o){var a=o;o=i,i=a}if(s>u){var a=u;u=s,s=a}return this.helper.css({left:i,top:s,width:o-i,height:u-s}),this.selectees.each(function(){var a=e.data(this,"selectable-item");if(!a||a.element==n.element[0])return;var f=!1;r.tolerance=="touch"?f=!(a.left>o||a.rightu||a.bottomi&&a.rights&&a.bottom *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1e3},_create:function(){var e=this.options;this.containerCache={},this.element.addClass("ui-sortable"),this.refresh(),this.floating=this.items.length?e.axis==="x"||/left|right/.test(this.items[0].item.css("float"))||/inline|table-cell/.test(this.items[0].item.css("display")):!1,this.offset=this.element.offset(),this._mouseInit(),this.ready=!0},_destroy:function(){this.element.removeClass("ui-sortable ui-sortable-disabled"),this._mouseDestroy();for(var e=this.items.length-1;e>=0;e--)this.items[e].item.removeData(this.widgetName+"-item");return this},_setOption:function(t,n){t==="disabled"?(this.options[t]=n,this.widget().toggleClass("ui-sortable-disabled",!!n)):e.Widget.prototype._setOption.apply(this,arguments)},_mouseCapture:function(t,n){var r=this;if(this.reverting)return!1;if(this.options.disabled||this.options.type=="static")return!1;this._refreshItems(t);var i=null,s=e(t.target).parents().each(function(){if(e.data(this,r.widgetName+"-item")==r)return i=e(this),!1});e.data(t.target,r.widgetName+"-item")==r&&(i=e(t.target));if(!i)return!1;if(this.options.handle&&!n){var o=!1;e(this.options.handle,i).find("*").andSelf().each(function(){this==t.target&&(o=!0)});if(!o)return!1}return this.currentItem=i,this._removeCurrentsFromItems(),!0},_mouseStart:function(t,n,r){var i=this.options;this.currentContainer=this,this.refreshPositions(),this.helper=this._createHelper(t),this._cacheHelperProportions(),this._cacheMargins(),this.scrollParent=this.helper.scrollParent(),this.offset=this.currentItem.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},e.extend(this.offset,{click:{left:t.pageX-this.offset.left,top:t.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.helper.css("position","absolute"),this.cssPosition=this.helper.css("position"),this.originalPosition=this._generatePosition(t),this.originalPageX=t.pageX,this.originalPageY=t.pageY,i.cursorAt&&this._adjustOffsetFromHelper(i.cursorAt),this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]},this.helper[0]!=this.currentItem[0]&&this.currentItem.hide(),this._createPlaceholder(),i.containment&&this._setContainment(),i.cursor&&(e("body").css("cursor")&&(this._storedCursor=e("body").css("cursor")),e("body").css("cursor",i.cursor)),i.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",i.opacity)),i.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",i.zIndex)),this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"&&(this.overflowOffset=this.scrollParent.offset()),this._trigger("start",t,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions();if(!r)for(var s=this.containers.length-1;s>=0;s--)this.containers[s]._trigger("activate",t,this._uiHash(this));return e.ui.ddmanager&&(e.ui.ddmanager.current=this),e.ui.ddmanager&&!i.dropBehaviour&&e.ui.ddmanager.prepareOffsets(this,t),this.dragging=!0,this.helper.addClass("ui-sortable-helper"),this._mouseDrag(t),!0},_mouseDrag:function(t){this.position=this._generatePosition(t),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs);if(this.options.scroll){var n=this.options,r=!1;this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-t.pageY=0;i--){var s=this.items[i],o=s.item[0],u=this._intersectsWithPointer(s);if(!u)continue;if(s.instance!==this.currentContainer)continue;if(o!=this.currentItem[0]&&this.placeholder[u==1?"next":"prev"]()[0]!=o&&!e.contains(this.placeholder[0],o)&&(this.options.type=="semi-dynamic"?!e.contains(this.element[0],o):!0)){this.direction=u==1?"down":"up";if(this.options.tolerance!="pointer"&&!this._intersectsWithSides(s))break;this._rearrange(t,s),this._trigger("change",t,this._uiHash());break}}return this._contactContainers(t),e.ui.ddmanager&&e.ui.ddmanager.drag(this,t),this._trigger("sort",t,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(t,n){if(!t)return;e.ui.ddmanager&&!this.options.dropBehaviour&&e.ui.ddmanager.drop(this,t);if(this.options.revert){var r=this,i=this.placeholder.offset();this.reverting=!0,e(this.helper).animate({left:i.left-this.offset.parent.left-this.margins.left+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollLeft),top:i.top-this.offset.parent.top-this.margins.top+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollTop)},parseInt(this.options.revert,10)||500,function(){r._clear(t)})}else this._clear(t,n);return!1},cancel:function(){if(this.dragging){this._mouseUp({target:null}),this.options.helper=="original"?this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"):this.currentItem.show();for(var t=this.containers.length-1;t>=0;t--)this.containers[t]._trigger("deactivate",null,this._uiHash(this)),this.containers[t].containerCache.over&&(this.containers[t]._trigger("out",null,this._uiHash(this)),this.containers[t].containerCache.over=0)}return this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.options.helper!="original"&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),e.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?e(this.domPosition.prev).after(this.currentItem):e(this.domPosition.parent).prepend(this.currentItem)),this},serialize:function(t){var n=this._getItemsAsjQuery(t&&t.connected),r=[];return t=t||{},e(n).each(function(){var n=(e(t.item||this).attr(t.attribute||"id")||"").match(t.expression||/(.+)[-=_](.+)/);n&&r.push((t.key||n[1]+"[]")+"="+(t.key&&t.expression?n[1]:n[2]))}),!r.length&&t.key&&r.push(t.key+"="),r.join("&")},toArray:function(t){var n=this._getItemsAsjQuery(t&&t.connected),r=[];return t=t||{},n.each(function(){r.push(e(t.item||this).attr(t.attribute||"id")||"")}),r},_intersectsWith:function(e){var t=this.positionAbs.left,n=t+this.helperProportions.width,r=this.positionAbs.top,i=r+this.helperProportions.height,s=e.left,o=s+e.width,u=e.top,a=u+e.height,f=this.offset.click.top,l=this.offset.click.left,c=r+f>u&&r+fs&&t+le[this.floating?"width":"height"]?c:s0?"down":"up")},_getDragHorizontalDirection:function(){var e=this.positionAbs.left-this.lastPositionAbs.left;return e!=0&&(e>0?"right":"left")},refresh:function(e){return this._refreshItems(e),this.refreshPositions(),this},_connectWith:function(){var e=this.options;return e.connectWith.constructor==String?[e.connectWith]:e.connectWith},_getItemsAsjQuery:function(t){var n=[],r=[],i=this._connectWith();if(i&&t)for(var s=i.length-1;s>=0;s--){var o=e(i[s]);for(var u=o.length-1;u>=0;u--){var a=e.data(o[u],this.widgetName);a&&a!=this&&!a.options.disabled&&r.push([e.isFunction(a.options.items)?a.options.items.call(a.element):e(a.options.items,a.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),a])}}r.push([e.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):e(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]);for(var s=r.length-1;s>=0;s--)r[s][0].each(function(){n.push(this)});return e(n)},_removeCurrentsFromItems:function(){var t=this.currentItem.find(":data("+this.widgetName+"-item)");this.items=e.grep(this.items,function(e){for(var n=0;n=0;s--){var o=e(i[s]);for(var u=o.length-1;u>=0;u--){var a=e.data(o[u],this.widgetName);a&&a!=this&&!a.options.disabled&&(r.push([e.isFunction(a.options.items)?a.options.items.call(a.element[0],t,{item:this.currentItem}):e(a.options.items,a.element),a]),this.containers.push(a))}}for(var s=r.length-1;s>=0;s--){var f=r[s][1],l=r[s][0];for(var u=0,c=l.length;u=0;n--){var r=this.items[n];if(r.instance!=this.currentContainer&&this.currentContainer&&r.item[0]!=this.currentItem[0])continue;var i=this.options.toleranceElement?e(this.options.toleranceElement,r.item):r.item;t||(r.width=i.outerWidth(),r.height=i.outerHeight());var s=i.offset();r.left=s.left,r.top=s.top}if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(var n=this.containers.length-1;n>=0;n--){var s=this.containers[n].element.offset();this.containers[n].containerCache.left=s.left,this.containers[n].containerCache.top=s.top,this.containers[n].containerCache.width=this.containers[n].element.outerWidth(),this.containers[n].containerCache.height=this.containers[n].element.outerHeight()}return this},_createPlaceholder:function(t){t=t||this;var n=t.options;if(!n.placeholder||n.placeholder.constructor==String){var r=n.placeholder;n.placeholder={element:function(){var n=e(document.createElement(t.currentItem[0].nodeName)).addClass(r||t.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper")[0];return r||(n.style.visibility="hidden"),n},update:function(e,i){if(r&&!n.forcePlaceholderSize)return;i.height()||i.height(t.currentItem.innerHeight()-parseInt(t.currentItem.css("paddingTop")||0,10)-parseInt(t.currentItem.css("paddingBottom")||0,10)),i.width()||i.width(t.currentItem.innerWidth()-parseInt(t.currentItem.css("paddingLeft")||0,10)-parseInt(t.currentItem.css("paddingRight")||0,10))}}}t.placeholder=e(n.placeholder.element.call(t.element,t.currentItem)),t.currentItem.after(t.placeholder),n.placeholder.update(t,t.placeholder)},_contactContainers:function(t){var n=null,r=null;for(var i=this.containers.length-1;i>=0;i--){if(e.contains(this.currentItem[0],this.containers[i].element[0]))continue;if(this._intersectsWith(this.containers[i].containerCache)){if(n&&e.contains(this.containers[i].element[0],n.element[0]))continue;n=this.containers[i],r=i}else this.containers[i].containerCache.over&&(this.containers[i]._trigger("out",t,this._uiHash(this)),this.containers[i].containerCache.over=0)}if(!n)return;if(this.containers.length===1)this.containers[r]._trigger("over",t,this._uiHash(this)),this.containers[r].containerCache.over=1;else{var s=1e4,o=null,u=this.containers[r].floating?"left":"top",a=this.containers[r].floating?"width":"height",f=this.positionAbs[u]+this.offset.click[u];for(var l=this.items.length-1;l>=0;l--){if(!e.contains(this.containers[r].element[0],this.items[l].item[0]))continue;if(this.items[l].item[0]==this.currentItem[0])continue;var c=this.items[l].item.offset()[u],h=!1;Math.abs(c-f)>Math.abs(c+this.items[l][a]-f)&&(h=!0,c+=this.items[l][a]),Math.abs(c-f)this.containment[2]&&(s=this.containment[2]+this.offset.click.left),t.pageY-this.offset.click.top>this.containment[3]&&(o=this.containment[3]+this.offset.click.top));if(n.grid){var u=this.originalPageY+Math.round((o-this.originalPageY)/n.grid[1])*n.grid[1];o=this.containment?u-this.offset.click.topthis.containment[3]?u-this.offset.click.topthis.containment[2]?a-this.offset.click.left=0;i--)n||r.push(function(e){return function(t){e._trigger("deactivate",t,this._uiHash(this))}}.call(this,this.containers[i])),this.containers[i].containerCache.over&&(r.push(function(e){return function(t){e._trigger("out",t,this._uiHash(this))}}.call(this,this.containers[i])),this.containers[i].containerCache.over=0);this._storedCursor&&e("body").css("cursor",this._storedCursor),this._storedOpacity&&this.helper.css("opacity",this._storedOpacity),this._storedZIndex&&this.helper.css("zIndex",this._storedZIndex=="auto"?"":this._storedZIndex),this.dragging=!1;if(this.cancelHelperRemoval){if(!n){this._trigger("beforeStop",t,this._uiHash());for(var i=0;i li > :first-child,> :not(li):even",heightStyle:"auto",icons:{activeHeader:"ui-icon-triangle-1-s",header:"ui-icon-triangle-1-e"},activate:null,beforeActivate:null},_create:function(){var t=this.accordionId="ui-accordion-"+(this.element.attr("id")||++n),r=this.options;this.prevShow=this.prevHide=e(),this.element.addClass("ui-accordion ui-widget ui-helper-reset"),this.headers=this.element.find(r.header).addClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all"),this._hoverable(this.headers),this._focusable(this.headers),this.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom").hide(),!r.collapsible&&(r.active===!1||r.active==null)&&(r.active=0),r.active<0&&(r.active+=this.headers.length),this.active=this._findActive(r.active).addClass("ui-accordion-header-active ui-state-active").toggleClass("ui-corner-all ui-corner-top"),this.active.next().addClass("ui-accordion-content-active").show(),this._createIcons(),this.refresh(),this.element.attr("role","tablist"),this.headers.attr("role","tab").each(function(n){var r=e(this),i=r.attr("id"),s=r.next(),o=s.attr("id");i||(i=t+"-header-"+n,r.attr("id",i)),o||(o=t+"-panel-"+n,s.attr("id",o)),r.attr("aria-controls",o),s.attr("aria-labelledby",i)}).next().attr("role","tabpanel"),this.headers.not(this.active).attr({"aria-selected":"false",tabIndex:-1}).next().attr({"aria-expanded":"false","aria-hidden":"true"}).hide(),this.active.length?this.active.attr({"aria-selected":"true",tabIndex:0}).next().attr({"aria-expanded":"true","aria-hidden":"false"}):this.headers.eq(0).attr("tabIndex",0),this._on(this.headers,{keydown:"_keydown"}),this._on(this.headers.next(),{keydown:"_panelKeyDown"}),this._setupEvents(r.event)},_getCreateEventData:function(){return{header:this.active,content:this.active.length?this.active.next():e()}},_createIcons:function(){var t=this.options.icons;t&&(e("").addClass("ui-accordion-header-icon ui-icon "+t.header).prependTo(this.headers),this.active.children(".ui-accordion-header-icon").removeClass(t.header).addClass(t.activeHeader),this.headers.addClass("ui-accordion-icons"))},_destroyIcons:function(){this.headers.removeClass("ui-accordion-icons").children(".ui-accordion-header-icon").remove()},_destroy:function(){var e;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role"),this.headers.removeClass("ui-accordion-header ui-accordion-header-active ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top").removeAttr("role").removeAttr("aria-selected").removeAttr("aria-controls").removeAttr("tabIndex").each(function(){/^ui-accordion/.test(this.id)&&this.removeAttribute("id")}),this._destroyIcons(),e=this.headers.next().css("display","").removeAttr("role").removeAttr("aria-expanded").removeAttr("aria-hidden").removeAttr("aria-labelledby").removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-state-disabled").each(function(){/^ui-accordion/.test(this.id)&&this.removeAttribute("id")}),this.options.heightStyle!=="content"&&e.css("height","")},_setOption:function(e,t){if(e==="active"){this._activate(t);return}e==="event"&&(this.options.event&&this._off(this.headers,this.options.event),this._setupEvents(t)),this._super(e,t),e==="collapsible"&&!t&&this.options.active===!1&&this._activate(0),e==="icons"&&(this._destroyIcons(),t&&this._createIcons()),e==="disabled"&&this.headers.add(this.headers.next()).toggleClass("ui-state-disabled",!!t)},_keydown:function(t){if(t.altKey||t.ctrlKey)return;var n=e.ui.keyCode,r=this.headers.length,i=this.headers.index(t.target),s=!1;switch(t.keyCode){case n.RIGHT:case n.DOWN:s=this.headers[(i+1)%r];break;case n.LEFT:case n.UP:s=this.headers[(i-1+r)%r];break;case n.SPACE:case n.ENTER:this._eventHandler(t);break;case n.HOME:s=this.headers[0];break;case n.END:s=this.headers[r-1]}s&&(e(t.target).attr("tabIndex",-1),e(s).attr("tabIndex",0),s.focus(),t.preventDefault())},_panelKeyDown:function(t){t.keyCode===e.ui.keyCode.UP&&t.ctrlKey&&e(t.currentTarget).prev().focus()},refresh:function(){var t,n,r=this.options.heightStyle,i=this.element.parent();r==="fill"?(e.support.minHeight||(n=i.css("overflow"),i.css("overflow","hidden")),t=i.height(),this.element.siblings(":visible").each(function(){var n=e(this),r=n.css("position");if(r==="absolute"||r==="fixed")return;t-=n.outerHeight(!0)}),n&&i.css("overflow",n),this.headers.each(function(){t-=e(this).outerHeight(!0)}),this.headers.next().each(function(){e(this).height(Math.max(0,t-e(this).innerHeight()+e(this).height()))}).css("overflow","auto")):r==="auto"&&(t=0,this.headers.next().each(function(){t=Math.max(t,e(this).css("height","").height())}).height(t))},_activate:function(t){var n=this._findActive(t)[0];if(n===this.active[0])return;n=n||this.active[0],this._eventHandler({target:n,currentTarget:n,preventDefault:e.noop})},_findActive:function(t){return typeof t=="number"?this.headers.eq(t):e()},_setupEvents:function(t){var n={};if(!t)return;e.each(t.split(" "),function(e,t){n[t]="_eventHandler"}),this._on(this.headers,n)},_eventHandler:function(t){var n=this.options,r=this.active,i=e(t.currentTarget),s=i[0]===r[0],o=s&&n.collapsible,u=o?e():i.next(),a=r.next(),f={oldHeader:r,oldPanel:a,newHeader:o?e():i,newPanel:u};t.preventDefault();if(s&&!n.collapsible||this._trigger("beforeActivate",t,f)===!1)return;n.active=o?!1:this.headers.index(i),this.active=s?e():i,this._toggle(f),r.removeClass("ui-accordion-header-active ui-state-active"),n.icons&&r.children(".ui-accordion-header-icon").removeClass(n.icons.activeHeader).addClass(n.icons.header),s||(i.removeClass("ui-corner-all").addClass("ui-accordion-header-active ui-state-active ui-corner-top"),n.icons&&i.children(".ui-accordion-header-icon").removeClass(n.icons.header).addClass(n.icons.activeHeader),i.next().addClass("ui-accordion-content-active"))},_toggle:function(t){var n=t.newPanel,r=this.prevShow.length?this.prevShow:t.oldPanel;this.prevShow.add(this.prevHide).stop(!0,!0),this.prevShow=n,this.prevHide=r,this.options.animate?this._animate(n,r,t):(r.hide(),n.show(),this._toggleComplete(t)),r.attr({"aria-expanded":"false","aria-hidden":"true"}),r.prev().attr("aria-selected","false"),n.length&&r.length?r.prev().attr("tabIndex",-1):n.length&&this.headers.filter(function(){return e(this).attr("tabIndex")===0}).attr("tabIndex",-1),n.attr({"aria-expanded":"true","aria-hidden":"false"}).prev().attr({"aria-selected":"true",tabIndex:0})},_animate:function(e,t,n){var s,o,u,a=this,f=0,l=e.length&&(!t.length||e.index()",options:{appendTo:"body",autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},pending:0,_create:function(){var t,n,r;this.isMultiLine=this._isMultiLine(),this.valueMethod=this.element[this.element.is("input,textarea")?"val":"text"],this.isNewMenu=!0,this.element.addClass("ui-autocomplete-input").attr("autocomplete","off"),this._on(this.element,{keydown:function(i){if(this.element.prop("readOnly")){t=!0,r=!0,n=!0;return}t=!1,r=!1,n=!1;var s=e.ui.keyCode;switch(i.keyCode){case s.PAGE_UP:t=!0,this._move("previousPage",i);break;case s.PAGE_DOWN:t=!0,this._move("nextPage",i);break;case s.UP:t=!0,this._keyEvent("previous",i);break;case s.DOWN:t=!0,this._keyEvent("next",i);break;case s.ENTER:case s.NUMPAD_ENTER:this.menu.active&&(t=!0,i.preventDefault(),this.menu.select(i));break;case s.TAB:this.menu.active&&this.menu.select(i);break;case s.ESCAPE:this.menu.element.is(":visible")&&(this._value(this.term),this.close(i),i.preventDefault());break;default:n=!0,this._searchTimeout(i)}},keypress:function(r){if(t){t=!1,r.preventDefault();return}if(n)return;var i=e.ui.keyCode;switch(r.keyCode){case i.PAGE_UP:this._move("previousPage",r);break;case i.PAGE_DOWN:this._move("nextPage",r);break;case i.UP:this._keyEvent("previous",r);break;case i.DOWN:this._keyEvent("next",r)}},input:function(e){if(r){r=!1,e.preventDefault();return}this._searchTimeout(e)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(e){if(this.cancelBlur){delete this.cancelBlur;return}clearTimeout(this.searching),this.close(e),this._change(e)}}),this._initSource(),this.menu=e("
      '+"";var z=N?'":"";for(var W=0;W<7;W++){var X=(W+T)%7;z+="=5?' class="ui-datepicker-week-end"':"")+">"+''+L[X]+""}U+=z+"";var V=this._getDaysInMonth(d,p);d==e.selectedYear&&p==e.selectedMonth&&(e.selectedDay=Math.min(e.selectedDay,V));var J=(this._getFirstDayOfMonth(d,p)-T+7)%7,K=Math.ceil((J+V)/7),Q=f?this.maxRows>K?this.maxRows:K:K;this.maxRows=Q;var G=this._daylightSavingAdjust(new Date(d,p,1-J));for(var Y=0;Y";var Z=N?'":"";for(var W=0;W<7;W++){var et=M?M.apply(e.input?e.input[0]:null,[G]):[!0,""],tt=G.getMonth()!=p,nt=tt&&!D||!et[0]||c&&Gh;Z+='",G.setDate(G.getDate()+1),G=this._daylightSavingAdjust(G)}U+=Z+""}p++,p>11&&(p=0,d++),U+="
      '+this._get(e,"weekHeader")+"
      '+this._get(e,"calculateWeek")(G)+""+(tt&&!_?" ":nt?''+G.getDate()+"":''+G.getDate()+"")+"
      "+(f?"
      "+(o[0]>0&&I==o[1]-1?'
      ':""):""),F+=U}B+=F}return B+=x+($.ui.ie6&&!e.inline?'':""),e._keyEvent=!1,B},_generateMonthYearHeader:function(e,t,n,r,i,s,o,u){var a=this._get(e,"changeMonth"),f=this._get(e,"changeYear"),l=this._get(e,"showMonthAfterYear"),c='
      ',h="";if(s||!a)h+=''+o[t]+"";else{var p=r&&r.getFullYear()==n,d=i&&i.getFullYear()==n;h+='"}l||(c+=h+(s||!a||!f?" ":""));if(!e.yearshtml){e.yearshtml="";if(s||!f)c+=''+n+"";else{var m=this._get(e,"yearRange").split(":"),g=(new Date).getFullYear(),y=function(e){var t=e.match(/c[+-].*/)?n+parseInt(e.substring(1),10):e.match(/[+-].*/)?g+parseInt(e,10):parseInt(e,10);return isNaN(t)?g:t},b=y(m[0]),w=Math.max(b,y(m[1]||""));b=r?Math.max(b,r.getFullYear()):b,w=i?Math.min(w,i.getFullYear()):w,e.yearshtml+='",c+=e.yearshtml,e.yearshtml=null}}return c+=this._get(e,"yearSuffix"),l&&(c+=(s||!a||!f?" ":"")+h),c+="
      ",c},_adjustInstDate:function(e,t,n){var r=e.drawYear+(n=="Y"?t:0),i=e.drawMonth+(n=="M"?t:0),s=Math.min(e.selectedDay,this._getDaysInMonth(r,i))+(n=="D"?t:0),o=this._restrictMinMax(e,this._daylightSavingAdjust(new Date(r,i,s)));e.selectedDay=o.getDate(),e.drawMonth=e.selectedMonth=o.getMonth(),e.drawYear=e.selectedYear=o.getFullYear(),(n=="M"||n=="Y")&&this._notifyChange(e)},_restrictMinMax:function(e,t){var n=this._getMinMaxDate(e,"min"),r=this._getMinMaxDate(e,"max"),i=n&&tr?r:i,i},_notifyChange:function(e){var t=this._get(e,"onChangeMonthYear");t&&t.apply(e.input?e.input[0]:null,[e.selectedYear,e.selectedMonth+1,e])},_getNumberOfMonths:function(e){var t=this._get(e,"numberOfMonths");return t==null?[1,1]:typeof t=="number"?[1,t]:t},_getMinMaxDate:function(e,t){return this._determineDate(e,this._get(e,t+"Date"),null)},_getDaysInMonth:function(e,t){return 32-this._daylightSavingAdjust(new Date(e,t,32)).getDate()},_getFirstDayOfMonth:function(e,t){return(new Date(e,t,1)).getDay()},_canAdjustMonth:function(e,t,n,r){var i=this._getNumberOfMonths(e),s=this._daylightSavingAdjust(new Date(n,r+(t<0?t:i[0]*i[1]),1));return t<0&&s.setDate(this._getDaysInMonth(s.getFullYear(),s.getMonth())),this._isInRange(e,s)},_isInRange:function(e,t){var n=this._getMinMaxDate(e,"min"),r=this._getMinMaxDate(e,"max");return(!n||t.getTime()>=n.getTime())&&(!r||t.getTime()<=r.getTime())},_getFormatConfig:function(e){var t=this._get(e,"shortYearCutoff");return t=typeof t!="string"?t:(new Date).getFullYear()%100+parseInt(t,10),{shortYearCutoff:t,dayNamesShort:this._get(e,"dayNamesShort"),dayNames:this._get(e,"dayNames"),monthNamesShort:this._get(e,"monthNamesShort"),monthNames:this._get(e,"monthNames")}},_formatDate:function(e,t,n,r){t||(e.currentDay=e.selectedDay,e.currentMonth=e.selectedMonth,e.currentYear=e.selectedYear);var i=t?typeof t=="object"?t:this._daylightSavingAdjust(new Date(r,n,t)):this._daylightSavingAdjust(new Date(e.currentYear,e.currentMonth,e.currentDay));return this.formatDate(this._get(e,"dateFormat"),i,this._getFormatConfig(e))}}),$.fn.datepicker=function(e){if(!this.length)return this;$.datepicker.initialized||($(document).mousedown($.datepicker._checkExternalClick).find(document.body).append($.datepicker.dpDiv),$.datepicker.initialized=!0);var t=Array.prototype.slice.call(arguments,1);return typeof e!="string"||e!="isDisabled"&&e!="getDate"&&e!="widget"?e=="option"&&arguments.length==2&&typeof arguments[1]=="string"?$.datepicker["_"+e+"Datepicker"].apply($.datepicker,[this[0]].concat(t)):this.each(function(){typeof e=="string"?$.datepicker["_"+e+"Datepicker"].apply($.datepicker,[this].concat(t)):$.datepicker._attachDatepicker(this,e)}):$.datepicker["_"+e+"Datepicker"].apply($.datepicker,[this[0]].concat(t))},$.datepicker=new Datepicker,$.datepicker.initialized=!1,$.datepicker.uuid=(new Date).getTime(),$.datepicker.version="1.9.2",window["DP_jQuery_"+dpuuid]=$})(jQuery);(function(e,t){var n="ui-dialog ui-widget ui-widget-content ui-corner-all ",r={buttons:!0,height:!0,maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0,width:!0},i={maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0};e.widget("ui.dialog",{version:"1.9.2",options:{autoOpen:!0,buttons:{},closeOnEscape:!0,closeText:"close",dialogClass:"",draggable:!0,hide:null,height:"auto",maxHeight:!1,maxWidth:!1,minHeight:150,minWidth:150,modal:!1,position:{my:"center",at:"center",of:window,collision:"fit",using:function(t){var n=e(this).css(t).offset().top;n<0&&e(this).css("top",t.top-n)}},resizable:!0,show:null,stack:!0,title:"",width:300,zIndex:1e3},_create:function(){this.originalTitle=this.element.attr("title"),typeof this.originalTitle!="string"&&(this.originalTitle=""),this.oldPosition={parent:this.element.parent(),index:this.element.parent().children().index(this.element)},this.options.title=this.options.title||this.originalTitle;var t=this,r=this.options,i=r.title||" ",s,o,u,a,f;s=(this.uiDialog=e("
      ")).addClass(n+r.dialogClass).css({display:"none",outline:0,zIndex:r.zIndex}).attr("tabIndex",-1).keydown(function(n){r.closeOnEscape&&!n.isDefaultPrevented()&&n.keyCode&&n.keyCode===e.ui.keyCode.ESCAPE&&(t.close(n),n.preventDefault())}).mousedown(function(e){t.moveToTop(!1,e)}).appendTo("body"),this.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(s),o=(this.uiDialogTitlebar=e("
      ")).addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").bind("mousedown",function(){s.focus()}).prependTo(s),u=e("").addClass("ui-dialog-titlebar-close ui-corner-all").attr("role","button").click(function(e){e.preventDefault(),t.close(e)}).appendTo(o),(this.uiDialogTitlebarCloseText=e("")).addClass("ui-icon ui-icon-closethick").text(r.closeText).appendTo(u),a=e("").uniqueId().addClass("ui-dialog-title").html(i).prependTo(o),f=(this.uiDialogButtonPane=e("
      ")).addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"),(this.uiButtonSet=e("
      ")).addClass("ui-dialog-buttonset").appendTo(f),s.attr({role:"dialog","aria-labelledby":a.attr("id")}),o.find("*").add(o).disableSelection(),this._hoverable(u),this._focusable(u),r.draggable&&e.fn.draggable&&this._makeDraggable(),r.resizable&&e.fn.resizable&&this._makeResizable(),this._createButtons(r.buttons),this._isOpen=!1,e.fn.bgiframe&&s.bgiframe(),this._on(s,{keydown:function(t){if(!r.modal||t.keyCode!==e.ui.keyCode.TAB)return;var n=e(":tabbable",s),i=n.filter(":first"),o=n.filter(":last");if(t.target===o[0]&&!t.shiftKey)return i.focus(1),!1;if(t.target===i[0]&&t.shiftKey)return o.focus(1),!1}})},_init:function(){this.options.autoOpen&&this.open()},_destroy:function(){var e,t=this.oldPosition;this.overlay&&this.overlay.destroy(),this.uiDialog.hide(),this.element.removeClass("ui-dialog-content ui-widget-content").hide().appendTo("body"),this.uiDialog.remove(),this.originalTitle&&this.element.attr("title",this.originalTitle),e=t.parent.children().eq(t.index),e.length&&e[0]!==this.element[0]?e.before(this.element):t.parent.append(this.element)},widget:function(){return this.uiDialog},close:function(t){var n=this,r,i;if(!this._isOpen)return;if(!1===this._trigger("beforeClose",t))return;return this._isOpen=!1,this.overlay&&this.overlay.destroy(),this.options.hide?this._hide(this.uiDialog,this.options.hide,function(){n._trigger("close",t)}):(this.uiDialog.hide(),this._trigger("close",t)),e.ui.dialog.overlay.resize(),this.options.modal&&(r=0,e(".ui-dialog").each(function(){this!==n.uiDialog[0]&&(i=e(this).css("z-index"),isNaN(i)||(r=Math.max(r,i)))}),e.ui.dialog.maxZ=r),this},isOpen:function(){return this._isOpen},moveToTop:function(t,n){var r=this.options,i;return r.modal&&!t||!r.stack&&!r.modal?this._trigger("focus",n):(r.zIndex>e.ui.dialog.maxZ&&(e.ui.dialog.maxZ=r.zIndex),this.overlay&&(e.ui.dialog.maxZ+=1,e.ui.dialog.overlay.maxZ=e.ui.dialog.maxZ,this.overlay.$el.css("z-index",e.ui.dialog.overlay.maxZ)),i={scrollTop:this.element.scrollTop(),scrollLeft:this.element.scrollLeft()},e.ui.dialog.maxZ+=1,this.uiDialog.css("z-index",e.ui.dialog.maxZ),this.element.attr(i),this._trigger("focus",n),this)},open:function(){if(this._isOpen)return;var t,n=this.options,r=this.uiDialog;return this._size(),this._position(n.position),r.show(n.show),this.overlay=n.modal?new e.ui.dialog.overlay(this):null,this.moveToTop(!0),t=this.element.find(":tabbable"),t.length||(t=this.uiDialogButtonPane.find(":tabbable"),t.length||(t=r)),t.eq(0).focus(),this._isOpen=!0,this._trigger("open"),this},_createButtons:function(t){var n=this,r=!1;this.uiDialogButtonPane.remove(),this.uiButtonSet.empty(),typeof t=="object"&&t!==null&&e.each(t,function(){return!(r=!0)}),r?(e.each(t,function(t,r){var i,s;r=e.isFunction(r)?{click:r,text:t}:r,r=e.extend({type:"button"},r),s=r.click,r.click=function(){s.apply(n.element[0],arguments)},i=e("",r).appendTo(n.uiButtonSet),e.fn.button&&i.button()}),this.uiDialog.addClass("ui-dialog-buttons"),this.uiDialogButtonPane.appendTo(this.uiDialog)):this.uiDialog.removeClass("ui-dialog-buttons")},_makeDraggable:function(){function r(e){return{position:e.position,offset:e.offset}}var t=this,n=this.options;this.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",handle:".ui-dialog-titlebar",containment:"document",start:function(n,i){e(this).addClass("ui-dialog-dragging"),t._trigger("dragStart",n,r(i))},drag:function(e,n){t._trigger("drag",e,r(n))},stop:function(i,s){n.position=[s.position.left-t.document.scrollLeft(),s.position.top-t.document.scrollTop()],e(this).removeClass("ui-dialog-dragging"),t._trigger("dragStop",i,r(s)),e.ui.dialog.overlay.resize()}})},_makeResizable:function(n){function u(e){return{originalPosition:e.originalPosition,originalSize:e.originalSize,position:e.position,size:e.size}}n=n===t?this.options.resizable:n;var r=this,i=this.options,s=this.uiDialog.css("position"),o=typeof n=="string"?n:"n,e,s,w,se,sw,ne,nw";this.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:this.element,maxWidth:i.maxWidth,maxHeight:i.maxHeight,minWidth:i.minWidth,minHeight:this._minHeight(),handles:o,start:function(t,n){e(this).addClass("ui-dialog-resizing"),r._trigger("resizeStart",t,u(n))},resize:function(e,t){r._trigger("resize",e,u(t))},stop:function(t,n){e(this).removeClass("ui-dialog-resizing"),i.height=e(this).height(),i.width=e(this).width(),r._trigger("resizeStop",t,u(n)),e.ui.dialog.overlay.resize()}}).css("position",s).find(".ui-resizable-se").addClass("ui-icon ui-icon-grip-diagonal-se")},_minHeight:function(){var e=this.options;return e.height==="auto"?e.minHeight:Math.min(e.minHeight,e.height)},_position:function(t){var n=[],r=[0,0],i;if(t){if(typeof t=="string"||typeof t=="object"&&"0"in t)n=t.split?t.split(" "):[t[0],t[1]],n.length===1&&(n[1]=n[0]),e.each(["left","top"],function(e,t){+n[e]===n[e]&&(r[e]=n[e],n[e]=t)}),t={my:n[0]+(r[0]<0?r[0]:"+"+r[0])+" "+n[1]+(r[1]<0?r[1]:"+"+r[1]),at:n.join(" ")};t=e.extend({},e.ui.dialog.prototype.options.position,t)}else t=e.ui.dialog.prototype.options.position;i=this.uiDialog.is(":visible"),i||this.uiDialog.show(),this.uiDialog.position(t),i||this.uiDialog.hide()},_setOptions:function(t){var n=this,s={},o=!1;e.each(t,function(e,t){n._setOption(e,t),e in r&&(o=!0),e in i&&(s[e]=t)}),o&&this._size(),this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option",s)},_setOption:function(t,r){var i,s,o=this.uiDialog;switch(t){case"buttons":this._createButtons(r);break;case"closeText":this.uiDialogTitlebarCloseText.text(""+r);break;case"dialogClass":o.removeClass(this.options.dialogClass).addClass(n+r);break;case"disabled":r?o.addClass("ui-dialog-disabled"):o.removeClass("ui-dialog-disabled");break;case"draggable":i=o.is(":data(draggable)"),i&&!r&&o.draggable("destroy"),!i&&r&&this._makeDraggable();break;case"position":this._position(r);break;case"resizable":s=o.is(":data(resizable)"),s&&!r&&o.resizable("destroy"),s&&typeof r=="string"&&o.resizable("option","handles",r),!s&&r!==!1&&this._makeResizable(r);break;case"title":e(".ui-dialog-title",this.uiDialogTitlebar).html(""+(r||" "))}this._super(t,r)},_size:function(){var t,n,r,i=this.options,s=this.uiDialog.is(":visible");this.element.show().css({width:"auto",minHeight:0,height:0}),i.minWidth>i.width&&(i.width=i.minWidth),t=this.uiDialog.css({height:"auto",width:i.width}).outerHeight(),n=Math.max(0,i.minHeight-t),i.height==="auto"?e.support.minHeight?this.element.css({minHeight:n,height:"auto"}):(this.uiDialog.show(),r=this.element.css("height","auto").height(),s||this.uiDialog.hide(),this.element.height(Math.max(r,n))):this.element.height(Math.max(i.height-t,0)),this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option","minHeight",this._minHeight())}}),e.extend(e.ui.dialog,{uuid:0,maxZ:0,getTitleId:function(e){var t=e.attr("id");return t||(this.uuid+=1,t=this.uuid),"ui-dialog-title-"+t},overlay:function(t){this.$el=e.ui.dialog.overlay.create(t)}}),e.extend(e.ui.dialog.overlay,{instances:[],oldInstances:[],maxZ:0,events:e.map("focus,mousedown,mouseup,keydown,keypress,click".split(","),function(e){return e+".dialog-overlay"}).join(" "),create:function(t){this.instances.length===0&&(setTimeout(function(){e.ui.dialog.overlay.instances.length&&e(document).bind(e.ui.dialog.overlay.events,function(t){if(e(t.target).zIndex()").addClass("ui-widget-overlay");return e(document).bind("keydown.dialog-overlay",function(r){var i=e.ui.dialog.overlay.instances;i.length!==0&&i[i.length-1]===n&&t.options.closeOnEscape&&!r.isDefaultPrevented()&&r.keyCode&&r.keyCode===e.ui.keyCode.ESCAPE&&(t.close(r),r.preventDefault())}),n.appendTo(document.body).css({width:this.width(),height:this.height()}),e.fn.bgiframe&&n.bgiframe(),this.instances.push(n),n},destroy:function(t){var n=e.inArray(t,this.instances),r=0;n!==-1&&this.oldInstances.push(this.instances.splice(n,1)[0]),this.instances.length===0&&e([document,window]).unbind(".dialog-overlay"),t.height(0).width(0).remove(),e.each(this.instances,function(){r=Math.max(r,this.css("z-index"))}),this.maxZ=r},height:function(){var t,n;return e.ui.ie?(t=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight),n=Math.max(document.documentElement.offsetHeight,document.body.offsetHeight),t",delay:300,options:{icons:{submenu:"ui-icon-carat-1-e"},menus:"ul",position:{my:"left top",at:"right top"},role:"menu",blur:null,focus:null,select:null},_create:function(){this.activeMenu=this.element,this.element.uniqueId().addClass("ui-menu ui-widget ui-widget-content ui-corner-all").toggleClass("ui-menu-icons",!!this.element.find(".ui-icon").length).attr({role:this.options.role,tabIndex:0}).bind("click"+this.eventNamespace,e.proxy(function(e){this.options.disabled&&e.preventDefault()},this)),this.options.disabled&&this.element.addClass("ui-state-disabled").attr("aria-disabled","true"),this._on({"mousedown .ui-menu-item > a":function(e){e.preventDefault()},"click .ui-state-disabled > a":function(e){e.preventDefault()},"click .ui-menu-item:has(a)":function(t){var r=e(t.target).closest(".ui-menu-item");!n&&r.not(".ui-state-disabled").length&&(n=!0,this.select(t),r.has(".ui-menu").length?this.expand(t):this.element.is(":focus")||(this.element.trigger("focus",[!0]),this.active&&this.active.parents(".ui-menu").length===1&&clearTimeout(this.timer)))},"mouseenter .ui-menu-item":function(t){var n=e(t.currentTarget);n.siblings().children(".ui-state-active").removeClass("ui-state-active"),this.focus(t,n)},mouseleave:"collapseAll","mouseleave .ui-menu":"collapseAll",focus:function(e,t){var n=this.active||this.element.children(".ui-menu-item").eq(0);t||this.focus(e,n)},blur:function(t){this._delay(function(){e.contains(this.element[0],this.document[0].activeElement)||this.collapseAll(t)})},keydown:"_keydown"}),this.refresh(),this._on(this.document,{click:function(t){e(t.target).closest(".ui-menu").length||this.collapseAll(t),n=!1}})},_destroy:function(){this.element.removeAttr("aria-activedescendant").find(".ui-menu").andSelf().removeClass("ui-menu ui-widget ui-widget-content ui-corner-all ui-menu-icons").removeAttr("role").removeAttr("tabIndex").removeAttr("aria-labelledby").removeAttr("aria-expanded").removeAttr("aria-hidden").removeAttr("aria-disabled").removeUniqueId().show(),this.element.find(".ui-menu-item").removeClass("ui-menu-item").removeAttr("role").removeAttr("aria-disabled").children("a").removeUniqueId().removeClass("ui-corner-all ui-state-hover").removeAttr("tabIndex").removeAttr("role").removeAttr("aria-haspopup").children().each(function(){var t=e(this);t.data("ui-menu-submenu-carat")&&t.remove()}),this.element.find(".ui-menu-divider").removeClass("ui-menu-divider ui-widget-content")},_keydown:function(t){function a(e){return e.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")}var n,r,i,s,o,u=!0;switch(t.keyCode){case e.ui.keyCode.PAGE_UP:this.previousPage(t);break;case e.ui.keyCode.PAGE_DOWN:this.nextPage(t);break;case e.ui.keyCode.HOME:this._move("first","first",t);break;case e.ui.keyCode.END:this._move("last","last",t);break;case e.ui.keyCode.UP:this.previous(t);break;case e.ui.keyCode.DOWN:this.next(t);break;case e.ui.keyCode.LEFT:this.collapse(t);break;case e.ui.keyCode.RIGHT:this.active&&!this.active.is(".ui-state-disabled")&&this.expand(t);break;case e.ui.keyCode.ENTER:case e.ui.keyCode.SPACE:this._activate(t);break;case e.ui.keyCode.ESCAPE:this.collapse(t);break;default:u=!1,r=this.previousFilter||"",i=String.fromCharCode(t.keyCode),s=!1,clearTimeout(this.filterTimer),i===r?s=!0:i=r+i,o=new RegExp("^"+a(i),"i"),n=this.activeMenu.children(".ui-menu-item").filter(function(){return o.test(e(this).children("a").text())}),n=s&&n.index(this.active.next())!==-1?this.active.nextAll(".ui-menu-item"):n,n.length||(i=String.fromCharCode(t.keyCode),o=new RegExp("^"+a(i),"i"),n=this.activeMenu.children(".ui-menu-item").filter(function(){return o.test(e(this).children("a").text())})),n.length?(this.focus(t,n),n.length>1?(this.previousFilter=i,this.filterTimer=this._delay(function(){delete this.previousFilter},1e3)):delete this.previousFilter):delete this.previousFilter}u&&t.preventDefault()},_activate:function(e){this.active.is(".ui-state-disabled")||(this.active.children("a[aria-haspopup='true']").length?this.expand(e):this.select(e))},refresh:function(){var t,n=this.options.icons.submenu,r=this.element.find(this.options.menus);r.filter(":not(.ui-menu)").addClass("ui-menu ui-widget ui-widget-content ui-corner-all").hide().attr({role:this.options.role,"aria-hidden":"true","aria-expanded":"false"}).each(function(){var t=e(this),r=t.prev("a"),i=e("").addClass("ui-menu-icon ui-icon "+n).data("ui-menu-submenu-carat",!0);r.attr("aria-haspopup","true").prepend(i),t.attr("aria-labelledby",r.attr("id"))}),t=r.add(this.element),t.children(":not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","presentation").children("a").uniqueId().addClass("ui-corner-all").attr({tabIndex:-1,role:this._itemRole()}),t.children(":not(.ui-menu-item)").each(function(){var t=e(this);/[^\-â€â€Ã¢â‚¬â€œ\s]/.test(t.text())||t.addClass("ui-widget-content ui-menu-divider")}),t.children(".ui-state-disabled").attr("aria-disabled","true"),this.active&&!e.contains(this.element[0],this.active[0])&&this.blur()},_itemRole:function(){return{menu:"menuitem",listbox:"option"}[this.options.role]},focus:function(e,t){var n,r;this.blur(e,e&&e.type==="focus"),this._scrollIntoView(t),this.active=t.first(),r=this.active.children("a").addClass("ui-state-focus"),this.options.role&&this.element.attr("aria-activedescendant",r.attr("id")),this.active.parent().closest(".ui-menu-item").children("a:first").addClass("ui-state-active"),e&&e.type==="keydown"?this._close():this.timer=this._delay(function(){this._close()},this.delay),n=t.children(".ui-menu"),n.length&&/^mouse/.test(e.type)&&this._startOpening(n),this.activeMenu=t.parent(),this._trigger("focus",e,{item:t})},_scrollIntoView:function(t){var n,r,i,s,o,u;this._hasScroll()&&(n=parseFloat(e.css(this.activeMenu[0],"borderTopWidth"))||0,r=parseFloat(e.css(this.activeMenu[0],"paddingTop"))||0,i=t.offset().top-this.activeMenu.offset().top-n-r,s=this.activeMenu.scrollTop(),o=this.activeMenu.height(),u=t.height(),i<0?this.activeMenu.scrollTop(s+i):i+u>o&&this.activeMenu.scrollTop(s+i-o+u))},blur:function(e,t){t||clearTimeout(this.timer);if(!this.active)return;this.active.children("a").removeClass("ui-state-focus"),this.active=null,this._trigger("blur",e,{item:this.active})},_startOpening:function(e){clearTimeout(this.timer);if(e.attr("aria-hidden")!=="true")return;this.timer=this._delay(function(){this._close(),this._open(e)},this.delay)},_open:function(t){var n=e.extend({of:this.active},this.options.position);clearTimeout(this.timer),this.element.find(".ui-menu").not(t.parents(".ui-menu")).hide().attr("aria-hidden","true"),t.show().removeAttr("aria-hidden").attr("aria-expanded","true").position(n)},collapseAll:function(t,n){clearTimeout(this.timer),this.timer=this._delay(function(){var r=n?this.element:e(t&&t.target).closest(this.element.find(".ui-menu"));r.length||(r=this.element),this._close(r),this.blur(t),this.activeMenu=r},this.delay)},_close:function(e){e||(e=this.active?this.active.parent():this.element),e.find(".ui-menu").hide().attr("aria-hidden","true").attr("aria-expanded","false").end().find("a.ui-state-active").removeClass("ui-state-active")},collapse:function(e){var t=this.active&&this.active.parent().closest(".ui-menu-item",this.element);t&&t.length&&(this._close(),this.focus(e,t))},expand:function(e){var t=this.active&&this.active.children(".ui-menu ").children(".ui-menu-item").first();t&&t.length&&(this._open(t.parent()),this._delay(function(){this.focus(e,t)}))},next:function(e){this._move("next","first",e)},previous:function(e){this._move("prev","last",e)},isFirstItem:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},isLastItem:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},_move:function(e,t,n){var r;this.active&&(e==="first"||e==="last"?r=this.active[e==="first"?"prevAll":"nextAll"](".ui-menu-item").eq(-1):r=this.active[e+"All"](".ui-menu-item").eq(0));if(!r||!r.length||!this.active)r=this.activeMenu.children(".ui-menu-item")[t]();this.focus(n,r)},nextPage:function(t){var n,r,i;if(!this.active){this.next(t);return}if(this.isLastItem())return;this._hasScroll()?(r=this.active.offset().top,i=this.element.height(),this.active.nextAll(".ui-menu-item").each(function(){return n=e(this),n.offset().top-r-i<0}),this.focus(t,n)):this.focus(t,this.activeMenu.children(".ui-menu-item")[this.active?"last":"first"]())},previousPage:function(t){var n,r,i;if(!this.active){this.next(t);return}if(this.isFirstItem())return;this._hasScroll()?(r=this.active.offset().top,i=this.element.height(),this.active.prevAll(".ui-menu-item").each(function(){return n=e(this),n.offset().top-r+i>0}),this.focus(t,n)):this.focus(t,this.activeMenu.children(".ui-menu-item").first())},_hasScroll:function(){return this.element.outerHeight()
      ").appendTo(this.element),this.oldValue=this._value(),this._refreshValue()},_destroy:function(){this.element.removeClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow"),this.valueDiv.remove()},value:function(e){return e===t?this._value():(this._setOption("value",e),this)},_setOption:function(e,t){e==="value"&&(this.options.value=t,this._refreshValue(),this._value()===this.options.max&&this._trigger("complete")),this._super(e,t)},_value:function(){var e=this.options.value;return typeof e!="number"&&(e=0),Math.min(this.options.max,Math.max(this.min,e))},_percentage:function(){return 100*this._value()/this.options.max},_refreshValue:function(){var e=this.value(),t=this._percentage();this.oldValue!==e&&(this.oldValue=e,this._trigger("change")),this.valueDiv.toggle(e>this.min).toggleClass("ui-corner-right",e===this.options.max).width(t.toFixed(0)+"%"),this.element.attr("aria-valuenow",e)}})})(jQuery);(function(e,t){var n=5;e.widget("ui.slider",e.ui.mouse,{version:"1.9.2",widgetEventPrefix:"slide",options:{animate:!1,distance:0,max:100,min:0,orientation:"horizontal",range:!1,step:1,value:0,values:null},_create:function(){var t,r,i=this.options,s=this.element.find(".ui-slider-handle").addClass("ui-state-default ui-corner-all"),o="",u=[];this._keySliding=!1,this._mouseSliding=!1,this._animateOff=!0,this._handleIndex=null,this._detectOrientation(),this._mouseInit(),this.element.addClass("ui-slider ui-slider-"+this.orientation+" ui-widget"+" ui-widget-content"+" ui-corner-all"+(i.disabled?" ui-slider-disabled ui-disabled":"")),this.range=e([]),i.range&&(i.range===!0&&(i.values||(i.values=[this._valueMin(),this._valueMin()]),i.values.length&&i.values.length!==2&&(i.values=[i.values[0],i.values[0]])),this.range=e("
      ").appendTo(this.element).addClass("ui-slider-range ui-widget-header"+(i.range==="min"||i.range==="max"?" ui-slider-range-"+i.range:""))),r=i.values&&i.values.length||1;for(t=s.length;tn&&(i=n,s=e(this),o=t)}),c.range===!0&&this.values(1)===c.min&&(o+=1,s=e(this.handles[o])),u=this._start(t,o),u===!1?!1:(this._mouseSliding=!0,this._handleIndex=o,s.addClass("ui-state-active").focus(),a=s.offset(),f=!e(t.target).parents().andSelf().is(".ui-slider-handle"),this._clickOffset=f?{left:0,top:0}:{left:t.pageX-a.left-s.width()/2,top:t.pageY-a.top-s.height()/2-(parseInt(s.css("borderTopWidth"),10)||0)-(parseInt(s.css("borderBottomWidth"),10)||0)+(parseInt(s.css("marginTop"),10)||0)},this.handles.hasClass("ui-state-hover")||this._slide(t,o,r),this._animateOff=!0,!0))},_mouseStart:function(){return!0},_mouseDrag:function(e){var t={x:e.pageX,y:e.pageY},n=this._normValueFromMouse(t);return this._slide(e,this._handleIndex,n),!1},_mouseStop:function(e){return this.handles.removeClass("ui-state-active"),this._mouseSliding=!1,this._stop(e,this._handleIndex),this._change(e,this._handleIndex),this._handleIndex=null,this._clickOffset=null,this._animateOff=!1,!1},_detectOrientation:function(){this.orientation=this.options.orientation==="vertical"?"vertical":"horizontal"},_normValueFromMouse:function(e){var t,n,r,i,s;return this.orientation==="horizontal"?(t=this.elementSize.width,n=e.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)):(t=this.elementSize.height,n=e.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)),r=n/t,r>1&&(r=1),r<0&&(r=0),this.orientation==="vertical"&&(r=1-r),i=this._valueMax()-this._valueMin(),s=this._valueMin()+r*i,this._trimAlignValue(s)},_start:function(e,t){var n={handle:this.handles[t],value:this.value()};return this.options.values&&this.options.values.length&&(n.value=this.values(t),n.values=this.values()),this._trigger("start",e,n)},_slide:function(e,t,n){var r,i,s;this.options.values&&this.options.values.length?(r=this.values(t?0:1),this.options.values.length===2&&this.options.range===!0&&(t===0&&n>r||t===1&&n1){this.options.values[t]=this._trimAlignValue(n),this._refreshValue(),this._change(null,t);return}if(!arguments.length)return this._values();if(!e.isArray(arguments[0]))return this.options.values&&this.options.values.length?this._values(t):this.value();r=this.options.values,i=arguments[0];for(s=0;s=this._valueMax())return this._valueMax();var t=this.options.step>0?this.options.step:1,n=(e-this._valueMin())%t,r=e-n;return Math.abs(n)*2>=t&&(r+=n>0?t:-t),parseFloat(r.toFixed(5))},_valueMin:function(){return this.options.min},_valueMax:function(){return this.options.max},_refreshValue:function(){var t,n,r,i,s,o=this.options.range,u=this.options,a=this,f=this._animateOff?!1:u.animate,l={};this.options.values&&this.options.values.length?this.handles.each(function(r){n=(a.values(r)-a._valueMin())/(a._valueMax()-a._valueMin())*100,l[a.orientation==="horizontal"?"left":"bottom"]=n+"%",e(this).stop(1,1)[f?"animate":"css"](l,u.animate),a.options.range===!0&&(a.orientation==="horizontal"?(r===0&&a.range.stop(1,1)[f?"animate":"css"]({left:n+"%"},u.animate),r===1&&a.range[f?"animate":"css"]({width:n-t+"%"},{queue:!1,duration:u.animate})):(r===0&&a.range.stop(1,1)[f?"animate":"css"]({bottom:n+"%"},u.animate),r===1&&a.range[f?"animate":"css"]({height:n-t+"%"},{queue:!1,duration:u.animate}))),t=n}):(r=this.value(),i=this._valueMin(),s=this._valueMax(),n=s!==i?(r-i)/(s-i)*100:0,l[this.orientation==="horizontal"?"left":"bottom"]=n+"%",this.handle.stop(1,1)[f?"animate":"css"](l,u.animate),o==="min"&&this.orientation==="horizontal"&&this.range.stop(1,1)[f?"animate":"css"]({width:n+"%"},u.animate),o==="max"&&this.orientation==="horizontal"&&this.range[f?"animate":"css"]({width:100-n+"%"},{queue:!1,duration:u.animate}),o==="min"&&this.orientation==="vertical"&&this.range.stop(1,1)[f?"animate":"css"]({height:n+"%"},u.animate),o==="max"&&this.orientation==="vertical"&&this.range[f?"animate":"css"]({height:100-n+"%"},{queue:!1,duration:u.animate}))}})})(jQuery);(function(e){function t(e){return function(){var t=this.element.val();e.apply(this,arguments),this._refresh(),t!==this.element.val()&&this._trigger("change")}}e.widget("ui.spinner",{version:"1.9.2",defaultElement:"",widgetEventPrefix:"spin",options:{culture:null,icons:{down:"ui-icon-triangle-1-s",up:"ui-icon-triangle-1-n"},incremental:!0,max:null,min:null,numberFormat:null,page:10,step:1,change:null,spin:null,start:null,stop:null},_create:function(){this._setOption("max",this.options.max),this._setOption("min",this.options.min),this._setOption("step",this.options.step),this._value(this.element.val(),!0),this._draw(),this._on(this._events),this._refresh(),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_getCreateOptions:function(){var t={},n=this.element;return e.each(["min","max","step"],function(e,r){var i=n.attr(r);i!==undefined&&i.length&&(t[r]=i)}),t},_events:{keydown:function(e){this._start(e)&&this._keydown(e)&&e.preventDefault()},keyup:"_stop",focus:function(){this.previous=this.element.val()},blur:function(e){if(this.cancelBlur){delete this.cancelBlur;return}this._refresh(),this.previous!==this.element.val()&&this._trigger("change",e)},mousewheel:function(e,t){if(!t)return;if(!this.spinning&&!this._start(e))return!1;this._spin((t>0?1:-1)*this.options.step,e),clearTimeout(this.mousewheelTimer),this.mousewheelTimer=this._delay(function(){this.spinning&&this._stop(e)},100),e.preventDefault()},"mousedown .ui-spinner-button":function(t){function r(){var e=this.element[0]===this.document[0].activeElement;e||(this.element.focus(),this.previous=n,this._delay(function(){this.previous=n}))}var n;n=this.element[0]===this.document[0].activeElement?this.previous:this.element.val(),t.preventDefault(),r.call(this),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur,r.call(this)});if(this._start(t)===!1)return;this._repeat(null,e(t.currentTarget).hasClass("ui-spinner-up")?1:-1,t)},"mouseup .ui-spinner-button":"_stop","mouseenter .ui-spinner-button":function(t){if(!e(t.currentTarget).hasClass("ui-state-active"))return;if(this._start(t)===!1)return!1;this._repeat(null,e(t.currentTarget).hasClass("ui-spinner-up")?1:-1,t)},"mouseleave .ui-spinner-button":"_stop"},_draw:function(){var e=this.uiSpinner=this.element.addClass("ui-spinner-input").attr("autocomplete","off").wrap(this._uiSpinnerHtml()).parent().append(this._buttonHtml());this.element.attr("role","spinbutton"),this.buttons=e.find(".ui-spinner-button").attr("tabIndex",-1).button().removeClass("ui-corner-all"),this.buttons.height()>Math.ceil(e.height()*.5)&&e.height()>0&&e.height(e.height()),this.options.disabled&&this.disable()},_keydown:function(t){var n=this.options,r=e.ui.keyCode;switch(t.keyCode){case r.UP:return this._repeat(null,1,t),!0;case r.DOWN:return this._repeat(null,-1,t),!0;case r.PAGE_UP:return this._repeat(null,n.page,t),!0;case r.PAGE_DOWN:return this._repeat(null,-n.page,t),!0}return!1},_uiSpinnerHtml:function(){return""},_buttonHtml:function(){return""+""+""+""+""},_start:function(e){return!this.spinning&&this._trigger("start",e)===!1?!1:(this.counter||(this.counter=1),this.spinning=!0,!0)},_repeat:function(e,t,n){e=e||500,clearTimeout(this.timer),this.timer=this._delay(function(){this._repeat(40,t,n)},e),this._spin(t*this.options.step,n)},_spin:function(e,t){var n=this.value()||0;this.counter||(this.counter=1),n=this._adjustValue(n+e*this._increment(this.counter));if(!this.spinning||this._trigger("spin",t,{value:n})!==!1)this._value(n),this.counter++},_increment:function(t){var n=this.options.incremental;return n?e.isFunction(n)?n(t):Math.floor(t*t*t/5e4-t*t/500+17*t/200+1):1},_precision:function(){var e=this._precisionOf(this.options.step);return this.options.min!==null&&(e=Math.max(e,this._precisionOf(this.options.min))),e},_precisionOf:function(e){var t=e.toString(),n=t.indexOf(".");return n===-1?0:t.length-n-1},_adjustValue:function(e){var t,n,r=this.options;return t=r.min!==null?r.min:0,n=e-t,n=Math.round(n/r.step)*r.step,e=t+n,e=parseFloat(e.toFixed(this._precision())),r.max!==null&&e>r.max?r.max:r.min!==null&&e1&&e.href.replace(r,"")===location.href.replace(r,"").replace(/\s/g,"%20")}var n=0,r=/#.*$/;e.widget("ui.tabs",{version:"1.9.2",delay:300,options:{active:null,collapsible:!1,event:"click",heightStyle:"content",hide:null,show:null,activate:null,beforeActivate:null,beforeLoad:null,load:null},_create:function(){var t=this,n=this.options,r=n.active,i=location.hash.substring(1);this.running=!1,this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all").toggleClass("ui-tabs-collapsible",n.collapsible).delegate(".ui-tabs-nav > li","mousedown"+this.eventNamespace,function(t){e(this).is(".ui-state-disabled")&&t.preventDefault()}).delegate(".ui-tabs-anchor","focus"+this.eventNamespace,function(){e(this).closest("li").is(".ui-state-disabled")&&this.blur()}),this._processTabs();if(r===null){i&&this.tabs.each(function(t,n){if(e(n).attr("aria-controls")===i)return r=t,!1}),r===null&&(r=this.tabs.index(this.tabs.filter(".ui-tabs-active")));if(r===null||r===-1)r=this.tabs.length?0:!1}r!==!1&&(r=this.tabs.index(this.tabs.eq(r)),r===-1&&(r=n.collapsible?!1:0)),n.active=r,!n.collapsible&&n.active===!1&&this.anchors.length&&(n.active=0),e.isArray(n.disabled)&&(n.disabled=e.unique(n.disabled.concat(e.map(this.tabs.filter(".ui-state-disabled"),function(e){return t.tabs.index(e)}))).sort()),this.options.active!==!1&&this.anchors.length?this.active=this._findActive(this.options.active):this.active=e(),this._refresh(),this.active.length&&this.load(n.active)},_getCreateEventData:function(){return{tab:this.active,panel:this.active.length?this._getPanelForTab(this.active):e()}},_tabKeydown:function(t){var n=e(this.document[0].activeElement).closest("li"),r=this.tabs.index(n),i=!0;if(this._handlePageNav(t))return;switch(t.keyCode){case e.ui.keyCode.RIGHT:case e.ui.keyCode.DOWN:r++;break;case e.ui.keyCode.UP:case e.ui.keyCode.LEFT:i=!1,r--;break;case e.ui.keyCode.END:r=this.anchors.length-1;break;case e.ui.keyCode.HOME:r=0;break;case e.ui.keyCode.SPACE:t.preventDefault(),clearTimeout(this.activating),this._activate(r);return;case e.ui.keyCode.ENTER:t.preventDefault(),clearTimeout(this.activating),this._activate(r===this.options.active?!1:r);return;default:return}t.preventDefault(),clearTimeout(this.activating),r=this._focusNextTab(r,i),t.ctrlKey||(n.attr("aria-selected","false"),this.tabs.eq(r).attr("aria-selected","true"),this.activating=this._delay(function(){this.option("active",r)},this.delay))},_panelKeydown:function(t){if(this._handlePageNav(t))return;t.ctrlKey&&t.keyCode===e.ui.keyCode.UP&&(t.preventDefault(),this.active.focus())},_handlePageNav:function(t){if(t.altKey&&t.keyCode===e.ui.keyCode.PAGE_UP)return this._activate(this._focusNextTab(this.options.active-1,!1)),!0;if(t.altKey&&t.keyCode===e.ui.keyCode.PAGE_DOWN)return this._activate(this._focusNextTab(this.options.active+1,!0)),!0},_findNextTab:function(t,n){function i(){return t>r&&(t=0),t<0&&(t=r),t}var r=this.tabs.length-1;while(e.inArray(i(),this.options.disabled)!==-1)t=n?t+1:t-1;return t},_focusNextTab:function(e,t){return e=this._findNextTab(e,t),this.tabs.eq(e).focus(),e},_setOption:function(e,t){if(e==="active"){this._activate(t);return}if(e==="disabled"){this._setupDisabled(t);return}this._super(e,t),e==="collapsible"&&(this.element.toggleClass("ui-tabs-collapsible",t),!t&&this.options.active===!1&&this._activate(0)),e==="event"&&this._setupEvents(t),e==="heightStyle"&&this._setupHeightStyle(t)},_tabId:function(e){return e.attr("aria-controls")||"ui-tabs-"+i()},_sanitizeSelector:function(e){return e?e.replace(/[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g,"\\$&"):""},refresh:function(){var t=this.options,n=this.tablist.children(":has(a[href])");t.disabled=e.map(n.filter(".ui-state-disabled"),function(e){return n.index(e)}),this._processTabs(),t.active===!1||!this.anchors.length?(t.active=!1,this.active=e()):this.active.length&&!e.contains(this.tablist[0],this.active[0])?this.tabs.length===t.disabled.length?(t.active=!1,this.active=e()):this._activate(this._findNextTab(Math.max(0,t.active-1),!1)):t.active=this.tabs.index(this.active),this._refresh()},_refresh:function(){this._setupDisabled(this.options.disabled),this._setupEvents(this.options.event),this._setupHeightStyle(this.options.heightStyle),this.tabs.not(this.active).attr({"aria-selected":"false",tabIndex:-1}),this.panels.not(this._getPanelForTab(this.active)).hide().attr({"aria-expanded":"false","aria-hidden":"true"}),this.active.length?(this.active.addClass("ui-tabs-active ui-state-active").attr({"aria-selected":"true",tabIndex:0}),this._getPanelForTab(this.active).show().attr({"aria-expanded":"true","aria-hidden":"false"})):this.tabs.eq(0).attr("tabIndex",0)},_processTabs:function(){var t=this;this.tablist=this._getList().addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all").attr("role","tablist"),this.tabs=this.tablist.find("> li:has(a[href])").addClass("ui-state-default ui-corner-top").attr({role:"tab",tabIndex:-1}),this.anchors=this.tabs.map(function(){return e("a",this)[0]}).addClass("ui-tabs-anchor").attr({role:"presentation",tabIndex:-1}),this.panels=e(),this.anchors.each(function(n,r){var i,o,u,a=e(r).uniqueId().attr("id"),f=e(r).closest("li"),l=f.attr("aria-controls");s(r)?(i=r.hash,o=t.element.find(t._sanitizeSelector(i))):(u=t._tabId(f),i="#"+u,o=t.element.find(i),o.length||(o=t._createPanel(u),o.insertAfter(t.panels[n-1]||t.tablist)),o.attr("aria-live","polite")),o.length&&(t.panels=t.panels.add(o)),l&&f.data("ui-tabs-aria-controls",l),f.attr({"aria-controls":i.substring(1),"aria-labelledby":a}),o.attr("aria-labelledby",a)}),this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").attr("role","tabpanel")},_getList:function(){return this.element.find("ol,ul").eq(0)},_createPanel:function(t){return e("
      ").attr("id",t).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").data("ui-tabs-destroy",!0)},_setupDisabled:function(t){e.isArray(t)&&(t.length?t.length===this.anchors.length&&(t=!0):t=!1);for(var n=0,r;r=this.tabs[n];n++)t===!0||e.inArray(n,t)!==-1?e(r).addClass("ui-state-disabled").attr("aria-disabled","true"):e(r).removeClass("ui-state-disabled").removeAttr("aria-disabled");this.options.disabled=t},_setupEvents:function(t){var n={click:function(e){e.preventDefault()}};t&&e.each(t.split(" "),function(e,t){n[t]="_eventHandler"}),this._off(this.anchors.add(this.tabs).add(this.panels)),this._on(this.anchors,n),this._on(this.tabs,{keydown:"_tabKeydown"}),this._on(this.panels,{keydown:"_panelKeydown"}),this._focusable(this.tabs),this._hoverable(this.tabs)},_setupHeightStyle:function(t){var n,r,i=this.element.parent();t==="fill"?(e.support.minHeight||(r=i.css("overflow"),i.css("overflow","hidden")),n=i.height(),this.element.siblings(":visible").each(function(){var t=e(this),r=t.css("position");if(r==="absolute"||r==="fixed")return;n-=t.outerHeight(!0)}),r&&i.css("overflow",r),this.element.children().not(this.panels).each(function(){n-=e(this).outerHeight(!0)}),this.panels.each(function(){e(this).height(Math.max(0,n-e(this).innerHeight()+e(this).height()))}).css("overflow","auto")):t==="auto"&&(n=0,this.panels.each(function(){n=Math.max(n,e(this).height("").height())}).height(n))},_eventHandler:function(t){var n=this.options,r=this.active,i=e(t.currentTarget),s=i.closest("li"),o=s[0]===r[0],u=o&&n.collapsible,a=u?e():this._getPanelForTab(s),f=r.length?this._getPanelForTab(r):e(),l={oldTab:r,oldPanel:f,newTab:u?e():s,newPanel:a};t.preventDefault();if(s.hasClass("ui-state-disabled")||s.hasClass("ui-tabs-loading")||this.running||o&&!n.collapsible||this._trigger("beforeActivate",t,l)===!1)return;n.active=u?!1:this.tabs.index(s),this.active=o?e():s,this.xhr&&this.xhr.abort(),!f.length&&!a.length&&e.error("jQuery UI Tabs: Mismatching fragment identifier."),a.length&&this.load(this.tabs.index(s),t),this._toggle(t,l)},_toggle:function(t,n){function o(){r.running=!1,r._trigger("activate",t,n)}function u(){n.newTab.closest("li").addClass("ui-tabs-active ui-state-active"),i.length&&r.options.show?r._show(i,r.options.show,o):(i.show(),o())}var r=this,i=n.newPanel,s=n.oldPanel;this.running=!0,s.length&&this.options.hide?this._hide(s,this.options.hide,function(){n.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),u()}):(n.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),s.hide(),u()),s.attr({"aria-expanded":"false","aria-hidden":"true"}),n.oldTab.attr("aria-selected","false"),i.length&&s.length?n.oldTab.attr("tabIndex",-1):i.length&&this.tabs.filter(function(){return e(this).attr("tabIndex")===0}).attr("tabIndex",-1),i.attr({"aria-expanded":"true","aria-hidden":"false"}),n.newTab.attr({"aria-selected":"true",tabIndex:0})},_activate:function(t){var n,r=this._findActive(t);if(r[0]===this.active[0])return;r.length||(r=this.active),n=r.find(".ui-tabs-anchor")[0],this._eventHandler({target:n,currentTarget:n,preventDefault:e.noop})},_findActive:function(t){return t===!1?e():this.tabs.eq(t)},_getIndex:function(e){return typeof e=="string"&&(e=this.anchors.index(this.anchors.filter("[href$='"+e+"']"))),e},_destroy:function(){this.xhr&&this.xhr.abort(),this.element.removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible"),this.tablist.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all").removeAttr("role"),this.anchors.removeClass("ui-tabs-anchor").removeAttr("role").removeAttr("tabIndex").removeData("href.tabs").removeData("load.tabs").removeUniqueId(),this.tabs.add(this.panels).each(function(){e.data(this,"ui-tabs-destroy")?e(this).remove():e(this).removeClass("ui-state-default ui-state-active ui-state-disabled ui-corner-top ui-corner-bottom ui-widget-content ui-tabs-active ui-tabs-panel").removeAttr("tabIndex").removeAttr("aria-live").removeAttr("aria-busy").removeAttr("aria-selected").removeAttr("aria-labelledby").removeAttr("aria-hidden").removeAttr("aria-expanded").removeAttr("role")}),this.tabs.each(function(){var t=e(this),n=t.data("ui-tabs-aria-controls");n?t.attr("aria-controls",n):t.removeAttr("aria-controls")}),this.panels.show(),this.options.heightStyle!=="content"&&this.panels.css("height","")},enable:function(n){var r=this.options.disabled;if(r===!1)return;n===t?r=!1:(n=this._getIndex(n),e.isArray(r)?r=e.map(r,function(e){return e!==n?e:null}):r=e.map(this.tabs,function(e,t){return t!==n?t:null})),this._setupDisabled(r)},disable:function(n){var r=this.options.disabled;if(r===!0)return;if(n===t)r=!0;else{n=this._getIndex(n);if(e.inArray(n,r)!==-1)return;e.isArray(r)?r=e.merge([n],r).sort():r=[n]}this._setupDisabled(r)},load:function(t,n){t=this._getIndex(t);var r=this,i=this.tabs.eq(t),o=i.find(".ui-tabs-anchor"),u=this._getPanelForTab(i),a={tab:i,panel:u};if(s(o[0]))return;this.xhr=e.ajax(this._ajaxSettings(o,n,a)),this.xhr&&this.xhr.statusText!=="canceled"&&(i.addClass("ui-tabs-loading"),u.attr("aria-busy","true"),this.xhr.success(function(e){setTimeout(function(){u.html(e),r._trigger("load",n,a)},1)}).complete(function(e,t){setTimeout(function(){t==="abort"&&r.panels.stop(!1,!0),i.removeClass("ui-tabs-loading"),u.removeAttr("aria-busy"),e===r.xhr&&delete r.xhr},1)}))},_ajaxSettings:function(t,n,r){var i=this;return{url:t.attr("href"),beforeSend:function(t,s){return i._trigger("beforeLoad",n,e.extend({jqXHR:t,ajaxSettings:s},r))}}},_getPanelForTab:function(t){var n=e(t).attr("aria-controls");return this.element.find(this._sanitizeSelector("#"+n))}}),e.uiBackCompat!==!1&&(e.ui.tabs.prototype._ui=function(e,t){return{tab:e,panel:t,index:this.anchors.index(e)}},e.widget("ui.tabs",e.ui.tabs,{url:function(e,t){this.anchors.eq(e).attr("href",t)}}),e.widget("ui.tabs",e.ui.tabs,{options:{ajaxOptions:null,cache:!1},_create:function(){this._super();var t=this;this._on({tabsbeforeload:function(n,r){if(e.data(r.tab[0],"cache.tabs")){n.preventDefault();return}r.jqXHR.success(function(){t.options.cache&&e.data(r.tab[0],"cache.tabs",!0)})}})},_ajaxSettings:function(t,n,r){var i=this.options.ajaxOptions;return e.extend({},i,{error:function(e,t){try{i.error(e,t,r.tab.closest("li").index(),r.tab[0])}catch(n){}}},this._superApply(arguments))},_setOption:function(e,t){e==="cache"&&t===!1&&this.anchors.removeData("cache.tabs"),this._super(e,t)},_destroy:function(){this.anchors.removeData("cache.tabs"),this._super()},url:function(e){this.anchors.eq(e).removeData("cache.tabs"),this._superApply(arguments)}}),e.widget("ui.tabs",e.ui.tabs,{abort:function(){this.xhr&&this.xhr.abort()}}),e.widget("ui.tabs",e.ui.tabs,{options:{spinner:"Loading…"},_create:function(){this._super(),this._on({tabsbeforeload:function(e,t){if(e.target!==this.element[0]||!this.options.spinner)return;var n=t.tab.find("span"),r=n.html();n.html(this.options.spinner),t.jqXHR.complete(function(){n.html(r)})}})}}),e.widget("ui.tabs",e.ui.tabs,{options:{enable:null,disable:null},enable:function(t){var n=this.options,r;if(t&&n.disabled===!0||e.isArray(n.disabled)&&e.inArray(t,n.disabled)!==-1)r=!0;this._superApply(arguments),r&&this._trigger("enable",null,this._ui(this.anchors[t],this.panels[t]))},disable:function(t){var n=this.options,r;if(t&&n.disabled===!1||e.isArray(n.disabled)&&e.inArray(t,n.disabled)===-1)r=!0;this._superApply(arguments),r&&this._trigger("disable",null,this._ui(this.anchors[t],this.panels[t]))}}),e.widget("ui.tabs",e.ui.tabs,{options:{add:null,remove:null,tabTemplate:"
    • #{label}
    • "},add:function(n,r,i){i===t&&(i=this.anchors.length);var s,o,u=this.options,a=e(u.tabTemplate.replace(/#\{href\}/g,n).replace(/#\{label\}/g,r)),f=n.indexOf("#")?this._tabId(a):n.replace("#","");return a.addClass("ui-state-default ui-corner-top").data("ui-tabs-destroy",!0),a.attr("aria-controls",f),s=i>=this.tabs.length,o=this.element.find("#"+f),o.length||(o=this._createPanel(f),s?i>0?o.insertAfter(this.panels.eq(-1)):o.appendTo(this.element):o.insertBefore(this.panels[i])),o.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").hide(),s?a.appendTo(this.tablist):a.insertBefore(this.tabs[i]),u.disabled=e.map(u.disabled,function(e){return e>=i?++e:e}),this.refresh(),this.tabs.length===1&&u.active===!1&&this.option("active",0),this._trigger("add",null,this._ui(this.anchors[i],this.panels[i])),this},remove:function(t){t=this._getIndex(t);var n=this.options,r=this.tabs.eq(t).remove(),i=this._getPanelForTab(r).remove();return r.hasClass("ui-tabs-active")&&this.anchors.length>2&&this._activate(t+(t+1=t?--e:e}),this.refresh(),this._trigger("remove",null,this._ui(r.find("a")[0],i[0])),this}}),e.widget("ui.tabs",e.ui.tabs,{length:function(){return this.anchors.length}}),e.widget("ui.tabs",e.ui.tabs,{options:{idPrefix:"ui-tabs-"},_tabId:function(t){var n=t.is("li")?t.find("a[href]"):t;return n=n[0],e(n).closest("li").attr("aria-controls")||n.title&&n.title.replace(/\s/g,"_").replace(/[^\w\u00c0-\uFFFF\-]/g,"")||this.options.idPrefix+i()}}),e.widget("ui.tabs",e.ui.tabs,{options:{panelTemplate:"
      "},_createPanel:function(t){return e(this.options.panelTemplate).attr("id",t).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").data("ui-tabs-destroy",!0)}}),e.widget("ui.tabs",e.ui.tabs,{_create:function(){var e=this.options;e.active===null&&e.selected!==t&&(e.active=e.selected===-1?!1:e.selected),this._super(),e.selected=e.active,e.selected===!1&&(e.selected=-1)},_setOption:function(e,t){if(e!=="selected")return this._super(e,t);var n=this.options;this._super("active",t===-1?!1:t),n.selected=n.active,n.selected===!1&&(n.selected=-1)},_eventHandler:function(){this._superApply(arguments),this.options.selected=this.options.active,this.options.selected===!1&&(this.options.selected=-1)}}),e.widget("ui.tabs",e.ui.tabs,{options:{show:null,select:null},_create:function(){this._super(),this.options.active!==!1&&this._trigger("show",null,this._ui(this.active.find(".ui-tabs-anchor")[0],this._getPanelForTab(this.active)[0]))},_trigger:function(e,t,n){var r,i,s=this._superApply(arguments);return s?(e==="beforeActivate"?(r=n.newTab.length?n.newTab:n.oldTab,i=n.newPanel.length?n.newPanel:n.oldPanel,s=this._super("select",t,{tab:r.find(".ui-tabs-anchor")[0],panel:i[0],index:r.closest("li").index()})):e==="activate"&&n.newTab.length&&(s=this._super("show",t,{tab:n.newTab.find(".ui-tabs-anchor")[0],panel:n.newPanel[0],index:n.newTab.closest("li").index()})),s):!1}}),e.widget("ui.tabs",e.ui.tabs,{select:function(e){e=this._getIndex(e);if(e===-1){if(!this.options.collapsible||this.options.selected===-1)return;e=this.options.selected}this.anchors.eq(e).trigger(this.options.event+this.eventNamespace)}}),function(){var t=0;e.widget("ui.tabs",e.ui.tabs,{options:{cookie:null},_create:function(){var e=this.options,t;e.active==null&&e.cookie&&(t=parseInt(this._cookie(),10),t===-1&&(t=!1),e.active=t),this._super()},_cookie:function(n){var r=[this.cookie||(this.cookie=this.options.cookie.name||"ui-tabs-"+ ++t)];return arguments.length&&(r.push(n===!1?-1:n),r.push(this.options.cookie)),e.cookie.apply(null,r)},_refresh:function(){this._super(),this.options.cookie&&this._cookie(this.options.active,this.options.cookie)},_eventHandler:function(){this._superApply(arguments),this.options.cookie&&this._cookie(this.options.active,this.options.cookie)},_destroy:function(){this._super(),this.options.cookie&&this._cookie(null,this.options.cookie)}})}(),e.widget("ui.tabs",e.ui.tabs,{_trigger:function(t,n,r){var i=e.extend({},r);return t==="load"&&(i.panel=i.panel[0],i.tab=i.tab.find(".ui-tabs-anchor")[0]),this._super(t,n,i)}}),e.widget("ui.tabs",e.ui.tabs,{options:{fx:null},_getFx:function(){var t,n,r=this.options.fx;return r&&(e.isArray(r)?(t=r[0],n=r[1]):t=n=r),r?{show:n,hide:t}:null},_toggle:function(e,t){function o(){n.running=!1,n._trigger("activate",e,t)}function u(){t.newTab.closest("li").addClass("ui-tabs-active ui-state-active"),r.length&&s.show?r.animate(s.show,s.show.duration,function(){o()}):(r.show(),o())}var n=this,r=t.newPanel,i=t.oldPanel,s=this._getFx();if(!s)return this._super(e,t);n.running=!0,i.length&&s.hide?i.animate(s.hide,s.hide.duration,function(){t.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),u()}):(t.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),i.hide(),u())}}))})(jQuery);(function(e){function n(t,n){var r=(t.attr("aria-describedby")||"").split(/\s+/);r.push(n),t.data("ui-tooltip-id",n).attr("aria-describedby",e.trim(r.join(" ")))}function r(t){var n=t.data("ui-tooltip-id"),r=(t.attr("aria-describedby")||"").split(/\s+/),i=e.inArray(n,r);i!==-1&&r.splice(i,1),t.removeData("ui-tooltip-id"),r=e.trim(r.join(" ")),r?t.attr("aria-describedby",r):t.removeAttr("aria-describedby")}var t=0;e.widget("ui.tooltip",{version:"1.9.2",options:{content:function(){return e(this).attr("title")},hide:!0,items:"[title]:not([disabled])",position:{my:"left top+15",at:"left bottom",collision:"flipfit flip"},show:!0,tooltipClass:null,track:!1,close:null,open:null},_create:function(){this._on({mouseover:"open",focusin:"open"}),this.tooltips={},this.parents={},this.options.disabled&&this._disable()},_setOption:function(t,n){var r=this;if(t==="disabled"){this[n?"_disable":"_enable"](),this.options[t]=n;return}this._super(t,n),t==="content"&&e.each(this.tooltips,function(e,t){r._updateContent(t)})},_disable:function(){var t=this;e.each(this.tooltips,function(n,r){var i=e.Event("blur");i.target=i.currentTarget=r[0],t.close(i,!0)}),this.element.find(this.options.items).andSelf().each(function(){var t=e(this);t.is("[title]")&&t.data("ui-tooltip-title",t.attr("title")).attr("title","")})},_enable:function(){this.element.find(this.options.items).andSelf().each(function(){var t=e(this);t.data("ui-tooltip-title")&&t.attr("title",t.data("ui-tooltip-title"))})},open:function(t){var n=this,r=e(t?t.target:this.element).closest(this.options.items);if(!r.length||r.data("ui-tooltip-id"))return;r.attr("title")&&r.data("ui-tooltip-title",r.attr("title")),r.data("ui-tooltip-open",!0),t&&t.type==="mouseover"&&r.parents().each(function(){var t=e(this),r;t.data("ui-tooltip-open")&&(r=e.Event("blur"),r.target=r.currentTarget=this,n.close(r,!0)),t.attr("title")&&(t.uniqueId(),n.parents[this.id]={element:this,title:t.attr("title")},t.attr("title",""))}),this._updateContent(r,t)},_updateContent:function(e,t){var n,r=this.options.content,i=this,s=t?t.type:null;if(typeof r=="string")return this._open(t,e,r);n=r.call(e[0],function(n){if(!e.data("ui-tooltip-open"))return;i._delay(function(){t&&(t.type=s),this._open(t,e,n)})}),n&&this._open(t,e,n)},_open:function(t,r,i){function f(e){a.of=e;if(s.is(":hidden"))return;s.position(a)}var s,o,u,a=e.extend({},this.options.position);if(!i)return;s=this._find(r);if(s.length){s.find(".ui-tooltip-content").html(i);return}r.is("[title]")&&(t&&t.type==="mouseover"?r.attr("title",""):r.removeAttr("title")),s=this._tooltip(r),n(r,s.attr("id")),s.find(".ui-tooltip-content").html(i),this.options.track&&t&&/^mouse/.test(t.type)?(this._on(this.document,{mousemove:f}),f(t)):s.position(e.extend({of:r},this.options.position)),s.hide(),this._show(s,this.options.show),this.options.show&&this.options.show.delay&&(u=setInterval(function(){s.is(":visible")&&(f(a.of),clearInterval(u))},e.fx.interval)),this._trigger("open",t,{tooltip:s}),o={keyup:function(t){if(t.keyCode===e.ui.keyCode.ESCAPE){var n=e.Event(t);n.currentTarget=r[0],this.close(n,!0)}},remove:function(){this._removeTooltip(s)}};if(!t||t.type==="mouseover")o.mouseleave="close";if(!t||t.type==="focusin")o.focusout="close";this._on(!0,r,o)},close:function(t){var n=this,i=e(t?t.currentTarget:this.element),s=this._find(i);if(this.closing)return;i.data("ui-tooltip-title")&&i.attr("title",i.data("ui-tooltip-title")),r(i),s.stop(!0),this._hide(s,this.options.hide,function(){n._removeTooltip(e(this))}),i.removeData("ui-tooltip-open"),this._off(i,"mouseleave focusout keyup"),i[0]!==this.element[0]&&this._off(i,"remove"),this._off(this.document,"mousemove"),t&&t.type==="mouseleave"&&e.each(this.parents,function(t,r){e(r.element).attr("title",r.title),delete n.parents[t]}),this.closing=!0,this._trigger("close",t,{tooltip:s}),this.closing=!1},_tooltip:function(n){var r="ui-tooltip-"+t++,i=e("
      ").attr({id:r,role:"tooltip"}).addClass("ui-tooltip ui-widget ui-corner-all ui-widget-content "+(this.options.tooltipClass||""));return e("
      ").addClass("ui-tooltip-content").appendTo(i),i.appendTo(this.document[0].body),e.fn.bgiframe&&i.bgiframe(),this.tooltips[r]=n,i},_find:function(t){var n=t.data("ui-tooltip-id");return n?e("#"+n):e()},_removeTooltip:function(e){e.remove(),delete this.tooltips[e.attr("id")]},_destroy:function(){var t=this;e.each(this.tooltips,function(n,r){var i=e.Event("blur");i.target=i.currentTarget=r[0],t.close(i,!0),e("#"+n).remove(),r.data("ui-tooltip-title")&&(r.attr("title",r.data("ui-tooltip-title")),r.removeData("ui-tooltip-title"))})}})})(jQuery);jQuery.effects||function(e,t){var n=e.uiBackCompat!==!1,r="ui-effects-";e.effects={effect:{}},function(t,n){function p(e,t,n){var r=a[t.type]||{};return e==null?n||!t.def?null:t.def:(e=r.floor?~~e:parseFloat(e),isNaN(e)?t.def:r.mod?(e+r.mod)%r.mod:0>e?0:r.max")[0],c,h=t.each;l.style.cssText="background-color:rgba(1,1,1,.5)",f.rgba=l.style.backgroundColor.indexOf("rgba")>-1,h(u,function(e,t){t.cache="_"+e,t.props.alpha={idx:3,type:"percent",def:1}}),o.fn=t.extend(o.prototype,{parse:function(r,i,s,a){if(r===n)return this._rgba=[null,null,null,null],this;if(r.jquery||r.nodeType)r=t(r).css(i),i=n;var f=this,l=t.type(r),v=this._rgba=[];i!==n&&(r=[r,i,s,a],l="array");if(l==="string")return this.parse(d(r)||c._default);if(l==="array")return h(u.rgba.props,function(e,t){v[t.idx]=p(r[t.idx],t)}),this;if(l==="object")return r instanceof o?h(u,function(e,t){r[t.cache]&&(f[t.cache]=r[t.cache].slice())}):h(u,function(t,n){var i=n.cache;h(n.props,function(e,t){if(!f[i]&&n.to){if(e==="alpha"||r[e]==null)return;f[i]=n.to(f._rgba)}f[i][t.idx]=p(r[e],t,!0)}),f[i]&&e.inArray(null,f[i].slice(0,3))<0&&(f[i][3]=1,n.from&&(f._rgba=n.from(f[i])))}),this},is:function(e){var t=o(e),n=!0,r=this;return h(u,function(e,i){var s,o=t[i.cache];return o&&(s=r[i.cache]||i.to&&i.to(r._rgba)||[],h(i.props,function(e,t){if(o[t.idx]!=null)return n=o[t.idx]===s[t.idx],n})),n}),n},_space:function(){var e=[],t=this;return h(u,function(n,r){t[r.cache]&&e.push(n)}),e.pop()},transition:function(e,t){var n=o(e),r=n._space(),i=u[r],s=this.alpha()===0?o("transparent"):this,f=s[i.cache]||i.to(s._rgba),l=f.slice();return n=n[i.cache],h(i.props,function(e,r){var i=r.idx,s=f[i],o=n[i],u=a[r.type]||{};if(o===null)return;s===null?l[i]=o:(u.mod&&(o-s>u.mod/2?s+=u.mod:s-o>u.mod/2&&(s-=u.mod)),l[i]=p((o-s)*t+s,r))}),this[r](l)},blend:function(e){if(this._rgba[3]===1)return this;var n=this._rgba.slice(),r=n.pop(),i=o(e)._rgba;return o(t.map(n,function(e,t){return(1-r)*i[t]+r*e}))},toRgbaString:function(){var e="rgba(",n=t.map(this._rgba,function(e,t){return e==null?t>2?1:0:e});return n[3]===1&&(n.pop(),e="rgb("),e+n.join()+")"},toHslaString:function(){var e="hsla(",n=t.map(this.hsla(),function(e,t){return e==null&&(e=t>2?1:0),t&&t<3&&(e=Math.round(e*100)+"%"),e});return n[3]===1&&(n.pop(),e="hsl("),e+n.join()+")"},toHexString:function(e){var n=this._rgba.slice(),r=n.pop();return e&&n.push(~~(r*255)),"#"+t.map(n,function(e){return e=(e||0).toString(16),e.length===1?"0"+e:e}).join("")},toString:function(){return this._rgba[3]===0?"transparent":this.toRgbaString()}}),o.fn.parse.prototype=o.fn,u.hsla.to=function(e){if(e[0]==null||e[1]==null||e[2]==null)return[null,null,null,e[3]];var t=e[0]/255,n=e[1]/255,r=e[2]/255,i=e[3],s=Math.max(t,n,r),o=Math.min(t,n,r),u=s-o,a=s+o,f=a*.5,l,c;return o===s?l=0:t===s?l=60*(n-r)/u+360:n===s?l=60*(r-t)/u+120:l=60*(t-n)/u+240,f===0||f===1?c=f:f<=.5?c=u/a:c=u/(2-a),[Math.round(l)%360,c,f,i==null?1:i]},u.hsla.from=function(e){if(e[0]==null||e[1]==null||e[2]==null)return[null,null,null,e[3]];var t=e[0]/360,n=e[1],r=e[2],i=e[3],s=r<=.5?r*(1+n):r+n-r*n,o=2*r-s;return[Math.round(v(o,s,t+1/3)*255),Math.round(v(o,s,t)*255),Math.round(v(o,s,t-1/3)*255),i]},h(u,function(e,r){var s=r.props,u=r.cache,a=r.to,f=r.from;o.fn[e]=function(e){a&&!this[u]&&(this[u]=a(this._rgba));if(e===n)return this[u].slice();var r,i=t.type(e),l=i==="array"||i==="object"?e:arguments,c=this[u].slice();return h(s,function(e,t){var n=l[i==="object"?e:t.idx];n==null&&(n=c[t.idx]),c[t.idx]=p(n,t)}),f?(r=o(f(c)),r[u]=c,r):o(c)},h(s,function(n,r){if(o.fn[n])return;o.fn[n]=function(s){var o=t.type(s),u=n==="alpha"?this._hsla?"hsla":"rgba":e,a=this[u](),f=a[r.idx],l;return o==="undefined"?f:(o==="function"&&(s=s.call(this,f),o=t.type(s)),s==null&&r.empty?this:(o==="string"&&(l=i.exec(s),l&&(s=f+parseFloat(l[2])*(l[1]==="+"?1:-1))),a[r.idx]=s,this[u](a)))}})}),h(r,function(e,n){t.cssHooks[n]={set:function(e,r){var i,s,u="";if(t.type(r)!=="string"||(i=d(r))){r=o(i||r);if(!f.rgba&&r._rgba[3]!==1){s=n==="backgroundColor"?e.parentNode:e;while((u===""||u==="transparent")&&s&&s.style)try{u=t.css(s,"backgroundColor"),s=s.parentNode}catch(a){}r=r.blend(u&&u!=="transparent"?u:"_default")}r=r.toRgbaString()}try{e.style[n]=r}catch(l){}}},t.fx.step[n]=function(e){e.colorInit||(e.start=o(e.elem,n),e.end=o(e.end),e.colorInit=!0),t.cssHooks[n].set(e.elem,e.start.transition(e.end,e.pos))}}),t.cssHooks.borderColor={expand:function(e){var t={};return h(["Top","Right","Bottom","Left"],function(n,r){t["border"+r+"Color"]=e}),t}},c=t.Color.names={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#008000",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00",transparent:[null,null,null,0],_default:"#ffffff"}}(jQuery),function(){function i(){var t=this.ownerDocument.defaultView?this.ownerDocument.defaultView.getComputedStyle(this,null):this.currentStyle,n={},r,i;if(t&&t.length&&t[0]&&t[t[0]]){i=t.length;while(i--)r=t[i],typeof t[r]=="string"&&(n[e.camelCase(r)]=t[r])}else for(r in t)typeof t[r]=="string"&&(n[r]=t[r]);return n}function s(t,n){var i={},s,o;for(s in n)o=n[s],t[s]!==o&&!r[s]&&(e.fx.step[s]||!isNaN(parseFloat(o)))&&(i[s]=o);return i}var n=["add","remove","toggle"],r={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};e.each(["borderLeftStyle","borderRightStyle","borderBottomStyle","borderTopStyle"],function(t,n){e.fx.step[n]=function(e){if(e.end!=="none"&&!e.setAttr||e.pos===1&&!e.setAttr)jQuery.style(e.elem,n,e.end),e.setAttr=!0}}),e.effects.animateClass=function(t,r,o,u){var a=e.speed(r,o,u);return this.queue(function(){var r=e(this),o=r.attr("class")||"",u,f=a.children?r.find("*").andSelf():r;f=f.map(function(){var t=e(this);return{el:t,start:i.call(this)}}),u=function(){e.each(n,function(e,n){t[n]&&r[n+"Class"](t[n])})},u(),f=f.map(function(){return this.end=i.call(this.el[0]),this.diff=s(this.start,this.end),this}),r.attr("class",o),f=f.map(function(){var t=this,n=e.Deferred(),r=jQuery.extend({},a,{queue:!1,complete:function(){n.resolve(t)}});return this.el.animate(this.diff,r),n.promise()}),e.when.apply(e,f.get()).done(function(){u(),e.each(arguments,function(){var t=this.el;e.each(this.diff,function(e){t.css(e,"")})}),a.complete.call(r[0])})})},e.fn.extend({_addClass:e.fn.addClass,addClass:function(t,n,r,i){return n?e.effects.animateClass.call(this,{add:t},n,r,i):this._addClass(t)},_removeClass:e.fn.removeClass,removeClass:function(t,n,r,i){return n?e.effects.animateClass.call(this,{remove:t},n,r,i):this._removeClass(t)},_toggleClass:e.fn.toggleClass,toggleClass:function(n,r,i,s,o){return typeof r=="boolean"||r===t?i?e.effects.animateClass.call(this,r?{add:n}:{remove:n},i,s,o):this._toggleClass(n,r):e.effects.animateClass.call(this,{toggle:n},r,i,s)},switchClass:function(t,n,r,i,s){return e.effects.animateClass.call(this,{add:n,remove:t},r,i,s)}})}(),function(){function i(t,n,r,i){e.isPlainObject(t)&&(n=t,t=t.effect),t={effect:t},n==null&&(n={}),e.isFunction(n)&&(i=n,r=null,n={});if(typeof n=="number"||e.fx.speeds[n])i=r,r=n,n={};return e.isFunction(r)&&(i=r,r=null),n&&e.extend(t,n),r=r||n.duration,t.duration=e.fx.off?0:typeof r=="number"?r:r in e.fx.speeds?e.fx.speeds[r]:e.fx.speeds._default,t.complete=i||n.complete,t}function s(t){return!t||typeof t=="number"||e.fx.speeds[t]?!0:typeof t=="string"&&!e.effects.effect[t]?n&&e.effects[t]?!1:!0:!1}e.extend(e.effects,{version:"1.9.2",save:function(e,t){for(var n=0;n
      ").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),i={width:t.width(),height:t.height()},s=document.activeElement;try{s.id}catch(o){s=document.body}return t.wrap(r),(t[0]===s||e.contains(t[0],s))&&e(s).focus(),r=t.parent(),t.css("position")==="static"?(r.css({position:"relative"}),t.css({position:"relative"})):(e.extend(n,{position:t.css("position"),zIndex:t.css("z-index")}),e.each(["top","left","bottom","right"],function(e,r){n[r]=t.css(r),isNaN(parseInt(n[r],10))&&(n[r]="auto")}),t.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),t.css(i),r.css(n).show()},removeWrapper:function(t){var n=document.activeElement;return t.parent().is(".ui-effects-wrapper")&&(t.parent().replaceWith(t),(t[0]===n||e.contains(t[0],n))&&e(n).focus()),t},setTransition:function(t,n,r,i){return i=i||{},e.each(n,function(e,n){var s=t.cssUnit(n);s[0]>0&&(i[n]=s[0]*r+s[1])}),i}}),e.fn.extend({effect:function(){function a(n){function u(){e.isFunction(i)&&i.call(r[0]),e.isFunction(n)&&n()}var r=e(this),i=t.complete,s=t.mode;(r.is(":hidden")?s==="hide":s==="show")?u():o.call(r[0],t,u)}var t=i.apply(this,arguments),r=t.mode,s=t.queue,o=e.effects.effect[t.effect],u=!o&&n&&e.effects[t.effect];return e.fx.off||!o&&!u?r?this[r](t.duration,t.complete):this.each(function(){t.complete&&t.complete.call(this)}):o?s===!1?this.each(a):this.queue(s||"fx",a):u.call(this,{options:t,duration:t.duration,callback:t.complete,mode:t.mode})},_show:e.fn.show,show:function(e){if(s(e))return this._show.apply(this,arguments);var t=i.apply(this,arguments);return t.mode="show",this.effect.call(this,t)},_hide:e.fn.hide,hide:function(e){if(s(e))return this._hide.apply(this,arguments);var t=i.apply(this,arguments);return t.mode="hide",this.effect.call(this,t)},__toggle:e.fn.toggle,toggle:function(t){if(s(t)||typeof t=="boolean"||e.isFunction(t))return this.__toggle.apply(this,arguments);var n=i.apply(this,arguments);return n.mode="toggle",this.effect.call(this,n)},cssUnit:function(t){var n=this.css(t),r=[];return e.each(["em","px","%","pt"],function(e,t){n.indexOf(t)>0&&(r=[parseFloat(n),t])}),r}})}(),function(){var t={};e.each(["Quad","Cubic","Quart","Quint","Expo"],function(e,n){t[n]=function(t){return Math.pow(t,e+2)}}),e.extend(t,{Sine:function(e){return 1-Math.cos(e*Math.PI/2)},Circ:function(e){return 1-Math.sqrt(1-e*e)},Elastic:function(e){return e===0||e===1?e:-Math.pow(2,8*(e-1))*Math.sin(((e-1)*80-7.5)*Math.PI/15)},Back:function(e){return e*e*(3*e-2)},Bounce:function(e){var t,n=4;while(e<((t=Math.pow(2,--n))-1)/11);return 1/Math.pow(4,3-n)-7.5625*Math.pow((t*3-2)/22-e,2)}}),e.each(t,function(t,n){e.easing["easeIn"+t]=n,e.easing["easeOut"+t]=function(e){return 1-n(1-e)},e.easing["easeInOut"+t]=function(e){return e<.5?n(e*2)/2:1-n(e*-2+2)/2}})}()}(jQuery);(function(e,t){var n=/up|down|vertical/,r=/up|left|vertical|horizontal/;e.effects.effect.blind=function(t,i){var s=e(this),o=["position","top","bottom","left","right","height","width"],u=e.effects.setMode(s,t.mode||"hide"),a=t.direction||"up",f=n.test(a),l=f?"height":"width",c=f?"top":"left",h=r.test(a),p={},d=u==="show",v,m,g;s.parent().is(".ui-effects-wrapper")?e.effects.save(s.parent(),o):e.effects.save(s,o),s.show(),v=e.effects.createWrapper(s).css({overflow:"hidden"}),m=v[l](),g=parseFloat(v.css(c))||0,p[l]=d?m:0,h||(s.css(f?"bottom":"right",0).css(f?"top":"left","auto").css({position:"absolute"}),p[c]=d?g:m+g),d&&(v.css(l,0),h||v.css(c,g+m)),v.animate(p,{duration:t.duration,easing:t.easing,queue:!1,complete:function(){u==="hide"&&s.hide(),e.effects.restore(s,o),e.effects.removeWrapper(s),i()}})}})(jQuery);(function(e,t){e.effects.effect.bounce=function(t,n){var r=e(this),i=["position","top","bottom","left","right","height","width"],s=e.effects.setMode(r,t.mode||"effect"),o=s==="hide",u=s==="show",a=t.direction||"up",f=t.distance,l=t.times||5,c=l*2+(u||o?1:0),h=t.duration/c,p=t.easing,d=a==="up"||a==="down"?"top":"left",v=a==="up"||a==="left",m,g,y,b=r.queue(),w=b.length;(u||o)&&i.push("opacity"),e.effects.save(r,i),r.show(),e.effects.createWrapper(r),f||(f=r[d==="top"?"outerHeight":"outerWidth"]()/3),u&&(y={opacity:1},y[d]=0,r.css("opacity",0).css(d,v?-f*2:f*2).animate(y,h,p)),o&&(f/=Math.pow(2,l-1)),y={},y[d]=0;for(m=0;m1&&b.splice.apply(b,[1,0].concat(b.splice(w,c+1))),r.dequeue()}})(jQuery);(function(e,t){e.effects.effect.clip=function(t,n){var r=e(this),i=["position","top","bottom","left","right","height","width"],s=e.effects.setMode(r,t.mode||"hide"),o=s==="show",u=t.direction||"vertical",a=u==="vertical",f=a?"height":"width",l=a?"top":"left",c={},h,p,d;e.effects.save(r,i),r.show(),h=e.effects.createWrapper(r).css({overflow:"hidden"}),p=r[0].tagName==="IMG"?h:r,d=p[f](),o&&(p.css(f,0),p.css(l,d/2)),c[f]=o?d:0,c[l]=o?0:d/2,p.animate(c,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){o||r.hide(),e.effects.restore(r,i),e.effects.removeWrapper(r),n()}})}})(jQuery);(function(e,t){e.effects.effect.drop=function(t,n){var r=e(this),i=["position","top","bottom","left","right","opacity","height","width"],s=e.effects.setMode(r,t.mode||"hide"),o=s==="show",u=t.direction||"left",a=u==="up"||u==="down"?"top":"left",f=u==="up"||u==="left"?"pos":"neg",l={opacity:o?1:0},c;e.effects.save(r,i),r.show(),e.effects.createWrapper(r),c=t.distance||r[a==="top"?"outerHeight":"outerWidth"](!0)/2,o&&r.css("opacity",0).css(a,f==="pos"?-c:c),l[a]=(o?f==="pos"?"+=":"-=":f==="pos"?"-=":"+=")+c,r.animate(l,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){s==="hide"&&r.hide(),e.effects.restore(r,i),e.effects.removeWrapper(r),n()}})}})(jQuery);(function(e,t){e.effects.effect.explode=function(t,n){function y(){c.push(this),c.length===r*i&&b()}function b(){s.css({visibility:"visible"}),e(c).remove(),u||s.hide(),n()}var r=t.pieces?Math.round(Math.sqrt(t.pieces)):3,i=r,s=e(this),o=e.effects.setMode(s,t.mode||"hide"),u=o==="show",a=s.show().css("visibility","hidden").offset(),f=Math.ceil(s.outerWidth()/i),l=Math.ceil(s.outerHeight()/r),c=[],h,p,d,v,m,g;for(h=0;h
      ").css({position:"absolute",visibility:"visible",left:-p*f,top:-h*l}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:f,height:l,left:d+(u?m*f:0),top:v+(u?g*l:0),opacity:u?0:1}).animate({left:d+(u?0:m*f),top:v+(u?0:g*l),opacity:u?1:0},t.duration||500,t.easing,y)}}})(jQuery);(function(e,t){e.effects.effect.fade=function(t,n){var r=e(this),i=e.effects.setMode(r,t.mode||"toggle");r.animate({opacity:i},{queue:!1,duration:t.duration,easing:t.easing,complete:n})}})(jQuery);(function(e,t){e.effects.effect.fold=function(t,n){var r=e(this),i=["position","top","bottom","left","right","height","width"],s=e.effects.setMode(r,t.mode||"hide"),o=s==="show",u=s==="hide",a=t.size||15,f=/([0-9]+)%/.exec(a),l=!!t.horizFirst,c=o!==l,h=c?["width","height"]:["height","width"],p=t.duration/2,d,v,m={},g={};e.effects.save(r,i),r.show(),d=e.effects.createWrapper(r).css({overflow:"hidden"}),v=c?[d.width(),d.height()]:[d.height(),d.width()],f&&(a=parseInt(f[1],10)/100*v[u?0:1]),o&&d.css(l?{height:0,width:a}:{height:a,width:0}),m[h[0]]=o?v[0]:a,g[h[1]]=o?v[1]:0,d.animate(m,p,t.easing).animate(g,p,t.easing,function(){u&&r.hide(),e.effects.restore(r,i),e.effects.removeWrapper(r),n()})}})(jQuery);(function(e,t){e.effects.effect.highlight=function(t,n){var r=e(this),i=["backgroundImage","backgroundColor","opacity"],s=e.effects.setMode(r,t.mode||"show"),o={backgroundColor:r.css("backgroundColor")};s==="hide"&&(o.opacity=0),e.effects.save(r,i),r.show().css({backgroundImage:"none",backgroundColor:t.color||"#ffff99"}).animate(o,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){s==="hide"&&r.hide(),e.effects.restore(r,i),n()}})}})(jQuery);(function(e,t){e.effects.effect.pulsate=function(t,n){var r=e(this),i=e.effects.setMode(r,t.mode||"show"),s=i==="show",o=i==="hide",u=s||i==="hide",a=(t.times||5)*2+(u?1:0),f=t.duration/a,l=0,c=r.queue(),h=c.length,p;if(s||!r.is(":visible"))r.css("opacity",0).show(),l=1;for(p=1;p1&&c.splice.apply(c,[1,0].concat(c.splice(h,a+1))),r.dequeue()}})(jQuery);(function(e,t){e.effects.effect.puff=function(t,n){var r=e(this),i=e.effects.setMode(r,t.mode||"hide"),s=i==="hide",o=parseInt(t.percent,10)||150,u=o/100,a={height:r.height(),width:r.width(),outerHeight:r.outerHeight(),outerWidth:r.outerWidth()};e.extend(t,{effect:"scale",queue:!1,fade:!0,mode:i,complete:n,percent:s?o:100,from:s?a:{height:a.height*u,width:a.width*u,outerHeight:a.outerHeight*u,outerWidth:a.outerWidth*u}}),r.effect(t)},e.effects.effect.scale=function(t,n){var r=e(this),i=e.extend(!0,{},t),s=e.effects.setMode(r,t.mode||"effect"),o=parseInt(t.percent,10)||(parseInt(t.percent,10)===0?0:s==="hide"?0:100),u=t.direction||"both",a=t.origin,f={height:r.height(),width:r.width(),outerHeight:r.outerHeight(),outerWidth:r.outerWidth()},l={y:u!=="horizontal"?o/100:1,x:u!=="vertical"?o/100:1};i.effect="size",i.queue=!1,i.complete=n,s!=="effect"&&(i.origin=a||["middle","center"],i.restore=!0),i.from=t.from||(s==="show"?{height:0,width:0,outerHeight:0,outerWidth:0}:f),i.to={height:f.height*l.y,width:f.width*l.x,outerHeight:f.outerHeight*l.y,outerWidth:f.outerWidth*l.x},i.fade&&(s==="show"&&(i.from.opacity=0,i.to.opacity=1),s==="hide"&&(i.from.opacity=1,i.to.opacity=0)),r.effect(i)},e.effects.effect.size=function(t,n){var r,i,s,o=e(this),u=["position","top","bottom","left","right","width","height","overflow","opacity"],a=["position","top","bottom","left","right","overflow","opacity"],f=["width","height","overflow"],l=["fontSize"],c=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],h=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],p=e.effects.setMode(o,t.mode||"effect"),d=t.restore||p!=="effect",v=t.scale||"both",m=t.origin||["middle","center"],g=o.css("position"),y=d?u:a,b={height:0,width:0,outerHeight:0,outerWidth:0};p==="show"&&o.show(),r={height:o.height(),width:o.width(),outerHeight:o.outerHeight(),outerWidth:o.outerWidth()},t.mode==="toggle"&&p==="show"?(o.from=t.to||b,o.to=t.from||r):(o.from=t.from||(p==="show"?b:r),o.to=t.to||(p==="hide"?b:r)),s={from:{y:o.from.height/r.height,x:o.from.width/r.width},to:{y:o.to.height/r.height,x:o.to.width/r.width}};if(v==="box"||v==="both")s.from.y!==s.to.y&&(y=y.concat(c),o.from=e.effects.setTransition(o,c,s.from.y,o.from),o.to=e.effects.setTransition(o,c,s.to.y,o.to)),s.from.x!==s.to.x&&(y=y.concat(h),o.from=e.effects.setTransition(o,h,s.from.x,o.from),o.to=e.effects.setTransition(o,h,s.to.x,o.to));(v==="content"||v==="both")&&s.from.y!==s.to.y&&(y=y.concat(l).concat(f),o.from=e.effects.setTransition(o,l,s.from.y,o.from),o.to=e.effects.setTransition(o,l,s.to.y,o.to)),e.effects.save(o,y),o.show(),e.effects.createWrapper(o),o.css("overflow","hidden").css(o.from),m&&(i=e.effects.getBaseline(m,r),o.from.top=(r.outerHeight-o.outerHeight())*i.y,o.from.left=(r.outerWidth-o.outerWidth())*i.x,o.to.top=(r.outerHeight-o.to.outerHeight)*i.y,o.to.left=(r.outerWidth-o.to.outerWidth)*i.x),o.css(o.from);if(v==="content"||v==="both")c=c.concat(["marginTop","marginBottom"]).concat(l),h=h.concat(["marginLeft","marginRight"]),f=u.concat(c).concat(h),o.find("*[width]").each(function(){var n=e(this),r={height:n.height(),width:n.width(),outerHeight:n.outerHeight(),outerWidth:n.outerWidth()};d&&e.effects.save(n,f),n.from={height:r.height*s.from.y,width:r.width*s.from.x,outerHeight:r.outerHeight*s.from.y,outerWidth:r.outerWidth*s.from.x},n.to={height:r.height*s.to.y,width:r.width*s.to.x,outerHeight:r.height*s.to.y,outerWidth:r.width*s.to.x},s.from.y!==s.to.y&&(n.from=e.effects.setTransition(n,c,s.from.y,n.from),n.to=e.effects.setTransition(n,c,s.to.y,n.to)),s.from.x!==s.to.x&&(n.from=e.effects.setTransition(n,h,s.from.x,n.from),n.to=e.effects.setTransition(n,h,s.to.x,n.to)),n.css(n.from),n.animate(n.to,t.duration,t.easing,function(){d&&e.effects.restore(n,f)})});o.animate(o.to,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){o.to.opacity===0&&o.css("opacity",o.from.opacity),p==="hide"&&o.hide(),e.effects.restore(o,y),d||(g==="static"?o.css({position:"relative",top:o.to.top,left:o.to.left}):e.each(["top","left"],function(e,t){o.css(t,function(t,n){var r=parseInt(n,10),i=e?o.to.left:o.to.top;return n==="auto"?i+"px":r+i+"px"})})),e.effects.removeWrapper(o),n()}})}})(jQuery);(function(e,t){e.effects.effect.shake=function(t,n){var r=e(this),i=["position","top","bottom","left","right","height","width"],s=e.effects.setMode(r,t.mode||"effect"),o=t.direction||"left",u=t.distance||20,a=t.times||3,f=a*2+1,l=Math.round(t.duration/f),c=o==="up"||o==="down"?"top":"left",h=o==="up"||o==="left",p={},d={},v={},m,g=r.queue(),y=g.length;e.effects.save(r,i),r.show(),e.effects.createWrapper(r),p[c]=(h?"-=":"+=")+u,d[c]=(h?"+=":"-=")+u*2,v[c]=(h?"-=":"+=")+u*2,r.animate(p,l,t.easing);for(m=1;m1&&g.splice.apply(g,[1,0].concat(g.splice(y,f+1))),r.dequeue()}})(jQuery);(function(e,t){e.effects.effect.slide=function(t,n){var r=e(this),i=["position","top","bottom","left","right","width","height"],s=e.effects.setMode(r,t.mode||"show"),o=s==="show",u=t.direction||"left",a=u==="up"||u==="down"?"top":"left",f=u==="up"||u==="left",l,c={};e.effects.save(r,i),r.show(),l=t.distance||r[a==="top"?"outerHeight":"outerWidth"](!0),e.effects.createWrapper(r).css({overflow:"hidden"}),o&&r.css(a,f?isNaN(l)?"-"+l:-l:l),c[a]=(o?f?"+=":"-=":f?"-=":"+=")+l,r.animate(c,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){s==="hide"&&r.hide(),e.effects.restore(r,i),e.effects.removeWrapper(r),n()}})}})(jQuery);(function(e,t){e.effects.effect.transfer=function(t,n){var r=e(this),i=e(t.to),s=i.css("position")==="fixed",o=e("body"),u=s?o.scrollTop():0,a=s?o.scrollLeft():0,f=i.offset(),l={top:f.top-u,left:f.left-a,height:i.innerHeight(),width:i.innerWidth()},c=r.offset(),h=e('
      ').appendTo(document.body).addClass(t.className).css({top:c.top-u,left:c.left-a,height:r.innerHeight(),width:r.innerWidth(),position:s?"fixed":"absolute"}).animate(l,t.duration,t.easing,function(){h.remove(),n()})}})(jQuery); + +/* JQuery UJS 2.0.3 */ +(function(a,b){var c=function(){var b=a(document).data("events");return b&&b.click&&a.grep(b.click,function(a){return a.namespace==="rails"}).length};if(c()){a.error("jquery-ujs has already been loaded!")}var d;a.rails=d={linkClickSelector:"a[data-confirm], a[data-method], a[data-remote], a[data-disable-with]",inputChangeSelector:"select[data-remote], input[data-remote], textarea[data-remote]",formSubmitSelector:"form",formInputClickSelector:"form input[type=submit], form input[type=image], form button[type=submit], form button:not([type])",disableSelector:"input[data-disable-with], button[data-disable-with], textarea[data-disable-with]",enableSelector:"input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled",requiredInputSelector:"input[name][required]:not([disabled]),textarea[name][required]:not([disabled])",fileInputSelector:"input:file",linkDisableSelector:"a[data-disable-with]",CSRFProtection:function(b){var c=a('meta[name="csrf-token"]').attr("content");if(c)b.setRequestHeader("X-CSRF-Token",c)},fire:function(b,c,d){var e=a.Event(c);b.trigger(e,d);return e.result!==false},confirm:function(a){return confirm(a)},ajax:function(b){return a.ajax(b)},href:function(a){return a.attr("href")},handleRemote:function(c){var e,f,g,h,i,j,k,l;if(d.fire(c,"ajax:before")){h=c.data("cross-domain");i=h===b?null:h;j=c.data("with-credentials")||null;k=c.data("type")||a.ajaxSettings&&a.ajaxSettings.dataType;if(c.is("form")){e=c.attr("method");f=c.attr("action");g=c.serializeArray();var m=c.data("ujs:submit-button");if(m){g.push(m);c.data("ujs:submit-button",null)}}else if(c.is(d.inputChangeSelector)){e=c.data("method");f=c.data("url");g=c.serialize();if(c.data("params"))g=g+"&"+c.data("params")}else{e=c.data("method");f=d.href(c);g=c.data("params")||null}l={type:e||"GET",data:g,dataType:k,beforeSend:function(a,e){if(e.dataType===b){a.setRequestHeader("accept","*/*;q=0.5, "+e.accepts.script)}return d.fire(c,"ajax:beforeSend",[a,e])},success:function(a,b,d){c.trigger("ajax:success",[a,b,d])},complete:function(a,b){c.trigger("ajax:complete",[a,b])},error:function(a,b,d){c.trigger("ajax:error",[a,b,d])},xhrFields:{withCredentials:j},crossDomain:i};if(f){l.url=f}var n=d.ajax(l);c.trigger("ajax:send",n);return n}else{return false}},handleMethod:function(c){var e=d.href(c),f=c.data("method"),g=c.attr("target"),h=a("meta[name=csrf-token]").attr("content"),i=a("meta[name=csrf-param]").attr("content"),j=a('
      '),k='';if(i!==b&&h!==b){k+=''}if(g){j.attr("target",g)}j.hide().append(k).appendTo("body");j.submit()},disableFormElements:function(b){b.find(d.disableSelector).each(function(){var b=a(this),c=b.is("button")?"html":"val";b.data("ujs:enable-with",b[c]());b[c](b.data("disable-with"));b.prop("disabled",true)})},enableFormElements:function(b){b.find(d.enableSelector).each(function(){var b=a(this),c=b.is("button")?"html":"val";if(b.data("ujs:enable-with"))b[c](b.data("ujs:enable-with"));b.prop("disabled",false)})},allowAction:function(a){var b=a.data("confirm"),c=false,e;if(!b){return true}if(d.fire(a,"confirm")){c=d.confirm(b);e=d.fire(a,"confirm:complete",[c])}return c&&e},blankInputs:function(b,c,d){var e=a(),f,g,h=c||"input,textarea";b.find(h).each(function(){f=a(this);g=f.is(":checkbox,:radio")?f.is(":checked"):f.val();if(g==!!d){e=e.add(f)}});return e.length?e:false},nonBlankInputs:function(a,b){return d.blankInputs(a,b,true)},stopEverything:function(b){a(b.target).trigger("ujs:everythingStopped");b.stopImmediatePropagation();return false},callFormSubmitBindings:function(c,d){var e=c.data("events"),f=true;if(e!==b&&e["submit"]!==b){a.each(e["submit"],function(a,b){if(typeof b.handler==="function")return f=b.handler(d)})}return f},disableElement:function(a){a.data("ujs:enable-with",a.html());a.html(a.data("disable-with"));a.bind("click.railsDisable",function(a){return d.stopEverything(a)})},enableElement:function(a){if(a.data("ujs:enable-with")!==b){a.html(a.data("ujs:enable-with"));a.data("ujs:enable-with",false)}a.unbind("click.railsDisable")}};if(d.fire(a(document),"rails:attachBindings")){a.ajaxPrefilter(function(a,b,c){if(!a.crossDomain){d.CSRFProtection(c)}});a(document).delegate(d.linkDisableSelector,"ajax:complete",function(){d.enableElement(a(this))});a(document).delegate(d.linkClickSelector,"click.rails",function(c){var e=a(this),f=e.data("method"),g=e.data("params");if(!d.allowAction(e))return d.stopEverything(c);if(e.is(d.linkDisableSelector))d.disableElement(e);if(e.data("remote")!==b){if((c.metaKey||c.ctrlKey)&&(!f||f==="GET")&&!g){return true}if(d.handleRemote(e)===false){d.enableElement(e)}return false}else if(e.data("method")){d.handleMethod(e);return false}});a(document).delegate(d.inputChangeSelector,"change.rails",function(b){var c=a(this);if(!d.allowAction(c))return d.stopEverything(b);d.handleRemote(c);return false});a(document).delegate(d.formSubmitSelector,"submit.rails",function(c){var e=a(this),f=e.data("remote")!==b,g=d.blankInputs(e,d.requiredInputSelector),h=d.nonBlankInputs(e,d.fileInputSelector);if(!d.allowAction(e))return d.stopEverything(c);if(g&&e.attr("novalidate")==b&&d.fire(e,"ajax:aborted:required",[g])){return d.stopEverything(c)}if(f){if(h){setTimeout(function(){d.disableFormElements(e)},13);return d.fire(e,"ajax:aborted:file",[h])}if(!a.support.submitBubbles&&a().jquery<"1.7"&&d.callFormSubmitBindings(e,c)===false)return d.stopEverything(c);d.handleRemote(e);return false}else{setTimeout(function(){d.disableFormElements(e)},13)}});a(document).delegate(d.formInputClickSelector,"click.rails",function(b){var c=a(this);if(!d.allowAction(c))return d.stopEverything(b);var e=c.attr("name"),f=e?{name:e,value:c.val()}:null;c.closest("form").data("ujs:submit-button",f)});a(document).delegate(d.formSubmitSelector,"ajax:beforeSend.rails",function(b){if(this==b.target)d.disableFormElements(a(this))});a(document).delegate(d.formSubmitSelector,"ajax:complete.rails",function(b){if(this==b.target)d.enableFormElements(a(this))});a(function(){csrf_token=a("meta[name=csrf-token]").attr("content");csrf_param=a("meta[name=csrf-param]").attr("content");a('form input[name="'+csrf_param+'"]').val(csrf_token)})}})(jQuery) diff -r 0a574315af3e -r 4f746d8966dd public/javascripts/jstoolbar/jstoolbar-textile.min.js --- a/public/javascripts/jstoolbar/jstoolbar-textile.min.js Fri Jun 14 09:07:32 2013 +0100 +++ b/public/javascripts/jstoolbar/jstoolbar-textile.min.js Fri Jun 14 09:28:30 2013 +0100 @@ -1,1 +1,2 @@ -function jsToolBar(e){if(!document.createElement){return}if(!e){return}if(typeof document["selection"]=="undefined"&&typeof e["setSelectionRange"]=="undefined"){return}this.textarea=e;this.editor=document.createElement("div");this.editor.className="jstEditor";this.textarea.parentNode.insertBefore(this.editor,this.textarea);this.editor.appendChild(this.textarea);this.toolbar=document.createElement("div");this.toolbar.className="jstElements";this.editor.parentNode.insertBefore(this.toolbar,this.editor);if(this.editor.addEventListener&&navigator.appVersion.match(/\bMSIE\b/)){this.handle=document.createElement("div");this.handle.className="jstHandle";var t=this.resizeDragStart;var n=this;this.handle.addEventListener("mousedown",function(e){t.call(n,e)},false);window.addEventListener("unload",function(){var e=n.handle.parentNode.removeChild(n.handle);delete n.handle},false);this.editor.parentNode.insertBefore(this.handle,this.editor.nextSibling)}this.context=null;this.toolNodes={}}function jsButton(e,t,n,r){if(typeof jsToolBar.strings=="undefined"){this.title=e||null}else{this.title=jsToolBar.strings[e]||e||null}this.fn=t||function(){};this.scope=n||null;this.className=r||null}function jsSpace(e){this.id=e||null;this.width=null}function jsCombo(e,t,n,r,i){this.title=e||null;this.options=t||null;this.scope=n||null;this.fn=r||function(){};this.className=i||null}jsButton.prototype.draw=function(){if(!this.scope)return null;var e=document.createElement("button");e.setAttribute("type","button");e.tabIndex=200;if(this.className)e.className=this.className;e.title=this.title;var t=document.createElement("span");t.appendChild(document.createTextNode(this.title));e.appendChild(t);if(this.icon!=undefined){e.style.backgroundImage="url("+this.icon+")"}if(typeof this.fn=="function"){var n=this;e.onclick=function(){try{n.fn.apply(n.scope,arguments)}catch(e){}return false}}return e};jsSpace.prototype.draw=function(){var e=document.createElement("span");if(this.id)e.id=this.id;e.appendChild(document.createTextNode(String.fromCharCode(160)));e.className="jstSpacer";if(this.width)e.style.marginRight=this.width+"px";return e};jsCombo.prototype.draw=function(){if(!this.scope||!this.options)return null;var e=document.createElement("select");if(this.className)e.className=className;e.title=this.title;for(var t in this.options){var n=document.createElement("option");n.value=t;n.appendChild(document.createTextNode(this.options[t]));e.appendChild(n)}var r=this;e.onchange=function(){try{r.fn.call(r.scope,this.value)}catch(e){alert(e)}return false};return e};jsToolBar.prototype={base_url:"",mode:"wiki",elements:{},help_link:"",getMode:function(){return this.mode},setMode:function(e){this.mode=e||"wiki"},switchMode:function(e){e=e||"wiki";this.draw(e)},setHelpLink:function(e){this.help_link=e},button:function(e){var t=this.elements[e];if(typeof t.fn[this.mode]!="function")return null;var n=new jsButton(t.title,t.fn[this.mode],this,"jstb_"+e);if(t.icon!=undefined)n.icon=t.icon;return n},space:function(e){var t=new jsSpace(e);if(this.elements[e].width!==undefined)t.width=this.elements[e].width;return t},combo:function(e){var t=this.elements[e];var n=t[this.mode].list.length;if(typeof t[this.mode].fn!="function"||n==0){return null}else{var r={};for(var i=0;iAide";this.toolbar.appendChild(t);var n,r,i;for(var s in this.elements){n=this.elements[s];var o=n.type==undefined||n.type==""||n.disabled!=undefined&&n.disabled||n.context!=undefined&&n.context!=null&&n.context!=this.context;if(!o&&typeof this[n.type]=="function"){r=this[n.type](s);if(r)i=r.draw();if(i){this.toolNodes[s]=i;this.toolbar.appendChild(i)}}}},singleTag:function(e,t){e=e||null;t=t||e;if(!e||!t){return}this.encloseSelection(e,t)},encloseLineSelection:function(e,t,n){this.textarea.focus();e=e||"";t=t||"";var r,i,s,o,u,a;if(typeof document["selection"]!="undefined"){s=document.selection.createRange().text}else if(typeof this.textarea["setSelectionRange"]!="undefined"){r=this.textarea.selectionStart;i=this.textarea.selectionEnd;o=this.textarea.scrollTop;r=this.textarea.value.substring(0,r).replace(/[^\r\n]*$/g,"").length;i=this.textarea.value.length-this.textarea.value.substring(i,this.textarea.value.length).replace(/^[^\r\n]*/,"").length;s=this.textarea.value.substring(r,i)}if(s.match(/ $/)){s=s.substring(0,s.length-1);t=t+" "}if(typeof n=="function"){a=s?n.call(this,s):n("")}else{a=s?s:""}u=e+a+t;if(typeof document["selection"]!="undefined"){document.selection.createRange().text=u;var f=this.textarea.createTextRange();f.collapse(false);f.move("character",-t.length);f.select()}else if(typeof this.textarea["setSelectionRange"]!="undefined"){this.textarea.value=this.textarea.value.substring(0,r)+u+this.textarea.value.substring(i);if(s){this.textarea.setSelectionRange(r+u.length,r+u.length)}else{this.textarea.setSelectionRange(r+e.length,r+e.length)}this.textarea.scrollTop=o}},encloseSelection:function(e,t,n){this.textarea.focus();e=e||"";t=t||"";var r,i,s,o,u,a;if(typeof document["selection"]!="undefined"){s=document.selection.createRange().text}else if(typeof this.textarea["setSelectionRange"]!="undefined"){r=this.textarea.selectionStart;i=this.textarea.selectionEnd;o=this.textarea.scrollTop;s=this.textarea.value.substring(r,i)}if(s.match(/ $/)){s=s.substring(0,s.length-1);t=t+" "}if(typeof n=="function"){a=s?n.call(this,s):n("")}else{a=s?s:""}u=e+a+t;if(typeof document["selection"]!="undefined"){document.selection.createRange().text=u;var f=this.textarea.createTextRange();f.collapse(false);f.move("character",-t.length);f.select()}else if(typeof this.textarea["setSelectionRange"]!="undefined"){this.textarea.value=this.textarea.value.substring(0,r)+u+this.textarea.value.substring(i);if(s){this.textarea.setSelectionRange(r+u.length,r+u.length)}else{this.textarea.setSelectionRange(r+e.length,r+e.length)}this.textarea.scrollTop=o}},stripBaseURL:function(e){if(this.base_url!=""){var t=e.indexOf(this.base_url);if(t==0){e=e.substr(this.base_url.length)}}return e}};jsToolBar.prototype.resizeSetStartH=function(){this.dragStartH=this.textarea.offsetHeight+0};jsToolBar.prototype.resizeDragStart=function(e){var t=this;this.dragStartY=e.clientY;this.resizeSetStartH();document.addEventListener("mousemove",this.dragMoveHdlr=function(e){t.resizeDragMove(e)},false);document.addEventListener("mouseup",this.dragStopHdlr=function(e){t.resizeDragStop(e)},false)};jsToolBar.prototype.resizeDragMove=function(e){this.textarea.style.height=this.dragStartH+e.clientY-this.dragStartY+"px"};jsToolBar.prototype.resizeDragStop=function(e){document.removeEventListener("mousemove",this.dragMoveHdlr,false);document.removeEventListener("mouseup",this.dragStopHdlr,false)};jsToolBar.prototype.elements.strong={type:"button",title:"Strong",fn:{wiki:function(){this.singleTag("*")}}};jsToolBar.prototype.elements.em={type:"button",title:"Italic",fn:{wiki:function(){this.singleTag("_")}}};jsToolBar.prototype.elements.ins={type:"button",title:"Underline",fn:{wiki:function(){this.singleTag("+")}}};jsToolBar.prototype.elements.del={type:"button",title:"Deleted",fn:{wiki:function(){this.singleTag("-")}}};jsToolBar.prototype.elements.code={type:"button",title:"Code",fn:{wiki:function(){this.singleTag("@")}}};jsToolBar.prototype.elements.space1={type:"space"};jsToolBar.prototype.elements.h1={type:"button",title:"Heading 1",fn:{wiki:function(){this.encloseLineSelection("h1. ","",function(e){e=e.replace(/^h\d+\.\s+/,"");return e})}}};jsToolBar.prototype.elements.h2={type:"button",title:"Heading 2",fn:{wiki:function(){this.encloseLineSelection("h2. ","",function(e){e=e.replace(/^h\d+\.\s+/,"");return e})}}};jsToolBar.prototype.elements.h3={type:"button",title:"Heading 3",fn:{wiki:function(){this.encloseLineSelection("h3. ","",function(e){e=e.replace(/^h\d+\.\s+/,"");return e})}}};jsToolBar.prototype.elements.space2={type:"space"};jsToolBar.prototype.elements.ul={type:"button",title:"Unordered list",fn:{wiki:function(){this.encloseLineSelection("","",function(e){e=e.replace(/\r/g,"");return e.replace(/(\n|^)[#-]?\s*/g,"$1* ")})}}};jsToolBar.prototype.elements.ol={type:"button",title:"Ordered list",fn:{wiki:function(){this.encloseLineSelection("","",function(e){e=e.replace(/\r/g,"");return e.replace(/(\n|^)[*-]?\s*/g,"$1# ")})}}};jsToolBar.prototype.elements.space3={type:"space"};jsToolBar.prototype.elements.bq={type:"button",title:"Quote",fn:{wiki:function(){this.encloseLineSelection("","",function(e){e=e.replace(/\r/g,"");return e.replace(/(\n|^) *([^\n]*)/g,"$1> $2")})}}};jsToolBar.prototype.elements.unbq={type:"button",title:"Unquote",fn:{wiki:function(){this.encloseLineSelection("","",function(e){e=e.replace(/\r/g,"");return e.replace(/(\n|^) *[>]? *([^\n]*)/g,"$1$2")})}}};jsToolBar.prototype.elements.pre={type:"button",title:"Preformatted text",fn:{wiki:function(){this.encloseLineSelection("
      \n","\n
      ")}}};jsToolBar.prototype.elements.space4={type:"space"};jsToolBar.prototype.elements.link={type:"button",title:"Wiki link",fn:{wiki:function(){this.encloseSelection("[[","]]")}}};jsToolBar.prototype.elements.img={type:"button",title:"Image",fn:{wiki:function(){this.encloseSelection("!","!")}}} \ No newline at end of file +function jsToolBar(e){if(!document.createElement){return}if(!e){return}if(typeof document["selection"]=="undefined"&&typeof e["setSelectionRange"]=="undefined"){return}this.textarea=e;this.editor=document.createElement("div");this.editor.className="jstEditor";this.textarea.parentNode.insertBefore(this.editor,this.textarea);this.editor.appendChild(this.textarea);this.toolbar=document.createElement("div");this.toolbar.className="jstElements";this.editor.parentNode.insertBefore(this.toolbar,this.editor);if(this.editor.addEventListener&&navigator.appVersion.match(/\bMSIE\b/)){this.handle=document.createElement("div");this.handle.className="jstHandle";var t=this.resizeDragStart;var n=this;this.handle.addEventListener("mousedown",function(e){t.call(n,e)},false);window.addEventListener("unload",function(){var e=n.handle.parentNode.removeChild(n.handle);delete n.handle},false);this.editor.parentNode.insertBefore(this.handle,this.editor.nextSibling)}this.context=null;this.toolNodes={}}function jsButton(e,t,n,r){if(typeof jsToolBar.strings=="undefined"){this.title=e||null}else{this.title=jsToolBar.strings[e]||e||null}this.fn=t||function(){};this.scope=n||null;this.className=r||null}function jsSpace(e){this.id=e||null;this.width=null}function jsCombo(e,t,n,r,i){this.title=e||null;this.options=t||null;this.scope=n||null;this.fn=r||function(){};this.className=i||null}jsButton.prototype.draw=function(){if(!this.scope)return null;var e=document.createElement("button");e.setAttribute("type","button");e.tabIndex=200;if(this.className)e.className=this.className;e.title=this.title;var t=document.createElement("span");t.appendChild(document.createTextNode(this.title));e.appendChild(t);if(this.icon!=undefined){e.style.backgroundImage="url("+this.icon+")"}if(typeof this.fn=="function"){var n=this;e.onclick=function(){try{n.fn.apply(n.scope,arguments)}catch(e){}return false}}return e};jsSpace.prototype.draw=function(){var e=document.createElement("span");if(this.id)e.id=this.id;e.appendChild(document.createTextNode(String.fromCharCode(160)));e.className="jstSpacer";if(this.width)e.style.marginRight=this.width+"px";return e};jsCombo.prototype.draw=function(){if(!this.scope||!this.options)return null;var e=document.createElement("select");if(this.className)e.className=className;e.title=this.title;for(var t in this.options){var n=document.createElement("option");n.value=t;n.appendChild(document.createTextNode(this.options[t]));e.appendChild(n)}var r=this;e.onchange=function(){try{r.fn.call(r.scope,this.value)}catch(e){alert(e)}return false};return e};jsToolBar.prototype={base_url:"",mode:"wiki",elements:{},help_link:"",getMode:function(){return this.mode},setMode:function(e){this.mode=e||"wiki"},switchMode:function(e){e=e||"wiki";this.draw(e)},setHelpLink:function(e){this.help_link=e},button:function(e){var t=this.elements[e];if(typeof t.fn[this.mode]!="function")return null;var n=new jsButton(t.title,t.fn[this.mode],this,"jstb_"+e);if(t.icon!=undefined)n.icon=t.icon;return n},space:function(e){var t=new jsSpace(e);if(this.elements[e].width!==undefined)t.width=this.elements[e].width;return t},combo:function(e){var t=this.elements[e];var n=t[this.mode].list.length;if(typeof t[this.mode].fn!="function"||n==0){return null}else{var r={};for(var i=0;i $2")})}}};jsToolBar.prototype.elements.unbq={type:"button",title:"Unquote",fn:{wiki:function(){this.encloseLineSelection("","",function(e){e=e.replace(/\r/g,"");return e.replace(/(\n|^) *[>]? *([^\n]*)/g,"$1$2")})}}};jsToolBar.prototype.elements.pre={type:"button",title:"Preformatted text",fn:{wiki:function(){this.encloseLineSelection("
      \n","\n
      ")}}};jsToolBar.prototype.elements.space4={type:"space"};jsToolBar.prototype.elements.link={type:"button",title:"Wiki link",fn:{wiki:function(){this.encloseSelection("[[","]]")}}};jsToolBar.prototype.elements.img={type:"button",title:"Image",fn:{wiki:function(){this.encloseSelection("!","!")}}};jsToolBar.prototype.elements.space5={type:"space"};jsToolBar.prototype.elements.help={type:"button",title:"Help",fn:{wiki:function(){window.open(this.help_link,"","resizable=yes, location=no, width=300, height=640, menubar=no, status=no, scrollbars=yes")}}} diff -r 0a574315af3e -r 4f746d8966dd public/javascripts/jstoolbar/jstoolbar.js --- a/public/javascripts/jstoolbar/jstoolbar.js Fri Jun 14 09:07:32 2013 +0100 +++ b/public/javascripts/jstoolbar/jstoolbar.js Fri Jun 14 09:28:30 2013 +0100 @@ -207,12 +207,6 @@ } this.toolNodes = {}; // vide les raccourcis DOM/**/ - var h = document.createElement('div'); - h.className = 'help' - h.innerHTML = this.help_link; - 'Aide'; - this.toolbar.appendChild(h); - // Draw toolbar elements var b, tool, newTool; diff -r 0a574315af3e -r 4f746d8966dd public/javascripts/jstoolbar/lang/jstoolbar-az.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/javascripts/jstoolbar/lang/jstoolbar-az.js Fri Jun 14 09:28:30 2013 +0100 @@ -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'] = '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'; diff -r 0a574315af3e -r 4f746d8966dd public/javascripts/jstoolbar/lang/jstoolbar-lt.js --- a/public/javascripts/jstoolbar/lang/jstoolbar-lt.js Fri Jun 14 09:07:32 2013 +0100 +++ b/public/javascripts/jstoolbar/lang/jstoolbar-lt.js Fri Jun 14 09:28:30 2013 +0100 @@ -9,8 +9,8 @@ jsToolBar.strings['Heading 3'] = 'Heading 3'; jsToolBar.strings['Unordered list'] = 'Nenumeruotas sÄ…raÅ¡as'; jsToolBar.strings['Ordered list'] = 'Numeruotas sÄ…raÅ¡as'; -jsToolBar.strings['Quote'] = 'Quote'; -jsToolBar.strings['Unquote'] = 'Remove Quote'; +jsToolBar.strings['Quote'] = 'Cituoti'; +jsToolBar.strings['Unquote'] = 'PaÅ¡alinti citavimÄ…'; jsToolBar.strings['Preformatted text'] = 'Preformatuotas tekstas'; jsToolBar.strings['Wiki link'] = 'Nuoroda į Wiki puslapį'; jsToolBar.strings['Image'] = 'Paveikslas'; diff -r 0a574315af3e -r 4f746d8966dd public/javascripts/jstoolbar/textile.js --- a/public/javascripts/jstoolbar/textile.js Fri Jun 14 09:07:32 2013 +0100 +++ b/public/javascripts/jstoolbar/textile.js Fri Jun 14 09:28:30 2013 +0100 @@ -198,3 +198,14 @@ wiki: function() { this.encloseSelection("!", "!") } } } + +// spacer +jsToolBar.prototype.elements.space5 = {type: 'space'} +// help +jsToolBar.prototype.elements.help = { + type: 'button', + title: 'Help', + fn: { + wiki: function() { window.open(this.help_link, '', 'resizable=yes, location=no, width=300, height=640, menubar=no, status=no, scrollbars=yes') } + } +} diff -r 0a574315af3e -r 4f746d8966dd public/javascripts/project_identifier.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/javascripts/project_identifier.js Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,78 @@ +// Automatic project identifier generation + +function generateProjectIdentifier(identifier, maxlength) { + var diacriticsMap = [ + {'base':'a', 'letters':/[\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250\u0041\u24B6\uFF21\u00C0\u00C1\u00C2\u1EA6\u1EA4\u1EAA\u1EA8\u00C3\u0100\u0102\u1EB0\u1EAE\u1EB4\u1EB2\u0226\u01E0\u01DE\u1EA2\u00C5\u01FA\u01CD\u0200\u0202\u1EA0\u1EAC\u1EB6\u1E00\u0104\u023A\u2C6F]/g}, + {'base':'aa','letters':/[\uA733\uA732]/g}, + {'base':'ae','letters':/[\u00E4\u00E6\u01FD\u01E3\u00C4\u00C6\u01FC\u01E2]/g}, + {'base':'ao','letters':/[\uA735\uA734]/g}, + {'base':'au','letters':/[\uA737\uA736]/g}, + {'base':'av','letters':/[\uA739\uA73B\uA738\uA73A]/g}, + {'base':'ay','letters':/[\uA73D\uA73C]/g}, + {'base':'b', 'letters':/[\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181]/g}, + {'base':'c', 'letters':/[\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E]/g}, + {'base':'d', 'letters':/[\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779]/g}, + {'base':'dz','letters':/[\u01F3\u01C6\u01F1\u01C4\u01F2\u01C5]/g}, + {'base':'e', 'letters':/[\u0065\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u025B\u01DD\u0045\u24BA\uFF25\u00C8\u00C9\u00CA\u1EC0\u1EBE\u1EC4\u1EC2\u1EBC\u0112\u1E14\u1E16\u0114\u0116\u00CB\u1EBA\u011A\u0204\u0206\u1EB8\u1EC6\u0228\u1E1C\u0118\u1E18\u1E1A\u0190\u018E]/g}, + {'base':'f', 'letters':/[\u0066\u24D5\uFF46\u1E1F\u0192\uA77C\u0046\u24BB\uFF26\u1E1E\u0191\uA77B]/g}, + {'base':'g', 'letters':/[\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F\u0047\u24BC\uFF27\u01F4\u011C\u1E20\u011E\u0120\u01E6\u0122\u01E4\u0193\uA7A0\uA77D\uA77E]/g}, + {'base':'h', 'letters':/[\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265\u0048\u24BD\uFF28\u0124\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u2C75\uA78D]/g}, + {'base':'hv','letters':/[\u0195]/g}, + {'base':'i', 'letters':/[\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131\u0049\u24BE\uFF29\u00CC\u00CD\u00CE\u0128\u012A\u012C\u0130\u00CF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197]/g}, + {'base':'j', 'letters':/[\u006A\u24D9\uFF4A\u0135\u01F0\u0249\u004A\u24BF\uFF2A\u0134\u0248]/g}, + {'base':'k', 'letters':/[\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3\u004B\u24C0\uFF2B\u1E30\u01E8\u1E32\u0136\u1E34\u0198\u2C69\uA740\uA742\uA744\uA7A2]/g}, + {'base':'l', 'letters':/[\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747\u004C\u24C1\uFF2C\u013F\u0139\u013D\u1E36\u1E38\u013B\u1E3C\u1E3A\u0141\u023D\u2C62\u2C60\uA748\uA746\uA780]/g}, + {'base':'lj','letters':/[\u01C9\u01C7\u01C8]/g}, + {'base':'m', 'letters':/[\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F\u004D\u24C2\uFF2D\u1E3E\u1E40\u1E42\u2C6E\u019C]/g}, + {'base':'n', 'letters':/[\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5\u004E\u24C3\uFF2E\u01F8\u0143\u00D1\u1E44\u0147\u1E46\u0145\u1E4A\u1E48\u0220\u019D\uA790\uA7A4]/g}, + {'base':'nj','letters':/[\u01CC\u01CA\u01CB]/g}, + {'base':'o', 'letters':/[\u006F\u24DE\uFF4F\u00F2\u00F3\u00F4\u1ED3\u1ED1\u1ED7\u1ED5\u00F5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\u00F8\u01FF\u0254\uA74B\uA74D\u0275\u004F\u24C4\uFF2F\u00D2\u00D3\u00D4\u1ED2\u1ED0\u1ED6\u1ED4\u00D5\u1E4C\u022C\u1E4E\u014C\u1E50\u1E52\u014E\u022E\u0230\u022A\u1ECE\u0150\u01D1\u020C\u020E\u01A0\u1EDC\u1EDA\u1EE0\u1EDE\u1EE2\u1ECC\u1ED8\u01EA\u01EC\u00D8\u01FE\u0186\u019F\uA74A\uA74C]/g}, + {'base':'oe','letters': /[\u00F6\u0153\u00D6\u0152]/g}, + {'base':'oi','letters':/[\u01A3\u01A2]/g}, + {'base':'ou','letters':/[\u0223\u0222]/g}, + {'base':'oo','letters':/[\uA74F\uA74E]/g}, + {'base':'p','letters':/[\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755\u0050\u24C5\uFF30\u1E54\u1E56\u01A4\u2C63\uA750\uA752\uA754]/g}, + {'base':'q','letters':/[\u0071\u24E0\uFF51\u024B\uA757\uA759\u0051\u24C6\uFF31\uA756\uA758\u024A]/g}, + {'base':'r','letters':/[\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783\u0052\u24C7\uFF32\u0154\u1E58\u0158\u0210\u0212\u1E5A\u1E5C\u0156\u1E5E\u024C\u2C64\uA75A\uA7A6\uA782]/g}, + {'base':'s','letters':/[\u0073\u24E2\uFF53\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B\u0053\u24C8\uFF33\u1E9E\u015A\u1E64\u015C\u1E60\u0160\u1E66\u1E62\u1E68\u0218\u015E\u2C7E\uA7A8\uA784]/g}, + {'base':'ss','letters':/[\u00DF]/g}, + {'base':'t','letters':/[\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787\u0054\u24C9\uFF34\u1E6A\u0164\u1E6C\u021A\u0162\u1E70\u1E6E\u0166\u01AC\u01AE\u023E\uA786]/g}, + {'base':'tz','letters':/[\uA729\uA728]/g}, + {'base':'u','letters':/[\u0075\u24E4\uFF55\u00F9\u00FA\u00FB\u0169\u1E79\u016B\u1E7B\u016D\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289\u0055\u24CA\uFF35\u00D9\u00DA\u00DB\u0168\u1E78\u016A\u1E7A\u016C\u01DB\u01D7\u01D5\u01D9\u1EE6\u016E\u0170\u01D3\u0214\u0216\u01AF\u1EEA\u1EE8\u1EEE\u1EEC\u1EF0\u1EE4\u1E72\u0172\u1E76\u1E74\u0244]/g}, + {'base':'ue','letters':/[\u00FC\u00DC]/g}, + {'base':'v','letters':/[\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C\u0056\u24CB\uFF36\u1E7C\u1E7E\u01B2\uA75E\u0245]/g}, + {'base':'vy','letters':/[\uA761\uA760]/g}, + {'base':'w','letters':/[\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73\u0057\u24CC\uFF37\u1E80\u1E82\u0174\u1E86\u1E84\u1E88\u2C72]/g}, + {'base':'x','letters':/[\u0078\u24E7\uFF58\u1E8B\u1E8D\u0058\u24CD\uFF38\u1E8A\u1E8C]/g}, + {'base':'y','letters':/[\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF\u0059\u24CE\uFF39\u1EF2\u00DD\u0176\u1EF8\u0232\u1E8E\u0178\u1EF6\u1EF4\u01B3\u024E\u1EFE]/g}, + {'base':'z','letters':/[\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763\u005A\u24CF\uFF3A\u0179\u1E90\u017B\u017D\u1E92\u1E94\u01B5\u0224\u2C7F\u2C6B\uA762]/g} + ]; + + for(var i=0; i hyphen + identifier = identifier.replace(/^[-_\d]*|[-_]*$/g, ''); // remove hyphens/underscores and numbers at beginning and hyphens/underscores at end + identifier = identifier.toLowerCase(); // to lower + identifier = identifier.substr(0, maxlength); // max characters + return identifier; +} + +function autoFillProjectIdentifier() { + var locked = ($('#project_identifier').val() != ''); + var maxlength = parseInt($('#project_identifier').attr('maxlength')); + + $('#project_name').keyup(function(){ + if(!locked) { + $('#project_identifier').val(generateProjectIdentifier($('#project_name').val(), maxlength)); + } + }); + + $('#project_identifier').keyup(function(){ + locked = ($('#project_identifier').val() != '' && $('#project_identifier').val() != generateProjectIdentifier($('#project_name').val(), maxlength)); + }); +} + +$(document).ready(function(){ + autoFillProjectIdentifier(); +}); diff -r 0a574315af3e -r 4f746d8966dd public/javascripts/revision_graph.js --- a/public/javascripts/revision_graph.js Fri Jun 14 09:07:32 2013 +0100 +++ b/public/javascripts/revision_graph.js Fri Jun 14 09:28:30 2013 +0100 @@ -43,7 +43,7 @@ revisionGraph.circle(x, y, 3) .attr({ fill: colors[commit.space], - stroke: 'none', + stroke: 'none' }).toFront(); // paths to parents $.each(commit.parent_scmids, function(index, parent_scmid) { diff -r 0a574315af3e -r 4f746d8966dd public/stylesheets/application.css --- a/public/stylesheets/application.css Fri Jun 14 09:07:32 2013 +0100 +++ b/public/stylesheets/application.css Fri Jun 14 09:28:30 2013 +0100 @@ -213,7 +213,7 @@ tr.wiki-page-version td.updated_on, tr.wiki-page-version td.author {text-align:center;} tr.time-entry { text-align: center; white-space: nowrap; } -tr.time-entry td.subject, tr.time-entry td.comments { text-align: left; white-space: normal; } +tr.time-entry td.issue, tr.time-entry td.comments { text-align: left; white-space: normal; } td.hours { text-align: right; font-weight: bold; padding-right: 0.5em; } td.hours .hours-dec { font-size: 0.9em; } @@ -275,7 +275,7 @@ #watchers ul {margin: 0; padding: 0;} #watchers li {list-style-type:none;margin: 0px 2px 0px 0px; padding: 0px 0px 0px 0px;} #watchers select {width: 95%; display: block;} -#watchers a.delete {opacity: 0.4;} +#watchers a.delete {opacity: 0.4; vertical-align: middle;} #watchers a.delete:hover {opacity: 1;} #watchers img.gravatar {margin: 0 4px 2px 0;} @@ -347,8 +347,8 @@ #relations td.buttons {padding:0;} fieldset.collapsible { border-width: 1px 0 0 0; font-size: 0.9em; } -fieldset.collapsible legend { padding-left: 16px; background: url(../images/arrow_expanded.png) no-repeat 0% 40%; cursor:pointer; } -fieldset.collapsible.collapsed legend { background-image: url(../images/arrow_collapsed.png); } +fieldset.collapsible>legend { padding-left: 16px; background: url(../images/arrow_expanded.png) no-repeat 0% 40%; cursor:pointer; } +fieldset.collapsible.collapsed>legend { background-image: url(../images/arrow_collapsed.png); } fieldset#date-range p { margin: 2px 0 2px 0; } fieldset#filters table { border-collapse: collapse; } @@ -383,6 +383,8 @@ div#activity dd .description, #search-results dd .description { font-style: italic; } div#activity span.project:after, div#news span.project:after, #search-results span.project:after { content: " -"; } div#activity dd span.description, #search-results dd span.description { display:block; color: #808080; } +div#activity dt.grouped {margin-left:5em;} +div#activity dd.grouped {margin-left:9em;} div#members dl { margin-left: 2em; } div#members dd { margin-bottom: 1em; padding-left: 18px; font-size: 0.9em; } @@ -496,10 +498,11 @@ table.fields_permissions td.required {background:#d88;} textarea#custom_field_possible_values {width: 99%} +textarea#custom_field_default_value {width: 99%} + input#content_comments {width: 99%} -.pagination {font-size: 90%} -p.pagination {margin-top:8px;} +p.pagination {margin-top:8px; font-size: 90%} /***** Tabular forms ******/ .tabular p{ @@ -581,11 +584,18 @@ span.required {color: #bb0000;} .summary {font-style: italic;} -#attachments_fields input.description {margin-left: 8px; width:340px;} +#attachments_fields input.description {margin-left:4px; width:340px;} #attachments_fields span {display:block; white-space:nowrap;} #attachments_fields input[type=text] {margin-left: 8px; } -#attachments_fields img {vertical-align: middle;} +#attachments_fields input.filename {border:0; height:1.8em; width:250px; color:#555; background-color:inherit; background:url(../images/attachment.png) no-repeat 1px 50%; padding-left:18px;} +#attachments_fields .ajax-waiting input.filename {background:url(../images/hourglass.png) no-repeat 0px 50%;} +#attachments_fields .ajax-loading input.filename {background:url(../images/loading.gif) no-repeat 0px 50%;} +#attachments_fields div.ui-progressbar { width: 100px; height:14px; margin: 2px 0 -5px 8px; display: inline-block; } +a.remove-upload {background: url(../images/delete.png) no-repeat 1px 50%; width:1px; display:inline-block; padding-left:16px;} +a.remove-upload:hover {text-decoration:none !important;} + +div.fileover { background-color: lavender; } div.attachments { margin-top: 12px; } div.attachments p { margin:4px 0 2px 0; } @@ -607,30 +617,30 @@ textarea.text_cf {width:90%;} -/* Project members tab */ -div#tab-content-members .splitcontentleft, div#tab-content-memberships .splitcontentleft, div#tab-content-users .splitcontentleft { width: 64% } -div#tab-content-members .splitcontentright, div#tab-content-memberships .splitcontentright, div#tab-content-users .splitcontentright { width: 34% } -div#tab-content-members fieldset, div#tab-content-memberships fieldset, div#tab-content-users fieldset { padding:1em; margin-bottom: 1em; } -div#tab-content-members fieldset legend, div#tab-content-memberships fieldset legend, div#tab-content-users fieldset legend { font-weight: bold; } -div#tab-content-members fieldset label, div#tab-content-memberships fieldset label, div#tab-content-users fieldset label { display: block; } -div#tab-content-members fieldset div, div#tab-content-users fieldset div { max-height: 400px; overflow:auto; } +#tab-content-modules fieldset p {margin:3px 0 4px 0;} + +#tab-content-members .splitcontentleft, #tab-content-memberships .splitcontentleft, #tab-content-users .splitcontentleft {width: 64%;} +#tab-content-members .splitcontentright, #tab-content-memberships .splitcontentright, #tab-content-users .splitcontentright {width: 34%;} +#tab-content-members fieldset, #tab-content-memberships fieldset, #tab-content-users fieldset {padding:1em; margin-bottom: 1em;} +#tab-content-members fieldset legend, #tab-content-memberships fieldset legend, #tab-content-users fieldset legend {font-weight: bold;} +#tab-content-members fieldset label, #tab-content-memberships fieldset label, #tab-content-users fieldset label {display: block;} +#tab-content-members #principals, #tab-content-users #principals {max-height: 400px; overflow: auto;} #users_for_watcher {height: 200px; overflow:auto;} #users_for_watcher label {display: block;} table.members td.group { padding-left: 20px; background: url(../images/group.png) no-repeat 0% 50%; } -input#principal_search, input#user_search {width:100%} -input#principal_search, input#user_search { - background: url(../images/magnifier.png) no-repeat 2px 50%; padding-left:20px; - border:1px solid #9EB1C2; border-radius:3px; height:1.5em; width:95%; +input#principal_search, input#user_search {width:90%} + +input.autocomplete { + background: #fff url(../images/magnifier.png) no-repeat 2px 50%; padding-left:20px; + border:1px solid #9EB1C2; border-radius:2px; height:1.5em; } -input#principal_search.ajax-loading, input#user_search.ajax-loading { +input.autocomplete.ajax-loading { background-image: url(../images/loading.gif); } -* html div#tab-content-members fieldset div { height: 450px; } - /***** Flash & error messages ****/ #errorExplanation, div.flash, .nodata, .warning, .conflict { padding: 4px 4px 4px 30px; @@ -752,7 +762,7 @@ table.progress td.closed { background: #BAE0BA none repeat scroll 0%; } table.progress td.done { background: #D3EDD3 none repeat scroll 0%; } table.progress td.todo { background: #eee none repeat scroll 0%; } -p.pourcent {font-size: 80%;} +p.percent {font-size: 80%;} p.progress-info {clear: left; font-size: 80%; margin-top:-4px; color:#777;} #roadmap table.progress td { height: 1.2em; } @@ -1183,13 +1193,13 @@ .syntaxhl .type { color:#339; font-weight:bold } .syntaxhl .value { color: #088; } .syntaxhl .variable { color:#037 } - + .syntaxhl .insert { background: hsla(120,100%,50%,0.12) } .syntaxhl .delete { background: hsla(0,100%,50%,0.12) } .syntaxhl .change { color: #bbf; background: #007; } .syntaxhl .head { color: #f8f; background: #505 } .syntaxhl .head .filename { color: white; } - + .syntaxhl .delete .eyecatcher { background-color: hsla(0,100%,50%,0.2); border: 1px solid hsla(0,100%,45%,0.5); margin: -1px; border-bottom: none; border-top-left-radius: 5px; border-top-right-radius: 5px; } .syntaxhl .insert .eyecatcher { background-color: hsla(120,100%,50%,0.2); border: 1px solid hsla(120,100%,25%,0.5); margin: -1px; border-top: none; border-bottom-left-radius: 5px; border-bottom-right-radius: 5px; } diff -r 0a574315af3e -r 4f746d8966dd public/stylesheets/jquery/images/ui-bg_gloss-wave_35_759fcf_500x100.png Binary file public/stylesheets/jquery/images/ui-bg_gloss-wave_35_759fcf_500x100.png has changed diff -r 0a574315af3e -r 4f746d8966dd public/stylesheets/jquery/jquery-ui-1.8.21.css --- a/public/stylesheets/jquery/jquery-ui-1.8.21.css Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,563 +0,0 @@ -/*! - * jQuery UI CSS Framework 1.8.22 - * - * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Theming/API - */ - -/* Layout helpers -----------------------------------*/ -.ui-helper-hidden { display: none; } -.ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px 1px 1px 1px); clip: rect(1px,1px,1px,1px); } -.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; } -.ui-helper-clearfix:before, .ui-helper-clearfix:after { content: ""; display: table; } -.ui-helper-clearfix:after { clear: both; } -.ui-helper-clearfix { zoom: 1; } -.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); } - - -/* Interaction Cues -----------------------------------*/ -.ui-state-disabled { cursor: default !important; } - - -/* Icons -----------------------------------*/ - -/* states and images */ -.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; } - - -/* Misc visuals -----------------------------------*/ - -/* Overlays */ -.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } - - -/*! - * jQuery UI CSS Framework 1.8.22 - * - * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Theming/API - * - * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana,%20sans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=759fcf&bgTextureHeader=12_gloss_wave.png&bgImgOpacityHeader=35&borderColorHeader=628db6&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=eeeeee&bgTextureContent=03_highlight_soft.png&bgImgOpacityContent=100&borderColorContent=dddddd&fcContent=333333&iconColorContent=222222&bgColorDefault=f6f6f6&bgTextureDefault=02_glass.png&bgImgOpacityDefault=100&borderColorDefault=cccccc&fcDefault=628db6&iconColorDefault=759fcf&bgColorHover=eef5fd&bgTextureHover=02_glass.png&bgImgOpacityHover=100&borderColorHover=628db6&fcHover=628db6&iconColorHover=759fcf&bgColorActive=ffffff&bgTextureActive=02_glass.png&bgImgOpacityActive=65&borderColorActive=628db6&fcActive=628db6&iconColorActive=759fcf&bgColorHighlight=759fcf&bgTextureHighlight=03_highlight_soft.png&bgImgOpacityHighlight=75&borderColorHighlight=628db6&fcHighlight=363636&iconColorHighlight=759fcf&bgColorError=b81900&bgTextureError=08_diagonals_thick.png&bgImgOpacityError=18&borderColorError=cd0a0a&fcError=ffffff&iconColorError=ffd27a&bgColorOverlay=666666&bgTextureOverlay=08_diagonals_thick.png&bgImgOpacityOverlay=20&opacityOverlay=50&bgColorShadow=000000&bgTextureShadow=01_flat.png&bgImgOpacityShadow=10&opacityShadow=20&thicknessShadow=5px&offsetTopShadow=-5px&offsetLeftShadow=-5px&cornerRadiusShadow=5px - */ - - -/* Component containers -----------------------------------*/ -.ui-widget { font-family: Verdana, sans-serif; font-size: 1.1em; } -.ui-widget .ui-widget { font-size: 1em; } -.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Verdana, sans-serif; font-size: 1em; } -.ui-widget-content { border: 1px solid #dddddd; background: #eeeeee url(images/ui-bg_highlight-soft_100_eeeeee_1x100.png) 50% top repeat-x; color: #333333; } -.ui-widget-content a { color: #333333; } -.ui-widget-header { border: 1px solid #628db6; background: #759fcf url(images/ui-bg_gloss-wave_35_759fcf_500x100.png) 50% 50% repeat-x; color: #ffffff; font-weight: bold; } -.ui-widget-header a { color: #ffffff; } - -/* Interaction states -----------------------------------*/ -.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #cccccc; background: #f6f6f6 url(images/ui-bg_glass_100_f6f6f6_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #628db6; } -.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #628db6; text-decoration: none; } -.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #628db6; background: #eef5fd url(images/ui-bg_glass_100_eef5fd_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #628db6; } -.ui-state-hover a, .ui-state-hover a:hover { color: #628db6; text-decoration: none; } -.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #628db6; background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #628db6; } -.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #628db6; text-decoration: none; } -.ui-widget :active { outline: none; } - -/* Interaction Cues -----------------------------------*/ -.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #628db6; background: #759fcf url(images/ui-bg_highlight-soft_75_759fcf_1x100.png) 50% top repeat-x; color: #363636; } -.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; } -.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a; background: #b81900 url(images/ui-bg_diagonals-thick_18_b81900_40x40.png) 50% 50% repeat; color: #ffffff; } -.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #ffffff; } -.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #ffffff; } -.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; } -.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; } -.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; } - -/* Icons -----------------------------------*/ - -/* states and images */ -.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png); } -.ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); } -.ui-widget-header .ui-icon {background-image: url(images/ui-icons_ffffff_256x240.png); } -.ui-state-default .ui-icon { background-image: url(images/ui-icons_759fcf_256x240.png); } -.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_759fcf_256x240.png); } -.ui-state-active .ui-icon {background-image: url(images/ui-icons_759fcf_256x240.png); } -.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_759fcf_256x240.png); } -.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_ffd27a_256x240.png); } - -/* positioning */ -.ui-icon-carat-1-n { background-position: 0 0; } -.ui-icon-carat-1-ne { background-position: -16px 0; } -.ui-icon-carat-1-e { background-position: -32px 0; } -.ui-icon-carat-1-se { background-position: -48px 0; } -.ui-icon-carat-1-s { background-position: -64px 0; } -.ui-icon-carat-1-sw { background-position: -80px 0; } -.ui-icon-carat-1-w { background-position: -96px 0; } -.ui-icon-carat-1-nw { background-position: -112px 0; } -.ui-icon-carat-2-n-s { background-position: -128px 0; } -.ui-icon-carat-2-e-w { background-position: -144px 0; } -.ui-icon-triangle-1-n { background-position: 0 -16px; } -.ui-icon-triangle-1-ne { background-position: -16px -16px; } -.ui-icon-triangle-1-e { background-position: -32px -16px; } -.ui-icon-triangle-1-se { background-position: -48px -16px; } -.ui-icon-triangle-1-s { background-position: -64px -16px; } -.ui-icon-triangle-1-sw { background-position: -80px -16px; } -.ui-icon-triangle-1-w { background-position: -96px -16px; } -.ui-icon-triangle-1-nw { background-position: -112px -16px; } -.ui-icon-triangle-2-n-s { background-position: -128px -16px; } -.ui-icon-triangle-2-e-w { background-position: -144px -16px; } -.ui-icon-arrow-1-n { background-position: 0 -32px; } -.ui-icon-arrow-1-ne { background-position: -16px -32px; } -.ui-icon-arrow-1-e { background-position: -32px -32px; } -.ui-icon-arrow-1-se { background-position: -48px -32px; } -.ui-icon-arrow-1-s { background-position: -64px -32px; } -.ui-icon-arrow-1-sw { background-position: -80px -32px; } -.ui-icon-arrow-1-w { background-position: -96px -32px; } -.ui-icon-arrow-1-nw { background-position: -112px -32px; } -.ui-icon-arrow-2-n-s { background-position: -128px -32px; } -.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } -.ui-icon-arrow-2-e-w { background-position: -160px -32px; } -.ui-icon-arrow-2-se-nw { background-position: -176px -32px; } -.ui-icon-arrowstop-1-n { background-position: -192px -32px; } -.ui-icon-arrowstop-1-e { background-position: -208px -32px; } -.ui-icon-arrowstop-1-s { background-position: -224px -32px; } -.ui-icon-arrowstop-1-w { background-position: -240px -32px; } -.ui-icon-arrowthick-1-n { background-position: 0 -48px; } -.ui-icon-arrowthick-1-ne { background-position: -16px -48px; } -.ui-icon-arrowthick-1-e { background-position: -32px -48px; } -.ui-icon-arrowthick-1-se { background-position: -48px -48px; } -.ui-icon-arrowthick-1-s { background-position: -64px -48px; } -.ui-icon-arrowthick-1-sw { background-position: -80px -48px; } -.ui-icon-arrowthick-1-w { background-position: -96px -48px; } -.ui-icon-arrowthick-1-nw { background-position: -112px -48px; } -.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } -.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } -.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } -.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } -.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } -.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } -.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } -.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } -.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } -.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } -.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } -.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } -.ui-icon-arrowreturn-1-w { background-position: -64px -64px; } -.ui-icon-arrowreturn-1-n { background-position: -80px -64px; } -.ui-icon-arrowreturn-1-e { background-position: -96px -64px; } -.ui-icon-arrowreturn-1-s { background-position: -112px -64px; } -.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } -.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } -.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } -.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } -.ui-icon-arrow-4 { background-position: 0 -80px; } -.ui-icon-arrow-4-diag { background-position: -16px -80px; } -.ui-icon-extlink { background-position: -32px -80px; } -.ui-icon-newwin { background-position: -48px -80px; } -.ui-icon-refresh { background-position: -64px -80px; } -.ui-icon-shuffle { background-position: -80px -80px; } -.ui-icon-transfer-e-w { background-position: -96px -80px; } -.ui-icon-transferthick-e-w { background-position: -112px -80px; } -.ui-icon-folder-collapsed { background-position: 0 -96px; } -.ui-icon-folder-open { background-position: -16px -96px; } -.ui-icon-document { background-position: -32px -96px; } -.ui-icon-document-b { background-position: -48px -96px; } -.ui-icon-note { background-position: -64px -96px; } -.ui-icon-mail-closed { background-position: -80px -96px; } -.ui-icon-mail-open { background-position: -96px -96px; } -.ui-icon-suitcase { background-position: -112px -96px; } -.ui-icon-comment { background-position: -128px -96px; } -.ui-icon-person { background-position: -144px -96px; } -.ui-icon-print { background-position: -160px -96px; } -.ui-icon-trash { background-position: -176px -96px; } -.ui-icon-locked { background-position: -192px -96px; } -.ui-icon-unlocked { background-position: -208px -96px; } -.ui-icon-bookmark { background-position: -224px -96px; } -.ui-icon-tag { background-position: -240px -96px; } -.ui-icon-home { background-position: 0 -112px; } -.ui-icon-flag { background-position: -16px -112px; } -.ui-icon-calendar { background-position: -32px -112px; } -.ui-icon-cart { background-position: -48px -112px; } -.ui-icon-pencil { background-position: -64px -112px; } -.ui-icon-clock { background-position: -80px -112px; } -.ui-icon-disk { background-position: -96px -112px; } -.ui-icon-calculator { background-position: -112px -112px; } -.ui-icon-zoomin { background-position: -128px -112px; } -.ui-icon-zoomout { background-position: -144px -112px; } -.ui-icon-search { background-position: -160px -112px; } -.ui-icon-wrench { background-position: -176px -112px; } -.ui-icon-gear { background-position: -192px -112px; } -.ui-icon-heart { background-position: -208px -112px; } -.ui-icon-star { background-position: -224px -112px; } -.ui-icon-link { background-position: -240px -112px; } -.ui-icon-cancel { background-position: 0 -128px; } -.ui-icon-plus { background-position: -16px -128px; } -.ui-icon-plusthick { background-position: -32px -128px; } -.ui-icon-minus { background-position: -48px -128px; } -.ui-icon-minusthick { background-position: -64px -128px; } -.ui-icon-close { background-position: -80px -128px; } -.ui-icon-closethick { background-position: -96px -128px; } -.ui-icon-key { background-position: -112px -128px; } -.ui-icon-lightbulb { background-position: -128px -128px; } -.ui-icon-scissors { background-position: -144px -128px; } -.ui-icon-clipboard { background-position: -160px -128px; } -.ui-icon-copy { background-position: -176px -128px; } -.ui-icon-contact { background-position: -192px -128px; } -.ui-icon-image { background-position: -208px -128px; } -.ui-icon-video { background-position: -224px -128px; } -.ui-icon-script { background-position: -240px -128px; } -.ui-icon-alert { background-position: 0 -144px; } -.ui-icon-info { background-position: -16px -144px; } -.ui-icon-notice { background-position: -32px -144px; } -.ui-icon-help { background-position: -48px -144px; } -.ui-icon-check { background-position: -64px -144px; } -.ui-icon-bullet { background-position: -80px -144px; } -.ui-icon-radio-off { background-position: -96px -144px; } -.ui-icon-radio-on { background-position: -112px -144px; } -.ui-icon-pin-w { background-position: -128px -144px; } -.ui-icon-pin-s { background-position: -144px -144px; } -.ui-icon-play { background-position: 0 -160px; } -.ui-icon-pause { background-position: -16px -160px; } -.ui-icon-seek-next { background-position: -32px -160px; } -.ui-icon-seek-prev { background-position: -48px -160px; } -.ui-icon-seek-end { background-position: -64px -160px; } -.ui-icon-seek-start { background-position: -80px -160px; } -/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */ -.ui-icon-seek-first { background-position: -80px -160px; } -.ui-icon-stop { background-position: -96px -160px; } -.ui-icon-eject { background-position: -112px -160px; } -.ui-icon-volume-off { background-position: -128px -160px; } -.ui-icon-volume-on { background-position: -144px -160px; } -.ui-icon-power { background-position: 0 -176px; } -.ui-icon-signal-diag { background-position: -16px -176px; } -.ui-icon-signal { background-position: -32px -176px; } -.ui-icon-battery-0 { background-position: -48px -176px; } -.ui-icon-battery-1 { background-position: -64px -176px; } -.ui-icon-battery-2 { background-position: -80px -176px; } -.ui-icon-battery-3 { background-position: -96px -176px; } -.ui-icon-circle-plus { background-position: 0 -192px; } -.ui-icon-circle-minus { background-position: -16px -192px; } -.ui-icon-circle-close { background-position: -32px -192px; } -.ui-icon-circle-triangle-e { background-position: -48px -192px; } -.ui-icon-circle-triangle-s { background-position: -64px -192px; } -.ui-icon-circle-triangle-w { background-position: -80px -192px; } -.ui-icon-circle-triangle-n { background-position: -96px -192px; } -.ui-icon-circle-arrow-e { background-position: -112px -192px; } -.ui-icon-circle-arrow-s { background-position: -128px -192px; } -.ui-icon-circle-arrow-w { background-position: -144px -192px; } -.ui-icon-circle-arrow-n { background-position: -160px -192px; } -.ui-icon-circle-zoomin { background-position: -176px -192px; } -.ui-icon-circle-zoomout { background-position: -192px -192px; } -.ui-icon-circle-check { background-position: -208px -192px; } -.ui-icon-circlesmall-plus { background-position: 0 -208px; } -.ui-icon-circlesmall-minus { background-position: -16px -208px; } -.ui-icon-circlesmall-close { background-position: -32px -208px; } -.ui-icon-squaresmall-plus { background-position: -48px -208px; } -.ui-icon-squaresmall-minus { background-position: -64px -208px; } -.ui-icon-squaresmall-close { background-position: -80px -208px; } -.ui-icon-grip-dotted-vertical { background-position: 0 -224px; } -.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } -.ui-icon-grip-solid-vertical { background-position: -32px -224px; } -.ui-icon-grip-solid-horizontal { background-position: -48px -224px; } -.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } -.ui-icon-grip-diagonal-se { background-position: -80px -224px; } - - -/* Misc visuals -----------------------------------*/ - -/* Corner radius */ -.ui-corner-all, .ui-corner-top, .ui-corner-left, .ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -khtml-border-top-left-radius: 4px; border-top-left-radius: 4px; } -.ui-corner-all, .ui-corner-top, .ui-corner-right, .ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; -khtml-border-top-right-radius: 4px; border-top-right-radius: 4px; } -.ui-corner-all, .ui-corner-bottom, .ui-corner-left, .ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; -khtml-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; } -.ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; -khtml-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; } - -/* Overlays */ -.ui-widget-overlay { background: #666666 url(images/ui-bg_diagonals-thick_20_666666_40x40.png) 50% 50% repeat; opacity: .50;filter:Alpha(Opacity=50); } -.ui-widget-shadow { margin: -5px 0 0 -5px; padding: 5px; background: #000000 url(images/ui-bg_flat_10_000000_40x100.png) 50% 50% repeat-x; opacity: .20;filter:Alpha(Opacity=20); -moz-border-radius: 5px; -khtml-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; }/*! - * jQuery UI Resizable 1.8.22 - * - * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Resizable#theming - */ -.ui-resizable { position: relative;} -.ui-resizable-handle { position: absolute;font-size: 0.1px; display: block; } -.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; } -.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; } -.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; } -.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; } -.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; } -.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; } -.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; } -.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; } -.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/*! - * jQuery UI Selectable 1.8.22 - * - * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Selectable#theming - */ -.ui-selectable-helper { position: absolute; z-index: 100; border:1px dotted black; } -/*! - * jQuery UI Accordion 1.8.22 - * - * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Accordion#theming - */ -/* IE/Win - Fix animation bug - #4615 */ -.ui-accordion { width: 100%; } -.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; } -.ui-accordion .ui-accordion-li-fix { display: inline; } -.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; } -.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em .7em; } -.ui-accordion-icons .ui-accordion-header a { padding-left: 2.2em; } -.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; } -.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; zoom: 1; } -.ui-accordion .ui-accordion-content-active { display: block; } -/*! - * jQuery UI Autocomplete 1.8.22 - * - * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Autocomplete#theming - */ -.ui-autocomplete { position: absolute; cursor: default; } - -/* workarounds */ -* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */ - -/* - * jQuery UI Menu 1.8.22 - * - * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Menu#theming - */ -.ui-menu { - list-style:none; - padding: 2px; - margin: 0; - display:block; - float: left; -} -.ui-menu .ui-menu { - margin-top: -3px; -} -.ui-menu .ui-menu-item { - margin:0; - padding: 0; - zoom: 1; - float: left; - clear: left; - width: 100%; -} -.ui-menu .ui-menu-item a { - text-decoration:none; - display:block; - padding:.2em .4em; - line-height:1.5; - zoom:1; -} -.ui-menu .ui-menu-item a.ui-state-hover, -.ui-menu .ui-menu-item a.ui-state-active { - font-weight: normal; - margin: -1px; -} -/*! - * jQuery UI Button 1.8.22 - * - * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Button#theming - */ -.ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */ -.ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */ -button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */ -.ui-button-icons-only { width: 3.4em; } -button.ui-button-icons-only { width: 3.7em; } - -/*button text element */ -.ui-button .ui-button-text { display: block; line-height: 1.4; } -.ui-button-text-only .ui-button-text { padding: .4em 1em; } -.ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; } -.ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; } -.ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 2.1em .4em 1em; } -.ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; } -/* no icon support for input elements, provide padding by default */ -input.ui-button { padding: .4em 1em; } - -/*button icon element(s) */ -.ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; } -.ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; } -.ui-button-text-icon-primary .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; } -.ui-button-text-icon-secondary .ui-button-icon-secondary, .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } -.ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } - -/*button sets*/ -.ui-buttonset { margin-right: 7px; } -.ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; } - -/* workarounds */ -button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */ -/*! - * jQuery UI Dialog 1.8.22 - * - * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Dialog#theming - */ -.ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; } -.ui-dialog .ui-dialog-titlebar { padding: .4em 1em; position: relative; } -.ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .1em 0; } -.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; } -.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; } -.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; } -.ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; } -.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; } -.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; } -.ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; } -.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; } -.ui-draggable .ui-dialog-titlebar { cursor: move; } -/*! - * jQuery UI Slider 1.8.22 - * - * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Slider#theming - */ -.ui-slider { position: relative; text-align: left; } -.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; } -.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; } - -.ui-slider-horizontal { height: .8em; } -.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; } -.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; } -.ui-slider-horizontal .ui-slider-range-min { left: 0; } -.ui-slider-horizontal .ui-slider-range-max { right: 0; } - -.ui-slider-vertical { width: .8em; height: 100px; } -.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; } -.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; } -.ui-slider-vertical .ui-slider-range-min { bottom: 0; } -.ui-slider-vertical .ui-slider-range-max { top: 0; }/*! - * jQuery UI Tabs 1.8.22 - * - * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Tabs#theming - */ -.ui-tabs { position: relative; padding: .2em; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */ -.ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; } -.ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 1px; margin: 0 .2em 1px 0; border-bottom: 0 !important; padding: 0; white-space: nowrap; } -.ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; } -.ui-tabs .ui-tabs-nav li.ui-tabs-selected { margin-bottom: 0; padding-bottom: 1px; } -.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; } -.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */ -.ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: none; } -.ui-tabs .ui-tabs-hide { display: none !important; } -/*! - * jQuery UI Datepicker 1.8.22 - * - * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Datepicker#theming - */ -.ui-datepicker { width: 17em; padding: .2em .2em 0; display: none; } -.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; } -.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; } -.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; } -.ui-datepicker .ui-datepicker-prev { left:2px; } -.ui-datepicker .ui-datepicker-next { right:2px; } -.ui-datepicker .ui-datepicker-prev-hover { left:1px; } -.ui-datepicker .ui-datepicker-next-hover { right:1px; } -.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; } -.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; } -.ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; } -.ui-datepicker select.ui-datepicker-month-year {width: 100%;} -.ui-datepicker select.ui-datepicker-month, -.ui-datepicker select.ui-datepicker-year { width: 49%;} -.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; } -.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; } -.ui-datepicker td { border: 0; padding: 1px; } -.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; } -.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; } -.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; } -.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; } - -/* with multiple calendars */ -.ui-datepicker.ui-datepicker-multi { width:auto; } -.ui-datepicker-multi .ui-datepicker-group { float:left; } -.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; } -.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; } -.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; } -.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; } -.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; } -.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; } -.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; } -.ui-datepicker-row-break { clear:both; width:100%; font-size:0em; } - -/* RTL support */ -.ui-datepicker-rtl { direction: rtl; } -.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; } -.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; } -.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; } -.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; } -.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; } -.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; } -.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; } -.ui-datepicker-rtl .ui-datepicker-group { float:right; } -.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; } -.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; } - -/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */ -.ui-datepicker-cover { - position: absolute; /*must have*/ - z-index: -1; /*must have*/ - filter: mask(); /*must have*/ - top: -4px; /*must have*/ - left: -4px; /*must have*/ - width: 200px; /*must have*/ - height: 200px; /*must have*/ -}/*! - * jQuery UI Progressbar 1.8.22 - * - * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Progressbar#theming - */ -.ui-progressbar { height:2em; text-align: left; overflow: hidden; } -.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; } diff -r 0a574315af3e -r 4f746d8966dd public/stylesheets/jquery/jquery-ui-1.9.2.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/public/stylesheets/jquery/jquery-ui-1.9.2.css Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,5 @@ +/*! jQuery UI - v1.9.2 - 2012-12-26 +* http://jqueryui.com +* Includes: jquery.ui.core.css, jquery.ui.resizable.css, jquery.ui.selectable.css, jquery.ui.accordion.css, jquery.ui.autocomplete.css, jquery.ui.button.css, jquery.ui.datepicker.css, jquery.ui.dialog.css, jquery.ui.menu.css, jquery.ui.progressbar.css, jquery.ui.slider.css, jquery.ui.spinner.css, jquery.ui.tabs.css, jquery.ui.tooltip.css +* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana%2C%20sans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=759fcf&bgTextureHeader=12_gloss_wave.png&bgImgOpacityHeader=35&borderColorHeader=628db6&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=eeeeee&bgTextureContent=03_highlight_soft.png&bgImgOpacityContent=100&borderColorContent=dddddd&fcContent=333333&iconColorContent=222222&bgColorDefault=f6f6f6&bgTextureDefault=02_glass.png&bgImgOpacityDefault=100&borderColorDefault=cccccc&fcDefault=628db6&iconColorDefault=759fcf&bgColorHover=eef5fd&bgTextureHover=02_glass.png&bgImgOpacityHover=100&borderColorHover=628db6&fcHover=628db6&iconColorHover=759fcf&bgColorActive=ffffff&bgTextureActive=02_glass.png&bgImgOpacityActive=65&borderColorActive=628db6&fcActive=628db6&iconColorActive=759fcf&bgColorHighlight=759fcf&bgTextureHighlight=03_highlight_soft.png&bgImgOpacityHighlight=75&borderColorHighlight=628db6&fcHighlight=363636&iconColorHighlight=759fcf&bgColorError=b81900&bgTextureError=08_diagonals_thick.png&bgImgOpacityError=18&borderColorError=cd0a0a&fcError=ffffff&iconColorError=ffd27a&bgColorOverlay=666666&bgTextureOverlay=08_diagonals_thick.png&bgImgOpacityOverlay=20&opacityOverlay=50&bgColorShadow=000000&bgTextureShadow=01_flat.png&bgImgOpacityShadow=10&opacityShadow=20&thicknessShadow=5px&offsetTopShadow=-5px&offsetLeftShadow=-5px&cornerRadiusShadow=5px +* Copyright (c) 2012 jQuery Foundation and other contributors Licensed MIT */.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table}.ui-helper-clearfix:after{clear:both}.ui-helper-clearfix{zoom:1}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:absolute;top:0;left:0;width:100%;height:100%}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:0.1px;display:block}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-selectable-helper{position:absolute;z-index:100;border:1px dotted black}.ui-accordion .ui-accordion-header{display:block;cursor:pointer;position:relative;margin-top:2px;padding:.5em .5em .5em .7em;zoom:1}.ui-accordion .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-noicons{padding-left:.7em}.ui-accordion .ui-accordion-icons .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-header .ui-accordion-header-icon{position:absolute;left:.5em;top:50%;margin-top:-8px}.ui-accordion .ui-accordion-content{padding:1em 2.2em;border-top:0;overflow:auto;zoom:1}.ui-autocomplete{position:absolute;top:0;left:0;cursor:default}* html .ui-autocomplete{width:1px}.ui-button{display:inline-block;position:relative;padding:0;margin-right:.1em;cursor:pointer;text-align:center;zoom:1;overflow:visible}.ui-button,.ui-button:link,.ui-button:visited,.ui-button:hover,.ui-button:active{text-decoration:none}.ui-button-icon-only{width:2.2em}button.ui-button-icon-only{width:2.4em}.ui-button-icons-only{width:3.4em}button.ui-button-icons-only{width:3.7em}.ui-button .ui-button-text{display:block;line-height:1.4}.ui-button-text-only .ui-button-text{padding:.4em 1em}.ui-button-icon-only .ui-button-text,.ui-button-icons-only .ui-button-text{padding:.4em;text-indent:-9999999px}.ui-button-text-icon-primary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 1em .4em 2.1em}.ui-button-text-icon-secondary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 2.1em .4em 1em}.ui-button-text-icons .ui-button-text{padding-left:2.1em;padding-right:2.1em}input.ui-button{padding:.4em 1em}.ui-button-icon-only .ui-icon,.ui-button-text-icon-primary .ui-icon,.ui-button-text-icon-secondary .ui-icon,.ui-button-text-icons .ui-icon,.ui-button-icons-only .ui-icon{position:absolute;top:50%;margin-top:-8px}.ui-button-icon-only .ui-icon{left:50%;margin-left:-8px}.ui-button-text-icon-primary .ui-button-icon-primary,.ui-button-text-icons .ui-button-icon-primary,.ui-button-icons-only .ui-button-icon-primary{left:.5em}.ui-button-text-icon-secondary .ui-button-icon-secondary,.ui-button-text-icons .ui-button-icon-secondary,.ui-button-icons-only .ui-button-icon-secondary{right:.5em}.ui-button-text-icons .ui-button-icon-secondary,.ui-button-icons-only .ui-button-icon-secondary{right:.5em}.ui-buttonset{margin-right:7px}.ui-buttonset .ui-button{margin-left:0;margin-right:-.3em}button.ui-button::-moz-focus-inner{border:0;padding:0}.ui-datepicker{width:17em;padding:.2em .2em 0;display:none}.ui-datepicker .ui-datepicker-header{position:relative;padding:.2em 0}.ui-datepicker .ui-datepicker-prev,.ui-datepicker .ui-datepicker-next{position:absolute;top:2px;width:1.8em;height:1.8em}.ui-datepicker .ui-datepicker-prev-hover,.ui-datepicker .ui-datepicker-next-hover{top:1px}.ui-datepicker .ui-datepicker-prev{left:2px}.ui-datepicker .ui-datepicker-next{right:2px}.ui-datepicker .ui-datepicker-prev-hover{left:1px}.ui-datepicker .ui-datepicker-next-hover{right:1px}.ui-datepicker .ui-datepicker-prev span,.ui-datepicker .ui-datepicker-next span{display:block;position:absolute;left:50%;margin-left:-8px;top:50%;margin-top:-8px}.ui-datepicker .ui-datepicker-title{margin:0 2.3em;line-height:1.8em;text-align:center}.ui-datepicker .ui-datepicker-title select{font-size:1em;margin:1px 0}.ui-datepicker select.ui-datepicker-month-year{width:100%}.ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{width:49%}.ui-datepicker table{width:100%;font-size:.9em;border-collapse:collapse;margin:0 0 .4em}.ui-datepicker th{padding:.7em .3em;text-align:center;font-weight:bold;border:0}.ui-datepicker td{border:0;padding:1px}.ui-datepicker td span,.ui-datepicker td a{display:block;padding:.2em;text-align:right;text-decoration:none}.ui-datepicker .ui-datepicker-buttonpane{background-image:none;margin:.7em 0 0 0;padding:0 .2em;border-left:0;border-right:0;border-bottom:0}.ui-datepicker .ui-datepicker-buttonpane button{float:right;margin:.5em .2em .4em;cursor:pointer;padding:.2em .6em .3em .6em;width:auto;overflow:visible}.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current{float:left}.ui-datepicker.ui-datepicker-multi{width:auto}.ui-datepicker-multi .ui-datepicker-group{float:left}.ui-datepicker-multi .ui-datepicker-group table{width:95%;margin:0 auto .4em}.ui-datepicker-multi-2 .ui-datepicker-group{width:50%}.ui-datepicker-multi-3 .ui-datepicker-group{width:33.3%}.ui-datepicker-multi-4 .ui-datepicker-group{width:25%}.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header{border-left-width:0}.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header{border-left-width:0}.ui-datepicker-multi .ui-datepicker-buttonpane{clear:left}.ui-datepicker-row-break{clear:both;width:100%;font-size:0em}.ui-datepicker-rtl{direction:rtl}.ui-datepicker-rtl .ui-datepicker-prev{right:2px;left:auto}.ui-datepicker-rtl .ui-datepicker-next{left:2px;right:auto}.ui-datepicker-rtl .ui-datepicker-prev:hover{right:1px;left:auto}.ui-datepicker-rtl .ui-datepicker-next:hover{left:1px;right:auto}.ui-datepicker-rtl .ui-datepicker-buttonpane{clear:right}.ui-datepicker-rtl .ui-datepicker-buttonpane button{float:left}.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current{float:right}.ui-datepicker-rtl .ui-datepicker-group{float:right}.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header{border-right-width:0;border-left-width:1px}.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{border-right-width:0;border-left-width:1px}.ui-datepicker-cover{position:absolute;z-index:-1;filter:mask();top:-4px;left:-4px;width:200px;height:200px}.ui-dialog{position:absolute;top:0;left:0;padding:.2em;width:300px;overflow:hidden}.ui-dialog .ui-dialog-titlebar{padding:.4em 1em;position:relative}.ui-dialog .ui-dialog-title{float:left;margin:.1em 16px .1em 0}.ui-dialog .ui-dialog-titlebar-close{position:absolute;right:.3em;top:50%;width:19px;margin:-10px 0 0 0;padding:1px;height:18px}.ui-dialog .ui-dialog-titlebar-close span{display:block;margin:1px}.ui-dialog .ui-dialog-titlebar-close:hover,.ui-dialog .ui-dialog-titlebar-close:focus{padding:0}.ui-dialog .ui-dialog-content{position:relative;border:0;padding:.5em 1em;background:none;overflow:auto;zoom:1}.ui-dialog .ui-dialog-buttonpane{text-align:left;border-width:1px 0 0 0;background-image:none;margin:.5em 0 0 0;padding:.3em 1em .5em .4em}.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset{float:right}.ui-dialog .ui-dialog-buttonpane button{margin:.5em .4em .5em 0;cursor:pointer}.ui-dialog .ui-resizable-se{width:14px;height:14px;right:3px;bottom:3px}.ui-draggable .ui-dialog-titlebar{cursor:move}.ui-menu{list-style:none;padding:2px;margin:0;display:block;outline:none}.ui-menu .ui-menu{margin-top:-3px;position:absolute}.ui-menu .ui-menu-item{margin:0;padding:0;zoom:1;width:100%}.ui-menu .ui-menu-divider{margin:5px -2px 5px -2px;height:0;font-size:0;line-height:0;border-width:1px 0 0 0}.ui-menu .ui-menu-item a{text-decoration:none;display:block;padding:2px .4em;line-height:1.5;zoom:1;font-weight:normal}.ui-menu .ui-menu-item a.ui-state-focus,.ui-menu .ui-menu-item a.ui-state-active{font-weight:normal;margin:-1px}.ui-menu .ui-state-disabled{font-weight:normal;margin:.4em 0 .2em;line-height:1.5}.ui-menu .ui-state-disabled a{cursor:default}.ui-menu-icons{position:relative}.ui-menu-icons .ui-menu-item a{position:relative;padding-left:2em}.ui-menu .ui-icon{position:absolute;top:.2em;left:.2em}.ui-menu .ui-menu-icon{position:static;float:right}.ui-progressbar{height:2em;text-align:left;overflow:hidden}.ui-progressbar .ui-progressbar-value{margin:-1px;height:100%}.ui-slider{position:relative;text-align:left}.ui-slider .ui-slider-handle{position:absolute;z-index:2;width:1.2em;height:1.2em;cursor:default}.ui-slider .ui-slider-range{position:absolute;z-index:1;font-size:.7em;display:block;border:0;background-position:0 0}.ui-slider-horizontal{height:.8em}.ui-slider-horizontal .ui-slider-handle{top:-.3em;margin-left:-.6em}.ui-slider-horizontal .ui-slider-range{top:0;height:100%}.ui-slider-horizontal .ui-slider-range-min{left:0}.ui-slider-horizontal .ui-slider-range-max{right:0}.ui-slider-vertical{width:.8em;height:100px}.ui-slider-vertical .ui-slider-handle{left:-.3em;margin-left:0;margin-bottom:-.6em}.ui-slider-vertical .ui-slider-range{left:0;width:100%}.ui-slider-vertical .ui-slider-range-min{bottom:0}.ui-slider-vertical .ui-slider-range-max{top:0}.ui-spinner{position:relative;display:inline-block;overflow:hidden;padding:0;vertical-align:middle}.ui-spinner-input{border:none;background:none;padding:0;margin:.2em 0;vertical-align:middle;margin-left:.4em;margin-right:22px}.ui-spinner-button{width:16px;height:50%;font-size:.5em;padding:0;margin:0;text-align:center;position:absolute;cursor:default;display:block;overflow:hidden;right:0}.ui-spinner a.ui-spinner-button{border-top:none;border-bottom:none;border-right:none}.ui-spinner .ui-icon{position:absolute;margin-top:-8px;top:50%;left:0}.ui-spinner-up{top:0}.ui-spinner-down{bottom:0}.ui-spinner .ui-icon-triangle-1-s{background-position:-65px -16px}.ui-tabs{position:relative;padding:.2em;zoom:1}.ui-tabs .ui-tabs-nav{margin:0;padding:.2em .2em 0}.ui-tabs .ui-tabs-nav li{list-style:none;float:left;position:relative;top:0;margin:1px .2em 0 0;border-bottom:0;padding:0;white-space:nowrap}.ui-tabs .ui-tabs-nav li a{float:left;padding:.5em 1em;text-decoration:none}.ui-tabs .ui-tabs-nav li.ui-tabs-active{margin-bottom:-1px;padding-bottom:1px}.ui-tabs .ui-tabs-nav li.ui-tabs-active a,.ui-tabs .ui-tabs-nav li.ui-state-disabled a,.ui-tabs .ui-tabs-nav li.ui-tabs-loading a{cursor:text}.ui-tabs .ui-tabs-nav li a,.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active a{cursor:pointer}.ui-tabs .ui-tabs-panel{display:block;border-width:0;padding:1em 1.4em;background:none}.ui-tooltip{padding:8px;position:absolute;z-index:9999;max-width:300px;-webkit-box-shadow:0 0 5px #aaa;box-shadow:0 0 5px #aaa}* html .ui-tooltip{background-image:none}body .ui-tooltip{border-width:2px}.ui-widget{font-family:Verdana,sans-serif;font-size:1.1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Verdana,sans-serif;font-size:1em}.ui-widget-content{border:1px solid #ddd;background:#eee url(images/ui-bg_highlight-soft_100_eeeeee_1x100.png) 50% top repeat-x;color:#333}.ui-widget-content a{color:#333}.ui-widget-header{border:1px solid #628db6;background:#759fcf url(images/ui-bg_gloss-wave_35_759fcf_500x100.png) 50% 50% repeat-x;color:#fff;font-weight:bold}.ui-widget-header a{color:#fff}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #ccc;background:#f6f6f6 url(images/ui-bg_glass_100_f6f6f6_1x400.png) 50% 50% repeat-x;font-weight:bold;color:#628db6}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#628db6;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus{border:1px solid #628db6;background:#eef5fd url(images/ui-bg_glass_100_eef5fd_1x400.png) 50% 50% repeat-x;font-weight:bold;color:#628db6}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited{color:#628db6;text-decoration:none}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #628db6;background:#fff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x;font-weight:bold;color:#628db6}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#628db6;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #628db6;background:#759fcf url(images/ui-bg_highlight-soft_75_759fcf_1x100.png) 50% top repeat-x;color:#363636}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#363636}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #cd0a0a;background:#b81900 url(images/ui-bg_diagonals-thick_18_b81900_40x40.png) 50% 50% repeat;color:#fff}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#fff}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#fff}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px;background-image:url(images/ui-icons_222222_256x240.png)}.ui-widget-content .ui-icon{background-image:url(images/ui-icons_222222_256x240.png)}.ui-widget-header .ui-icon{background-image:url(images/ui-icons_ffffff_256x240.png)}.ui-state-default .ui-icon{background-image:url(images/ui-icons_759fcf_256x240.png)}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{background-image:url(images/ui-icons_759fcf_256x240.png)}.ui-state-active .ui-icon{background-image:url(images/ui-icons_759fcf_256x240.png)}.ui-state-highlight .ui-icon{background-image:url(images/ui-icons_759fcf_256x240.png)}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url(images/ui-icons_ffd27a_256x240.png)}.ui-icon-carat-1-n{background-position:0 0}.ui-icon-carat-1-ne{background-position:-16px 0}.ui-icon-carat-1-e{background-position:-32px 0}.ui-icon-carat-1-se{background-position:-48px 0}.ui-icon-carat-1-s{background-position:-64px 0}.ui-icon-carat-1-sw{background-position:-80px 0}.ui-icon-carat-1-w{background-position:-96px 0}.ui-icon-carat-1-nw{background-position:-112px 0}.ui-icon-carat-2-n-s{background-position:-128px 0}.ui-icon-carat-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-64px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-64px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:0 -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{-moz-border-radius-topleft:4px;-webkit-border-top-left-radius:4px;-khtml-border-top-left-radius:4px;border-top-left-radius:4px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{-moz-border-radius-topright:4px;-webkit-border-top-right-radius:4px;-khtml-border-top-right-radius:4px;border-top-right-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{-moz-border-radius-bottomleft:4px;-webkit-border-bottom-left-radius:4px;-khtml-border-bottom-left-radius:4px;border-bottom-left-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{-moz-border-radius-bottomright:4px;-webkit-border-bottom-right-radius:4px;-khtml-border-bottom-right-radius:4px;border-bottom-right-radius:4px}.ui-widget-overlay{background:#666 url(images/ui-bg_diagonals-thick_20_666666_40x40.png) 50% 50% repeat;opacity:.5;filter:Alpha(Opacity=50)}.ui-widget-shadow{margin:-5px 0 0 -5px;padding:5px;background:#000 url(images/ui-bg_flat_10_000000_40x100.png) 50% 50% repeat-x;opacity:.2;filter:Alpha(Opacity=20);-moz-border-radius:5px;-khtml-border-radius:5px;-webkit-border-radius:5px;border-radius:5px} diff -r 0a574315af3e -r 4f746d8966dd public/stylesheets/jstoolbar.css --- a/public/stylesheets/jstoolbar.css Fri Jun 14 09:07:32 2013 +0100 +++ b/public/stylesheets/jstoolbar.css Fri Jun 14 09:28:30 2013 +0100 @@ -95,3 +95,6 @@ .jstb_img { background-image: url(../images/jstoolbar/bt_img.png); } +.jstb_help { + background-image: url(../images/help.png); +} diff -r 0a574315af3e -r 4f746d8966dd test/extra/redmine_pm/repository_git_test.rb --- a/test/extra/redmine_pm/repository_git_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/extra/redmine_pm/repository_git_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/extra/redmine_pm/repository_subversion_test.rb --- a/test/extra/redmine_pm/repository_subversion_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/extra/redmine_pm/repository_subversion_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/extra/redmine_pm/test_case.rb --- a/test/extra/redmine_pm/test_case.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/extra/redmine_pm/test_case.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/fixtures/attachments.yml --- a/test/fixtures/attachments.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/test/fixtures/attachments.yml Fri Jun 14 09:28:30 2013 +0100 @@ -4,6 +4,7 @@ downloads: 0 content_type: text/plain disk_filename: 060719210727_error281.txt + disk_directory: "2006/07" container_id: 3 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2 id: 1 @@ -16,6 +17,7 @@ downloads: 0 content_type: text/plain disk_filename: 060719210727_document.txt + disk_directory: "2006/07" container_id: 1 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2 id: 2 @@ -28,6 +30,7 @@ downloads: 0 content_type: image/gif disk_filename: 060719210727_logo.gif + disk_directory: "2006/07" container_id: 4 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2 id: 3 @@ -42,6 +45,7 @@ container_id: 3 downloads: 0 disk_filename: 060719210727_source.rb + disk_directory: "2006/07" digest: b91e08d0cf966d5c6ff411bd8c4cc3a2 id: 4 filesize: 153 @@ -55,6 +59,7 @@ container_id: 3 downloads: 0 disk_filename: 060719210727_changeset_iso8859-1.diff + disk_directory: "2006/07" digest: b91e08d0cf966d5c6ff411bd8c4cc3a2 id: 5 filesize: 687 @@ -67,6 +72,7 @@ container_id: 3 downloads: 0 disk_filename: 060719210727_archive.zip + disk_directory: "2006/07" digest: b91e08d0cf966d5c6ff411bd8c4cc3a2 id: 6 filesize: 157 @@ -79,6 +85,7 @@ container_id: 4 downloads: 0 disk_filename: 060719210727_archive.zip + disk_directory: "2006/07" digest: b91e08d0cf966d5c6ff411bd8c4cc3a2 id: 7 filesize: 157 @@ -91,6 +98,7 @@ container_id: 1 downloads: 0 disk_filename: 060719210727_project_file.zip + disk_directory: "2006/07" digest: b91e08d0cf966d5c6ff411bd8c4cc3a2 id: 8 filesize: 320 @@ -103,6 +111,7 @@ container_id: 1 downloads: 0 disk_filename: 060719210727_archive.zip + disk_directory: "2006/07" digest: b91e08d0cf966d5c6ff411bd8c4cc3a2 id: 9 filesize: 452 @@ -115,6 +124,7 @@ container_id: 2 downloads: 0 disk_filename: 060719210727_picture.jpg + disk_directory: "2006/07" digest: b91e08d0cf966d5c6ff411bd8c4cc3a2 id: 10 filesize: 452 @@ -127,6 +137,7 @@ container_id: 1 downloads: 0 disk_filename: 060719210727_picture.jpg + disk_directory: "2006/07" digest: b91e08d0cf966d5c6ff411bd8c4cc3a2 id: 11 filesize: 452 @@ -139,6 +150,7 @@ container_id: 1 downloads: 0 disk_filename: 060719210727_version_file.zip + disk_directory: "2006/07" digest: b91e08d0cf966d5c6ff411bd8c4cc3a2 id: 12 filesize: 452 @@ -151,6 +163,7 @@ container_id: 1 downloads: 0 disk_filename: 060719210727_foo.zip + disk_directory: "2006/07" digest: b91e08d0cf966d5c6ff411bd8c4cc3a2 id: 13 filesize: 452 @@ -163,6 +176,7 @@ container_id: 3 downloads: 0 disk_filename: 060719210727_changeset_utf8.diff + disk_directory: "2006/07" digest: b91e08d0cf966d5c6ff411bd8c4cc3a2 id: 14 filesize: 687 @@ -176,6 +190,7 @@ container_id: 14 downloads: 0 disk_filename: 060719210727_changeset_utf8.diff + disk_directory: "2006/07" digest: b91e08d0cf966d5c6ff411bd8c4cc3a2 filesize: 687 filename: private.diff @@ -187,6 +202,7 @@ downloads: 0 created_on: 2010-11-23 16:14:50 +09:00 disk_filename: 101123161450_testfile_1.png + disk_directory: "2010/11" container_id: 14 digest: 8e0294de2441577c529f170b6fb8f638 id: 16 @@ -200,6 +216,7 @@ downloads: 0 created_on: 2010-12-23 16:14:50 +09:00 disk_filename: 101223161450_testfile_2.png + disk_directory: "2010/12" container_id: 14 digest: 6bc2963e8d7ea0d3e68d12d1fba3d6ca id: 17 @@ -213,6 +230,7 @@ downloads: 0 created_on: 2011-01-23 16:14:50 +09:00 disk_filename: 101123161450_testfile_1.png + disk_directory: "2010/11" container_id: 14 digest: 8e0294de2441577c529f170b6fb8f638 id: 18 @@ -226,6 +244,7 @@ downloads: 0 created_on: 2011-02-23 16:14:50 +09:00 disk_filename: 101223161450_testfile_2.png + disk_directory: "2010/12" container_id: 14 digest: 6bc2963e8d7ea0d3e68d12d1fba3d6ca id: 19 @@ -234,3 +253,17 @@ filename: Testテスト.PNG filesize: 3582 author_id: 2 +attachments_020: + content_type: text/plain + downloads: 0 + created_on: 2012-05-12 16:14:50 +09:00 + disk_filename: 120512161450_root_attachment.txt + disk_directory: + container_id: 14 + digest: b0fe2abdb2599743d554a61d7da7ff74 + id: 20 + container_type: Issue + description: "" + filename: root_attachment.txt + filesize: 54 + author_id: 2 diff -r 0a574315af3e -r 4f746d8966dd test/fixtures/changesets.yml --- a/test/fixtures/changesets.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/test/fixtures/changesets.yml Fri Jun 14 09:28:30 2013 +0100 @@ -3,8 +3,9 @@ commit_date: 2007-04-11 committed_on: 2007-04-11 15:14:44 +02:00 revision: 1 + scmid: 691322a8eb01e11fd7 id: 100 - comments: My very first commit + comments: 'My very first commit do not escaping #<>&' repository_id: 10 committer: dlopper user_id: 3 diff -r 0a574315af3e -r 4f746d8966dd test/fixtures/diffs/issue-12641-ja.diff --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/fixtures/diffs/issue-12641-ja.diff Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,25 @@ +# HG changeset patch +# User tmaruyama +# Date 1362559296 0 +# Node ID ee54942e0289c30bea1b1973750b698b1ee7c466 +# Parent 738777832f379f6f099c25251593fc57bc17f586 +fix some Japanese "issue" translations (#13350) + +Contributed by Go MAEDA. + +diff --git a/config/locales/ja.yml b/config/locales/ja.yml +--- a/config/locales/ja.yml ++++ b/config/locales/ja.yml +@@ -904,9 +904,9 @@ ja: + text_journal_set_to: "%{label} ã‚’ %{value} ã«ã‚»ãƒƒãƒˆ" + text_journal_deleted: "%{label} を削除 (%{old})" + text_journal_added: "%{label} %{value} を追加" +- text_tip_issue_begin_day: ã“ã®æ—¥ã«é–‹å§‹ã™ã‚‹ã‚¿ã‚¹ã‚¯ +- text_tip_issue_end_day: ã“ã®æ—¥ã«çµ‚了ã™ã‚‹ã‚¿ã‚¹ã‚¯ +- text_tip_issue_begin_end_day: ã“ã®æ—¥ã®ã†ã¡ã«é–‹å§‹ã—ã¦çµ‚了ã™ã‚‹ã‚¿ã‚¹ã‚¯ ++ text_tip_issue_begin_day: ã“ã®æ—¥ã«é–‹å§‹ã™ã‚‹ãƒã‚±ãƒƒãƒˆ ++ text_tip_issue_end_day: ã“ã®æ—¥ã«çµ‚了ã™ã‚‹ãƒã‚±ãƒƒãƒˆ ++ text_tip_issue_begin_end_day: ã“ã®æ—¥ã«é–‹å§‹ãƒ»çµ‚了ã™ã‚‹ãƒã‚±ãƒƒãƒˆ + text_caracters_maximum: "最大%{count}文字ã§ã™ã€‚" + text_caracters_minimum: "最低%{count}文字ã®é•·ã•ãŒå¿…è¦ã§ã™" + text_length_between: "é•·ã•ã¯%{min}ã‹ã‚‰%{max}文字ã¾ã§ã§ã™ã€‚" diff -r 0a574315af3e -r 4f746d8966dd test/fixtures/diffs/issue-12641-ru.diff --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/fixtures/diffs/issue-12641-ru.diff Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,19 @@ +# HG changeset patch +# User tmaruyama +# Date 1355872765 0 +# Node ID 8a13ebed1779c2e85fa644ecdd0de81996c969c4 +# Parent 5c3c5f917ae92f278fe42c6978366996595b0796 +Russian "about_x_hours" translation changed by Mikhail Velkin (#12640) + +diff --git a/config/locales/ru.yml b/config/locales/ru.yml +--- a/config/locales/ru.yml ++++ b/config/locales/ru.yml +@@ -115,7 +115,7 @@ ru: + one: "около %{count} чаÑа" + few: "около %{count} чаÑов" + many: "около %{count} чаÑов" +- other: "около %{count} чаÑа" ++ other: "около %{count} чаÑов" + x_hours: + one: "1 чаÑ" + other: "%{count} чаÑов" diff -r 0a574315af3e -r 4f746d8966dd test/fixtures/diffs/issue-13644-1.diff --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/fixtures/diffs/issue-13644-1.diff Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,7 @@ +--- a.txt 2013-04-05 14:19:39.000000000 +0900 ++++ b.txt 2013-04-05 14:19:51.000000000 +0900 +@@ -1,3 +1,3 @@ + aaaa +-日本 ++日本語 + bbbb diff -r 0a574315af3e -r 4f746d8966dd test/fixtures/diffs/issue-13644-2.diff --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/fixtures/diffs/issue-13644-2.diff Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,7 @@ +--- a.txt 2013-04-05 14:19:39.000000000 +0900 ++++ b.txt 2013-04-05 14:19:51.000000000 +0900 +@@ -1,3 +1,3 @@ + aaaa +-日本 ++ã«ã£ã½ã‚“日本 + bbbb diff -r 0a574315af3e -r 4f746d8966dd test/fixtures/files/060719210727_archive.zip Binary file test/fixtures/files/060719210727_archive.zip has changed diff -r 0a574315af3e -r 4f746d8966dd test/fixtures/files/060719210727_changeset_iso8859-1.diff --- a/test/fixtures/files/060719210727_changeset_iso8859-1.diff Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,13 +0,0 @@ -Index: trunk/app/controllers/issues_controller.rb -=================================================================== ---- trunk/app/controllers/issues_controller.rb (révision 1483) -+++ trunk/app/controllers/issues_controller.rb (révision 1484) -@@ -149,7 +149,7 @@ - attach_files(@issue, params[:attachments]) - flash[:notice] = 'Demande créée avec succès' - Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added') -- redirect_to :controller => 'issues', :action => 'show', :id => @issue, :project_id => @project -+ redirect_to :controller => 'issues', :action => 'show', :id => @issue - return - end - end diff -r 0a574315af3e -r 4f746d8966dd test/fixtures/files/060719210727_changeset_utf8.diff --- a/test/fixtures/files/060719210727_changeset_utf8.diff Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,13 +0,0 @@ -Index: trunk/app/controllers/issues_controller.rb -=================================================================== ---- trunk/app/controllers/issues_controller.rb (révision 1483) -+++ trunk/app/controllers/issues_controller.rb (révision 1484) -@@ -149,7 +149,7 @@ - attach_files(@issue, params[:attachments]) - flash[:notice] = 'Demande créée avec succès' - Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added') -- redirect_to :controller => 'issues', :action => 'show', :id => @issue, :project_id => @project -+ redirect_to :controller => 'issues', :action => 'show', :id => @issue - return - end - end diff -r 0a574315af3e -r 4f746d8966dd test/fixtures/files/060719210727_source.rb --- a/test/fixtures/files/060719210727_source.rb Fri Jun 14 09:07:32 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,10 +0,0 @@ -# The Greeter class -class Greeter - def initialize(name) - @name = name.capitalize - end - - def salute - puts "Hello #{@name}!" - end -end diff -r 0a574315af3e -r 4f746d8966dd test/fixtures/files/101123161450_testfile_1.png Binary file test/fixtures/files/101123161450_testfile_1.png has changed diff -r 0a574315af3e -r 4f746d8966dd test/fixtures/files/101223161450_testfile_2.png Binary file test/fixtures/files/101223161450_testfile_2.png has changed diff -r 0a574315af3e -r 4f746d8966dd test/fixtures/issues.yml --- a/test/fixtures/issues.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/test/fixtures/issues.yml Fri Jun 14 09:28:30 2013 +0100 @@ -152,6 +152,7 @@ root_id: 8 lft: 1 rgt: 2 + closed_on: <%= 3.days.ago.to_s(:db) %> issues_009: created_on: <%= 1.minute.ago.to_s(:db) %> project_id: 5 @@ -209,6 +210,7 @@ root_id: 11 lft: 1 rgt: 2 + closed_on: <%= 1.day.ago.to_s(:db) %> issues_012: created_on: <%= 3.days.ago.to_s(:db) %> project_id: 1 @@ -228,6 +230,7 @@ root_id: 12 lft: 1 rgt: 2 + closed_on: <%= 1.day.ago.to_s(:db) %> issues_013: created_on: <%= 5.days.ago.to_s(:db) %> project_id: 3 diff -r 0a574315af3e -r 4f746d8966dd test/fixtures/journal_details.yml --- a/test/fixtures/journal_details.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/test/fixtures/journal_details.yml Fri Jun 14 09:28:30 2013 +0100 @@ -14,7 +14,7 @@ prop_key: done_ratio journal_id: 1 journal_details_003: - old_value: nil + old_value: property: attr id: 3 value: "6" @@ -35,7 +35,7 @@ prop_key: 2 journal_id: 3 journal_details_006: - old_value: nil + old_value: property: attachment id: 6 value: 060719210727_picture.jpg diff -r 0a574315af3e -r 4f746d8966dd test/fixtures/queries.yml --- a/test/fixtures/queries.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/test/fixtures/queries.yml Fri Jun 14 09:28:30 2013 +0100 @@ -1,6 +1,7 @@ --- queries_001: id: 1 + type: IssueQuery project_id: 1 is_public: true name: Multiple custom fields query @@ -23,6 +24,7 @@ column_names: queries_002: id: 2 + type: IssueQuery project_id: 1 is_public: false name: Private query for cookbook @@ -41,6 +43,7 @@ column_names: queries_003: id: 3 + type: IssueQuery project_id: is_public: false name: Private query for all projects @@ -55,6 +58,7 @@ column_names: queries_004: id: 4 + type: IssueQuery project_id: is_public: true name: Public query for all projects @@ -69,6 +73,7 @@ column_names: queries_005: id: 5 + type: IssueQuery project_id: is_public: true name: Open issues by priority and tracker @@ -89,6 +94,7 @@ - asc queries_006: id: 6 + type: IssueQuery project_id: is_public: true name: Open issues grouped by tracker @@ -108,6 +114,7 @@ - desc queries_007: id: 7 + type: IssueQuery project_id: 2 is_public: true name: Public query for project 2 @@ -122,6 +129,7 @@ column_names: queries_008: id: 8 + type: IssueQuery project_id: 2 is_public: false name: Private query for project 2 @@ -136,6 +144,7 @@ column_names: queries_009: id: 9 + type: IssueQuery project_id: is_public: true name: Open issues grouped by list custom field diff -r 0a574315af3e -r 4f746d8966dd test/fixtures/roles.yml --- a/test/fixtures/roles.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/test/fixtures/roles.yml Fri Jun 14 09:28:30 2013 +0100 @@ -38,7 +38,9 @@ - :manage_news - :comment_news - :view_documents - - :manage_documents + - :add_documents + - :edit_documents + - :delete_documents - :view_wiki_pages - :export_wiki_pages - :view_wiki_edits @@ -89,7 +91,9 @@ - :manage_news - :comment_news - :view_documents - - :manage_documents + - :add_documents + - :edit_documents + - :delete_documents - :view_wiki_pages - :view_wiki_edits - :edit_wiki_pages @@ -131,7 +135,9 @@ - :manage_news - :comment_news - :view_documents - - :manage_documents + - :add_documents + - :edit_documents + - :delete_documents - :view_wiki_pages - :view_wiki_edits - :edit_wiki_pages @@ -163,7 +169,6 @@ - :view_time_entries - :comment_news - :view_documents - - :manage_documents - :view_wiki_pages - :view_wiki_edits - :edit_wiki_pages diff -r 0a574315af3e -r 4f746d8966dd test/fixtures/users.yml --- a/test/fixtures/users.yml Fri Jun 14 09:07:32 2013 +0100 +++ b/test/fixtures/users.yml Fri Jun 14 09:28:30 2013 +0100 @@ -29,7 +29,7 @@ admin: true mail: admin@somenet.foo lastname: Admin - firstname: redMine + firstname: Redmine id: 1 auth_source_id: mail_notification: all @@ -105,12 +105,15 @@ login: '' type: AnonymousUser users_007: + # A user who does not belong to any project id: 7 created_on: 2006-07-19 19:33:19 +02:00 status: 1 last_login_on: - language: '' - hashed_password: 1 + language: 'en' + # password = foo + salt: 7599f9963ec07b5a3b55b354407120c0 + hashed_password: 8f659c8d7c072f189374edacfa90d6abbc26d8ed updated_on: 2006-07-19 19:33:19 +02:00 admin: false mail: someone@foo.bar diff -r 0a574315af3e -r 4f746d8966dd test/functional/account_controller_openid_test.rb --- a/test/functional/account_controller_openid_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/account_controller_openid_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/functional/account_controller_test.rb --- a/test/functional/account_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/account_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,18 +16,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'account_controller' - -# Re-raise errors caught by the controller. -class AccountController; def rescue_action(e) raise e end; end class AccountControllerTest < ActionController::TestCase fixtures :users, :roles def setup - @controller = AccountController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil end @@ -40,6 +33,14 @@ assert_select 'input[name=password]' end + def test_get_login_while_logged_in_should_redirect_to_home + @request.session[:user_id] = 2 + + get :login + assert_redirected_to '/' + assert_equal 2, @request.session[:user_id] + end + def test_login_should_redirect_to_back_url_param # request.uri is "test.host" in test environment post :login, :username => 'jsmith', :password => 'jsmith', :back_url => 'http://test.host/issues/show/1' @@ -79,9 +80,18 @@ assert_response 302 end + def test_get_logout_should_not_logout + @request.session[:user_id] = 2 + get :logout + assert_response :success + assert_template 'logout' + + assert_equal 2, @request.session[:user_id] + end + def test_logout @request.session[:user_id] = 2 - get :logout + post :logout assert_redirected_to '/' assert_nil @request.session[:user_id] end @@ -90,7 +100,7 @@ @controller.expects(:reset_session).once @request.session[:user_id] = 2 - get :logout + post :logout assert_response 302 end @@ -101,8 +111,21 @@ assert_template 'register' assert_not_nil assigns(:user) - assert_tag 'input', :attributes => {:name => 'user[password]'} - assert_tag 'input', :attributes => {:name => 'user[password_confirmation]'} + assert_select 'input[name=?]', 'user[password]' + assert_select 'input[name=?]', 'user[password_confirmation]' + end + end + + def test_get_register_should_detect_user_language + with_settings :self_registration => '3' do + @request.env['HTTP_ACCEPT_LANGUAGE'] = 'fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3' + get :register + assert_response :success + assert_not_nil assigns(:user) + assert_equal 'fr', assigns(:user).language + assert_select 'select[name=?]', 'user[language]' do + assert_select 'option[value=fr][selected=selected]' + end end end diff -r 0a574315af3e -r 4f746d8966dd test/functional/activities_controller_test.rb --- a/test/functional/activities_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/activities_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,3 +1,20 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) class ActivitiesControllerTest < ActionController::TestCase @@ -9,7 +26,6 @@ :members, :groups_users, :enabled_modules, - :workflows, :journals, :journal_details @@ -19,16 +35,8 @@ assert_template 'index' assert_not_nil assigns(:events_by_day) - assert_tag :tag => "h3", - :content => /#{2.days.ago.to_date.day}/, - :sibling => { :tag => "dl", - :child => { :tag => "dt", - :attributes => { :class => /issue-edit/ }, - :child => { :tag => "a", - :content => /(#{IssueStatus.find(2).name})/, - } - } - } + assert_select 'h3', :text => /#{2.days.ago.to_date.day}/ + assert_select 'dl dt.issue-edit a', :text => /(#{IssueStatus.find(2).name})/ end def test_project_index_with_invalid_project_id_should_respond_404 @@ -42,16 +50,8 @@ assert_template 'index' 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 => /Can't print recipes/, - } - } - } + assert_select 'h3', :text => /#{3.days.ago.to_date.day}/ + assert_select 'dl dt.issue a', :text => /Can't print recipes/ end def test_global_index @@ -63,16 +63,9 @@ i5 = Issue.find(5) d5 = User.find(1).time_to_date(i5.created_on) - assert_tag :tag => "h3", - :content => /#{d5.day}/, - :sibling => { :tag => "dl", - :child => { :tag => "dt", - :attributes => { :class => /issue/ }, - :child => { :tag => "a", - :content => /Subproject issue/, - } - } - } + + assert_select 'h3', :text => /#{d5.day}/ + assert_select 'dl dt.issue a', :text => /Subproject issue/ end def test_user_index @@ -87,16 +80,8 @@ i1 = Issue.find(1) d1 = User.find(1).time_to_date(i1.created_on) - assert_tag :tag => "h3", - :content => /#{d1.day}/, - :sibling => { :tag => "dl", - :child => { :tag => "dt", - :attributes => { :class => /issue/ }, - :child => { :tag => "a", - :content => /Can't print recipes/, - } - } - } + assert_select 'h3', :text => /#{d1.day}/ + assert_select 'dl dt.issue a', :text => /Can't print recipes/ end def test_user_index_with_invalid_user_id_should_respond_404 @@ -109,14 +94,13 @@ assert_response :success assert_template 'common/feed' - assert_tag :tag => 'link', :parent => {:tag => 'feed', :parent => nil }, - :attributes => {:rel => 'self', :href => 'http://test.host/activity.atom?with_subprojects=0'} - assert_tag :tag => 'link', :parent => {:tag => 'feed', :parent => nil }, - :attributes => {:rel => 'alternate', :href => 'http://test.host/activity?with_subprojects=0'} - - assert_tag :tag => 'entry', :child => { - :tag => 'link', - :attributes => {:href => 'http://test.host/issues/11'}} + assert_select 'feed' do + assert_select 'link[rel=self][href=?]', 'http://test.host/activity.atom?with_subprojects=0' + assert_select 'link[rel=alternate][href=?]', 'http://test.host/activity?with_subprojects=0' + assert_select 'entry' do + assert_select 'link[href=?]', 'http://test.host/issues/11' + end + end end def test_index_atom_feed_with_explicit_selection @@ -133,21 +117,21 @@ assert_response :success assert_template 'common/feed' - assert_tag :tag => 'link', :parent => {:tag => 'feed', :parent => nil }, - :attributes => {:rel => 'self', :href => 'http://test.host/activity.atom?show_changesets=1&show_documents=1&show_files=1&show_issues=1&show_messages=1&show_news=1&show_time_entries=1&show_wiki_edits=1&with_subprojects=0'} - assert_tag :tag => 'link', :parent => {:tag => 'feed', :parent => nil }, - :attributes => {:rel => 'alternate', :href => 'http://test.host/activity?show_changesets=1&show_documents=1&show_files=1&show_issues=1&show_messages=1&show_news=1&show_time_entries=1&show_wiki_edits=1&with_subprojects=0'} - - assert_tag :tag => 'entry', :child => { - :tag => 'link', - :attributes => {:href => 'http://test.host/issues/11'}} + assert_select 'feed' do + assert_select 'link[rel=self][href=?]', 'http://test.host/activity.atom?show_changesets=1&show_documents=1&show_files=1&show_issues=1&show_messages=1&show_news=1&show_time_entries=1&show_wiki_edits=1&with_subprojects=0' + assert_select 'link[rel=alternate][href=?]', 'http://test.host/activity?show_changesets=1&show_documents=1&show_files=1&show_issues=1&show_messages=1&show_news=1&show_time_entries=1&show_wiki_edits=1&with_subprojects=0' + assert_select 'entry' do + assert_select 'link[href=?]', 'http://test.host/issues/11' + end + end end def test_index_atom_feed_with_one_item_type get :index, :format => 'atom', :show_issues => '1' assert_response :success assert_template 'common/feed' - assert_tag :tag => 'title', :content => /Issues/ + + assert_select 'title', :text => /Issues/ end def test_index_should_show_private_notes_with_permission_only diff -r 0a574315af3e -r 4f746d8966dd test/functional/admin_controller_test.rb --- a/test/functional/admin_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/admin_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -27,15 +27,13 @@ def test_index get :index - assert_no_tag :tag => 'div', - :attributes => { :class => /nodata/ } + assert_select 'div.nodata', 0 end def test_index_with_no_configuration_data delete_configuration_data get :index - assert_tag :tag => 'div', - :attributes => { :class => /nodata/ } + assert_select 'div.nodata' end def test_projects @@ -90,7 +88,7 @@ ActionMailer::Base.deliveries.clear get :test_email - assert_redirected_to '/settings/edit?tab=notifications' + assert_redirected_to '/settings?tab=notifications' mail = ActionMailer::Base.deliveries.last assert_not_nil mail user = User.find(1) @@ -100,7 +98,7 @@ def test_test_email_failure_should_display_the_error Mailer.stubs(:test_email).raises(Exception, 'Some error message') get :test_email - assert_redirected_to '/settings/edit?tab=notifications' + assert_redirected_to '/settings?tab=notifications' assert_match /Some error message/, flash[:error] end @@ -128,8 +126,14 @@ assert_response :success assert_template 'plugins' - assert_tag :td, :child => { :tag => 'span', :content => 'Foo plugin' } - assert_tag :td, :child => { :tag => 'span', :content => 'Bar' } + assert_select 'tr#plugin-foo' do + assert_select 'td span.name', :text => 'Foo plugin' + assert_select 'td.configure a[href=/settings/plugin/foo]' + end + assert_select 'tr#plugin-bar' do + assert_select 'td span.name', :text => 'Bar' + assert_select 'td.configure a', 0 + end end def test_info @@ -145,8 +149,7 @@ get :index assert_response :success - assert_tag :a, :attributes => { :href => '/foo/bar' }, - :content => 'Test' + assert_select 'div#admin-menu a[href=/foo/bar]', :text => 'Test' Redmine::MenuManager.map :admin_menu do |menu| menu.delete :test_admin_menu_plugin_extension diff -r 0a574315af3e -r 4f746d8966dd test/functional/attachments_controller_test.rb --- a/test/functional/attachments_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/attachments_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -18,10 +18,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'attachments_controller' - -# Re-raise errors caught by the controller. -class AttachmentsController; def rescue_action(e) raise e end; end class AttachmentsControllerTest < ActionController::TestCase fixtures :users, :projects, :roles, :members, :member_roles, @@ -29,9 +25,6 @@ :versions, :wiki_pages, :wikis, :documents def setup - @controller = AttachmentsController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil set_fixtures_attachments_directory end @@ -57,7 +50,7 @@ set_tmp_attachments_directory end - def test_show_diff_replcace_cannot_convert_content + def test_show_diff_replace_cannot_convert_content with_settings :repositories_encodings => 'UTF-8' do ['inline', 'sbs'].each do |dt| # 060719210727_changeset_iso8859-1.diff @@ -159,7 +152,7 @@ :sibling => { :tag => 'td', :content => /#{str_japanese}/ } end - def test_show_text_file_replcace_cannot_convert_content + def test_show_text_file_replace_cannot_convert_content set_tmp_attachments_directory with_settings :repositories_encodings => 'UTF-8' do a = Attachment.new(:container => Issue.find(1), @@ -230,12 +223,21 @@ set_tmp_attachments_directory end - def test_show_file_without_container_should_be_denied + def test_show_file_without_container_should_be_allowed_to_author set_tmp_attachments_directory attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2) @request.session[:user_id] = 2 get :show, :id => attachment.id + assert_response 200 + end + + def test_show_file_without_container_should_be_denied_to_other_users + set_tmp_attachments_directory + attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2) + + @request.session[:user_id] = 3 + get :show, :id => attachment.id assert_response 403 end diff -r 0a574315af3e -r 4f746d8966dd test/functional/auth_sources_controller_test.rb --- a/test/functional/auth_sources_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/auth_sources_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -42,8 +42,15 @@ assert_equal AuthSourceLdap, source.class assert source.new_record? - assert_tag 'input', :attributes => {:name => 'type', :value => 'AuthSourceLdap'} - assert_tag 'input', :attributes => {:name => 'auth_source[host]'} + assert_select 'form#auth_source_form' do + assert_select 'input[name=type][value=AuthSourceLdap]' + assert_select 'input[name=?]', 'auth_source[host]' + end + end + + def test_new_with_invalid_type_should_respond_with_404 + get :new, :type => 'foo' + assert_response 404 end def test_create @@ -52,7 +59,7 @@ assert_redirected_to '/auth_sources' end - source = AuthSourceLdap.first(:order => 'id DESC') + source = AuthSourceLdap.order('id DESC').first assert_equal 'Test', source.name assert_equal '127.0.0.1', source.host assert_equal 389, source.port @@ -74,7 +81,23 @@ assert_response :success assert_template 'edit' - assert_tag 'input', :attributes => {:name => 'auth_source[host]'} + assert_select 'form#auth_source_form' do + assert_select 'input[name=?]', 'auth_source[host]' + end + end + + def test_edit_should_not_contain_password + AuthSource.find(1).update_column :account_password, 'secret' + + get :edit, :id => 1 + assert_response :success + assert_select 'input[value=secret]', 0 + assert_select 'input[name=dummy_password][value=?]', /x+/ + end + + def test_edit_invalid_should_respond_with_404 + get :edit, :id => 99 + assert_response 404 end def test_update @@ -96,6 +119,7 @@ def test_destroy assert_difference 'AuthSourceLdap.count', -1 do delete :destroy, :id => 1 + assert_redirected_to '/auth_sources' end end @@ -104,6 +128,7 @@ assert_no_difference 'AuthSourceLdap.count' do delete :destroy, :id => 1 + assert_redirected_to '/auth_sources' end end @@ -124,4 +149,20 @@ assert_not_nil flash[:error] assert_include 'Something went wrong', flash[:error] end + + def test_autocomplete_for_new_user + AuthSource.expects(:search).with('foo').returns([ + {:login => 'foo1', :firstname => 'John', :lastname => 'Smith', :mail => 'foo1@example.net', :auth_source_id => 1}, + {:login => 'Smith', :firstname => 'John', :lastname => 'Doe', :mail => 'foo2@example.net', :auth_source_id => 1} + ]) + + get :autocomplete_for_new_user, :term => 'foo' + assert_response :success + assert_equal 'application/json', response.content_type + json = ActiveSupport::JSON.decode(response.body) + assert_kind_of Array, json + assert_equal 2, json.size + assert_equal 'foo1', json.first['value'] + assert_equal 'foo1 (John Smith)', json.first['label'] + end end diff -r 0a574315af3e -r 4f746d8966dd test/functional/auto_completes_controller_test.rb --- a/test/functional/auto_completes_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/auto_completes_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,3 +1,20 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) class AutoCompletesControllerTest < ActionController::TestCase @@ -9,7 +26,6 @@ :member_roles, :members, :enabled_modules, - :workflows, :journals, :journal_details def test_issues_should_not_be_case_sensitive @@ -33,6 +49,13 @@ assert assigns(:issues).include?(Issue.find(13)) end + def test_issues_should_return_issue_with_given_id_preceded_with_hash + get :issues, :project_id => 'subproject1', :q => '#13' + assert_response :success + assert_not_nil assigns(:issues) + assert assigns(:issues).include?(Issue.find(13)) + end + def test_auto_complete_with_scope_all_should_search_other_projects get :issues, :project_id => 'ecookbook', :q => '13', :scope => 'all' assert_response :success diff -r 0a574315af3e -r 4f746d8966dd test/functional/boards_controller_test.rb --- a/test/functional/boards_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/boards_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -90,8 +90,9 @@ assert_response :success assert_template 'show' - assert_tag 'form', :attributes => {:id => 'message-form'} - assert_tag 'input', :attributes => {:name => 'message[subject]'} + assert_select 'form#message-form' do + assert_select 'input[name=?]', 'message[subject]' + end end def test_show_atom diff -r 0a574315af3e -r 4f746d8966dd test/functional/calendars_controller_test.rb --- a/test/functional/calendars_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/calendars_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,3 +1,20 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) class CalendarsControllerTest < ActionController::TestCase @@ -9,13 +26,20 @@ :members, :enabled_modules - def test_calendar + def test_show get :show, :project_id => 1 assert_response :success assert_template 'calendar' assert_not_nil assigns(:calendar) end + def test_show_should_run_custom_queries + @query = IssueQuery.create!(:name => 'Calendar', :is_public => true) + + get :show, :query_id => @query.id + assert_response :success + end + def test_cross_project_calendar get :show assert_response :success @@ -23,58 +47,38 @@ assert_not_nil assigns(:calendar) end - context "GET :show" do - should "run custom queries" do - @query = Query.create!(:name => 'Calendar', :is_public => true) - - get :show, :query_id => @query.id - assert_response :success - end - - end - def test_week_number_calculation Setting.start_of_week = 7 get :show, :month => '1', :year => '2010' assert_response :success - assert_tag :tag => 'tr', - :descendant => {:tag => 'td', - :attributes => {:class => 'week-number'}, :content => '53'}, - :descendant => {:tag => 'td', - :attributes => {:class => 'odd'}, :content => '27'}, - :descendant => {:tag => 'td', - :attributes => {:class => 'even'}, :content => '2'} + assert_select 'tr' do + assert_select 'td.week-number', :text => '53' + assert_select 'td.odd', :text => '27' + assert_select 'td.even', :text => '2' + end - assert_tag :tag => 'tr', - :descendant => {:tag => 'td', - :attributes => {:class => 'week-number'}, :content => '1'}, - :descendant => {:tag => 'td', - :attributes => {:class => 'odd'}, :content => '3'}, - :descendant => {:tag => 'td', - :attributes => {:class => 'even'}, :content => '9'} - + assert_select 'tr' do + assert_select 'td.week-number', :text => '1' + assert_select 'td.odd', :text => '3' + assert_select 'td.even', :text => '9' + end Setting.start_of_week = 1 get :show, :month => '1', :year => '2010' assert_response :success - assert_tag :tag => 'tr', - :descendant => {:tag => 'td', - :attributes => {:class => 'week-number'}, :content => '53'}, - :descendant => {:tag => 'td', - :attributes => {:class => 'even'}, :content => '28'}, - :descendant => {:tag => 'td', - :attributes => {:class => 'even'}, :content => '3'} + assert_select 'tr' do + assert_select 'td.week-number', :text => '53' + assert_select 'td.even', :text => '28' + assert_select 'td.even', :text => '3' + end - assert_tag :tag => 'tr', - :descendant => {:tag => 'td', - :attributes => {:class => 'week-number'}, :content => '1'}, - :descendant => {:tag => 'td', - :attributes => {:class => 'even'}, :content => '4'}, - :descendant => {:tag => 'td', - :attributes => {:class => 'even'}, :content => '10'} - + assert_select 'tr' do + assert_select 'td.week-number', :text => '1' + assert_select 'td.even', :text => '4' + assert_select 'td.even', :text => '10' + end end end diff -r 0a574315af3e -r 4f746d8966dd test/functional/comments_controller_test.rb --- a/test/functional/comments_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/comments_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/functional/context_menus_controller_test.rb --- a/test/functional/context_menus_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/context_menus_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,3 +1,20 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) class ContextMenusControllerTest < ActionController::TestCase @@ -21,34 +38,21 @@ get :issues, :ids => [1] assert_response :success assert_template 'context_menu' - assert_tag :tag => 'a', :content => 'Edit', - :attributes => { :href => '/issues/1/edit', - :class => 'icon-edit' } - assert_tag :tag => 'a', :content => 'Closed', - :attributes => { :href => '/issues/bulk_update?ids%5B%5D=1&issue%5Bstatus_id%5D=5', - :class => '' } - assert_tag :tag => 'a', :content => 'Immediate', - :attributes => { :href => '/issues/bulk_update?ids%5B%5D=1&issue%5Bpriority_id%5D=8', - :class => '' } - assert_no_tag :tag => 'a', :content => 'Inactive Priority' + + assert_select 'a.icon-edit[href=?]', '/issues/1/edit', :text => 'Edit' + assert_select 'a.icon-copy[href=?]', '/projects/ecookbook/issues/1/copy', :text => 'Copy' + assert_select 'a.icon-del[href=?]', '/issues?ids%5B%5D=1', :text => 'Delete' + + # Statuses + assert_select 'a[href=?]', '/issues/bulk_update?ids%5B%5D=1&issue%5Bstatus_id%5D=5', :text => 'Closed' + assert_select 'a[href=?]', '/issues/bulk_update?ids%5B%5D=1&issue%5Bpriority_id%5D=8', :text => 'Immediate' + # No inactive priorities + assert_select 'a', :text => /Inactive Priority/, :count => 0 # Versions - assert_tag :tag => 'a', :content => '2.0', - :attributes => { :href => '/issues/bulk_update?ids%5B%5D=1&issue%5Bfixed_version_id%5D=3', - :class => '' } - assert_tag :tag => 'a', :content => 'eCookbook Subproject 1 - 2.0', - :attributes => { :href => '/issues/bulk_update?ids%5B%5D=1&issue%5Bfixed_version_id%5D=4', - :class => '' } - - assert_tag :tag => 'a', :content => 'Dave Lopper', - :attributes => { :href => '/issues/bulk_update?ids%5B%5D=1&issue%5Bassigned_to_id%5D=3', - :class => '' } - assert_tag :tag => 'a', :content => 'Copy', - :attributes => { :href => '/projects/ecookbook/issues/1/copy', - :class => 'icon-copy' } - assert_no_tag :tag => 'a', :content => 'Move' - assert_tag :tag => 'a', :content => 'Delete', - :attributes => { :href => '/issues?ids%5B%5D=1', - :class => 'icon-del' } + assert_select 'a[href=?]', '/issues/bulk_update?ids%5B%5D=1&issue%5Bfixed_version_id%5D=3', :text => '2.0' + assert_select 'a[href=?]', '/issues/bulk_update?ids%5B%5D=1&issue%5Bfixed_version_id%5D=4', :text => 'eCookbook Subproject 1 - 2.0' + # Assignees + assert_select 'a[href=?]', '/issues/bulk_update?ids%5B%5D=1&issue%5Bassigned_to_id%5D=3', :text => 'Dave Lopper' end def test_context_menu_one_issue_by_anonymous @@ -69,25 +73,14 @@ assert_equal [1, 2], assigns(:issues).map(&:id).sort ids = assigns(:issues).map(&:id).sort.map {|i| "ids%5B%5D=#{i}"}.join('&') - assert_tag :tag => 'a', :content => 'Edit', - :attributes => { :href => "/issues/bulk_edit?#{ids}", - :class => 'icon-edit' } - assert_tag :tag => 'a', :content => 'Closed', - :attributes => { :href => "/issues/bulk_update?#{ids}&issue%5Bstatus_id%5D=5", - :class => '' } - assert_tag :tag => 'a', :content => 'Immediate', - :attributes => { :href => "/issues/bulk_update?#{ids}&issue%5Bpriority_id%5D=8", - :class => '' } - assert_tag :tag => 'a', :content => 'Dave Lopper', - :attributes => { :href => "/issues/bulk_update?#{ids}&issue%5Bassigned_to_id%5D=3", - :class => '' } - assert_tag :tag => 'a', :content => 'Copy', - :attributes => { :href => "/issues/bulk_edit?copy=1&#{ids}", - :class => 'icon-copy' } - assert_no_tag :tag => 'a', :content => 'Move' - assert_tag :tag => 'a', :content => 'Delete', - :attributes => { :href => "/issues?#{ids}", - :class => 'icon-del' } + + assert_select 'a.icon-edit[href=?]', "/issues/bulk_edit?#{ids}", :text => 'Edit' + assert_select 'a.icon-copy[href=?]', "/issues/bulk_edit?copy=1&#{ids}", :text => 'Copy' + assert_select 'a.icon-del[href=?]', "/issues?#{ids}", :text => 'Delete' + + assert_select 'a[href=?]', "/issues/bulk_update?#{ids}&issue%5Bstatus_id%5D=5", :text => 'Closed' + assert_select 'a[href=?]', "/issues/bulk_update?#{ids}&issue%5Bpriority_id%5D=8", :text => 'Immediate' + assert_select 'a[href=?]', "/issues/bulk_update?#{ids}&issue%5Bassigned_to_id%5D=3", :text => 'Dave Lopper' end def test_context_menu_multiple_issues_of_different_projects @@ -99,21 +92,13 @@ assert_equal [1, 2, 6], assigns(:issues).map(&:id).sort ids = assigns(:issues).map(&:id).sort.map {|i| "ids%5B%5D=#{i}"}.join('&') - assert_tag :tag => 'a', :content => 'Edit', - :attributes => { :href => "/issues/bulk_edit?#{ids}", - :class => 'icon-edit' } - assert_tag :tag => 'a', :content => 'Closed', - :attributes => { :href => "/issues/bulk_update?#{ids}&issue%5Bstatus_id%5D=5", - :class => '' } - assert_tag :tag => 'a', :content => 'Immediate', - :attributes => { :href => "/issues/bulk_update?#{ids}&issue%5Bpriority_id%5D=8", - :class => '' } - assert_tag :tag => 'a', :content => 'John Smith', - :attributes => { :href => "/issues/bulk_update?#{ids}&issue%5Bassigned_to_id%5D=2", - :class => '' } - assert_tag :tag => 'a', :content => 'Delete', - :attributes => { :href => "/issues?#{ids}", - :class => 'icon-del' } + + assert_select 'a.icon-edit[href=?]', "/issues/bulk_edit?#{ids}", :text => 'Edit' + assert_select 'a.icon-del[href=?]', "/issues?#{ids}", :text => 'Delete' + + assert_select 'a[href=?]', "/issues/bulk_update?#{ids}&issue%5Bstatus_id%5D=5", :text => 'Closed' + assert_select 'a[href=?]', "/issues/bulk_update?#{ids}&issue%5Bpriority_id%5D=8", :text => 'Immediate' + assert_select 'a[href=?]', "/issues/bulk_update?#{ids}&issue%5Bassigned_to_id%5D=2", :text => 'John Smith' end def test_context_menu_should_include_list_custom_fields @@ -122,17 +107,14 @@ @request.session[:user_id] = 2 get :issues, :ids => [1] - assert_tag 'a', - :content => 'List', - :attributes => {:href => '#'}, - :sibling => {:tag => 'ul', :children => {:count => 3}} - - assert_tag 'a', - :content => 'Foo', - :attributes => {:href => "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=Foo"} - assert_tag 'a', - :content => 'none', - :attributes => {:href => "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=__none__"} + assert_select "li.cf_#{field.id}" do + assert_select 'a[href=#]', :text => 'List' + assert_select 'ul' do + assert_select 'a', 3 + assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=Foo", :text => 'Foo' + assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=__none__", :text => 'none' + end + end end def test_context_menu_should_not_include_null_value_for_required_custom_fields @@ -141,10 +123,13 @@ @request.session[:user_id] = 2 get :issues, :ids => [1, 2] - assert_tag 'a', - :content => 'List', - :attributes => {:href => '#'}, - :sibling => {:tag => 'ul', :children => {:count => 2}} + assert_select "li.cf_#{field.id}" do + assert_select 'a[href=#]', :text => 'List' + assert_select 'ul' do + assert_select 'a', 2 + assert_select 'a', :text => 'none', :count => 0 + end + end end def test_context_menu_on_single_issue_should_select_current_custom_field_value @@ -156,13 +141,13 @@ @request.session[:user_id] = 2 get :issues, :ids => [1] - assert_tag 'a', - :content => 'List', - :attributes => {:href => '#'}, - :sibling => {:tag => 'ul', :children => {:count => 3}} - assert_tag 'a', - :content => 'Bar', - :attributes => {:class => /icon-checked/} + assert_select "li.cf_#{field.id}" do + assert_select 'a[href=#]', :text => 'List' + assert_select 'ul' do + assert_select 'a', 3 + assert_select 'a.icon-checked', :text => 'Bar' + end + end end def test_context_menu_should_include_bool_custom_fields @@ -171,14 +156,15 @@ @request.session[:user_id] = 2 get :issues, :ids => [1] - assert_tag 'a', - :content => 'Bool', - :attributes => {:href => '#'}, - :sibling => {:tag => 'ul', :children => {:count => 3}} - - assert_tag 'a', - :content => 'Yes', - :attributes => {:href => "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=1"} + assert_select "li.cf_#{field.id}" do + assert_select 'a[href=#]', :text => 'Bool' + assert_select 'ul' do + assert_select 'a', 3 + assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=0", :text => 'No' + assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=1", :text => 'Yes' + assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=__none__", :text => 'none' + end + end end def test_context_menu_should_include_user_custom_fields @@ -187,14 +173,14 @@ @request.session[:user_id] = 2 get :issues, :ids => [1] - assert_tag 'a', - :content => 'User', - :attributes => {:href => '#'}, - :sibling => {:tag => 'ul', :children => {:count => Project.find(1).members.count + 1}} - - assert_tag 'a', - :content => 'John Smith', - :attributes => {:href => "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=2"} + assert_select "li.cf_#{field.id}" do + assert_select 'a[href=#]', :text => 'User' + assert_select 'ul' do + assert_select 'a', Project.find(1).members.count + 1 + assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=2", :text => 'John Smith' + assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=__none__", :text => 'none' + end + end end def test_context_menu_should_include_version_custom_fields @@ -202,14 +188,14 @@ @request.session[:user_id] = 2 get :issues, :ids => [1] - assert_tag 'a', - :content => 'Version', - :attributes => {:href => '#'}, - :sibling => {:tag => 'ul', :children => {:count => Project.find(1).shared_versions.count + 1}} - - assert_tag 'a', - :content => '2.0', - :attributes => {:href => "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=3"} + assert_select "li.cf_#{field.id}" do + assert_select 'a[href=#]', :text => 'Version' + assert_select 'ul' do + assert_select 'a', Project.find(1).shared_versions.count + 1 + assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=3", :text => '2.0' + assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=__none__", :text => 'none' + end + end end def test_context_menu_by_assignable_user_should_include_assigned_to_me_link @@ -218,9 +204,7 @@ assert_response :success assert_template 'context_menu' - assert_tag :tag => 'a', :content => / me /, - :attributes => { :href => '/issues/bulk_update?ids%5B%5D=1&issue%5Bassigned_to_id%5D=2', - :class => '' } + assert_select 'a[href=?]', '/issues/bulk_update?ids%5B%5D=1&issue%5Bassigned_to_id%5D=2', :text => / me / end def test_context_menu_should_propose_shared_versions_for_issues_from_different_projects @@ -232,7 +216,7 @@ assert_template 'context_menu' assert_include version, assigns(:versions) - assert_tag :tag => 'a', :content => 'eCookbook - Shared' + assert_select 'a', :text => 'eCookbook - Shared' end def test_context_menu_issue_visibility @@ -241,16 +225,21 @@ assert_template 'context_menu' assert_equal [1], assigns(:issues).collect(&:id) end - + + def test_should_respond_with_404_without_ids + get :issues + assert_response 404 + end + def test_time_entries_context_menu @request.session[:user_id] = 2 get :time_entries, :ids => [1, 2] assert_response :success assert_template 'time_entries' - assert_tag 'a', :content => 'Edit' - assert_no_tag 'a', :content => 'Edit', :attributes => {:class => /disabled/} + + assert_select 'a:not(.disabled)', :text => 'Edit' end - + def test_time_entries_context_menu_without_edit_permission @request.session[:user_id] = 2 Role.find_by_name('Manager').remove_permission! :edit_time_entries @@ -258,6 +247,6 @@ get :time_entries, :ids => [1, 2] assert_response :success assert_template 'time_entries' - assert_tag 'a', :content => 'Edit', :attributes => {:class => /disabled/} + assert_select 'a.disabled', :text => 'Edit' end end diff -r 0a574315af3e -r 4f746d8966dd test/functional/custom_fields_controller_test.rb --- a/test/functional/custom_fields_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/custom_fields_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -56,6 +56,30 @@ end end + def test_default_value_should_be_an_input_for_string_custom_field + get :new, :type => 'IssueCustomField', :custom_field => {:field_format => 'string'} + assert_response :success + assert_select 'input[name=?]', 'custom_field[default_value]' + end + + def test_default_value_should_be_a_textarea_for_text_custom_field + get :new, :type => 'IssueCustomField', :custom_field => {:field_format => 'text'} + assert_response :success + assert_select 'textarea[name=?]', 'custom_field[default_value]' + end + + def test_default_value_should_be_a_checkbox_for_bool_custom_field + get :new, :type => 'IssueCustomField', :custom_field => {:field_format => 'bool'} + assert_response :success + assert_select 'input[name=?][type=checkbox]', 'custom_field[default_value]' + end + + def test_default_value_should_not_be_present_for_user_custom_field + get :new, :type => 'IssueCustomField', :custom_field => {:field_format => 'user'} + assert_response :success + assert_select '[name=?]', 'custom_field[default_value]', 0 + end + def test_new_js get :new, :type => 'IssueCustomField', :custom_field => {:field_format => 'list'}, :format => 'js' assert_response :success diff -r 0a574315af3e -r 4f746d8966dd test/functional/documents_controller_test.rb --- a/test/functional/documents_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/documents_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/functional/enumerations_controller_test.rb --- a/test/functional/enumerations_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/enumerations_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -117,7 +117,7 @@ end def test_destroy_enumeration_in_use_with_reassignment - issue = Issue.find(:first, :conditions => {:priority_id => 4}) + issue = Issue.where(:priority_id => 4).first assert_difference 'IssuePriority.count', -1 do delete :destroy, :id => 4, :reassign_to_id => 6 end diff -r 0a574315af3e -r 4f746d8966dd test/functional/files_controller_test.rb --- a/test/functional/files_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/files_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,3 +1,20 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) class FilesControllerTest < ActionController::TestCase @@ -8,15 +25,11 @@ :member_roles, :members, :enabled_modules, - :workflows, :journals, :journal_details, :attachments, :versions def setup - @controller = FilesController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new @request.session[:user_id] = nil Setting.default_language = 'en' end @@ -68,7 +81,7 @@ end end assert_redirected_to '/projects/ecookbook/files' - a = Attachment.find(:first, :order => 'created_on DESC') + a = Attachment.order('created_on DESC').first assert_equal 'testfile.txt', a.filename assert_equal Project.find(1), a.container @@ -88,7 +101,7 @@ assert_response :redirect end assert_redirected_to '/projects/ecookbook/files' - a = Attachment.find(:first, :order => 'created_on DESC') + a = Attachment.order('created_on DESC').first assert_equal 'testfile.txt', a.filename assert_equal Version.find(2), a.container end diff -r 0a574315af3e -r 4f746d8966dd test/functional/gantts_controller_test.rb --- a/test/functional/gantts_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/gantts_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -25,83 +25,98 @@ :member_roles, :members, :enabled_modules, - :workflows, :versions - def test_gantt_should_work - i2 = Issue.find(2) - i2.update_attribute(:due_date, 1.month.from_now) - get :show, :project_id => 1 + def test_gantt_should_work + i2 = Issue.find(2) + i2.update_attribute(:due_date, 1.month.from_now) + get :show, :project_id => 1 + assert_response :success + assert_template 'gantts/show' + assert_not_nil assigns(:gantt) + # Issue with start and due dates + i = Issue.find(1) + assert_not_nil i.due_date + assert_select "div a.issue", /##{i.id}/ + # Issue with on a targeted version should not be in the events but loaded in the html + i = Issue.find(2) + assert_select "div a.issue", /##{i.id}/ + end + + def test_gantt_should_work_without_issue_due_dates + Issue.update_all("due_date = NULL") + get :show, :project_id => 1 + assert_response :success + assert_template 'gantts/show' + assert_not_nil assigns(:gantt) + end + + def test_gantt_should_work_without_issue_and_version_due_dates + Issue.update_all("due_date = NULL") + Version.update_all("effective_date = NULL") + get :show, :project_id => 1 + assert_response :success + assert_template 'gantts/show' + assert_not_nil assigns(:gantt) + end + + def test_gantt_should_work_cross_project + get :show + assert_response :success + assert_template 'gantts/show' + assert_not_nil assigns(:gantt) + assert_not_nil assigns(:gantt).query + assert_nil assigns(:gantt).project + end + + def test_gantt_should_not_disclose_private_projects + get :show + assert_response :success + assert_template 'gantts/show' + assert_tag 'a', :content => /eCookbook/ + # Root private project + assert_no_tag 'a', {:content => /OnlineStore/} + # Private children of a public project + assert_no_tag 'a', :content => /Private child of eCookbook/ + end + + def test_gantt_should_display_relations + IssueRelation.delete_all + issue1 = Issue.generate!(:start_date => 1.day.from_now, :due_date => 3.day.from_now) + issue2 = Issue.generate!(:start_date => 1.day.from_now, :due_date => 3.day.from_now) + IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => 'precedes') + + get :show + assert_response :success + + relations = assigns(:gantt).relations + assert_kind_of Hash, relations + assert relations.present? + assert_select 'div.task_todo[id=?][data-rels*=?]', "task-todo-issue-#{issue1.id}", issue2.id.to_s + assert_select 'div.task_todo[id=?]:not([data-rels])', "task-todo-issue-#{issue2.id}" + end + + def test_gantt_should_export_to_pdf + get :show, :project_id => 1, :format => 'pdf' + assert_response :success + assert_equal 'application/pdf', @response.content_type + assert @response.body.starts_with?('%PDF') + assert_not_nil assigns(:gantt) + end + + def test_gantt_should_export_to_pdf_cross_project + get :show, :format => 'pdf' + assert_response :success + assert_equal 'application/pdf', @response.content_type + assert @response.body.starts_with?('%PDF') + assert_not_nil assigns(:gantt) + end + + if Object.const_defined?(:Magick) + def test_gantt_should_export_to_png + get :show, :project_id => 1, :format => 'png' assert_response :success - assert_template 'gantts/show' - assert_not_nil assigns(:gantt) - # Issue with start and due dates - i = Issue.find(1) - assert_not_nil i.due_date - assert_select "div a.issue", /##{i.id}/ - # Issue with on a targeted version should not be in the events but loaded in the html - i = Issue.find(2) - assert_select "div a.issue", /##{i.id}/ + assert_equal 'image/png', @response.content_type end - - def test_gantt_should_work_without_issue_due_dates - Issue.update_all("due_date = NULL") - get :show, :project_id => 1 - assert_response :success - assert_template 'gantts/show' - assert_not_nil assigns(:gantt) - end - - def test_gantt_should_work_without_issue_and_version_due_dates - Issue.update_all("due_date = NULL") - Version.update_all("effective_date = NULL") - get :show, :project_id => 1 - assert_response :success - assert_template 'gantts/show' - assert_not_nil assigns(:gantt) - end - - def test_gantt_should_work_cross_project - get :show - assert_response :success - assert_template 'gantts/show' - assert_not_nil assigns(:gantt) - assert_not_nil assigns(:gantt).query - assert_nil assigns(:gantt).project - end - - def test_gantt_should_not_disclose_private_projects - get :show - assert_response :success - assert_template 'gantts/show' - assert_tag 'a', :content => /eCookbook/ - # Root private project - assert_no_tag 'a', {:content => /OnlineStore/} - # Private children of a public project - assert_no_tag 'a', :content => /Private child of eCookbook/ - end - - def test_gantt_should_export_to_pdf - get :show, :project_id => 1, :format => 'pdf' - assert_response :success - assert_equal 'application/pdf', @response.content_type - assert @response.body.starts_with?('%PDF') - assert_not_nil assigns(:gantt) - end - - def test_gantt_should_export_to_pdf_cross_project - get :show, :format => 'pdf' - assert_response :success - assert_equal 'application/pdf', @response.content_type - assert @response.body.starts_with?('%PDF') - assert_not_nil assigns(:gantt) - end - - if Object.const_defined?(:Magick) - def test_gantt_should_export_to_png - get :show, :project_id => 1, :format => 'png' - assert_response :success - assert_equal 'image/png', @response.content_type - end - end + end end diff -r 0a574315af3e -r 4f746d8966dd test/functional/groups_controller_test.rb --- a/test/functional/groups_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/groups_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -79,8 +79,11 @@ get :edit, :id => 10 assert_response :success assert_template 'edit' - assert_tag 'div', :attributes => {:id => 'tab-content-users'} - assert_tag 'div', :attributes => {:id => 'tab-content-memberships'} + + assert_select 'div#tab-content-users' + assert_select 'div#tab-content-memberships' do + assert_select 'a', :text => 'Private child of eCookbook' + end end def test_update @@ -192,11 +195,8 @@ end def test_autocomplete_for_user - get :autocomplete_for_user, :id => 10, :q => 'mis' + get :autocomplete_for_user, :id => 10, :q => 'smi', :format => 'js' assert_response :success - users = assigns(:users) - assert_not_nil users - assert users.any? - assert !users.include?(Group.find(10).users.first) + assert_include 'John Smith', response.body end end diff -r 0a574315af3e -r 4f746d8966dd test/functional/issue_categories_controller_test.rb --- a/test/functional/issue_categories_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/issue_categories_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,19 +16,12 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'issue_categories_controller' - -# Re-raise errors caught by the controller. -class IssueCategoriesController; def rescue_action(e) raise e end; end class IssueCategoriesControllerTest < ActionController::TestCase fixtures :projects, :users, :members, :member_roles, :roles, :enabled_modules, :issue_categories, :issues def setup - @controller = IssueCategoriesController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil @request.session[:user_id] = 2 end @@ -133,7 +126,7 @@ end def test_destroy_category_in_use_with_reassignment - issue = Issue.find(:first, :conditions => {:category_id => 1}) + issue = Issue.where(:category_id => 1).first delete :destroy, :id => 1, :todo => 'reassign', :reassign_to_id => 2 assert_redirected_to '/projects/ecookbook/settings/categories' assert_nil IssueCategory.find_by_id(1) @@ -142,7 +135,7 @@ end def test_destroy_category_in_use_without_reassignment - issue = Issue.find(:first, :conditions => {:category_id => 1}) + issue = Issue.where(:category_id => 1).first delete :destroy, :id => 1, :todo => 'nullify' assert_redirected_to '/projects/ecookbook/settings/categories' assert_nil IssueCategory.find_by_id(1) diff -r 0a574315af3e -r 4f746d8966dd test/functional/issue_relations_controller_test.rb --- a/test/functional/issue_relations_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/issue_relations_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -28,7 +28,8 @@ :issue_relations, :enabled_modules, :enumerations, - :trackers + :trackers, + :projects_trackers def setup User.current = nil @@ -87,6 +88,17 @@ end end + def test_create_follows_relation_should_update_relations_list + issue1 = Issue.generate!(:subject => 'Followed issue', :start_date => Date.yesterday, :due_date => Date.today) + issue2 = Issue.generate! + + assert_difference 'IssueRelation.count' do + xhr :post, :create, :issue_id => issue2.id, + :relation => {:issue_to_id => issue1.id, :relation_type => 'follows', :delay => ''} + end + assert_match /Followed issue/, response.body + 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) diff -r 0a574315af3e -r 4f746d8966dd test/functional/issue_statuses_controller_test.rb --- a/test/functional/issue_statuses_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/issue_statuses_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,17 +1,26 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) -require 'issue_statuses_controller' - -# Re-raise errors caught by the controller. -class IssueStatusesController; def rescue_action(e) raise e end; end - class IssueStatusesControllerTest < ActionController::TestCase fixtures :issue_statuses, :issues, :users def setup - @controller = IssueStatusesController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil @request.session[:user_id] = 1 # admin end @@ -45,7 +54,7 @@ post :create, :issue_status => {:name => 'New status'} end assert_redirected_to :action => 'index' - status = IssueStatus.find(:first, :order => 'id DESC') + status = IssueStatus.order('id DESC').first assert_equal 'New status', status.name end diff -r 0a574315af3e -r 4f746d8966dd test/functional/issues_controller_test.rb --- a/test/functional/issues_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/issues_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,7 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'issues_controller' class IssuesControllerTest < ActionController::TestCase fixtures :projects, @@ -48,9 +47,6 @@ include Redmine::I18n def setup - @controller = IssuesController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil end @@ -331,7 +327,7 @@ end def test_index_with_cross_project_query_in_session_should_show_project_issues - q = Query.create!(:name => "test", :user_id => 2, :is_public => false, :project => nil) + q = IssueQuery.create!(:name => "test", :user_id => 2, :is_public => false, :project => nil) @request.session[:query] = {:id => q.id, :project_id => 1} with_settings :display_subprojects_issues => '0' do @@ -345,7 +341,7 @@ end def test_private_query_should_not_be_available_to_other_users - q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil) + q = IssueQuery.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil) @request.session[:user_id] = 3 get :index, :query_id => q.id @@ -353,7 +349,7 @@ end def test_private_query_should_be_available_to_its_user - q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil) + q = IssueQuery.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil) @request.session[:user_id] = 2 get :index, :query_id => q.id @@ -361,7 +357,7 @@ end def test_public_query_should_be_available_to_other_users - q = Query.create!(:name => "private", :user => User.find(2), :is_public => true, :project => nil) + q = IssueQuery.create!(:name => "private", :user => User.find(2), :is_public => true, :project => nil) @request.session[:user_id] = 3 get :index, :query_id => q.id @@ -384,7 +380,7 @@ assert_equal 'text/csv; header=present', @response.content_type assert @response.body.starts_with?("#,") lines = @response.body.chomp.split("\n") - assert_equal assigns(:query).columns.size + 1, lines[0].split(',').size + assert_equal assigns(:query).columns.size, lines[0].split(',').size end def test_index_csv_with_project @@ -395,13 +391,18 @@ end def test_index_csv_with_description - get :index, :format => 'csv', :description => '1' - assert_response :success - assert_not_nil assigns(:issues) - assert_equal 'text/csv; header=present', @response.content_type - assert @response.body.starts_with?("#,") - lines = @response.body.chomp.split("\n") - assert_equal assigns(:query).columns.size + 2, lines[0].split(',').size + Issue.generate!(:description => 'test_index_csv_with_description') + + with_settings :default_language => 'en' do + get :index, :format => 'csv', :description => '1' + assert_response :success + assert_not_nil assigns(:issues) + end + + assert_equal 'text/csv; header=present', response.content_type + headers = response.body.chomp.split("\n").first.split(',') + assert_include 'Description', headers + assert_include 'test_index_csv_with_description', response.body end def test_index_csv_with_spent_time_column @@ -420,9 +421,9 @@ assert_response :success assert_not_nil assigns(:issues) assert_equal 'text/csv; header=present', @response.content_type - assert @response.body.starts_with?("#,") - lines = @response.body.chomp.split("\n") - assert_equal assigns(:query).available_inline_columns.size + 1, lines[0].split(',').size + assert_match /\A#,/, response.body + lines = response.body.chomp.split("\n") + assert_equal assigns(:query).available_inline_columns.size, lines[0].split(',').size end def test_index_csv_with_multi_column_field @@ -437,6 +438,25 @@ assert lines.detect {|line| line.include?('"MySQL, Oracle"')} end + def test_index_csv_should_format_float_custom_fields_with_csv_decimal_separator + field = IssueCustomField.create!(:name => 'Float', :is_for_all => true, :tracker_ids => [1], :field_format => 'float') + issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id => '185.6'}) + + with_settings :default_language => 'fr' do + get :index, :format => 'csv', :columns => 'all' + assert_response :success + issue_line = response.body.chomp.split("\n").map {|line| line.split(';')}.detect {|line| line[0]==issue.id.to_s} + assert_include '185,60', issue_line + end + + with_settings :default_language => 'en' do + get :index, :format => 'csv', :columns => 'all' + assert_response :success + issue_line = response.body.chomp.split("\n").map {|line| line.split(',')}.detect {|line| line[0]==issue.id.to_s} + assert_include '185.60', issue_line + end + end + def test_index_csv_big_5 with_settings :default_language => "zh-TW" do str_utf8 = "\xe4\xb8\x80\xe6\x9c\x88" @@ -457,8 +477,8 @@ if str_utf8.respond_to?(:force_encoding) s1.force_encoding('Big5') end - assert lines[0].include?(s1) - assert lines[1].include?(str_big5) + assert_include s1, lines[0] + assert_include str_big5, lines[1] end end @@ -670,7 +690,7 @@ # query should use specified columns query = assigns(:query) - assert_kind_of Query, query + assert_kind_of IssueQuery, query assert_equal columns, query.column_names.map(&:to_s) # columns should be stored in session @@ -692,18 +712,18 @@ # query should use specified columns query = assigns(:query) - assert_kind_of Query, query - assert_equal [:project, :tracker, :subject, :assigned_to], query.columns.map(&:name) + assert_kind_of IssueQuery, query + assert_equal [:id, :project, :tracker, :subject, :assigned_to], query.columns.map(&:name) end def test_index_without_project_and_explicit_default_columns_should_not_add_project_column Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to'] - columns = ['tracker', 'subject', 'assigned_to'] + columns = ['id', 'tracker', 'subject', 'assigned_to'] get :index, :set_filter => 1, :c => columns # query should use specified columns query = assigns(:query) - assert_kind_of Query, query + assert_kind_of IssueQuery, query assert_equal columns.map(&:to_sym), query.columns.map(&:name) end @@ -714,7 +734,7 @@ # query should use specified columns query = assigns(:query) - assert_kind_of Query, query + assert_kind_of IssueQuery, query assert_equal columns, query.column_names.map(&:to_s) assert_select 'table.issues td.cf_2.string' @@ -899,24 +919,25 @@ get :show, :id => 1 assert_response :success - assert_tag 'form', :attributes => {:id => 'issue-form'} - assert_tag 'input', :attributes => {:name => 'issue[is_private]'} - assert_tag 'select', :attributes => {:name => 'issue[project_id]'} - assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'} - assert_tag 'input', :attributes => {:name => 'issue[subject]'} - assert_tag 'textarea', :attributes => {:name => 'issue[description]'} - assert_tag 'select', :attributes => {:name => 'issue[status_id]'} - assert_tag 'select', :attributes => {:name => 'issue[priority_id]'} - assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'} - assert_tag 'select', :attributes => {:name => 'issue[category_id]'} - assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'} - assert_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'} - assert_tag 'input', :attributes => {:name => 'issue[start_date]'} - assert_tag 'input', :attributes => {:name => 'issue[due_date]'} - assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'} - assert_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]' } - assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'} - assert_tag 'textarea', :attributes => {:name => 'issue[notes]'} + assert_select 'form#issue-form' do + assert_select 'input[name=?]', 'issue[is_private]' + assert_select 'select[name=?]', 'issue[project_id]' + assert_select 'select[name=?]', 'issue[tracker_id]' + assert_select 'input[name=?]', 'issue[subject]' + assert_select 'textarea[name=?]', 'issue[description]' + assert_select 'select[name=?]', 'issue[status_id]' + assert_select 'select[name=?]', 'issue[priority_id]' + assert_select 'select[name=?]', 'issue[assigned_to_id]' + assert_select 'select[name=?]', 'issue[category_id]' + assert_select 'select[name=?]', 'issue[fixed_version_id]' + assert_select 'input[name=?]', 'issue[parent_issue_id]' + assert_select 'input[name=?]', 'issue[start_date]' + assert_select 'input[name=?]', 'issue[due_date]' + assert_select 'select[name=?]', 'issue[done_ratio]' + assert_select 'input[name=?]', 'issue[custom_field_values][2]' + assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0 + assert_select 'textarea[name=?]', 'issue[notes]' + end end def test_show_should_display_update_form_with_minimal_permissions @@ -927,24 +948,25 @@ get :show, :id => 1 assert_response :success - assert_tag 'form', :attributes => {:id => 'issue-form'} - assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'} - assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'} - assert_no_tag 'select', :attributes => {:name => 'issue[tracker_id]'} - assert_no_tag 'input', :attributes => {:name => 'issue[subject]'} - assert_no_tag 'textarea', :attributes => {:name => 'issue[description]'} - assert_no_tag 'select', :attributes => {:name => 'issue[status_id]'} - assert_no_tag 'select', :attributes => {:name => 'issue[priority_id]'} - assert_no_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'} - assert_no_tag 'select', :attributes => {:name => 'issue[category_id]'} - assert_no_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'} - assert_no_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'} - assert_no_tag 'input', :attributes => {:name => 'issue[start_date]'} - assert_no_tag 'input', :attributes => {:name => 'issue[due_date]'} - assert_no_tag 'select', :attributes => {:name => 'issue[done_ratio]'} - assert_no_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]' } - assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'} - assert_tag 'textarea', :attributes => {:name => 'issue[notes]'} + assert_select 'form#issue-form' do + assert_select 'input[name=?]', 'issue[is_private]', 0 + assert_select 'select[name=?]', 'issue[project_id]', 0 + assert_select 'select[name=?]', 'issue[tracker_id]', 0 + assert_select 'input[name=?]', 'issue[subject]', 0 + assert_select 'textarea[name=?]', 'issue[description]', 0 + assert_select 'select[name=?]', 'issue[status_id]', 0 + assert_select 'select[name=?]', 'issue[priority_id]', 0 + assert_select 'select[name=?]', 'issue[assigned_to_id]', 0 + assert_select 'select[name=?]', 'issue[category_id]', 0 + assert_select 'select[name=?]', 'issue[fixed_version_id]', 0 + assert_select 'input[name=?]', 'issue[parent_issue_id]', 0 + assert_select 'input[name=?]', 'issue[start_date]', 0 + assert_select 'input[name=?]', 'issue[due_date]', 0 + assert_select 'select[name=?]', 'issue[done_ratio]', 0 + assert_select 'input[name=?]', 'issue[custom_field_values][2]', 0 + assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0 + assert_select 'textarea[name=?]', 'issue[notes]' + end end def test_show_should_display_update_form_with_workflow_permissions @@ -954,24 +976,25 @@ get :show, :id => 1 assert_response :success - assert_tag 'form', :attributes => {:id => 'issue-form'} - assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'} - assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'} - assert_no_tag 'select', :attributes => {:name => 'issue[tracker_id]'} - assert_no_tag 'input', :attributes => {:name => 'issue[subject]'} - assert_no_tag 'textarea', :attributes => {:name => 'issue[description]'} - assert_tag 'select', :attributes => {:name => 'issue[status_id]'} - assert_no_tag 'select', :attributes => {:name => 'issue[priority_id]'} - assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'} - assert_no_tag 'select', :attributes => {:name => 'issue[category_id]'} - assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'} - assert_no_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'} - assert_no_tag 'input', :attributes => {:name => 'issue[start_date]'} - assert_no_tag 'input', :attributes => {:name => 'issue[due_date]'} - assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'} - assert_no_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]' } - assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'} - assert_tag 'textarea', :attributes => {:name => 'issue[notes]'} + assert_select 'form#issue-form' do + assert_select 'input[name=?]', 'issue[is_private]', 0 + assert_select 'select[name=?]', 'issue[project_id]', 0 + assert_select 'select[name=?]', 'issue[tracker_id]', 0 + assert_select 'input[name=?]', 'issue[subject]', 0 + assert_select 'textarea[name=?]', 'issue[description]', 0 + assert_select 'select[name=?]', 'issue[status_id]' + assert_select 'select[name=?]', 'issue[priority_id]', 0 + assert_select 'select[name=?]', 'issue[assigned_to_id]' + assert_select 'select[name=?]', 'issue[category_id]', 0 + assert_select 'select[name=?]', 'issue[fixed_version_id]' + assert_select 'input[name=?]', 'issue[parent_issue_id]', 0 + assert_select 'input[name=?]', 'issue[start_date]', 0 + assert_select 'input[name=?]', 'issue[due_date]', 0 + assert_select 'select[name=?]', 'issue[done_ratio]' + assert_select 'input[name=?]', 'issue[custom_field_values][2]', 0 + assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0 + assert_select 'textarea[name=?]', 'issue[notes]' + end end def test_show_should_not_display_update_form_without_permissions @@ -1004,7 +1027,7 @@ get :show, :id => 1 assert_select 'form#issue-form[method=post][enctype=multipart/form-data]' do - assert_select 'input[type=file][name=?]', 'attachments[1][file]' + assert_select 'input[type=file][name=?]', 'attachments[dummy][file]' end end @@ -1140,7 +1163,7 @@ end def test_show_should_display_prev_next_links_with_saved_query_in_session - query = Query.create!(:name => 'test', :is_public => true, :user_id => 1, + query = IssueQuery.create!(:name => 'test', :is_public => true, :user_id => 1, :filters => {'status_id' => {:values => ['5'], :operator => '='}}, :sort_criteria => [['id', 'asc']]) @request.session[:query] = {:id => query.id, :project_id => nil} @@ -1232,7 +1255,7 @@ CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '3') CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '') - query = Query.create!(:name => 'test', :is_public => true, :user_id => 1, :filters => {}, + query = IssueQuery.create!(:name => 'test', :is_public => true, :user_id => 1, :filters => {}, :sort_criteria => [["cf_#{cf.id}", 'asc'], ['id', 'asc']]) @request.session[:query] = {:id => query.id, :project_id => nil} @@ -1420,33 +1443,41 @@ assert @response.body.starts_with?('%PDF') end + def test_show_invalid_should_respond_with_404 + get :show, :id => 999 + assert_response 404 + end + def test_get_new @request.session[:user_id] = 2 get :new, :project_id => 1, :tracker_id => 1 assert_response :success assert_template 'new' - assert_tag 'input', :attributes => {:name => 'issue[is_private]'} - assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'} - assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'} - assert_tag 'input', :attributes => {:name => 'issue[subject]'} - assert_tag 'textarea', :attributes => {:name => 'issue[description]'} - assert_tag 'select', :attributes => {:name => 'issue[status_id]'} - assert_tag 'select', :attributes => {:name => 'issue[priority_id]'} - assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'} - assert_tag 'select', :attributes => {:name => 'issue[category_id]'} - assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'} - assert_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'} - assert_tag 'input', :attributes => {:name => 'issue[start_date]'} - assert_tag 'input', :attributes => {:name => 'issue[due_date]'} - assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'} - assert_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]', :value => 'Default string' } - assert_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'} + assert_select 'form#issue-form' do + assert_select 'input[name=?]', 'issue[is_private]' + assert_select 'select[name=?]', 'issue[project_id]', 0 + assert_select 'select[name=?]', 'issue[tracker_id]' + assert_select 'input[name=?]', 'issue[subject]' + assert_select 'textarea[name=?]', 'issue[description]' + assert_select 'select[name=?]', 'issue[status_id]' + assert_select 'select[name=?]', 'issue[priority_id]' + assert_select 'select[name=?]', 'issue[assigned_to_id]' + assert_select 'select[name=?]', 'issue[category_id]' + assert_select 'select[name=?]', 'issue[fixed_version_id]' + assert_select 'input[name=?]', 'issue[parent_issue_id]' + assert_select 'input[name=?]', 'issue[start_date]' + assert_select 'input[name=?]', 'issue[due_date]' + assert_select 'select[name=?]', 'issue[done_ratio]' + assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Default string' + assert_select 'input[name=?]', 'issue[watcher_user_ids][]' + end # Be sure we don't display inactive IssuePriorities assert ! IssuePriority.find(15).active? - assert_no_tag :option, :attributes => {:value => '15'}, - :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} } + assert_select 'select[name=?]', 'issue[priority_id]' do + assert_select 'option[value=15]', 0 + end end def test_get_new_with_minimal_permissions @@ -1458,22 +1489,24 @@ assert_response :success assert_template 'new' - assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'} - assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'} - assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'} - assert_tag 'input', :attributes => {:name => 'issue[subject]'} - assert_tag 'textarea', :attributes => {:name => 'issue[description]'} - assert_tag 'select', :attributes => {:name => 'issue[status_id]'} - assert_tag 'select', :attributes => {:name => 'issue[priority_id]'} - assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'} - assert_tag 'select', :attributes => {:name => 'issue[category_id]'} - assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'} - assert_no_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'} - assert_tag 'input', :attributes => {:name => 'issue[start_date]'} - assert_tag 'input', :attributes => {:name => 'issue[due_date]'} - assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'} - assert_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]', :value => 'Default string' } - assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'} + assert_select 'form#issue-form' do + assert_select 'input[name=?]', 'issue[is_private]', 0 + assert_select 'select[name=?]', 'issue[project_id]', 0 + assert_select 'select[name=?]', 'issue[tracker_id]' + assert_select 'input[name=?]', 'issue[subject]' + assert_select 'textarea[name=?]', 'issue[description]' + assert_select 'select[name=?]', 'issue[status_id]' + assert_select 'select[name=?]', 'issue[priority_id]' + assert_select 'select[name=?]', 'issue[assigned_to_id]' + assert_select 'select[name=?]', 'issue[category_id]' + assert_select 'select[name=?]', 'issue[fixed_version_id]' + assert_select 'input[name=?]', 'issue[parent_issue_id]', 0 + assert_select 'input[name=?]', 'issue[start_date]' + assert_select 'input[name=?]', 'issue[due_date]' + assert_select 'select[name=?]', 'issue[done_ratio]' + assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Default string' + assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0 + end end def test_get_new_with_list_custom_field @@ -1568,8 +1601,7 @@ get :new, :project_id => 1, :tracker_id => 1 assert_select 'form[id=issue-form][method=post][enctype=multipart/form-data]' do - assert_select 'input[name=?][type=file]', 'attachments[1][file]' - assert_select 'input[name=?][maxlength=255]', 'attachments[1][description]' + assert_select 'input[name=?][type=file]', 'attachments[dummy][file]' end end @@ -1663,9 +1695,9 @@ assert_error_tag :content => /No tracker/ end - def test_update_new_form + def test_update_form_for_new_issue @request.session[:user_id] = 2 - xhr :post, :new, :project_id => 1, + xhr :post, :update_form, :project_id => 1, :issue => {:tracker_id => 2, :subject => 'This is the test_new issue', :description => 'This is the description', @@ -1682,14 +1714,14 @@ assert_equal 'This is the test_new issue', issue.subject end - def test_update_new_form_should_propose_transitions_based_on_initial_status + def test_update_form_for_new_issue_should_propose_transitions_based_on_initial_status @request.session[:user_id] = 2 WorkflowTransition.delete_all WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2) WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 5) WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 5, :new_status_id => 4) - xhr :post, :new, :project_id => 1, + xhr :post, :update_form, :project_id => 1, :issue => {:tracker_id => 1, :status_id => 5, :subject => 'This is an issue'} @@ -1720,7 +1752,7 @@ assert_equal 2, issue.status_id assert_equal Date.parse('2010-11-07'), issue.start_date assert_nil issue.estimated_hours - v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2}) + v = issue.custom_values.where(:custom_field_id => 2).first assert_not_nil v assert_equal 'Value for field 2', v.value end @@ -2082,19 +2114,15 @@ assert_response :success assert_template 'new' - assert_tag :textarea, :attributes => { :name => 'issue[description]' }, - :content => "\nThis is a description" - assert_tag :select, :attributes => { :name => 'issue[priority_id]' }, - :child => { :tag => 'option', :attributes => { :selected => 'selected', - :value => '6' }, - :content => 'High' } + assert_select 'textarea[name=?]', 'issue[description]', :text => 'This is a description' + assert_select 'select[name=?]', 'issue[priority_id]' do + assert_select 'option[value=6][selected=selected]', :text => 'High' + end # Custom fields - assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' }, - :child => { :tag => 'option', :attributes => { :selected => 'selected', - :value => 'Oracle' }, - :content => 'Oracle' } - assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]', - :value => 'Value for field 2'} + assert_select 'select[name=?]', 'issue[custom_field_values][1]' do + assert_select 'option[value=Oracle][selected=selected]', :text => 'Oracle' + end + assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Value for field 2' end def test_post_create_with_failure_should_preserve_watchers @@ -2107,9 +2135,9 @@ assert_response :success assert_template 'new' - assert_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]', :value => '2', :checked => nil} - assert_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]', :value => '3', :checked => 'checked'} - assert_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]', :value => '8', :checked => 'checked'} + assert_select 'input[name=?][value=2]:not(checked)', 'issue[watcher_user_ids][]' + assert_select 'input[name=?][value=3][checked=checked]', 'issue[watcher_user_ids][]' + assert_select 'input[name=?][value=8][checked=checked]', 'issue[watcher_user_ids][]' end def test_post_create_should_ignore_non_safe_attributes @@ -2163,8 +2191,8 @@ assert File.exists?(attachment.diskfile) assert_nil attachment.container - assert_tag 'input', :attributes => {:name => 'attachments[p0][token]', :value => attachment.token} - assert_tag 'span', :content => /testfile.txt/ + assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token + assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt' end def test_post_create_with_failure_should_keep_saved_attachments @@ -2182,8 +2210,8 @@ end end - assert_tag 'input', :attributes => {:name => 'attachments[p0][token]', :value => attachment.token} - assert_tag 'span', :content => /testfile.txt/ + assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token + assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt' end def test_post_create_should_attach_saved_attachments @@ -2218,10 +2246,10 @@ get :new, :project_id => 1 assert_response :success assert_template 'new' - assert_tag :tag => 'select', - :attributes => {:name => 'issue[status_id]'}, - :children => {:count => 1}, - :child => {:tag => 'option', :attributes => {:value => IssueStatus.default.id.to_s}} + assert_select 'select[name=?]', 'issue[status_id]' do + assert_select 'option', 1 + assert_select 'option[value=?]', IssueStatus.default.id.to_s + end end should "accept default status" do @@ -2349,13 +2377,13 @@ assert_equal orig.subject, assigns(:issue).subject assert assigns(:issue).copy? - assert_tag 'form', :attributes => {:id => 'issue-form', :action => '/projects/ecookbook/issues'} - assert_tag 'select', :attributes => {:name => 'issue[project_id]'} - assert_tag 'select', :attributes => {:name => 'issue[project_id]'}, - :child => {:tag => 'option', :attributes => {:value => '1', :selected => 'selected'}, :content => 'eCookbook'} - assert_tag 'select', :attributes => {:name => 'issue[project_id]'}, - :child => {:tag => 'option', :attributes => {:value => '2', :selected => nil}, :content => 'OnlineStore'} - assert_tag 'input', :attributes => {:name => 'copy_from', :value => '1'} + assert_select 'form[id=issue-form][action=/projects/ecookbook/issues]' do + assert_select 'select[name=?]', 'issue[project_id]' do + assert_select 'option[value=1][selected=selected]', :text => 'eCookbook' + assert_select 'option[value=2]:not([selected])', :text => 'OnlineStore' + end + assert_select 'input[name=copy_from][value=1]' + end # "New issue" menu item should not link to copy assert_select '#main-menu a.new-issue[href=/projects/ecookbook/issues/new]' @@ -2367,7 +2395,7 @@ assert issue.attachments.count > 0 get :new, :project_id => 1, :copy_from => 3 - assert_tag 'input', :attributes => {:name => 'copy_attachments', :type => 'checkbox', :checked => 'checked', :value => '1'} + assert_select 'input[name=copy_attachments][type=checkbox][checked=checked][value=1]' end def test_new_as_copy_without_attachments_should_not_show_copy_attachments_checkbox @@ -2376,7 +2404,7 @@ issue.attachments.delete_all get :new, :project_id => 1, :copy_from => 3 - assert_no_tag 'input', :attributes => {:name => 'copy_attachments', :type => 'checkbox', :checked => 'checked', :value => '1'} + assert_select 'input[name=copy_attachments]', 0 end def test_new_as_copy_with_subtasks_should_show_copy_subtasks_checkbox @@ -2523,13 +2551,13 @@ assert_not_nil assigns(:issue) assert assigns(:issue).copy? - assert_tag 'form', :attributes => {:id => 'issue-form', :action => '/projects/ecookbook/issues'} - assert_tag 'select', :attributes => {:name => 'issue[project_id]'} - assert_tag 'select', :attributes => {:name => 'issue[project_id]'}, - :child => {:tag => 'option', :attributes => {:value => '1', :selected => nil}, :content => 'eCookbook'} - assert_tag 'select', :attributes => {:name => 'issue[project_id]'}, - :child => {:tag => 'option', :attributes => {:value => '2', :selected => 'selected'}, :content => 'OnlineStore'} - assert_tag 'input', :attributes => {:name => 'copy_from', :value => '1'} + assert_select 'form#issue-form[action=/projects/ecookbook/issues]' do + assert_select 'select[name=?]', 'issue[project_id]' do + assert_select 'option[value=1]:not([selected])', :text => 'eCookbook' + assert_select 'option[value=2][selected=selected]', :text => 'OnlineStore' + end + assert_select 'input[name=copy_from][value=1]' + end end def test_create_as_copy_on_project_without_permission_should_ignore_target_project @@ -2554,8 +2582,9 @@ # Be sure we don't display inactive IssuePriorities assert ! IssuePriority.find(15).active? - assert_no_tag :option, :attributes => {:value => '15'}, - :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} } + assert_select 'select[name=?]', 'issue[priority_id]' do + assert_select 'option[value=15]', 0 + end end def test_get_edit_should_display_the_time_entry_form_with_log_time_permission @@ -2563,7 +2592,7 @@ Role.find_by_name('Manager').update_attribute :permissions, [:view_issues, :edit_issues, :log_time] get :edit, :id => 1 - assert_tag 'input', :attributes => {:name => 'time_entry[hours]'} + assert_select 'input[name=?]', 'time_entry[hours]' end def test_get_edit_should_not_display_the_time_entry_form_without_log_time_permission @@ -2571,13 +2600,13 @@ Role.find_by_name('Manager').remove_permission! :log_time get :edit, :id => 1 - assert_no_tag 'input', :attributes => {:name => 'time_entry[hours]'} + assert_select 'input[name=?]', 'time_entry[hours]', 0 end def test_get_edit_with_params @request.session[:user_id] = 2 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 }, - :time_entry => { :hours => '2.5', :comments => 'test_get_edit_with_params', :activity_id => TimeEntryActivity.first.id } + :time_entry => { :hours => '2.5', :comments => 'test_get_edit_with_params', :activity_id => 10 } assert_response :success assert_template 'edit' @@ -2585,22 +2614,20 @@ assert_not_nil issue assert_equal 5, issue.status_id - assert_tag :select, :attributes => { :name => 'issue[status_id]' }, - :child => { :tag => 'option', - :content => 'Closed', - :attributes => { :selected => 'selected' } } + assert_select 'select[name=?]', 'issue[status_id]' do + assert_select 'option[value=5][selected=selected]', :text => 'Closed' + end assert_equal 7, issue.priority_id - assert_tag :select, :attributes => { :name => 'issue[priority_id]' }, - :child => { :tag => 'option', - :content => 'Urgent', - :attributes => { :selected => 'selected' } } - - assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => '2.5' } - assert_tag :select, :attributes => { :name => 'time_entry[activity_id]' }, - :child => { :tag => 'option', - :attributes => { :selected => 'selected', :value => TimeEntryActivity.first.id } } - assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => 'test_get_edit_with_params' } + assert_select 'select[name=?]', 'issue[priority_id]' do + assert_select 'option[value=7][selected=selected]', :text => 'Urgent' + end + + assert_select 'input[name=?][value=2.5]', 'time_entry[hours]' + assert_select 'select[name=?]', 'time_entry[activity_id]' do + assert_select 'option[value=10][selected=selected]', :text => 'Development' + end + assert_select 'input[name=?][value=test_get_edit_with_params]', 'time_entry[comments]' end def test_get_edit_with_multi_custom_field @@ -2615,18 +2642,17 @@ assert_response :success assert_template 'edit' - assert_tag 'select', :attributes => {:name => 'issue[custom_field_values][1][]', :multiple => 'multiple'} - assert_tag 'select', :attributes => {:name => 'issue[custom_field_values][1][]'}, - :child => {:tag => 'option', :attributes => {:value => 'MySQL', :selected => 'selected'}} - assert_tag 'select', :attributes => {:name => 'issue[custom_field_values][1][]'}, - :child => {:tag => 'option', :attributes => {:value => 'PostgreSQL', :selected => nil}} - assert_tag 'select', :attributes => {:name => 'issue[custom_field_values][1][]'}, - :child => {:tag => 'option', :attributes => {:value => 'Oracle', :selected => 'selected'}} + assert_select 'select[name=?][multiple=multiple]', 'issue[custom_field_values][1][]' do + assert_select 'option', 3 + assert_select 'option[value=MySQL][selected=selected]' + assert_select 'option[value=Oracle][selected=selected]' + assert_select 'option[value=PostgreSQL]:not([selected])' + end end - def test_update_edit_form + def test_update_form_for_existing_issue @request.session[:user_id] = 2 - xhr :put, :new, :project_id => 1, + xhr :put, :update_form, :project_id => 1, :id => 1, :issue => {:tracker_id => 2, :subject => 'This is the test_new issue', @@ -2645,9 +2671,9 @@ assert_equal 'This is the test_new issue', issue.subject end - def test_update_edit_form_should_keep_issue_author + def test_update_form_for_existing_issue_should_keep_issue_author @request.session[:user_id] = 3 - xhr :put, :new, :project_id => 1, :id => 1, :issue => {:subject => 'Changed'} + xhr :put, :update_form, :project_id => 1, :id => 1, :issue => {:subject => 'Changed'} assert_response :success assert_equal 'text/javascript', response.content_type @@ -2657,14 +2683,14 @@ assert_not_equal User.current, issue.author end - def test_update_edit_form_should_propose_transitions_based_on_initial_status + def test_update_form_for_existing_issue_should_propose_transitions_based_on_initial_status @request.session[:user_id] = 2 WorkflowTransition.delete_all WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 1) WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 5) WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 5, :new_status_id => 4) - xhr :put, :new, :project_id => 1, + xhr :put, :update_form, :project_id => 1, :id => 2, :issue => {:tracker_id => 2, :status_id => 5, @@ -2674,9 +2700,9 @@ assert_equal [1,2,5], assigns(:allowed_statuses).map(&:id).sort end - def test_update_edit_form_with_project_change + def test_update_form_for_existing_issue_with_project_change @request.session[:user_id] = 2 - xhr :put, :new, :project_id => 1, + xhr :put, :update_form, :project_id => 1, :id => 1, :issue => {:project_id => 2, :tracker_id => 2, @@ -2831,7 +2857,7 @@ assert_redirected_to :action => 'show', :id => '1' issue.reload assert_equal 2, issue.status_id - j = Journal.find(:first, :order => 'id DESC') + j = Journal.order('id DESC').first assert_equal 'Assigned to dlopper', j.notes assert_equal 2, j.details.size @@ -2848,7 +2874,7 @@ :id => 1, :issue => { :notes => notes } assert_redirected_to :action => 'show', :id => '1' - j = Journal.find(:first, :order => 'id DESC') + j = Journal.order('id DESC').first assert_equal notes, j.notes assert_equal 0, j.details.size assert_equal User.anonymous, j.user @@ -2904,7 +2930,7 @@ issue = Issue.find(1) - j = Journal.find(:first, :order => 'id DESC') + j = Journal.order('id DESC').first assert_equal '2.5 hours added', j.notes assert_equal 0, j.details.size @@ -2943,7 +2969,7 @@ end assert_redirected_to :action => 'show', :id => '1' - j = Issue.find(1).journals.find(:first, :order => 'id DESC') + j = Issue.find(1).journals.reorder('id DESC').first assert j.notes.blank? assert_equal 1, j.details.size assert_equal 'testfile.txt', j.details.first.value @@ -2982,8 +3008,8 @@ assert File.exists?(attachment.diskfile) assert_nil attachment.container - assert_tag 'input', :attributes => {:name => 'attachments[p0][token]', :value => attachment.token} - assert_tag 'span', :content => /testfile.txt/ + assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token + assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt' end def test_put_update_with_failure_should_keep_saved_attachments @@ -3001,8 +3027,8 @@ end end - assert_tag 'input', :attributes => {:name => 'attachments[p0][token]', :value => attachment.token} - assert_tag 'span', :content => /testfile.txt/ + assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token + assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt' end def test_put_update_should_attach_saved_attachments @@ -3092,8 +3118,8 @@ assert_template 'edit' assert_error_tag :descendant => {:content => /Activity can't be blank/} - assert_tag :textarea, :attributes => { :name => 'issue[notes]' }, :content => "\n"+notes - assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" } + assert_select 'textarea[name=?]', 'issue[notes]', :text => notes + assert_select 'input[name=?][value=?]', 'time_entry[hours]', '2z' end def test_put_update_with_invalid_spent_time_comments_only @@ -3111,8 +3137,8 @@ assert_error_tag :descendant => {:content => /Activity can't be blank/} assert_error_tag :descendant => {:content => /Hours can't be blank/} - assert_tag :textarea, :attributes => { :name => 'issue[notes]' }, :content => "\n"+notes - assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => "this is my comment" } + assert_select 'textarea[name=?]', 'issue[notes]', :text => notes + assert_select 'input[name=?][value=?]', 'time_entry[comments]', 'this is my comment' end def test_put_update_should_allow_fixed_version_to_be_set_to_a_subproject @@ -3167,23 +3193,34 @@ assert_response :success assert_template 'bulk_edit' - assert_tag :select, :attributes => {:name => 'issue[project_id]'} - assert_tag :input, :attributes => {:name => 'issue[parent_issue_id]'} - - # Project specific custom field, date type - field = CustomField.find(9) - assert !field.is_for_all? - assert_equal 'date', field.field_format - assert_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'} - - # System wide custom field - assert CustomField.find(1).is_for_all? - assert_tag :select, :attributes => {:name => 'issue[custom_field_values][1]'} - - # Be sure we don't display inactive IssuePriorities - assert ! IssuePriority.find(15).active? - assert_no_tag :option, :attributes => {:value => '15'}, - :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} } + assert_select 'ul#bulk-selection' do + assert_select 'li', 2 + assert_select 'li a', :text => 'Bug #1' + end + + assert_select 'form#bulk_edit_form[action=?]', '/issues/bulk_update' do + assert_select 'input[name=?]', 'ids[]', 2 + assert_select 'input[name=?][value=1][type=hidden]', 'ids[]' + + assert_select 'select[name=?]', 'issue[project_id]' + assert_select 'input[name=?]', 'issue[parent_issue_id]' + + # Project specific custom field, date type + field = CustomField.find(9) + assert !field.is_for_all? + assert_equal 'date', field.field_format + assert_select 'input[name=?]', 'issue[custom_field_values][9]' + + # System wide custom field + assert CustomField.find(1).is_for_all? + assert_select 'select[name=?]', 'issue[custom_field_values][1]' + + # Be sure we don't display inactive IssuePriorities + assert ! IssuePriority.find(15).active? + assert_select 'select[name=?]', 'issue[priority_id]' do + assert_select 'option[value=15]', 0 + end + end end def test_get_bulk_edit_on_different_projects @@ -3193,13 +3230,13 @@ assert_template 'bulk_edit' # Can not set issues from different projects as children of an issue - assert_no_tag :input, :attributes => {:name => 'issue[parent_issue_id]'} + assert_select 'input[name=?]', 'issue[parent_issue_id]', 0 # Project specific custom field, date type field = CustomField.find(9) assert !field.is_for_all? assert !field.project_ids.include?(Issue.find(6).project_id) - assert_no_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'} + assert_select 'input[name=?]', 'issue[custom_field_values][9]', 0 end def test_get_bulk_edit_with_user_custom_field @@ -3210,12 +3247,9 @@ assert_response :success assert_template 'bulk_edit' - assert_tag :select, - :attributes => {:name => "issue[custom_field_values][#{field.id}]", :class => 'user_cf'}, - :children => { - :only => {:tag => 'option'}, - :count => Project.find(1).users.count + 2 # "no change" + "none" options - } + assert_select 'select.user_cf[name=?]', "issue[custom_field_values][#{field.id}]" do + assert_select 'option', Project.find(1).users.count + 2 # "no change" + "none" options + end end def test_get_bulk_edit_with_version_custom_field @@ -3226,12 +3260,9 @@ assert_response :success assert_template 'bulk_edit' - assert_tag :select, - :attributes => {:name => "issue[custom_field_values][#{field.id}]"}, - :children => { - :only => {:tag => 'option'}, - :count => Project.find(1).shared_versions.count + 2 # "no change" + "none" options - } + assert_select 'select.version_cf[name=?]', "issue[custom_field_values][#{field.id}]" do + assert_select 'option', Project.find(1).shared_versions.count + 2 # "no change" + "none" options + end end def test_get_bulk_edit_with_multi_custom_field @@ -3243,12 +3274,9 @@ assert_response :success assert_template 'bulk_edit' - assert_tag :select, - :attributes => {:name => "issue[custom_field_values][1][]"}, - :children => { - :only => {:tag => 'option'}, - :count => field.possible_values.size + 1 # "none" options - } + assert_select 'select[name=?]', 'issue[custom_field_values][1][]' do + assert_select 'option', field.possible_values.size + 1 # "none" options + end end def test_bulk_edit_should_only_propose_statuses_allowed_for_all_issues @@ -3267,8 +3295,9 @@ assert_not_nil statuses assert_equal [1, 3], statuses.map(&:id).sort - assert_tag 'select', :attributes => {:name => 'issue[status_id]'}, - :children => {:count => 3} # 2 statuses + "no change" option + assert_select 'select[name=?]', 'issue[status_id]' do + assert_select 'option', 3 # 2 statuses + "no change" option + end end def test_bulk_edit_should_propose_target_project_open_shared_versions @@ -3277,9 +3306,10 @@ assert_response :success assert_template 'bulk_edit' assert_equal Project.find(1).shared_versions.open.all.sort, assigns(:versions).sort - assert_tag 'select', - :attributes => {:name => 'issue[fixed_version_id]'}, - :descendant => {:tag => 'option', :content => '2.0'} + + assert_select 'select[name=?]', 'issue[fixed_version_id]' do + assert_select 'option', :text => '2.0' + end end def test_bulk_edit_should_propose_target_project_categories @@ -3288,9 +3318,10 @@ assert_response :success assert_template 'bulk_edit' assert_equal Project.find(1).issue_categories.sort, assigns(:categories).sort - assert_tag 'select', - :attributes => {:name => 'issue[category_id]'}, - :descendant => {:tag => 'option', :content => 'Recipes'} + + assert_select 'select[name=?]', 'issue[category_id]' do + assert_select 'option', :text => 'Recipes' + end end def test_bulk_update @@ -3306,7 +3337,7 @@ assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id} issue = Issue.find(1) - journal = issue.journals.find(:first, :order => 'created_on DESC') + journal = issue.journals.reorder('created_on DESC').first assert_equal '125', issue.custom_value_for(2).value assert_equal 'Bulk editing', journal.notes assert_equal 1, journal.details.size @@ -3341,7 +3372,7 @@ assert_equal [7, 7, 7], Issue.find([1,2,6]).map(&:priority_id) issue = Issue.find(1) - journal = issue.journals.find(:first, :order => 'created_on DESC') + journal = issue.journals.reorder('created_on DESC').first assert_equal '125', issue.custom_value_for(2).value assert_equal 'Bulk editing', journal.notes assert_equal 1, journal.details.size @@ -3443,6 +3474,8 @@ end def test_bulk_update_parent_id + IssueRelation.delete_all + @request.session[:user_id] = 2 post :bulk_update, :ids => [1, 3], :notes => 'Bulk editing parent', @@ -3466,7 +3499,7 @@ assert_response 302 issue = Issue.find(1) - journal = issue.journals.find(:first, :order => 'created_on DESC') + journal = issue.journals.reorder('created_on DESC').first assert_equal '777', issue.custom_value_for(2).value assert_equal 1, journal.details.size assert_equal '125', journal.details.first.old_value @@ -3778,8 +3811,10 @@ assert_template 'destroy' assert_not_nil assigns(:hours) assert Issue.find_by_id(1) && Issue.find_by_id(3) - assert_tag 'form', - :descendant => {:tag => 'input', :attributes => {:name => '_method', :value => 'delete'}} + + assert_select 'form' do + assert_select 'input[name=_method][value=delete]' + end end def test_destroy_issues_and_destroy_time_entries @@ -3845,10 +3880,19 @@ assert_response 302 end + def test_destroy_invalid_should_respond_with_404 + @request.session[:user_id] = 2 + assert_no_difference 'Issue.count' do + delete :destroy, :id => 999 + end + assert_response 404 + end + def test_default_search_scope get :index - assert_tag :div, :attributes => {:id => 'quick-search'}, - :child => {:tag => 'form', - :child => {:tag => 'input', :attributes => {:name => 'issues', :type => 'hidden', :value => '1'}}} + + assert_select 'div#quick-search form' do + assert_select 'input[name=issues][value=1][type=hidden]' + end end end diff -r 0a574315af3e -r 4f746d8966dd test/functional/issues_controller_transaction_test.rb --- a/test/functional/issues_controller_transaction_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/issues_controller_transaction_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,6 +19,7 @@ require 'issues_controller' class IssuesControllerTransactionTest < ActionController::TestCase + tests IssuesController fixtures :projects, :users, :roles, @@ -46,9 +47,6 @@ self.use_transactional_fixtures = false def setup - @controller = IssuesController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil end @@ -106,7 +104,7 @@ assert_template 'edit' attachment = Attachment.first(:order => 'id DESC') assert_tag 'input', :attributes => {:name => 'attachments[p0][token]', :value => attachment.token} - assert_tag 'span', :content => /testfile.txt/ + assert_tag 'input', :attributes => {:name => 'attachments[p0][filename]', :value => 'testfile.txt'} end def test_update_stale_issue_without_notes_should_not_show_add_notes_option @@ -254,7 +252,7 @@ end def test_index_should_rescue_invalid_sql_query - Query.any_instance.stubs(:statement).returns("INVALID STATEMENT") + IssueQuery.any_instance.stubs(:statement).returns("INVALID STATEMENT") get :index assert_response 500 diff -r 0a574315af3e -r 4f746d8966dd test/functional/journals_controller_test.rb --- a/test/functional/journals_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/journals_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,19 +16,12 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'journals_controller' - -# Re-raise errors caught by the controller. -class JournalsController; def rescue_action(e) raise e end; end class JournalsControllerTest < ActionController::TestCase fixtures :projects, :users, :members, :member_roles, :roles, :issues, :journals, :journal_details, :enabled_modules, :trackers, :issue_statuses, :enumerations, :custom_fields, :custom_values, :custom_fields_projects def setup - @controller = JournalsController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil end diff -r 0a574315af3e -r 4f746d8966dd test/functional/mail_handler_controller_test.rb --- a/test/functional/mail_handler_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/mail_handler_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,10 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'mail_handler_controller' - -# Re-raise errors caught by the controller. -class MailHandlerController; def rescue_action(e) raise e end; end class MailHandlerControllerTest < ActionController::TestCase fixtures :users, :projects, :enabled_modules, :roles, :members, :member_roles, :issues, :issue_statuses, @@ -28,9 +24,6 @@ FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/mail_handler' def setup - @controller = MailHandlerController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil end diff -r 0a574315af3e -r 4f746d8966dd test/functional/members_controller_test.rb --- a/test/functional/members_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/members_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,19 +16,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'members_controller' - -# Re-raise errors caught by the controller. -class MembersController; def rescue_action(e) raise e end; end - class MembersControllerTest < ActionController::TestCase fixtures :projects, :members, :member_roles, :roles, :users def setup - @controller = MembersController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil @request.session[:user_id] = 2 end @@ -112,11 +104,8 @@ end def test_autocomplete - get :autocomplete, :project_id => 1, :q => 'mis' + get :autocomplete, :project_id => 1, :q => 'mis', :format => 'js' assert_response :success - assert_template 'autocomplete' - - assert_tag :label, :content => /User Misc/, - :child => { :tag => 'input', :attributes => { :name => 'membership[user_ids][]', :value => '8' } } + assert_include 'User Misc', response.body end end diff -r 0a574315af3e -r 4f746d8966dd test/functional/messages_controller_test.rb --- a/test/functional/messages_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/messages_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,18 +16,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'messages_controller' - -# Re-raise errors caught by the controller. -class MessagesController; def rescue_action(e) raise e end; end class MessagesControllerTest < ActionController::TestCase fixtures :projects, :users, :members, :member_roles, :roles, :boards, :messages, :enabled_modules def setup - @controller = MessagesController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil end @@ -93,6 +86,12 @@ assert_template 'new' end + def test_get_new_with_invalid_board + @request.session[:user_id] = 2 + get :new, :board_id => 99 + assert_response 404 + end + def test_post_new @request.session[:user_id] = 2 ActionMailer::Base.deliveries.clear @@ -164,7 +163,7 @@ 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' } - reply = Message.find(:first, :order => 'id DESC') + reply = Message.order('id DESC').first assert_redirected_to "/boards/1/topics/1?r=#{reply.id}" assert Message.find_by_subject('Test reply') end diff -r 0a574315af3e -r 4f746d8966dd test/functional/my_controller_test.rb --- a/test/functional/my_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/my_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,20 +16,13 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'my_controller' - -# Re-raise errors caught by the controller. -class MyController; def rescue_action(e) raise e end; end class MyControllerTest < ActionController::TestCase fixtures :users, :user_preferences, :roles, :projects, :members, :member_roles, :issues, :issue_statuses, :trackers, :enumerations, :custom_fields, :auth_sources def setup - @controller = MyController.new - @request = ActionController::TestRequest.new @request.session[:user_id] = 2 - @response = ActionController::TestResponse.new end def test_index @@ -58,6 +51,17 @@ end end + def test_page_with_all_blocks + blocks = MyController::BLOCKS.keys + preferences = User.find(2).pref + preferences[:my_page_layout] = {'top' => blocks} + preferences.save! + + get :page + assert_response :success + assert_select 'div.mypage-box', blocks.size + end + def test_my_account_should_show_editable_custom_fields get :account assert_response :success diff -r 0a574315af3e -r 4f746d8966dd test/functional/news_controller_test.rb --- a/test/functional/news_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/news_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,18 +16,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'news_controller' - -# Re-raise errors caught by the controller. -class NewsController; def rescue_action(e) raise e end; end class NewsControllerTest < ActionController::TestCase fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules, :news, :comments def setup - @controller = NewsController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil end diff -r 0a574315af3e -r 4f746d8966dd test/functional/previews_controller_test.rb --- a/test/functional/previews_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/previews_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -25,7 +25,6 @@ :member_roles, :members, :enabled_modules, - :workflows, :journals, :journal_details, :news diff -r 0a574315af3e -r 4f746d8966dd test/functional/project_enumerations_controller_test.rb --- a/test/functional/project_enumerations_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/project_enumerations_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,3 +1,20 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) class ProjectEnumerationsControllerTest < ActionController::TestCase @@ -8,7 +25,6 @@ :member_roles, :members, :enabled_modules, - :workflows, :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values, :time_entries @@ -75,14 +91,14 @@ project_activity = TimeEntryActivity.new({ :name => 'Project Specific', - :parent => TimeEntryActivity.find(:first), + :parent => TimeEntryActivity.first, :project => Project.find(1), :active => true }) assert project_activity.save project_activity_two = TimeEntryActivity.new({ :name => 'Project Specific Two', - :parent => TimeEntryActivity.find(:last), + :parent => TimeEntryActivity.last, :project => Project.find(1), :active => true }) @@ -156,14 +172,14 @@ @request.session[:user_id] = 2 # manager project_activity = TimeEntryActivity.new({ :name => 'Project Specific', - :parent => TimeEntryActivity.find(:first), + :parent => TimeEntryActivity.first, :project => Project.find(1), :active => true }) assert project_activity.save project_activity_two = TimeEntryActivity.new({ :name => 'Project Specific Two', - :parent => TimeEntryActivity.find(:last), + :parent => TimeEntryActivity.last, :project => Project.find(1), :active => true }) diff -r 0a574315af3e -r 4f746d8966dd test/functional/projects_controller_test.rb --- a/test/functional/projects_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/projects_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,10 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'projects_controller' - -# Re-raise errors caught by the controller. -class ProjectsController; def rescue_action(e) raise e end; end class ProjectsControllerTest < ActionController::TestCase fixtures :projects, :versions, :users, :roles, :members, :member_roles, :issues, :journals, :journal_details, @@ -27,29 +23,27 @@ :attachments, :custom_fields, :custom_values, :time_entries 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 + def test_index_by_anonymous_should_not_show_private_projects get :index assert_response :success assert_template 'index' - assert_not_nil assigns(:projects) + projects = assigns(:projects) + assert_not_nil projects + assert projects.all?(&:is_public?) - 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/ + assert_select 'ul' do + assert_select 'li' do + assert_select 'a', :text => 'eCookbook' + assert_select 'ul' do + assert_select 'a', :text => 'Child of private child' + end + end + end + assert_select 'a', :text => /Private child of eCookbook/, :count => 0 end def test_index_atom @@ -60,239 +54,237 @@ assert_select 'feed>entry', :count => Project.count(:conditions => Project.visible_condition(User.current)) end - context "#index" do - context "by non-admin user with view_time_entries permission" do - setup do - @request.session[:user_id] = 3 - end - should "show overall spent time link" do - get :index - assert_template 'index' - assert_tag :a, :attributes => {:href => '/time_entries'} - end - end + test "#index by non-admin user with view_time_entries permission should show overall spent time link" do + @request.session[:user_id] = 3 + get :index + assert_template 'index' + assert_select 'a[href=?]', '/time_entries' + end - context "by non-admin user without view_time_entries permission" do - setup do - Role.find(2).remove_permission! :view_time_entries - Role.non_member.remove_permission! :view_time_entries - Role.anonymous.remove_permission! :view_time_entries - @request.session[:user_id] = 3 - end - should "not show overall spent time link" do - get :index - assert_template 'index' - assert_no_tag :a, :attributes => {:href => '/time_entries'} - end + test "#index by non-admin user without view_time_entries permission should not show overall spent time link" do + Role.find(2).remove_permission! :view_time_entries + Role.non_member.remove_permission! :view_time_entries + Role.anonymous.remove_permission! :view_time_entries + @request.session[:user_id] = 3 + + get :index + assert_template 'index' + assert_select 'a[href=?]', '/time_entries', 0 + end + + test "#new by admin user should accept get" do + @request.session[:user_id] = 1 + + get :new + assert_response :success + assert_template 'new' + end + + test "#new by non-admin user with add_project permission should accept get" do + Role.non_member.add_permission! :add_project + @request.session[:user_id] = 9 + + get :new + assert_response :success + assert_template 'new' + assert_select 'select[name=?]', 'project[parent_id]', 0 + end + + test "#new by non-admin user with add_subprojects permission should accept get" do + Role.find(1).remove_permission! :add_project + Role.find(1).add_permission! :add_subprojects + @request.session[:user_id] = 2 + + get :new, :parent_id => 'ecookbook' + assert_response :success + assert_template 'new' + + assert_select 'select[name=?]', 'project[parent_id]' do + # parent project selected + assert_select 'option[value=1][selected=selected]' + # no empty value + assert_select 'option[value=]', 0 end end - context "#new" do - context "by admin user" do - setup do - @request.session[:user_id] = 1 - end + test "#create by admin user should create a new project" do + @request.session[:user_id] = 1 - should "accept get" do - get :new - assert_response :success - assert_template 'new' - end + post :create, + :project => { + :name => "blog", + :description => "weblog", + :homepage => 'http://weblog', + :identifier => "blog", + :is_public => 1, + :custom_field_values => { '3' => 'Beta' }, + :tracker_ids => ['1', '3'], + # an issue custom field that is not for all project + :issue_custom_field_ids => ['9'], + :enabled_module_names => ['issue_tracking', 'news', 'repository'] + } + assert_redirected_to '/projects/blog/settings' + project = Project.find_by_name('blog') + assert_kind_of Project, project + assert project.active? + assert_equal 'weblog', project.description + assert_equal 'http://weblog', project.homepage + assert_equal true, project.is_public? + assert_nil project.parent + assert_equal 'Beta', project.custom_value_for(3).value + assert_equal [1, 3], project.trackers.map(&:id).sort + assert_equal ['issue_tracking', 'news', 'repository'], project.enabled_module_names.sort + assert project.issue_custom_fields.include?(IssueCustomField.find(9)) + end + + test "#create by admin user should create a new subproject" do + @request.session[:user_id] = 1 + + assert_difference 'Project.count' do + post :create, :project => { :name => "blog", + :description => "weblog", + :identifier => "blog", + :is_public => 1, + :custom_field_values => { '3' => 'Beta' }, + :parent_id => 1 + } + assert_redirected_to '/projects/blog/settings' end - context "by non-admin user with add_project permission" do - setup do - Role.non_member.add_permission! :add_project - @request.session[:user_id] = 9 - end + project = Project.find_by_name('blog') + assert_kind_of Project, project + assert_equal Project.find(1), project.parent + end - should "accept get" do - get :new - assert_response :success - assert_template 'new' - assert_no_tag :select, :attributes => {:name => 'project[parent_id]'} - end + test "#create by admin user should continue" do + @request.session[:user_id] = 1 + + assert_difference 'Project.count' do + post :create, :project => {:name => "blog", :identifier => "blog"}, :continue => 'Create and continue' + end + assert_redirected_to '/projects/new' + end + + test "#create by non-admin user with add_project permission should create a new project" do + Role.non_member.add_permission! :add_project + @request.session[:user_id] = 9 + + post :create, :project => { :name => "blog", + :description => "weblog", + :identifier => "blog", + :is_public => 1, + :custom_field_values => { '3' => 'Beta' }, + :tracker_ids => ['1', '3'], + :enabled_module_names => ['issue_tracking', 'news', 'repository'] + } + + assert_redirected_to '/projects/blog/settings' + + project = Project.find_by_name('blog') + assert_kind_of Project, project + assert_equal 'weblog', project.description + assert_equal true, project.is_public? + assert_equal [1, 3], project.trackers.map(&:id).sort + assert_equal ['issue_tracking', 'news', 'repository'], project.enabled_module_names.sort + + # User should be added as a project member + assert User.find(9).member_of?(project) + assert_equal 1, project.members.size + end + + test "#create by non-admin user with add_project permission should fail with parent_id" do + Role.non_member.add_permission! :add_project + @request.session[:user_id] = 9 + + assert_no_difference 'Project.count' do + post :create, :project => { :name => "blog", + :description => "weblog", + :identifier => "blog", + :is_public => 1, + :custom_field_values => { '3' => 'Beta' }, + :parent_id => 1 + } + end + assert_response :success + project = assigns(:project) + assert_kind_of Project, project + assert_not_nil project.errors[:parent_id] + end + + test "#create by non-admin user with add_subprojects permission should create a project with a parent_id" do + Role.find(1).remove_permission! :add_project + Role.find(1).add_permission! :add_subprojects + @request.session[:user_id] = 2 + + post :create, :project => { :name => "blog", + :description => "weblog", + :identifier => "blog", + :is_public => 1, + :custom_field_values => { '3' => 'Beta' }, + :parent_id => 1 + } + assert_redirected_to '/projects/blog/settings' + project = Project.find_by_name('blog') + end + + test "#create by non-admin user with add_subprojects permission should fail without parent_id" do + Role.find(1).remove_permission! :add_project + Role.find(1).add_permission! :add_subprojects + @request.session[:user_id] = 2 + + assert_no_difference 'Project.count' do + post :create, :project => { :name => "blog", + :description => "weblog", + :identifier => "blog", + :is_public => 1, + :custom_field_values => { '3' => 'Beta' } + } + end + assert_response :success + project = assigns(:project) + assert_kind_of Project, project + assert_not_nil project.errors[:parent_id] + end + + test "#create by non-admin user with add_subprojects permission should fail with unauthorized parent_id" do + Role.find(1).remove_permission! :add_project + Role.find(1).add_permission! :add_subprojects + @request.session[:user_id] = 2 + + assert !User.find(2).member_of?(Project.find(6)) + assert_no_difference 'Project.count' do + post :create, :project => { :name => "blog", + :description => "weblog", + :identifier => "blog", + :is_public => 1, + :custom_field_values => { '3' => 'Beta' }, + :parent_id => 6 + } + end + assert_response :success + project = assigns(:project) + assert_kind_of Project, project + assert_not_nil project.errors[:parent_id] + end + + def test_create_subproject_with_inherit_members_should_inherit_members + Role.find_by_name('Manager').add_permission! :add_subprojects + parent = Project.find(1) + @request.session[:user_id] = 2 + + assert_difference 'Project.count' do + post :create, :project => { + :name => 'inherited', :identifier => 'inherited', :parent_id => parent.id, :inherit_members => '1' + } + assert_response 302 end - context "by non-admin user with add_subprojects permission" do - setup do - Role.find(1).remove_permission! :add_project - Role.find(1).add_permission! :add_subprojects - @request.session[:user_id] = 2 - end - - should "accept get" do - get :new, :parent_id => 'ecookbook' - assert_response :success - assert_template 'new' - # parent project selected - assert_tag :select, :attributes => {:name => 'project[parent_id]'}, - :child => {:tag => 'option', :attributes => {:value => '1', :selected => 'selected'}} - # no empty value - assert_no_tag :select, :attributes => {:name => 'project[parent_id]'}, - :child => {:tag => 'option', :attributes => {:value => ''}} - end - end - - end - - context "POST :create" do - context "by admin user" do - setup do - @request.session[:user_id] = 1 - end - - should "create a new project" do - post :create, - :project => { - :name => "blog", - :description => "weblog", - :homepage => 'http://weblog', - :identifier => "blog", - :is_public => 1, - :custom_field_values => { '3' => 'Beta' }, - :tracker_ids => ['1', '3'], - # an issue custom field that is not for all project - :issue_custom_field_ids => ['9'], - :enabled_module_names => ['issue_tracking', 'news', 'repository'] - } - assert_redirected_to '/projects/blog/settings' - - project = Project.find_by_name('blog') - assert_kind_of Project, project - assert project.active? - assert_equal 'weblog', project.description - assert_equal 'http://weblog', project.homepage - assert_equal true, project.is_public? - assert_nil project.parent - assert_equal 'Beta', project.custom_value_for(3).value - assert_equal [1, 3], project.trackers.map(&:id).sort - assert_equal ['issue_tracking', 'news', 'repository'], project.enabled_module_names.sort - assert project.issue_custom_fields.include?(IssueCustomField.find(9)) - end - - should "create a new subproject" do - post :create, :project => { :name => "blog", - :description => "weblog", - :identifier => "blog", - :is_public => 1, - :custom_field_values => { '3' => 'Beta' }, - :parent_id => 1 - } - assert_redirected_to '/projects/blog/settings' - - project = Project.find_by_name('blog') - assert_kind_of Project, project - assert_equal Project.find(1), project.parent - end - - should "continue" do - assert_difference 'Project.count' do - post :create, :project => {:name => "blog", :identifier => "blog"}, :continue => 'Create and continue' - end - assert_redirected_to '/projects/new?' - end - end - - context "by non-admin user with add_project permission" do - setup do - Role.non_member.add_permission! :add_project - @request.session[:user_id] = 9 - end - - should "accept create a Project" do - post :create, :project => { :name => "blog", - :description => "weblog", - :identifier => "blog", - :is_public => 1, - :custom_field_values => { '3' => 'Beta' }, - :tracker_ids => ['1', '3'], - :enabled_module_names => ['issue_tracking', 'news', 'repository'] - } - - assert_redirected_to '/projects/blog/settings' - - project = Project.find_by_name('blog') - assert_kind_of Project, project - assert_equal 'weblog', project.description - assert_equal true, project.is_public? - assert_equal [1, 3], project.trackers.map(&:id).sort - assert_equal ['issue_tracking', 'news', 'repository'], project.enabled_module_names.sort - - # User should be added as a project member - assert User.find(9).member_of?(project) - assert_equal 1, project.members.size - end - - should "fail with parent_id" do - assert_no_difference 'Project.count' do - post :create, :project => { :name => "blog", - :description => "weblog", - :identifier => "blog", - :is_public => 1, - :custom_field_values => { '3' => 'Beta' }, - :parent_id => 1 - } - end - assert_response :success - project = assigns(:project) - assert_kind_of Project, project - assert_not_nil project.errors[:parent_id] - end - end - - context "by non-admin user with add_subprojects permission" do - setup do - Role.find(1).remove_permission! :add_project - Role.find(1).add_permission! :add_subprojects - @request.session[:user_id] = 2 - end - - should "create a project with a parent_id" do - post :create, :project => { :name => "blog", - :description => "weblog", - :identifier => "blog", - :is_public => 1, - :custom_field_values => { '3' => 'Beta' }, - :parent_id => 1 - } - assert_redirected_to '/projects/blog/settings' - project = Project.find_by_name('blog') - end - - should "fail without parent_id" do - assert_no_difference 'Project.count' do - post :create, :project => { :name => "blog", - :description => "weblog", - :identifier => "blog", - :is_public => 1, - :custom_field_values => { '3' => 'Beta' } - } - end - assert_response :success - project = assigns(:project) - assert_kind_of Project, project - assert_not_nil project.errors[:parent_id] - end - - should "fail with unauthorized parent_id" do - assert !User.find(2).member_of?(Project.find(6)) - assert_no_difference 'Project.count' do - post :create, :project => { :name => "blog", - :description => "weblog", - :identifier => "blog", - :is_public => 1, - :custom_field_values => { '3' => 'Beta' }, - :parent_id => 6 - } - end - assert_response :success - project = assigns(:project) - assert_kind_of Project, project - assert_not_nil project.errors[:parent_id] - end - end + project = Project.order('id desc').first + assert_equal 'inherited', project.name + assert_equal parent, project.parent + assert project.memberships.count > 0 + assert_equal parent.memberships.count, project.memberships.count end def test_create_should_preserve_modules_on_validation_failure @@ -325,7 +317,7 @@ assert_not_nil assigns(:project) assert_equal Project.find_by_identifier('ecookbook'), assigns(:project) - assert_tag 'li', :content => /Development status/ + assert_select 'li', :text => /Development status/ end def test_show_should_not_display_hidden_custom_fields @@ -335,7 +327,7 @@ assert_template 'show' assert_not_nil assigns(:project) - assert_no_tag 'li', :content => /Development status/ + assert_select 'li', :text => /Development status/, :count => 0 end def test_show_should_not_fail_when_custom_values_are_nil @@ -355,22 +347,22 @@ get :show, :id => 'ecookbook' assert_response 403 assert_nil assigns(:project) - assert_tag :tag => 'p', :content => /archived/ + assert_select 'p', :text => /archived/ end - def test_private_subprojects_hidden + def test_show_should_not_show_private_subprojects_that_are_not_visible get :show, :id => 'ecookbook' assert_response :success assert_template 'show' - assert_no_tag :tag => 'a', :content => /Private child/ + assert_select 'a', :text => /Private child/, :count => 0 end - def test_private_subprojects_visible + def test_show_should_show_private_subprojects_that_are_visible @request.session[:user_id] = 2 # manager who is a member of the private subproject get :show, :id => 'ecookbook' assert_response :success assert_template 'show' - assert_tag :tag => 'a', :content => /Private child/ + assert_select 'a', :text => /Private child/ end def test_settings @@ -380,6 +372,15 @@ assert_template 'settings' end + def test_settings_of_subproject + @request.session[:user_id] = 2 + get :settings, :id => 'private-child' + assert_response :success + assert_template 'settings' + + assert_select 'input[type=checkbox][name=?]', 'project[inherit_members]' + end + def test_settings_should_be_denied_for_member_on_closed_project Project.find(1).close @request.session[:user_id] = 2 # manager @@ -438,22 +439,37 @@ assert_equal ['documents', 'issue_tracking', 'repository'], Project.find(1).enabled_module_names.sort end - def test_destroy_without_confirmation + def test_destroy_leaf_project_without_confirmation_should_show_confirmation @request.session[:user_id] = 1 # admin - delete :destroy, :id => 1 - assert_response :success - assert_template 'destroy' - assert_not_nil Project.find_by_id(1) - assert_tag :tag => 'strong', - :content => ['Private child of eCookbook', + + assert_no_difference 'Project.count' do + delete :destroy, :id => 2 + assert_response :success + assert_template 'destroy' + end + end + + def test_destroy_without_confirmation_should_show_confirmation_with_subprojects + @request.session[:user_id] = 1 # admin + + assert_no_difference 'Project.count' do + delete :destroy, :id => 1 + assert_response :success + assert_template 'destroy' + end + assert_select 'strong', + :text => ['Private child of eCookbook', 'Child of private child, eCookbook Subproject 1', 'eCookbook Subproject 2'].join(', ') end - def test_destroy + def test_destroy_with_confirmation_should_destroy_the_project_and_subprojects @request.session[:user_id] = 1 # admin - delete :destroy, :id => 1, :confirm => 1 - assert_redirected_to '/admin/projects' + + assert_difference 'Project.count', -5 do + delete :destroy, :id => 1, :confirm => 1 + assert_redirected_to '/admin/projects' + end assert_nil Project.find_by_id(1) end @@ -499,12 +515,11 @@ CustomField.delete_all parent = nil 6.times do |i| - p = Project.create!(:name => "Breadcrumbs #{i}", :identifier => "breadcrumbs-#{i}") - p.set_parent!(parent) + p = Project.generate_with_parent!(parent) get :show, :id => p - assert_tag :h1, :parent => { :attributes => {:id => 'header'}}, - :children => { :count => [i, 3].min, - :only => { :tag => 'a' } } + assert_select '#header h1' do + assert_select 'a', :count => [i, 3].min + end parent = p end @@ -519,8 +534,7 @@ assert_equal Project.find(1).description, assigns(:project).description assert_nil assigns(:project).id - assert_tag :tag => 'input', - :attributes => {:name => 'project[enabled_module_names][]', :value => 'issue_tracking'} + assert_select 'input[name=?][value=?]', 'project[enabled_module_names][]', 'issue_tracking', 1 end def test_get_copy_with_invalid_source_should_respond_with_404 diff -r 0a574315af3e -r 4f746d8966dd test/functional/queries_controller_test.rb --- a/test/functional/queries_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/queries_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,6 +24,12 @@ User.current = nil end + def test_index + get :index + # HTML response not implemented + assert_response 406 + end + def test_new_project_query @request.session[:user_id] = 2 get :new, :project_id => 1 @@ -108,7 +114,7 @@ assert_redirected_to :controller => 'issues', :action => 'index', :project_id => nil, :query_id => q assert !q.is_public? assert !q.has_default_columns? - assert_equal [:tracker, :subject, :priority, :category], q.columns.collect {|c| c.name} + assert_equal [:id, :tracker, :subject, :priority, :category], q.columns.collect {|c| c.name} assert q.valid? end diff -r 0a574315af3e -r 4f746d8966dd test/functional/reports_controller_test.rb --- a/test/functional/reports_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/reports_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -25,12 +25,8 @@ :member_roles, :members, :enabled_modules, - :workflows, :versions - def setup - end - def test_get_issue_report get :issue_report, :id => 1 diff -r 0a574315af3e -r 4f746d8966dd test/functional/repositories_bazaar_controller_test.rb --- a/test/functional/repositories_bazaar_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/repositories_bazaar_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/functional/repositories_controller_test.rb --- a/test/functional/repositories_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/repositories_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,10 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'repositories_controller' - -# Re-raise errors caught by the controller. -class RepositoriesController; def rescue_action(e) raise e end; end class RepositoriesControllerTest < ActionController::TestCase fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules, @@ -27,9 +23,6 @@ :issue_categories, :enumerations, :custom_fields, :custom_values, :trackers def setup - @controller = RepositoriesController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil end diff -r 0a574315af3e -r 4f746d8966dd test/functional/repositories_cvs_controller_test.rb --- a/test/functional/repositories_cvs_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/repositories_cvs_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/functional/repositories_darcs_controller_test.rb --- a/test/functional/repositories_darcs_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/repositories_darcs_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/functional/repositories_filesystem_controller_test.rb --- a/test/functional/repositories_filesystem_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/repositories_filesystem_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -109,7 +109,8 @@ end def test_show_utf16 - with_settings :repositories_encodings => 'UTF-16' do + enc = (RUBY_VERSION == "1.9.2" ? 'UTF-16LE' : 'UTF-16') + with_settings :repositories_encodings => enc do get :entry, :id => PRJ_ID, :path => repository_path_hash(['japanese', 'utf-16.txt'])[:param] assert_response :success diff -r 0a574315af3e -r 4f746d8966dd test/functional/repositories_git_controller_test.rb --- a/test/functional/repositories_git_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/repositories_git_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/functional/repositories_mercurial_controller_test.rb --- a/test/functional/repositories_mercurial_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/repositories_mercurial_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/functional/repositories_subversion_controller_test.rb --- a/test/functional/repositories_subversion_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/repositories_subversion_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/functional/roles_controller_test.rb --- a/test/functional/roles_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/roles_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -21,9 +21,6 @@ fixtures :roles, :users, :members, :member_roles, :workflows, :trackers def setup - @controller = RolesController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil @request.session[:user_id] = 1 # admin end @@ -34,7 +31,7 @@ assert_template 'index' assert_not_nil assigns(:roles) - assert_equal Role.find(:all, :order => 'builtin, position'), assigns(:roles) + assert_equal Role.order('builtin, position').all, assigns(:roles) assert_tag :tag => 'a', :attributes => { :href => '/roles/1/edit' }, :content => 'Manager' @@ -163,7 +160,7 @@ assert_template 'permissions' assert_not_nil assigns(:roles) - assert_equal Role.find(:all, :order => 'builtin, position'), assigns(:roles) + assert_equal Role.order('builtin, position').all, assigns(:roles) assert_tag :tag => 'input', :attributes => { :type => 'checkbox', :name => 'permissions[3][]', diff -r 0a574315af3e -r 4f746d8966dd test/functional/search_controller_test.rb --- a/test/functional/search_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/search_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,8 +1,21 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) -require 'search_controller' - -# Re-raise errors caught by the controller. -class SearchController; def rescue_action(e) raise e end; end class SearchControllerTest < ActionController::TestCase fixtures :projects, :enabled_modules, :roles, :users, :members, :member_roles, @@ -11,9 +24,6 @@ :repositories, :changesets def setup - @controller = SearchController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil end diff -r 0a574315af3e -r 4f746d8966dd test/functional/sessions_test.rb --- a/test/functional/sessions_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/sessions_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/functional/settings_controller_test.rb --- a/test/functional/settings_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/settings_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,18 +16,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'settings_controller' - -# Re-raise errors caught by the controller. -class SettingsController; def rescue_action(e) raise e end; end class SettingsControllerTest < ActionController::TestCase fixtures :users def setup - @controller = SettingsController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil @request.session[:user_id] = 1 # admin end @@ -79,7 +72,7 @@ :notified_events => %w(issue_added issue_updated news_added), :emails_footer => 'Test footer' } - assert_redirected_to '/settings/edit' + assert_redirected_to '/settings' assert_equal 'functional@test.foo', Setting.mail_from assert !Setting.bcc_recipients? assert_equal %w(issue_added issue_updated news_added), Setting.notified_events @@ -108,11 +101,31 @@ assert_response 404 end + def test_get_non_configurable_plugin_settings + Redmine::Plugin.register(:foo) {} + + get :plugin, :id => 'foo' + assert_response 404 + + Redmine::Plugin.clear + end + def test_post_plugin_settings Setting.expects(:plugin_foo=).with({'sample_setting' => 'Value'}).returns(true) - Redmine::Plugin.register(:foo) {} + Redmine::Plugin.register(:foo) do + settings :partial => 'not blank' # so that configurable? is true + end post :plugin, :id => 'foo', :settings => {'sample_setting' => 'Value'} assert_redirected_to '/settings/plugin/foo' end + + def test_post_non_configurable_plugin_settings + Redmine::Plugin.register(:foo) {} + + post :plugin, :id => 'foo', :settings => {'sample_setting' => 'Value'} + assert_response 404 + + Redmine::Plugin.clear + end end diff -r 0a574315af3e -r 4f746d8966dd test/functional/sys_controller_test.rb --- a/test/functional/sys_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/sys_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,19 +16,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'sys_controller' -require 'mocha' - -# Re-raise errors caught by the controller. -class SysController; def rescue_action(e) raise e end; end class SysControllerTest < ActionController::TestCase fixtures :projects, :repositories, :enabled_modules def setup - @controller = SysController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new Setting.sys_api_enabled = '1' Setting.enabled_scm = %w(Subversion Git) end diff -r 0a574315af3e -r 4f746d8966dd test/functional/time_entry_reports_controller_test.rb --- a/test/functional/time_entry_reports_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/time_entry_reports_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,4 +1,21 @@ # -*- coding: utf-8 -*- +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) class TimeEntryReportsControllerTest < ActionController::TestCase @@ -73,7 +90,7 @@ end def test_report_two_criteria - get :report, :project_id => 1, :columns => 'month', :from => "2007-01-01", :to => "2007-12-31", :criteria => ["member", "activity"] + get :report, :project_id => 1, :columns => 'month', :from => "2007-01-01", :to => "2007-12-31", :criteria => ["user", "activity"] assert_response :success assert_template 'report' assert_not_nil assigns(:report) @@ -91,7 +108,7 @@ end def test_report_one_day - get :report, :project_id => 1, :columns => 'day', :from => "2007-03-23", :to => "2007-03-23", :criteria => ["member", "activity"] + get :report, :project_id => 1, :columns => 'day', :from => "2007-03-23", :to => "2007-03-23", :criteria => ["user", "activity"] assert_response :success assert_template 'report' assert_not_nil assigns(:report) @@ -99,7 +116,7 @@ end def test_report_at_issue_level - get :report, :project_id => 1, :issue_id => 1, :columns => 'month', :from => "2007-01-01", :to => "2007-12-31", :criteria => ["member", "activity"] + get :report, :project_id => 1, :issue_id => 1, :columns => 'month', :from => "2007-01-01", :to => "2007-12-31", :criteria => ["user", "activity"] assert_response :success assert_template 'report' assert_not_nil assigns(:report) @@ -108,22 +125,62 @@ :attributes => {:action => "/projects/ecookbook/issues/1/time_entries/report", :id => 'query_form'} end - def test_report_custom_field_criteria - get :report, :project_id => 1, :criteria => ['project', 'cf_1', 'cf_7'] + def test_report_by_week_should_use_commercial_year + TimeEntry.delete_all + TimeEntry.generate!(:hours => '2', :spent_on => '2009-12-25') # 2009-52 + TimeEntry.generate!(:hours => '4', :spent_on => '2009-12-31') # 2009-53 + TimeEntry.generate!(:hours => '8', :spent_on => '2010-01-01') # 2009-53 + TimeEntry.generate!(:hours => '16', :spent_on => '2010-01-05') # 2010-1 + + get :report, :columns => 'week', :from => "2009-12-25", :to => "2010-01-05", :criteria => ["project"] + assert_response :success + + assert_select '#time-report thead tr' do + assert_select 'th:nth-child(1)', :text => 'Project' + assert_select 'th:nth-child(2)', :text => '2009-52' + assert_select 'th:nth-child(3)', :text => '2009-53' + assert_select 'th:nth-child(4)', :text => '2010-1' + assert_select 'th:nth-child(5)', :text => 'Total time' + end + assert_select '#time-report tbody tr' do + assert_select 'td:nth-child(1)', :text => 'eCookbook' + assert_select 'td:nth-child(2)', :text => '2.00' + assert_select 'td:nth-child(3)', :text => '12.00' + assert_select 'td:nth-child(4)', :text => '16.00' + assert_select 'td:nth-child(5)', :text => '30.00' # Total + end + end + + def test_report_should_propose_association_custom_fields + get :report + assert_response :success + assert_template 'report' + + assert_select 'select[name=?]', 'criteria[]' do + assert_select 'option[value=cf_1]', {:text => 'Database'}, 'Issue custom field not found' + assert_select 'option[value=cf_3]', {:text => 'Development status'}, 'Project custom field not found' + assert_select 'option[value=cf_7]', {:text => 'Billable'}, 'TimeEntryActivity custom field not found' + end + end + + def test_report_with_association_custom_fields + get :report, :criteria => ['cf_1', 'cf_3', 'cf_7'] assert_response :success assert_template 'report' assert_not_nil assigns(:report) assert_equal 3, assigns(:report).criteria.size assert_equal "162.90", "%.2f" % assigns(:report).total_hours - # Custom field column - assert_tag :tag => 'th', :content => 'Database' + + # Custom fields columns + assert_select 'th', :text => 'Database' + assert_select 'th', :text => 'Development status' + assert_select 'th', :text => 'Billable' + # Custom field row - assert_tag :tag => 'td', :content => 'MySQL', - :sibling => { :tag => 'td', :attributes => { :class => 'hours' }, - :child => { :tag => 'span', :attributes => { :class => 'hours hours-int' }, - :content => '1' }} - # Second custom field column - assert_tag :tag => 'th', :content => 'Billable' + assert_select 'tr' do + assert_select 'td', :text => 'MySQL' + assert_select 'td.hours', :text => '1.00' + end end def test_report_one_criteria_no_result @@ -144,29 +201,27 @@ def test_report_all_projects_csv_export get :report, :columns => 'month', :from => "2007-01-01", :to => "2007-06-30", - :criteria => ["project", "member", "activity"], :format => "csv" + :criteria => ["project", "user", "activity"], :format => "csv" assert_response :success assert_equal 'text/csv; header=present', @response.content_type lines = @response.body.chomp.split("\n") # Headers - assert_equal 'Project,Member,Activity,2007-1,2007-2,2007-3,2007-4,2007-5,2007-6,Total', - lines.first + assert_equal 'Project,User,Activity,2007-3,2007-4,Total time', lines.first # Total row - assert_equal 'Total,"","","","",154.25,8.65,"","",162.90', lines.last + assert_equal 'Total time,"","",154.25,8.65,162.90', lines.last end def test_report_csv_export get :report, :project_id => 1, :columns => 'month', :from => "2007-01-01", :to => "2007-06-30", - :criteria => ["project", "member", "activity"], :format => "csv" + :criteria => ["project", "user", "activity"], :format => "csv" assert_response :success assert_equal 'text/csv; header=present', @response.content_type lines = @response.body.chomp.split("\n") # Headers - assert_equal 'Project,Member,Activity,2007-1,2007-2,2007-3,2007-4,2007-5,2007-6,Total', - lines.first + assert_equal 'Project,User,Activity,2007-3,2007-4,Total time', lines.first # Total row - assert_equal 'Total,"","","","",154.25,8.65,"","",162.90', lines.last + assert_equal 'Total time,"","",154.25,8.65,162.90', lines.last end def test_csv_big_5 @@ -196,12 +251,12 @@ get :report, :project_id => 1, :columns => 'day', :from => "2011-11-11", :to => "2011-11-11", - :criteria => ["member"], :format => "csv" + :criteria => ["user"], :format => "csv" assert_response :success assert_equal 'text/csv; header=present', @response.content_type lines = @response.body.chomp.split("\n") # Headers - s1 = "\xa6\xa8\xad\xfb,2011-11-11,\xc1`\xadp" + s1 = "\xa5\xce\xa4\xe1,2011-11-11,\xc1`\xadp" s2 = "\xc1`\xadp" if s1.respond_to?(:force_encoding) s1.force_encoding('Big5') @@ -247,12 +302,12 @@ get :report, :project_id => 1, :columns => 'day', :from => "2011-11-11", :to => "2011-11-11", - :criteria => ["member"], :format => "csv" + :criteria => ["user"], :format => "csv" assert_response :success assert_equal 'text/csv; header=present', @response.content_type lines = @response.body.chomp.split("\n") # Headers - s1 = "\xa6\xa8\xad\xfb,2011-11-11,\xc1`\xadp" + s1 = "\xa5\xce\xa4\xe1,2011-11-11,\xc1`\xadp" if s1.respond_to?(:force_encoding) s1.force_encoding('Big5') end @@ -288,13 +343,13 @@ get :report, :project_id => 1, :columns => 'day', :from => "2011-11-11", :to => "2011-11-11", - :criteria => ["member"], :format => "csv" + :criteria => ["user"], :format => "csv" assert_response :success assert_equal 'text/csv; header=present', @response.content_type lines = @response.body.chomp.split("\n") # Headers - s1 = "Membre;2011-11-11;Total" - s2 = "Total" + s1 = "Utilisateur;2011-11-11;Temps total" + s2 = "Temps total" if s1.respond_to?(:force_encoding) s1.force_encoding('ISO-8859-1') s2.force_encoding('ISO-8859-1') diff -r 0a574315af3e -r 4f746d8966dd test/functional/timelog_controller_test.rb --- a/test/functional/timelog_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/timelog_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,25 +17,17 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'timelog_controller' - -# Re-raise errors caught by the controller. -class TimelogController; def rescue_action(e) raise e end; end class TimelogControllerTest < ActionController::TestCase fixtures :projects, :enabled_modules, :roles, :members, :member_roles, :issues, :time_entries, :users, :trackers, :enumerations, :issue_statuses, - :custom_fields, :custom_values + :custom_fields, :custom_values, + :projects_trackers, :custom_fields_trackers, + :custom_fields_projects include Redmine::I18n - def setup - @controller = TimelogController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - end - def test_new_with_project_id @request.session[:user_id] = 3 get :new, :project_id => 1 @@ -303,13 +295,20 @@ assert_response :success assert_template 'bulk_edit' - # System wide custom field - assert_tag :select, :attributes => {:name => 'time_entry[custom_field_values][10]'} + assert_select 'ul#bulk-selection' do + assert_select 'li', 2 + assert_select 'li a', :text => '03/23/2007 - eCookbook: 4.25 hours' + end - # Activities - assert_select 'select[name=?]', 'time_entry[activity_id]' do - assert_select 'option[value=]', :text => '(No change)' - assert_select 'option[value=9]', :text => 'Design' + assert_select 'form#bulk_edit_form[action=?]', '/time_entries/bulk_update' do + # System wide custom field + assert_select 'select[name=?]', 'time_entry[custom_field_values][10]' + + # Activities + assert_select 'select[name=?]', 'time_entry[activity_id]' do + assert_select 'option[value=]', :text => '(No change)' + assert_select 'option[value=9]', :text => 'Design' + end end end @@ -440,14 +439,26 @@ assert_equal [1, 3], assigns(:entries).collect(&:project_id).uniq.sort assert_not_nil assigns(:total_hours) assert_equal "162.90", "%.2f" % assigns(:total_hours) - # display all time by default - assert_nil assigns(:from) - assert_nil assigns(:to) assert_tag :form, :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'} end def test_index_at_project_level_with_date_range + get :index, :project_id => 'ecookbook', + :f => ['spent_on'], + :op => {'spent_on' => '><'}, + :v => {'spent_on' => ['2007-03-20', '2007-04-30']} + assert_response :success + assert_template 'index' + assert_not_nil assigns(:entries) + assert_equal 3, assigns(:entries).size + assert_not_nil assigns(:total_hours) + assert_equal "12.90", "%.2f" % assigns(:total_hours) + assert_tag :form, + :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'} + end + + def test_index_at_project_level_with_date_range_using_from_and_to_params get :index, :project_id => 'ecookbook', :from => '2007-03-20', :to => '2007-04-30' assert_response :success assert_template 'index' @@ -455,116 +466,23 @@ assert_equal 3, assigns(:entries).size assert_not_nil assigns(:total_hours) assert_equal "12.90", "%.2f" % assigns(:total_hours) - assert_equal '2007-03-20'.to_date, assigns(:from) - assert_equal '2007-04-30'.to_date, assigns(:to) assert_tag :form, :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'} end def test_index_at_project_level_with_period - get :index, :project_id => 'ecookbook', :period => '7_days' + get :index, :project_id => 'ecookbook', + :f => ['spent_on'], + :op => {'spent_on' => '>t-'}, + :v => {'spent_on' => ['7']} assert_response :success assert_template 'index' assert_not_nil assigns(:entries) assert_not_nil assigns(:total_hours) - assert_equal Date.today - 7, assigns(:from) - assert_equal Date.today, assigns(:to) assert_tag :form, :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'} end - def test_index_one_day - get :index, :project_id => 'ecookbook', :from => "2007-03-23", :to => "2007-03-23" - assert_response :success - assert_template 'index' - assert_not_nil assigns(:total_hours) - assert_equal "4.25", "%.2f" % assigns(:total_hours) - assert_tag :form, - :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'} - end - - def test_index_from_a_date - get :index, :project_id => 'ecookbook', :from => "2007-03-23", :to => "" - assert_equal '2007-03-23'.to_date, assigns(:from) - assert_nil assigns(:to) - end - - def test_index_to_a_date - get :index, :project_id => 'ecookbook', :from => "", :to => "2007-03-23" - assert_nil assigns(:from) - assert_equal '2007-03-23'.to_date, assigns(:to) - end - - def test_index_today - Date.stubs(:today).returns('2011-12-15'.to_date) - get :index, :period => 'today' - assert_equal '2011-12-15'.to_date, assigns(:from) - assert_equal '2011-12-15'.to_date, assigns(:to) - end - - def test_index_yesterday - Date.stubs(:today).returns('2011-12-15'.to_date) - get :index, :period => 'yesterday' - assert_equal '2011-12-14'.to_date, assigns(:from) - assert_equal '2011-12-14'.to_date, assigns(:to) - end - - def test_index_current_week - Date.stubs(:today).returns('2011-12-15'.to_date) - get :index, :period => 'current_week' - assert_equal '2011-12-12'.to_date, assigns(:from) - assert_equal '2011-12-18'.to_date, assigns(:to) - end - - def test_index_last_week - Date.stubs(:today).returns('2011-12-15'.to_date) - get :index, :period => 'last_week' - assert_equal '2011-12-05'.to_date, assigns(:from) - assert_equal '2011-12-11'.to_date, assigns(:to) - end - - def test_index_last_2_week - Date.stubs(:today).returns('2011-12-15'.to_date) - get :index, :period => 'last_2_weeks' - assert_equal '2011-11-28'.to_date, assigns(:from) - assert_equal '2011-12-11'.to_date, assigns(:to) - end - - def test_index_7_days - Date.stubs(:today).returns('2011-12-15'.to_date) - get :index, :period => '7_days' - assert_equal '2011-12-08'.to_date, assigns(:from) - assert_equal '2011-12-15'.to_date, assigns(:to) - end - - def test_index_current_month - Date.stubs(:today).returns('2011-12-15'.to_date) - get :index, :period => 'current_month' - assert_equal '2011-12-01'.to_date, assigns(:from) - assert_equal '2011-12-31'.to_date, assigns(:to) - end - - def test_index_last_month - Date.stubs(:today).returns('2011-12-15'.to_date) - get :index, :period => 'last_month' - assert_equal '2011-11-01'.to_date, assigns(:from) - assert_equal '2011-11-30'.to_date, assigns(:to) - end - - def test_index_30_days - Date.stubs(:today).returns('2011-12-15'.to_date) - get :index, :period => '30_days' - assert_equal '2011-11-15'.to_date, assigns(:from) - assert_equal '2011-12-15'.to_date, assigns(:to) - end - - def test_index_current_year - Date.stubs(:today).returns('2011-12-15'.to_date) - get :index, :period => 'current_year' - assert_equal '2011-01-01'.to_date, assigns(:from) - assert_equal '2011-12-31'.to_date, assigns(:to) - end - def test_index_at_issue_level get :index, :issue_id => 1 assert_response :success @@ -587,15 +505,41 @@ t2 = TimeEntry.create!(:user => User.find(1), :project => Project.find(1), :hours => 1, :spent_on => '2012-06-16', :created_on => '2012-06-16 20:05:00', :activity_id => 10) t3 = TimeEntry.create!(:user => User.find(1), :project => Project.find(1), :hours => 1, :spent_on => '2012-06-15', :created_on => '2012-06-16 20:10:00', :activity_id => 10) - get :index, :project_id => 1, :from => '2012-06-15', :to => '2012-06-16' + get :index, :project_id => 1, + :f => ['spent_on'], + :op => {'spent_on' => '><'}, + :v => {'spent_on' => ['2012-06-15', '2012-06-16']} assert_response :success assert_equal [t2, t1, t3], assigns(:entries) - get :index, :project_id => 1, :from => '2012-06-15', :to => '2012-06-16', :sort => 'spent_on' + get :index, :project_id => 1, + :f => ['spent_on'], + :op => {'spent_on' => '><'}, + :v => {'spent_on' => ['2012-06-15', '2012-06-16']}, + :sort => 'spent_on' assert_response :success assert_equal [t3, t1, t2], assigns(:entries) end + def test_index_with_filter_on_issue_custom_field + issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {2 => 'filter_on_issue_custom_field'}) + entry = TimeEntry.generate!(:issue => issue, :hours => 2.5) + + get :index, :f => ['issue.cf_2'], :op => {'issue.cf_2' => '='}, :v => {'issue.cf_2' => ['filter_on_issue_custom_field']} + assert_response :success + assert_equal [entry], assigns(:entries) + end + + def test_index_with_issue_custom_field_column + issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {2 => 'filter_on_issue_custom_field'}) + entry = TimeEntry.generate!(:issue => issue, :hours => 2.5) + + get :index, :c => %w(project spent_on issue comments hours issue.cf_2) + assert_response :success + assert_include :'issue.cf_2', assigns(:query).column_names + assert_select 'td.issue_cf_2', :text => 'filter_on_issue_custom_field' + end + def test_index_atom_feed get :index, :project_id => 1, :format => 'atom' assert_response :success @@ -604,186 +548,57 @@ assert assigns(:items).first.is_a?(TimeEntry) end - def test_index_all_projects_csv_export + def test_index_at_project_level_should_include_csv_export_dialog + get :index, :project_id => 'ecookbook', + :f => ['spent_on'], + :op => {'spent_on' => '>='}, + :v => {'spent_on' => ['2007-04-01']}, + :c => ['spent_on', 'user'] + assert_response :success + + assert_select '#csv-export-options' do + assert_select 'form[action=?][method=get]', '/projects/ecookbook/time_entries.csv' do + # filter + assert_select 'input[name=?][value=?]', 'f[]', 'spent_on' + assert_select 'input[name=?][value=?]', 'op[spent_on]', '>=' + assert_select 'input[name=?][value=?]', 'v[spent_on][]', '2007-04-01' + # columns + assert_select 'input[name=?][value=?]', 'c[]', 'spent_on' + assert_select 'input[name=?][value=?]', 'c[]', 'user' + assert_select 'input[name=?]', 'c[]', 2 + end + end + end + + def test_index_cross_project_should_include_csv_export_dialog + get :index + assert_response :success + + assert_select '#csv-export-options' do + assert_select 'form[action=?][method=get]', '/time_entries.csv' + end + end + + def test_index_at_issue_level_should_include_csv_export_dialog + get :index, :project_id => 'ecookbook', :issue_id => 3 + assert_response :success + + assert_select '#csv-export-options' do + assert_select 'form[action=?][method=get]', '/projects/ecookbook/issues/3/time_entries.csv' + end + end + + def test_index_csv_all_projects Setting.date_format = '%m/%d/%Y' get :index, :format => 'csv' assert_response :success - assert_equal 'text/csv; header=present', @response.content_type - assert @response.body.include?("Date,User,Activity,Project,Issue,Tracker,Subject,Hours,Comment,Overtime\n") - assert @response.body.include?("\n04/21/2007,redMine Admin,Design,eCookbook,3,Bug,Error 281 when updating a recipe,1.0,\"\",\"\"\n") + assert_equal 'text/csv; header=present', response.content_type end - def test_index_csv_export + def test_index_csv Setting.date_format = '%m/%d/%Y' get :index, :project_id => 1, :format => 'csv' assert_response :success - assert_equal 'text/csv; header=present', @response.content_type - assert @response.body.include?("Date,User,Activity,Project,Issue,Tracker,Subject,Hours,Comment,Overtime\n") - assert @response.body.include?("\n04/21/2007,redMine Admin,Design,eCookbook,3,Bug,Error 281 when updating a recipe,1.0,\"\",\"\"\n") - end - - def test_index_csv_export_with_multi_custom_field - field = TimeEntryCustomField.create!(:name => 'Test', :field_format => 'list', - :multiple => true, :possible_values => ['value1', 'value2']) - entry = TimeEntry.find(1) - entry.custom_field_values = {field.id => ['value1', 'value2']} - entry.save! - - get :index, :project_id => 1, :format => 'csv' - assert_response :success - assert_include '"value1, value2"', @response.body - end - - def test_csv_big_5 - user = User.find_by_id(3) - user.language = "zh-TW" - assert user.save - str_utf8 = "\xe4\xb8\x80\xe6\x9c\x88" - str_big5 = "\xa4@\xa4\xeb" - if str_utf8.respond_to?(:force_encoding) - str_utf8.force_encoding('UTF-8') - str_big5.force_encoding('Big5') - end - @request.session[:user_id] = 3 - post :create, :project_id => 1, - :time_entry => {:comments => str_utf8, - # Not the default activity - :activity_id => '11', - :issue_id => '', - :spent_on => '2011-11-10', - :hours => '7.3'} - assert_redirected_to :action => 'index', :project_id => 'ecookbook' - - t = TimeEntry.find_by_comments(str_utf8) - assert_not_nil t - assert_equal 11, t.activity_id - assert_equal 7.3, t.hours - assert_equal 3, t.user_id - - get :index, :project_id => 1, :format => 'csv', - :from => '2011-11-10', :to => '2011-11-10' - assert_response :success - assert_equal 'text/csv; header=present', @response.content_type - ar = @response.body.chomp.split("\n") - s1 = "\xa4\xe9\xb4\xc1" - if str_utf8.respond_to?(:force_encoding) - s1.force_encoding('Big5') - end - assert ar[0].include?(s1) - assert ar[1].include?(str_big5) - end - - def test_csv_cannot_convert_should_be_replaced_big_5 - user = User.find_by_id(3) - user.language = "zh-TW" - assert user.save - str_utf8 = "\xe4\xbb\xa5\xe5\x86\x85" - if str_utf8.respond_to?(:force_encoding) - str_utf8.force_encoding('UTF-8') - end - @request.session[:user_id] = 3 - post :create, :project_id => 1, - :time_entry => {:comments => str_utf8, - # Not the default activity - :activity_id => '11', - :issue_id => '', - :spent_on => '2011-11-10', - :hours => '7.3'} - assert_redirected_to :action => 'index', :project_id => 'ecookbook' - - t = TimeEntry.find_by_comments(str_utf8) - assert_not_nil t - assert_equal 11, t.activity_id - assert_equal 7.3, t.hours - assert_equal 3, t.user_id - - get :index, :project_id => 1, :format => 'csv', - :from => '2011-11-10', :to => '2011-11-10' - assert_response :success - assert_equal 'text/csv; header=present', @response.content_type - ar = @response.body.chomp.split("\n") - s1 = "\xa4\xe9\xb4\xc1" - if str_utf8.respond_to?(:force_encoding) - s1.force_encoding('Big5') - end - assert ar[0].include?(s1) - s2 = ar[1].split(",")[8] - if s2.respond_to?(:force_encoding) - s3 = "\xa5H?" - s3.force_encoding('Big5') - assert_equal s3, s2 - elsif RUBY_PLATFORM == 'java' - assert_equal "??", s2 - else - assert_equal "\xa5H???", s2 - end - end - - def test_csv_tw - with_settings :default_language => "zh-TW" do - str1 = "test_csv_tw" - user = User.find_by_id(3) - te1 = TimeEntry.create(:spent_on => '2011-11-10', - :hours => 999.9, - :project => Project.find(1), - :user => user, - :activity => TimeEntryActivity.find_by_name('Design'), - :comments => str1) - te2 = TimeEntry.find_by_comments(str1) - assert_not_nil te2 - assert_equal 999.9, te2.hours - assert_equal 3, te2.user_id - - get :index, :project_id => 1, :format => 'csv', - :from => '2011-11-10', :to => '2011-11-10' - assert_response :success - assert_equal 'text/csv; header=present', @response.content_type - - ar = @response.body.chomp.split("\n") - s2 = ar[1].split(",")[7] - assert_equal '999.9', s2 - - str_tw = "Traditional Chinese (\xe7\xb9\x81\xe9\xab\x94\xe4\xb8\xad\xe6\x96\x87)" - if str_tw.respond_to?(:force_encoding) - str_tw.force_encoding('UTF-8') - end - assert_equal str_tw, l(:general_lang_name) - assert_equal ',', l(:general_csv_separator) - assert_equal '.', l(:general_csv_decimal_separator) - end - end - - def test_csv_fr - with_settings :default_language => "fr" do - str1 = "test_csv_fr" - user = User.find_by_id(3) - te1 = TimeEntry.create(:spent_on => '2011-11-10', - :hours => 999.9, - :project => Project.find(1), - :user => user, - :activity => TimeEntryActivity.find_by_name('Design'), - :comments => str1) - te2 = TimeEntry.find_by_comments(str1) - assert_not_nil te2 - assert_equal 999.9, te2.hours - assert_equal 3, te2.user_id - - get :index, :project_id => 1, :format => 'csv', - :from => '2011-11-10', :to => '2011-11-10' - assert_response :success - assert_equal 'text/csv; header=present', @response.content_type - - ar = @response.body.chomp.split("\n") - s2 = ar[1].split(";")[7] - assert_equal '999,9', s2 - - str_fr = "Fran\xc3\xa7ais" - if str_fr.respond_to?(:force_encoding) - str_fr.force_encoding('UTF-8') - end - assert_equal str_fr, l(:general_lang_name) - assert_equal ';', l(:general_csv_separator) - assert_equal ',', l(:general_csv_decimal_separator) - end + assert_equal 'text/csv; header=present', response.content_type end end diff -r 0a574315af3e -r 4f746d8966dd test/functional/trackers_controller_test.rb --- a/test/functional/trackers_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/trackers_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,18 +16,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'trackers_controller' - -# Re-raise errors caught by the controller. -class TrackersController; def rescue_action(e) raise e end; end class TrackersControllerTest < ActionController::TestCase fixtures :trackers, :projects, :projects_trackers, :users, :issues, :custom_fields def setup - @controller = TrackersController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil @request.session[:user_id] = 1 # admin end diff -r 0a574315af3e -r 4f746d8966dd test/functional/users_controller_test.rb --- a/test/functional/users_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/users_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,10 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'users_controller' - -# Re-raise errors caught by the controller. -class UsersController; def rescue_action(e) raise e end; end class UsersControllerTest < ActionController::TestCase include Redmine::I18n @@ -29,9 +25,6 @@ :auth_sources def setup - @controller = UsersController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil @request.session[:user_id] = 1 # admin end diff -r 0a574315af3e -r 4f746d8966dd test/functional/versions_controller_test.rb --- a/test/functional/versions_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/versions_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,18 +16,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'versions_controller' - -# Re-raise errors caught by the controller. -class VersionsController; def rescue_action(e) raise e end; end class VersionsControllerTest < ActionController::TestCase fixtures :projects, :versions, :issues, :users, :roles, :members, :member_roles, :enabled_modules, :issue_statuses, :issue_categories def setup - @controller = VersionsController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil end diff -r 0a574315af3e -r 4f746d8966dd test/functional/watchers_controller_test.rb --- a/test/functional/watchers_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/watchers_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,23 +16,16 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'watchers_controller' - -# Re-raise errors caught by the controller. -class WatchersController; def rescue_action(e) raise e end; end class WatchersControllerTest < ActionController::TestCase fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules, :issues, :trackers, :projects_trackers, :issue_statuses, :enumerations, :watchers def setup - @controller = WatchersController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil end - def test_watch + def test_watch_a_single_object @request.session[:user_id] = 3 assert_difference('Watcher.count') do xhr :post, :watch, :object_type => 'issue', :object_id => '1' @@ -42,6 +35,27 @@ assert Issue.find(1).watched_by?(User.find(3)) end + def test_watch_a_collection_with_a_single_object + @request.session[:user_id] = 3 + assert_difference('Watcher.count') do + xhr :post, :watch, :object_type => 'issue', :object_id => ['1'] + assert_response :success + assert_include '$(".issue-1-watcher")', response.body + end + assert Issue.find(1).watched_by?(User.find(3)) + end + + def test_watch_a_collection_with_multiple_objects + @request.session[:user_id] = 3 + assert_difference('Watcher.count', 2) do + xhr :post, :watch, :object_type => 'issue', :object_id => ['1', '3'] + assert_response :success + assert_include '$(".issue-bulk-watcher")', response.body + end + assert Issue.find(1).watched_by?(User.find(3)) + assert Issue.find(3).watched_by?(User.find(3)) + end + def test_watch_should_be_denied_without_permission Role.find(2).remove_permission! :view_issues @request.session[:user_id] = 3 @@ -70,13 +84,27 @@ def test_unwatch @request.session[:user_id] = 3 assert_difference('Watcher.count', -1) do - xhr :post, :unwatch, :object_type => 'issue', :object_id => '2' + xhr :delete, :unwatch, :object_type => 'issue', :object_id => '2' assert_response :success assert_include '$(".issue-2-watcher")', response.body end assert !Issue.find(1).watched_by?(User.find(3)) end + def test_unwatch_a_collection_with_multiple_objects + @request.session[:user_id] = 3 + Watcher.create!(:user_id => 3, :watchable => Issue.find(1)) + Watcher.create!(:user_id => 3, :watchable => Issue.find(3)) + + assert_difference('Watcher.count', -2) do + xhr :delete, :unwatch, :object_type => 'issue', :object_id => ['1', '3'] + assert_response :success + assert_include '$(".issue-bulk-watcher")', response.body + end + assert !Issue.find(1).watched_by?(User.find(3)) + assert !Issue.find(3).watched_by?(User.find(3)) + end + def test_new @request.session[:user_id] = 2 xhr :get, :new, :object_type => 'issue', :object_id => '2' @@ -84,7 +112,7 @@ assert_match /ajax-modal/, response.body end - def test_new_for_new_record_with_id + def test_new_for_new_record_with_project_id @request.session[:user_id] = 2 xhr :get, :new, :project_id => 1 assert_response :success @@ -92,7 +120,7 @@ assert_match /ajax-modal/, response.body end - def test_new_for_new_record_with_identifier + def test_new_for_new_record_with_project_identifier @request.session[:user_id] = 2 xhr :get, :new, :project_id => 'ecookbook' assert_response :success @@ -124,7 +152,8 @@ end def test_autocomplete_on_watchable_creation - xhr :get, :autocomplete_for_user, :q => 'mi' + @request.session[:user_id] = 2 + xhr :get, :autocomplete_for_user, :q => 'mi', :project_id => 'ecookbook' assert_response :success assert_select 'input', :count => 4 assert_select 'input[name=?][value=1]', 'watcher[user_ids][]' @@ -134,7 +163,8 @@ end def test_autocomplete_on_watchable_update - xhr :get, :autocomplete_for_user, :q => 'mi', :object_id => '2' , :object_type => 'issue' + @request.session[:user_id] = 2 + xhr :get, :autocomplete_for_user, :q => 'mi', :object_id => '2' , :object_type => 'issue', :project_id => 'ecookbook' assert_response :success assert_select 'input', :count => 3 assert_select 'input[name=?][value=2]', 'watcher[user_ids][]' @@ -146,7 +176,7 @@ def test_append @request.session[:user_id] = 2 assert_no_difference 'Watcher.count' do - xhr :post, :append, :watcher => {:user_ids => ['4', '7']} + xhr :post, :append, :watcher => {:user_ids => ['4', '7']}, :project_id => 'ecookbook' assert_response :success assert_include 'watchers_inputs', response.body assert_include 'issue[watcher_user_ids][]', response.body @@ -156,7 +186,7 @@ def test_remove_watcher @request.session[:user_id] = 2 assert_difference('Watcher.count', -1) do - xhr :post, :destroy, :object_type => 'issue', :object_id => '2', :user_id => '3' + xhr :delete, :destroy, :object_type => 'issue', :object_id => '2', :user_id => '3' assert_response :success assert_match /watchers/, response.body end diff -r 0a574315af3e -r 4f746d8966dd test/functional/welcome_controller_test.rb --- a/test/functional/welcome_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/welcome_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,18 +16,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'welcome_controller' - -# Re-raise errors caught by the controller. -class WelcomeController; def rescue_action(e) raise e end; end class WelcomeControllerTest < ActionController::TestCase fixtures :projects, :news, :users, :members def setup - @controller = WelcomeController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil end @@ -37,7 +30,7 @@ assert_template 'index' assert_not_nil assigns(:news) assert_not_nil assigns(:projects) - assert !assigns(:projects).include?(Project.find(:first, :conditions => {:is_public => false})) + assert !assigns(:projects).include?(Project.where(:is_public => false).first) end def test_browser_language @@ -92,6 +85,13 @@ :content => %r{warnLeavingUnsaved} end + def test_logout_link_should_post + @request.session[:user_id] = 2 + + get :index + assert_select 'a[href=/logout][data-method=post]', :text => 'Sign out' + end + def test_call_hook_mixed_in assert @controller.respond_to?(:call_hook) end diff -r 0a574315af3e -r 4f746d8966dd test/functional/wiki_controller_test.rb --- a/test/functional/wiki_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/wiki_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,10 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'wiki_controller' - -# Re-raise errors caught by the controller. -class WikiController; def rescue_action(e) raise e end; end class WikiControllerTest < ActionController::TestCase fixtures :projects, :users, :roles, :members, :member_roles, @@ -27,9 +23,6 @@ :wiki_content_versions, :attachments def setup - @controller = WikiController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil end @@ -60,7 +53,7 @@ assert_tag :tag => 'h1', :content => /Another page/ # Included page with an inline image assert_tag :tag => 'p', :content => /This is an inline image/ - assert_tag :tag => 'img', :attributes => { :src => '/attachments/download/3', + assert_tag :tag => 'img', :attributes => { :src => '/attachments/download/3/logo.gif', :alt => 'This is a logo' } end @@ -592,7 +585,7 @@ # Line 5 assert_tag :tag => 'tr', :child => { :tag => 'th', :attributes => {:class => 'line-num'}, :content => '5', :sibling => { - :tag => 'td', :attributes => {:class => 'author'}, :content => /redMine Admin/, :sibling => { + :tag => 'td', :attributes => {:class => 'author'}, :content => /Redmine Admin/, :sibling => { :tag => 'td', :content => /Some updated \[\[documentation\]\] here/ } } diff -r 0a574315af3e -r 4f746d8966dd test/functional/wikis_controller_test.rb --- a/test/functional/wikis_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/wikis_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,18 +16,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'wikis_controller' - -# Re-raise errors caught by the controller. -class WikisController; def rescue_action(e) raise e end; end class WikisControllerTest < ActionController::TestCase fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules, :wikis def setup - @controller = WikisController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil end diff -r 0a574315af3e -r 4f746d8966dd test/functional/workflows_controller_test.rb --- a/test/functional/workflows_controller_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/functional/workflows_controller_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,18 +16,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../test_helper', __FILE__) -require 'workflows_controller' - -# Re-raise errors caught by the controller. -class WorkflowsController; def rescue_action(e) raise e end; end class WorkflowsControllerTest < ActionController::TestCase fixtures :roles, :trackers, :workflows, :users, :issue_statuses def setup - @controller = WorkflowsController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new User.current = nil @request.session[:user_id] = 1 # admin end @@ -102,9 +95,9 @@ } assert_redirected_to '/workflows/edit?role_id=2&tracker_id=1' - assert_equal 3, WorkflowTransition.count(:conditions => {:tracker_id => 1, :role_id => 2}) - assert_not_nil WorkflowTransition.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 2}) - assert_nil WorkflowTransition.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 5, :new_status_id => 4}) + assert_equal 3, WorkflowTransition.where(:tracker_id => 1, :role_id => 2).count + assert_not_nil WorkflowTransition.where(:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 2).first + assert_nil WorkflowTransition.where(:role_id => 2, :tracker_id => 1, :old_status_id => 5, :new_status_id => 4).first end def test_post_edit_with_additional_transitions @@ -115,18 +108,18 @@ } assert_redirected_to '/workflows/edit?role_id=2&tracker_id=1' - assert_equal 4, WorkflowTransition.count(:conditions => {:tracker_id => 1, :role_id => 2}) + assert_equal 4, WorkflowTransition.where(:tracker_id => 1, :role_id => 2).count - w = WorkflowTransition.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 4, :new_status_id => 5}) + w = WorkflowTransition.where(:role_id => 2, :tracker_id => 1, :old_status_id => 4, :new_status_id => 5).first assert ! w.author assert ! w.assignee - w = WorkflowTransition.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 1}) + w = WorkflowTransition.where(:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 1).first assert w.author assert ! w.assignee - w = WorkflowTransition.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 2}) + w = WorkflowTransition.where(:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 2).first assert ! w.author assert w.assignee - w = WorkflowTransition.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 4}) + w = WorkflowTransition.where(:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 4).first assert w.author assert w.assignee end @@ -304,9 +297,32 @@ assert_equal source_t3, status_transitions(:tracker_id => 3, :role_id => 3) end + def test_post_copy_with_incomplete_source_specification_should_fail + assert_no_difference 'WorkflowRule.count' do + post :copy, + :source_tracker_id => '', :source_role_id => '2', + :target_tracker_ids => ['2', '3'], :target_role_ids => ['1', '3'] + assert_response 200 + assert_select 'div.flash.error', :text => 'Please select a source tracker or role' + end + end + + def test_post_copy_with_incomplete_target_specification_should_fail + assert_no_difference 'WorkflowRule.count' do + post :copy, + :source_tracker_id => '1', :source_role_id => '2', + :target_tracker_ids => ['2', '3'] + assert_response 200 + assert_select 'div.flash.error', :text => 'Please select target tracker(s) and role(s)' + end + end + # Returns an array of status transitions that can be compared def status_transitions(conditions) - WorkflowTransition.find(:all, :conditions => conditions, - :order => 'tracker_id, role_id, old_status_id, new_status_id').collect {|w| [w.old_status, w.new_status_id]} + WorkflowTransition. + where(conditions). + order('tracker_id, role_id, old_status_id, new_status_id'). + all. + collect {|w| [w.old_status, w.new_status_id]} end end diff -r 0a574315af3e -r 4f746d8966dd test/integration/account_test.rb --- a/test/integration/account_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/account_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -45,7 +45,7 @@ # User logs in with 'autologin' checked post '/login', :username => user.login, :password => 'admin', :autologin => 1 assert_redirected_to '/my/page' - token = Token.find :first + token = Token.first assert_not_nil token assert_equal user, token.user assert_equal 'autologin', token.action @@ -68,6 +68,33 @@ assert_not_nil user.reload.last_login_on end + def test_autologin_should_use_autologin_cookie_name + Token.delete_all + Redmine::Configuration.stubs(:[]).with('autologin_cookie_name').returns('custom_autologin') + Redmine::Configuration.stubs(:[]).with('autologin_cookie_path').returns('/') + Redmine::Configuration.stubs(:[]).with('autologin_cookie_secure').returns(false) + + with_settings :autologin => '7' do + assert_difference 'Token.count' do + post '/login', :username => 'admin', :password => 'admin', :autologin => 1 + end + assert_response 302 + assert cookies['custom_autologin'].present? + token = cookies['custom_autologin'] + + # Session is cleared + reset! + cookies['custom_autologin'] = token + get '/my/page' + assert_response :success + + assert_difference 'Token.count', -1 do + post '/logout' + end + assert cookies['custom_autologin'].blank? + end + end + def test_lost_password Token.delete_all @@ -79,7 +106,7 @@ post "account/lost_password", :mail => 'jSmith@somenet.foo' assert_redirected_to "/login" - token = Token.find(:first) + token = Token.first assert_equal 'recovery', token.action assert_equal 'jsmith@somenet.foo', token.user.mail assert !token.expired? @@ -137,7 +164,7 @@ assert_redirected_to '/login' assert !User.find_by_login('newuser').active? - token = Token.find(:first) + token = Token.first assert_equal 'register', token.action assert_equal 'newuser@foo.bar', token.user.mail assert !token.expired? diff -r 0a574315af3e -r 4f746d8966dd test/integration/admin_test.rb --- a/test/integration/admin_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/admin_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,8 +24,7 @@ :roles, :member_roles, :members, - :enabled_modules, - :workflows + :enabled_modules def test_add_user log_user("admin", "admin") diff -r 0a574315af3e -r 4f746d8966dd test/integration/api_test/attachments_test.rb --- a/test/integration/api_test/attachments_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/api_test/attachments_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,7 @@ require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::AttachmentsTest < ActionController::IntegrationTest +class Redmine::ApiTest::AttachmentsTest < Redmine::ApiTest::Base fixtures :projects, :trackers, :issue_statuses, :issues, :enumerations, :users, :issue_categories, :projects_trackers, @@ -25,7 +25,6 @@ :member_roles, :members, :enabled_modules, - :workflows, :attachments def setup diff -r 0a574315af3e -r 4f746d8966dd test/integration/api_test/authentication_test.rb --- a/test/integration/api_test/authentication_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/api_test/authentication_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,7 @@ require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::AuthenticationTest < ActionController::IntegrationTest +class Redmine::ApiTest::AuthenticationTest < Redmine::ApiTest::Base fixtures :users def setup diff -r 0a574315af3e -r 4f746d8966dd test/integration/api_test/disabled_rest_api_test.rb --- a/test/integration/api_test/disabled_rest_api_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/api_test/disabled_rest_api_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,14 +1,30 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) -class ApiTest::DisabledRestApiTest < ActionController::IntegrationTest +class Redmine::ApiTest::DisabledRestApiTest < Redmine::ApiTest::Base fixtures :projects, :trackers, :issue_statuses, :issues, :enumerations, :users, :issue_categories, :projects_trackers, :roles, :member_roles, :members, - :enabled_modules, - :workflows + :enabled_modules def setup Setting.rest_api_enabled = '0' diff -r 0a574315af3e -r 4f746d8966dd test/integration/api_test/enumerations_test.rb --- a/test/integration/api_test/enumerations_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/api_test/enumerations_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,7 @@ require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::EnumerationsTest < ActionController::IntegrationTest +class Redmine::ApiTest::EnumerationsTest < Redmine::ApiTest::Base fixtures :enumerations def setup diff -r 0a574315af3e -r 4f746d8966dd test/integration/api_test/groups_test.rb --- a/test/integration/api_test/groups_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/api_test/groups_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,7 @@ require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::GroupsTest < ActionController::IntegrationTest +class Redmine::ApiTest::GroupsTest < Redmine::ApiTest::Base fixtures :users, :groups_users def setup diff -r 0a574315af3e -r 4f746d8966dd test/integration/api_test/http_basic_login_test.rb --- a/test/integration/api_test/http_basic_login_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/api_test/http_basic_login_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,14 +1,30 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) -class ApiTest::HttpBasicLoginTest < ActionController::IntegrationTest +class Redmine::ApiTest::HttpBasicLoginTest < Redmine::ApiTest::Base fixtures :projects, :trackers, :issue_statuses, :issues, :enumerations, :users, :issue_categories, :projects_trackers, :roles, :member_roles, :members, - :enabled_modules, - :workflows + :enabled_modules def setup Setting.rest_api_enabled = '1' diff -r 0a574315af3e -r 4f746d8966dd test/integration/api_test/http_basic_login_with_api_token_test.rb --- a/test/integration/api_test/http_basic_login_with_api_token_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/api_test/http_basic_login_with_api_token_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,14 +1,30 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) -class ApiTest::HttpBasicLoginWithApiTokenTest < ActionController::IntegrationTest +class Redmine::ApiTest::HttpBasicLoginWithApiTokenTest < Redmine::ApiTest::Base fixtures :projects, :trackers, :issue_statuses, :issues, :enumerations, :users, :issue_categories, :projects_trackers, :roles, :member_roles, :members, - :enabled_modules, - :workflows + :enabled_modules def setup Setting.rest_api_enabled = '1' diff -r 0a574315af3e -r 4f746d8966dd test/integration/api_test/issue_categories_test.rb --- a/test/integration/api_test/issue_categories_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/api_test/issue_categories_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,7 @@ require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::IssueCategoriesTest < ActionController::IntegrationTest +class Redmine::ApiTest::IssueCategoriesTest < Redmine::ApiTest::Base fixtures :projects, :users, :issue_categories, :issues, :roles, :member_roles, diff -r 0a574315af3e -r 4f746d8966dd test/integration/api_test/issue_relations_test.rb --- a/test/integration/api_test/issue_relations_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/api_test/issue_relations_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,7 @@ require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::IssueRelationsTest < ActionController::IntegrationTest +class Redmine::ApiTest::IssueRelationsTest < Redmine::ApiTest::Base fixtures :projects, :trackers, :issue_statuses, :issues, :enumerations, :users, :issue_categories, :projects_trackers, @@ -25,7 +25,6 @@ :member_roles, :members, :enabled_modules, - :workflows, :issue_relations def setup diff -r 0a574315af3e -r 4f746d8966dd test/integration/api_test/issue_statuses_test.rb --- a/test/integration/api_test/issue_statuses_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/api_test/issue_statuses_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,7 @@ require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::IssueStatusesTest < ActionController::IntegrationTest +class Redmine::ApiTest::IssueStatusesTest < Redmine::ApiTest::Base fixtures :issue_statuses def setup diff -r 0a574315af3e -r 4f746d8966dd test/integration/api_test/issues_test.rb --- a/test/integration/api_test/issues_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/api_test/issues_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,7 @@ require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::IssuesTest < ActionController::IntegrationTest +class Redmine::ApiTest::IssuesTest < Redmine::ApiTest::Base fixtures :projects, :users, :roles, @@ -453,6 +453,21 @@ end end + test "GET /issues/:id.xml?include=watchers should include watchers" do + Watcher.create!(:user_id => 3, :watchable => Issue.find(1)) + + get '/issues/1.xml?include=watchers', {}, credentials('jsmith') + + assert_response :ok + assert_equal 'application/xml', response.content_type + assert_select 'issue' do + assert_select 'watchers', Issue.find(1).watchers.count + assert_select 'watchers' do + assert_select 'user[id=3]' + end + end + end + context "POST /issues.xml" do should_allow_api_authentication( :post, @@ -478,6 +493,18 @@ end end + test "POST /issues.xml with watcher_user_ids should create issue with watchers" do + assert_difference('Issue.count') do + post '/issues.xml', + {:issue => {:project_id => 1, :subject => 'Watchers', + :tracker_id => 2, :status_id => 3, :watcher_user_ids => [3, 1]}}, credentials('jsmith') + assert_response :created + end + issue = Issue.order('id desc').first + assert_equal 2, issue.watchers.size + assert_equal [1, 3], issue.watcher_user_ids.sort + end + context "POST /issues.xml with failure" do should "have an errors tag" do assert_no_difference('Issue.count') do @@ -720,6 +747,30 @@ end end + test "POST /issues/:id/watchers.xml should add watcher" do + assert_difference 'Watcher.count' do + post '/issues/1/watchers.xml', {:user_id => 3}, credentials('jsmith') + + assert_response :ok + assert_equal '', response.body + end + watcher = Watcher.order('id desc').first + assert_equal Issue.find(1), watcher.watchable + assert_equal User.find(3), watcher.user + end + + test "DELETE /issues/:id/watchers/:user_id.xml should remove watcher" do + Watcher.create!(:user_id => 3, :watchable => Issue.find(1)) + + assert_difference 'Watcher.count', -1 do + delete '/issues/1/watchers/3.xml', {}, credentials('jsmith') + + assert_response :ok + assert_equal '', response.body + end + assert_equal false, Issue.find(1).watched_by?(User.find(3)) + end + def test_create_issue_with_uploaded_file set_tmp_attachments_directory # upload the file diff -r 0a574315af3e -r 4f746d8966dd test/integration/api_test/jsonp_test.rb --- a/test/integration/api_test/jsonp_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/api_test/jsonp_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,11 +17,23 @@ require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::JsonpTest < ActionController::IntegrationTest +class Redmine::ApiTest::JsonpTest < Redmine::ApiTest::Base fixtures :trackers + def test_should_ignore_jsonp_callback_with_jsonp_disabled + with_settings :jsonp_enabled => '0' do + get '/trackers.json?jsonp=handler' + end + + assert_response :success + assert_match %r{^\{"trackers":.+\}$}, response.body + assert_equal 'application/json; charset=utf-8', response.headers['Content-Type'] + end + def test_jsonp_should_accept_callback_param - get '/trackers.json?callback=handler' + with_settings :jsonp_enabled => '1' do + get '/trackers.json?callback=handler' + end assert_response :success assert_match %r{^handler\(\{"trackers":.+\}\)$}, response.body @@ -29,7 +41,9 @@ end def test_jsonp_should_accept_jsonp_param - get '/trackers.json?jsonp=handler' + with_settings :jsonp_enabled => '1' do + get '/trackers.json?jsonp=handler' + end assert_response :success assert_match %r{^handler\(\{"trackers":.+\}\)$}, response.body @@ -37,7 +51,9 @@ end def test_jsonp_should_strip_invalid_characters_from_callback - get '/trackers.json?callback=+-aA$1_' + with_settings :jsonp_enabled => '1' do + get '/trackers.json?callback=+-aA$1_' + end assert_response :success assert_match %r{^aA1_\(\{"trackers":.+\}\)$}, response.body @@ -45,7 +61,9 @@ end def test_jsonp_without_callback_should_return_json - get '/trackers.json?callback=' + with_settings :jsonp_enabled => '1' do + get '/trackers.json?callback=' + end assert_response :success assert_match %r{^\{"trackers":.+\}$}, response.body diff -r 0a574315af3e -r 4f746d8966dd test/integration/api_test/memberships_test.rb --- a/test/integration/api_test/memberships_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/api_test/memberships_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,7 @@ require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::MembershipsTest < ActionController::IntegrationTest +class Redmine::ApiTest::MembershipsTest < Redmine::ApiTest::Base fixtures :projects, :users, :roles, :members, :member_roles def setup diff -r 0a574315af3e -r 4f746d8966dd test/integration/api_test/news_test.rb --- a/test/integration/api_test/news_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/api_test/news_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,8 +16,8 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../../test_helper', __FILE__) -require 'pp' -class ApiTest::NewsTest < ActionController::IntegrationTest + +class Redmine::ApiTest::NewsTest < Redmine::ApiTest::Base fixtures :projects, :trackers, :issue_statuses, :issues, :enumerations, :users, :issue_categories, :projects_trackers, @@ -25,7 +25,6 @@ :member_roles, :members, :enabled_modules, - :workflows, :news def setup diff -r 0a574315af3e -r 4f746d8966dd test/integration/api_test/projects_test.rb --- a/test/integration/api_test/projects_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/api_test/projects_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,7 @@ require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::ProjectsTest < ActionController::IntegrationTest +class Redmine::ApiTest::ProjectsTest < Redmine::ApiTest::Base fixtures :projects, :versions, :users, :roles, :members, :member_roles, :issues, :journals, :journal_details, :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages, :attachments, :custom_fields, :custom_values, :time_entries, :issue_categories diff -r 0a574315af3e -r 4f746d8966dd test/integration/api_test/queries_test.rb --- a/test/integration/api_test/queries_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/api_test/queries_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,7 @@ require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::QueriesTest < ActionController::IntegrationTest +class Redmine::ApiTest::QueriesTest < Redmine::ApiTest::Base fixtures :projects, :trackers, :issue_statuses, :issues, :enumerations, :users, :issue_categories, :projects_trackers, @@ -25,7 +25,6 @@ :member_roles, :members, :enabled_modules, - :workflows, :queries def setup diff -r 0a574315af3e -r 4f746d8966dd test/integration/api_test/roles_test.rb --- a/test/integration/api_test/roles_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/api_test/roles_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,7 @@ require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::RolesTest < ActionController::IntegrationTest +class Redmine::ApiTest::RolesTest < Redmine::ApiTest::Base fixtures :roles def setup diff -r 0a574315af3e -r 4f746d8966dd test/integration/api_test/time_entries_test.rb --- a/test/integration/api_test/time_entries_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/api_test/time_entries_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,7 @@ require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::TimeEntriesTest < ActionController::IntegrationTest +class Redmine::ApiTest::TimeEntriesTest < Redmine::ApiTest::Base fixtures :projects, :trackers, :issue_statuses, :issues, :enumerations, :users, :issue_categories, :projects_trackers, @@ -25,7 +25,6 @@ :member_roles, :members, :enabled_modules, - :workflows, :time_entries def setup diff -r 0a574315af3e -r 4f746d8966dd test/integration/api_test/token_authentication_test.rb --- a/test/integration/api_test/token_authentication_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/api_test/token_authentication_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,14 +1,30 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) -class ApiTest::TokenAuthenticationTest < ActionController::IntegrationTest +class Redmine::ApiTest::TokenAuthenticationTest < Redmine::ApiTest::Base fixtures :projects, :trackers, :issue_statuses, :issues, :enumerations, :users, :issue_categories, :projects_trackers, :roles, :member_roles, :members, - :enabled_modules, - :workflows + :enabled_modules def setup Setting.rest_api_enabled = '1' diff -r 0a574315af3e -r 4f746d8966dd test/integration/api_test/trackers_test.rb --- a/test/integration/api_test/trackers_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/api_test/trackers_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,7 @@ require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::TrackersTest < ActionController::IntegrationTest +class Redmine::ApiTest::TrackersTest < Redmine::ApiTest::Base fixtures :trackers def setup diff -r 0a574315af3e -r 4f746d8966dd test/integration/api_test/users_test.rb --- a/test/integration/api_test/users_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/api_test/users_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,8 +16,8 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require File.expand_path('../../../test_helper', __FILE__) -require 'pp' -class ApiTest::UsersTest < ActionController::IntegrationTest + +class Redmine::ApiTest::UsersTest < Redmine::ApiTest::Base fixtures :users, :members, :member_roles, :roles, :projects def setup @@ -96,6 +96,30 @@ end end + test "GET /users/:id should not return login for other user" do + get '/users/3.xml', {}, credentials('jsmith') + assert_response :success + assert_no_tag 'user', :child => {:tag => 'login'} + end + + test "GET /users/:id should return login for current user" do + get '/users/2.xml', {}, credentials('jsmith') + assert_response :success + assert_tag 'user', :child => {:tag => 'login', :content => 'jsmith'} + end + + test "GET /users/:id should not return api_key for other user" do + get '/users/3.xml', {}, credentials('jsmith') + assert_response :success + assert_no_tag 'user', :child => {:tag => 'api_key'} + end + + test "GET /users/:id should return api_key for current user" do + get '/users/2.xml', {}, credentials('jsmith') + assert_response :success + assert_tag 'user', :child => {:tag => 'api_key', :content => User.find(2).api_key} + end + context "POST /users" do context "with valid parameters" do setup do diff -r 0a574315af3e -r 4f746d8966dd test/integration/api_test/versions_test.rb --- a/test/integration/api_test/versions_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/api_test/versions_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,7 @@ require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::VersionsTest < ActionController::IntegrationTest +class Redmine::ApiTest::VersionsTest < Redmine::ApiTest::Base fixtures :projects, :trackers, :issue_statuses, :issues, :enumerations, :users, :issue_categories, :projects_trackers, @@ -25,7 +25,6 @@ :member_roles, :members, :enabled_modules, - :workflows, :versions def setup @@ -83,6 +82,29 @@ assert_tag 'version', :child => {:tag => 'id', :content => version.id.to_s} end + should "create the version with custom fields" do + field = VersionCustomField.generate! + + assert_difference 'Version.count' do + post '/projects/1/versions.xml', { + :version => { + :name => 'API test', + :custom_fields => [ + {'id' => field.id.to_s, 'value' => 'Some value'} + ] + } + }, credentials('jsmith') + end + + version = Version.first(:order => 'id DESC') + assert_equal 'API test', version.name + assert_equal 'Some value', version.custom_field_value(field) + + assert_response :created + assert_equal 'application/xml', @response.content_type + assert_select 'version>custom_fields>custom_field[id=?]>value', field.id.to_s, 'Some value' + end + context "with failure" do should "return the errors" do assert_no_difference('Version.count') do diff -r 0a574315af3e -r 4f746d8966dd test/integration/api_test/wiki_pages_test.rb --- a/test/integration/api_test/wiki_pages_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/api_test/wiki_pages_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,7 @@ require File.expand_path('../../../test_helper', __FILE__) -class ApiTest::WikiPagesTest < ActionController::IntegrationTest +class Redmine::ApiTest::WikiPagesTest < Redmine::ApiTest::Base fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules, :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions, :attachments diff -r 0a574315af3e -r 4f746d8966dd test/integration/application_test.rb --- a/test/integration/application_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/application_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,8 +26,7 @@ :roles, :member_roles, :members, - :enabled_modules, - :workflows + :enabled_modules def test_set_localization Setting.default_language = 'en' diff -r 0a574315af3e -r 4f746d8966dd test/integration/attachments_test.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/integration/attachments_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,132 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) + +class AttachmentsTest < ActionController::IntegrationTest + fixtures :projects, :enabled_modules, + :users, :roles, :members, :member_roles, + :trackers, :projects_trackers, + :issue_statuses, :enumerations + + def test_upload_as_js_and_attach_to_an_issue + log_user('jsmith', 'jsmith') + + token = ajax_upload('myupload.txt', 'File content') + + assert_difference 'Issue.count' do + post '/projects/ecookbook/issues', { + :issue => {:tracker_id => 1, :subject => 'Issue with upload'}, + :attachments => {'1' => {:filename => 'myupload.txt', :description => 'My uploaded file', :token => token}} + } + assert_response 302 + end + + issue = Issue.order('id DESC').first + assert_equal 'Issue with upload', issue.subject + assert_equal 1, issue.attachments.count + + attachment = issue.attachments.first + assert_equal 'myupload.txt', attachment.filename + assert_equal 'My uploaded file', attachment.description + assert_equal 'File content'.length, attachment.filesize + end + + def test_upload_as_js_and_preview_as_inline_attachment + log_user('jsmith', 'jsmith') + + token = ajax_upload('myupload.jpg', 'JPEG content') + + post '/issues/preview/new/ecookbook', { + :issue => {:tracker_id => 1, :description => 'Inline upload: !myupload.jpg!'}, + :attachments => {'1' => {:filename => 'myupload.jpg', :description => 'My uploaded file', :token => token}} + } + assert_response :success + + attachment_path = response.body.match(%r{ {:tracker_id => 1, :subject => ''}, + :attachments => {'1' => {:filename => 'myupload.txt', :description => 'My uploaded file', :token => token}} + } + assert_response :success + end + assert_select 'input[type=hidden][name=?][value=?]', 'attachments[p0][token]', token + assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'myupload.txt' + assert_select 'input[name=?][value=?]', 'attachments[p0][description]', 'My uploaded file' + + assert_difference 'Issue.count' do + post '/projects/ecookbook/issues', { + :issue => {:tracker_id => 1, :subject => 'Issue with upload'}, + :attachments => {'p0' => {:filename => 'myupload.txt', :description => 'My uploaded file', :token => token}} + } + assert_response 302 + end + + issue = Issue.order('id DESC').first + assert_equal 'Issue with upload', issue.subject + assert_equal 1, issue.attachments.count + + attachment = issue.attachments.first + assert_equal 'myupload.txt', attachment.filename + assert_equal 'My uploaded file', attachment.description + assert_equal 'File content'.length, attachment.filesize + end + + def test_upload_as_js_and_destroy + log_user('jsmith', 'jsmith') + + token = ajax_upload('myupload.txt', 'File content') + + attachment = Attachment.order('id DESC').first + attachment_path = "/attachments/#{attachment.id}.js?attachment_id=1" + assert_include "href: '#{attachment_path}'", response.body, "Path to attachment: #{attachment_path} not found in response:\n#{response.body}" + + assert_difference 'Attachment.count', -1 do + delete attachment_path + assert_response :success + end + + assert_include "$('#attachments_1').remove();", response.body + end + + private + + def ajax_upload(filename, content, attachment_id=1) + assert_difference 'Attachment.count' do + post "/uploads.js?attachment_id=#{attachment_id}&filename=#{filename}", content, {"CONTENT_TYPE" => 'application/octet-stream'} + assert_response :success + assert_equal 'text/javascript', response.content_type + end + + token = response.body.match(/\.val\('(\d+\.[0-9a-f]+)'\)/)[1] + assert_not_nil token, "No upload token found in response:\n#{response.body}" + token + end +end diff -r 0a574315af3e -r 4f746d8966dd test/integration/issues_test.rb --- a/test/integration/issues_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/issues_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -65,15 +65,6 @@ assert_equal 1, issue.status.id end - def test_update_issue_form - log_user('jsmith', 'jsmith') - post 'projects/ecookbook/issues/new', :issue => { :tracker_id => "2"} - assert_response :success - assert_tag 'select', - :attributes => {:name => 'issue[tracker_id]'}, - :child => {:tag => 'option', :attributes => {:value => '2', :selected => 'selected'}} - end - # add then remove 2 attachments to an issue def test_issue_attachments log_user('jsmith', 'jsmith') diff -r 0a574315af3e -r 4f746d8966dd test/integration/layout_test.rb --- a/test/integration/layout_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/layout_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,8 +24,7 @@ :roles, :member_roles, :members, - :enabled_modules, - :workflows + :enabled_modules test "browsing to a missing page should render the base layout" do get "/users/100000000" @@ -86,6 +85,26 @@ get '/issues' assert_not_include "/javascripts/i18n/jquery.ui.datepicker", response.body end + + with_settings :default_language => 'zh' do + get '/issues' + assert_include "/javascripts/i18n/jquery.ui.datepicker-zh-CN.js", response.body + end + + with_settings :default_language => 'zh-TW' do + get '/issues' + assert_include "/javascripts/i18n/jquery.ui.datepicker-zh-TW.js", response.body + end + + with_settings :default_language => 'pt' do + get '/issues' + assert_include "/javascripts/i18n/jquery.ui.datepicker-pt.js", response.body + end + + with_settings :default_language => 'pt-BR' do + get '/issues' + assert_include "/javascripts/i18n/jquery.ui.datepicker-pt-BR.js", response.body + end end def test_search_field_outside_project_should_link_to_global_search diff -r 0a574315af3e -r 4f746d8966dd test/integration/lib/redmine/hook_test.rb --- a/test/integration/lib/redmine/hook_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/lib/redmine/hook_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/lib/redmine/menu_manager_test.rb --- a/test/integration/lib/redmine/menu_manager_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/lib/redmine/menu_manager_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,8 +26,7 @@ :roles, :member_roles, :members, - :enabled_modules, - :workflows + :enabled_modules def test_project_menu_with_specific_locale get 'projects/ecookbook/issues', { }, 'HTTP_ACCEPT_LANGUAGE' => 'fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3' diff -r 0a574315af3e -r 4f746d8966dd test/integration/lib/redmine/themes_test.rb --- a/test/integration/lib/redmine/themes_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/lib/redmine/themes_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/projects_test.rb --- a/test/integration/projects_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/projects_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/repositories_git_test.rb --- a/test/integration/repositories_git_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/repositories_git_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/account_test.rb --- a/test/integration/routing/account_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/account_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -25,10 +25,12 @@ { :controller => 'account', :action => 'login' } ) end - assert_routing( - { :method => 'get', :path => "/logout" }, - { :controller => 'account', :action => 'logout' } - ) + ["get", "post"].each do |method| + assert_routing( + { :method => method, :path => "/logout" }, + { :controller => 'account', :action => 'logout' } + ) + end ["get", "post"].each do |method| assert_routing( { :method => method, :path => "/account/register" }, diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/activities_test.rb --- a/test/integration/routing/activities_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/activities_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/admin_test.rb --- a/test/integration/routing/admin_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/admin_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/attachments_test.rb --- a/test/integration/routing/attachments_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/attachments_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/auth_sources_test.rb --- a/test/integration/routing/auth_sources_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/auth_sources_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -51,5 +51,9 @@ { :controller => 'auth_sources', :action => 'test_connection', :id => '1234' } ) + assert_routing( + { :method => 'get', :path => "/auth_sources/autocomplete_for_new_user" }, + { :controller => 'auth_sources', :action => 'autocomplete_for_new_user' } + ) end end diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/auto_completes_test.rb --- a/test/integration/routing/auto_completes_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/auto_completes_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/boards_test.rb --- a/test/integration/routing/boards_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/boards_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/calendars_test.rb --- a/test/integration/routing/calendars_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/calendars_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/comments_test.rb --- a/test/integration/routing/comments_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/comments_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/context_menus_test.rb --- a/test/integration/routing/context_menus_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/context_menus_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/custom_fields_test.rb --- a/test/integration/routing/custom_fields_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/custom_fields_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/documents_test.rb --- a/test/integration/routing/documents_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/documents_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/enumerations_test.rb --- a/test/integration/routing/enumerations_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/enumerations_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/files_test.rb --- a/test/integration/routing/files_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/files_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/gantts_test.rb --- a/test/integration/routing/gantts_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/gantts_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/groups_test.rb --- a/test/integration/routing/groups_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/groups_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -48,6 +48,10 @@ { :controller => 'groups', :action => 'autocomplete_for_user', :id => '1' } ) assert_routing( + { :method => 'get', :path => "/groups/1/autocomplete_for_user.js" }, + { :controller => 'groups', :action => 'autocomplete_for_user', :id => '1', :format => 'js' } + ) + assert_routing( { :method => 'get', :path => "/groups/1" }, { :controller => 'groups', :action => 'show', :id => '1' } ) diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/issue_categories_test.rb --- a/test/integration/routing/issue_categories_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/issue_categories_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/issue_relations_test.rb --- a/test/integration/routing/issue_relations_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/issue_relations_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/issue_statuses_test.rb --- a/test/integration/routing/issue_statuses_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/issue_statuses_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/issues_test.rb --- a/test/integration/routing/issues_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/issues_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -107,8 +107,8 @@ def test_issues_form_update ["post", "put"].each do |method| assert_routing( - { :method => method, :path => "/projects/23/issues/new" }, - { :controller => 'issues', :action => 'new', :project_id => '23' } + { :method => method, :path => "/projects/23/issues/update_form" }, + { :controller => 'issues', :action => 'update_form', :project_id => '23' } ) end end diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/journals_test.rb --- a/test/integration/routing/journals_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/journals_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/mail_handler_test.rb --- a/test/integration/routing/mail_handler_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/mail_handler_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/members_test.rb --- a/test/integration/routing/members_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/members_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -55,5 +55,9 @@ { :method => 'get', :path => "/projects/5234/memberships/autocomplete" }, { :controller => 'members', :action => 'autocomplete', :project_id => '5234' } ) + assert_routing( + { :method => 'get', :path => "/projects/5234/memberships/autocomplete.js" }, + { :controller => 'members', :action => 'autocomplete', :project_id => '5234', :format => 'js' } + ) end end diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/messages_test.rb --- a/test/integration/routing/messages_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/messages_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/my_test.rb --- a/test/integration/routing/my_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/my_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/news_test.rb --- a/test/integration/routing/news_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/news_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/previews_test.rb --- a/test/integration/routing/previews_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/previews_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,7 +19,7 @@ class RoutingPreviewsTest < ActionController::IntegrationTest def test_previews - ["get", "post"].each do |method| + ["get", "post", "put"].each do |method| assert_routing( { :method => method, :path => "/issues/preview/new/123" }, { :controller => 'previews', :action => 'issue', :project_id => '123' } diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/project_enumerations_test.rb --- a/test/integration/routing/project_enumerations_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/project_enumerations_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/projects_test.rb --- a/test/integration/routing/projects_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/projects_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/queries_test.rb --- a/test/integration/routing/queries_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/queries_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/reports_test.rb --- a/test/integration/routing/reports_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/reports_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/repositories_test.rb --- a/test/integration/routing/repositories_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/repositories_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/roles_test.rb --- a/test/integration/routing/roles_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/roles_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/search_test.rb --- a/test/integration/routing/search_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/search_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/settings_test.rb --- a/test/integration/routing/settings_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/settings_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/sys_test.rb --- a/test/integration/routing/sys_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/sys_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/timelog_test.rb --- a/test/integration/routing/timelog_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/timelog_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/trackers_test.rb --- a/test/integration/routing/trackers_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/trackers_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/users_test.rb --- a/test/integration/routing/users_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/users_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/versions_test.rb --- a/test/integration/routing/versions_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/versions_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/watchers_test.rb --- a/test/integration/routing/watchers_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/watchers_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -32,7 +32,7 @@ { :controller => 'watchers', :action => 'create' } ) assert_routing( - { :method => 'post', :path => "/watchers/destroy" }, + { :method => 'delete', :path => "/watchers" }, { :controller => 'watchers', :action => 'destroy' } ) assert_routing( @@ -44,8 +44,18 @@ { :controller => 'watchers', :action => 'watch' } ) assert_routing( - { :method => 'post', :path => "/watchers/unwatch" }, + { :method => 'delete', :path => "/watchers/watch" }, { :controller => 'watchers', :action => 'unwatch' } ) + assert_routing( + { :method => 'post', :path => "/issues/12/watchers.xml" }, + { :controller => 'watchers', :action => 'create', + :object_type => 'issue', :object_id => '12', :format => 'xml' } + ) + assert_routing( + { :method => 'delete', :path => "/issues/12/watchers/3.xml" }, + { :controller => 'watchers', :action => 'destroy', + :object_type => 'issue', :object_id => '12', :user_id => '3', :format => 'xml'} + ) end end diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/welcome_test.rb --- a/test/integration/routing/welcome_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/welcome_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/wiki_test.rb --- a/test/integration/routing/wiki_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/wiki_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -53,6 +53,10 @@ { :controller => 'wiki', :action => 'annotate', :project_id => '1', :id => 'CookBook_documentation', :version => '2' } ) + # Make sure we don't route wiki page sub-uris to let plugins handle them + assert_raise(ActionController::RoutingError) do + assert_recognizes({}, {:method => 'get', :path => "/projects/1/wiki/CookBook_documentation/whatever"}) + end end def test_wiki_misc diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/wikis_test.rb --- a/test/integration/routing/wikis_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/wikis_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/routing/workflows_test.rb --- a/test/integration/routing/workflows_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/routing/workflows_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/integration/users_test.rb --- a/test/integration/users_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/integration/users_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/object_helpers.rb --- a/test/object_helpers.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/object_helpers.rb Fri Jun 14 09:28:30 2013 +0100 @@ -3,7 +3,7 @@ @generated_user_login ||= 'user0' @generated_user_login.succ! user = User.new(attributes) - user.login = @generated_user_login if user.login.blank? + user.login = @generated_user_login.dup if user.login.blank? user.mail = "#{@generated_user_login}@example.com" if user.mail.blank? user.firstname = "Bob" if user.firstname.blank? user.lastname = "Doe" if user.lastname.blank? @@ -22,7 +22,7 @@ @generated_group_name ||= 'Group 0' @generated_group_name.succ! group = Group.new(attributes) - group.name = @generated_group_name if group.name.blank? + group.name = @generated_group_name.dup if group.name.blank? yield group if block_given? group.save! group @@ -32,18 +32,24 @@ @generated_project_identifier ||= 'project-0000' @generated_project_identifier.succ! project = Project.new(attributes) - project.name = @generated_project_identifier if project.name.blank? - project.identifier = @generated_project_identifier if project.identifier.blank? + project.name = @generated_project_identifier.dup if project.name.blank? + project.identifier = @generated_project_identifier.dup if project.identifier.blank? yield project if block_given? project.save! project end + def Project.generate_with_parent!(parent, attributes={}) + project = Project.generate!(attributes) + project.set_parent!(parent) + project + end + def Tracker.generate!(attributes={}) @generated_tracker_name ||= 'Tracker 0' @generated_tracker_name.succ! tracker = Tracker.new(attributes) - tracker.name = @generated_tracker_name if tracker.name.blank? + tracker.name = @generated_tracker_name.dup if tracker.name.blank? yield tracker if block_given? tracker.save! tracker @@ -53,7 +59,7 @@ @generated_role_name ||= 'Role 0' @generated_role_name.succ! role = Role.new(attributes) - role.name = @generated_role_name if role.name.blank? + role.name = @generated_role_name.dup if role.name.blank? yield role if block_given? role.save! role @@ -92,17 +98,29 @@ @generated_version_name ||= 'Version 0' @generated_version_name.succ! version = Version.new(attributes) - version.name = @generated_version_name if version.name.blank? + version.name = @generated_version_name.dup if version.name.blank? yield version if block_given? version.save! version end + def TimeEntry.generate!(attributes={}) + entry = TimeEntry.new(attributes) + entry.user ||= User.find(2) + entry.issue ||= Issue.find(1) unless entry.project + entry.project ||= entry.issue.project + entry.activity ||= TimeEntryActivity.first + entry.spent_on ||= Date.today + entry.hours ||= 1.0 + entry.save! + entry + end + def AuthSource.generate!(attributes={}) @generated_auth_source_name ||= 'Auth 0' @generated_auth_source_name.succ! source = AuthSource.new(attributes) - source.name = @generated_auth_source_name if source.name.blank? + source.name = @generated_auth_source_name.dup if source.name.blank? yield source if block_given? source.save! source @@ -112,8 +130,8 @@ @generated_board_name ||= 'Forum 0' @generated_board_name.succ! board = Board.new(attributes) - board.name = @generated_board_name if board.name.blank? - board.description = @generated_board_name if board.description.blank? + board.name = @generated_board_name.dup if board.name.blank? + board.description = @generated_board_name.dup if board.description.blank? yield board if block_given? board.save! board @@ -126,8 +144,19 @@ attachment = Attachment.new(attributes) attachment.container ||= Issue.find(1) attachment.author ||= User.find(2) - attachment.filename = @generated_filename if attachment.filename.blank? + attachment.filename = @generated_filename.dup if attachment.filename.blank? attachment.save! attachment end + + def CustomField.generate!(attributes={}) + @generated_custom_field_name ||= 'Custom field 0' + @generated_custom_field_name.succ! + field = new(attributes) + field.name = @generated_custom_field_name.dup if field.name.blank? + field.field_format = 'string' if field.field_format.blank? + yield field if block_given? + field.save! + field + end end diff -r 0a574315af3e -r 4f746d8966dd test/test_helper.rb --- a/test/test_helper.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/test_helper.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,30 +26,10 @@ class ActiveSupport::TestCase include ActionDispatch::TestProcess - - # Transactional fixtures accelerate your tests by wrapping each test method - # in a transaction that's rolled back on completion. This ensures that the - # test database remains unchanged so your fixtures don't have to be reloaded - # between every test method. Fewer database queries means faster tests. - # - # Read Mike Clark's excellent walkthrough at - # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting - # - # Every Active Record database supports transactions except MyISAM tables - # in MySQL. Turn off transactional fixtures in this case; however, if you - # don't care one way or the other, switching from MyISAM to InnoDB tables - # is recommended. + self.use_transactional_fixtures = true - - # Instantiated fixtures are slow, but give you @david where otherwise you - # would need people(:david). If you don't want to migrate your existing - # test cases which use the @david style and don't mind the speed hit (each - # instantiated fixtures translates to a database query per test method), - # then set this back to true. self.use_instantiated_fixtures = false - # Add more helper methods to be used by all tests here... - def log_user(login, password) User.anonymous get "/login" @@ -107,7 +87,15 @@ end def with_settings(options, &block) - saved_settings = options.keys.inject({}) {|h, k| h[k] = Setting[k].is_a?(Symbol) ? Setting[k] : Setting[k].dup; h} + saved_settings = options.keys.inject({}) do |h, k| + h[k] = case Setting[k] + when Symbol, false, true, nil + Setting[k] + else + Setting[k].dup + end + h + end options.each {|k, v| Setting[k] = v} yield ensure @@ -209,286 +197,272 @@ def mail_body(mail) mail.parts.first.body.encoded end +end - # Shoulda macros - def self.should_render_404 - should_respond_with :not_found - should_render_template 'common/error' - end - - def self.should_have_before_filter(expected_method, options = {}) - should_have_filter('before', expected_method, options) - end - - def self.should_have_after_filter(expected_method, options = {}) - should_have_filter('after', expected_method, options) - end - - def self.should_have_filter(filter_type, expected_method, options) - description = "have #{filter_type}_filter :#{expected_method}" - description << " with #{options.inspect}" unless options.empty? - - should description do - klass = "action_controller/filters/#{filter_type}_filter".classify.constantize - expected = klass.new(:filter, expected_method.to_sym, options) - assert_equal 1, @controller.class.filter_chain.select { |filter| - filter.method == expected.method && filter.kind == expected.kind && - filter.options == expected.options && filter.class == expected.class - }.size - end - end - - # Test that a request allows the three types of API authentication - # - # * HTTP Basic with username and password - # * HTTP Basic with an api key for the username - # * Key based with the key=X parameter - # - # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete) - # @param [String] url the request url - # @param [optional, Hash] parameters additional request parameters - # @param [optional, Hash] options additional options - # @option options [Symbol] :success_code Successful response code (:success) - # @option options [Symbol] :failure_code Failure response code (:unauthorized) - def self.should_allow_api_authentication(http_method, url, parameters={}, options={}) - should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters, options) - should_allow_http_basic_auth_with_key(http_method, url, parameters, options) - should_allow_key_based_auth(http_method, url, parameters, options) - end - - # Test that a request allows the username and password for HTTP BASIC - # - # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete) - # @param [String] url the request url - # @param [optional, Hash] parameters additional request parameters - # @param [optional, Hash] options additional options - # @option options [Symbol] :success_code Successful response code (:success) - # @option options [Symbol] :failure_code Failure response code (:unauthorized) - def self.should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters={}, options={}) - success_code = options[:success_code] || :success - failure_code = options[:failure_code] || :unauthorized - - context "should allow http basic auth using a username and password for #{http_method} #{url}" do - context "with a valid HTTP authentication" do - setup do - @user = User.generate! do |user| - user.admin = true - user.password = 'my_password' +module Redmine + module ApiTest + # Base class for API tests + class Base < ActionDispatch::IntegrationTest + # Test that a request allows the three types of API authentication + # + # * HTTP Basic with username and password + # * HTTP Basic with an api key for the username + # * Key based with the key=X parameter + # + # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete) + # @param [String] url the request url + # @param [optional, Hash] parameters additional request parameters + # @param [optional, Hash] options additional options + # @option options [Symbol] :success_code Successful response code (:success) + # @option options [Symbol] :failure_code Failure response code (:unauthorized) + def self.should_allow_api_authentication(http_method, url, parameters={}, options={}) + should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters, options) + should_allow_http_basic_auth_with_key(http_method, url, parameters, options) + should_allow_key_based_auth(http_method, url, parameters, options) + end + + # Test that a request allows the username and password for HTTP BASIC + # + # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete) + # @param [String] url the request url + # @param [optional, Hash] parameters additional request parameters + # @param [optional, Hash] options additional options + # @option options [Symbol] :success_code Successful response code (:success) + # @option options [Symbol] :failure_code Failure response code (:unauthorized) + def self.should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters={}, options={}) + success_code = options[:success_code] || :success + failure_code = options[:failure_code] || :unauthorized + + context "should allow http basic auth using a username and password for #{http_method} #{url}" do + context "with a valid HTTP authentication" do + setup do + @user = User.generate! do |user| + user.admin = true + user.password = 'my_password' + end + send(http_method, url, parameters, credentials(@user.login, 'my_password')) + end + + should_respond_with success_code + should_respond_with_content_type_based_on_url(url) + should "login as the user" do + assert_equal @user, User.current + end end - send(http_method, url, parameters, credentials(@user.login, 'my_password')) - end - - should_respond_with success_code - should_respond_with_content_type_based_on_url(url) - should "login as the user" do - assert_equal @user, User.current + + context "with an invalid HTTP authentication" do + setup do + @user = User.generate! + send(http_method, url, parameters, credentials(@user.login, 'wrong_password')) + end + + should_respond_with failure_code + should_respond_with_content_type_based_on_url(url) + should "not login as the user" do + assert_equal User.anonymous, User.current + end + end + + context "without credentials" do + setup do + send(http_method, url, parameters) + end + + should_respond_with failure_code + should_respond_with_content_type_based_on_url(url) + should "include_www_authenticate_header" do + assert @controller.response.headers.has_key?('WWW-Authenticate') + end + end end end - - context "with an invalid HTTP authentication" do - setup do - @user = User.generate! - send(http_method, url, parameters, credentials(@user.login, 'wrong_password')) - end - - should_respond_with failure_code - should_respond_with_content_type_based_on_url(url) - should "not login as the user" do - assert_equal User.anonymous, User.current + + # Test that a request allows the API key with HTTP BASIC + # + # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete) + # @param [String] url the request url + # @param [optional, Hash] parameters additional request parameters + # @param [optional, Hash] options additional options + # @option options [Symbol] :success_code Successful response code (:success) + # @option options [Symbol] :failure_code Failure response code (:unauthorized) + def self.should_allow_http_basic_auth_with_key(http_method, url, parameters={}, options={}) + success_code = options[:success_code] || :success + failure_code = options[:failure_code] || :unauthorized + + context "should allow http basic auth with a key for #{http_method} #{url}" do + context "with a valid HTTP authentication using the API token" do + setup do + @user = User.generate! do |user| + user.admin = true + end + @token = Token.create!(:user => @user, :action => 'api') + send(http_method, url, parameters, credentials(@token.value, 'X')) + end + should_respond_with success_code + should_respond_with_content_type_based_on_url(url) + should_be_a_valid_response_string_based_on_url(url) + should "login as the user" do + assert_equal @user, User.current + end + end + + context "with an invalid HTTP authentication" do + setup do + @user = User.generate! + @token = Token.create!(:user => @user, :action => 'feeds') + send(http_method, url, parameters, credentials(@token.value, 'X')) + end + should_respond_with failure_code + should_respond_with_content_type_based_on_url(url) + should "not login as the user" do + assert_equal User.anonymous, User.current + end + end end end - - context "without credentials" do - setup do - send(http_method, url, parameters) + + # Test that a request allows full key authentication + # + # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete) + # @param [String] url the request url, without the key=ZXY parameter + # @param [optional, Hash] parameters additional request parameters + # @param [optional, Hash] options additional options + # @option options [Symbol] :success_code Successful response code (:success) + # @option options [Symbol] :failure_code Failure response code (:unauthorized) + def self.should_allow_key_based_auth(http_method, url, parameters={}, options={}) + success_code = options[:success_code] || :success + failure_code = options[:failure_code] || :unauthorized + + context "should allow key based auth using key=X for #{http_method} #{url}" do + context "with a valid api token" do + setup do + @user = User.generate! do |user| + user.admin = true + end + @token = Token.create!(:user => @user, :action => 'api') + # Simple url parse to add on ?key= or &key= + request_url = if url.match(/\?/) + url + "&key=#{@token.value}" + else + url + "?key=#{@token.value}" + end + send(http_method, request_url, parameters) + end + should_respond_with success_code + should_respond_with_content_type_based_on_url(url) + should_be_a_valid_response_string_based_on_url(url) + should "login as the user" do + assert_equal @user, User.current + end + end + + context "with an invalid api token" do + setup do + @user = User.generate! do |user| + user.admin = true + end + @token = Token.create!(:user => @user, :action => 'feeds') + # Simple url parse to add on ?key= or &key= + request_url = if url.match(/\?/) + url + "&key=#{@token.value}" + else + url + "?key=#{@token.value}" + end + send(http_method, request_url, parameters) + end + should_respond_with failure_code + should_respond_with_content_type_based_on_url(url) + should "not login as the user" do + assert_equal User.anonymous, User.current + end + end end - - should_respond_with failure_code - should_respond_with_content_type_based_on_url(url) - should "include_www_authenticate_header" do - assert @controller.response.headers.has_key?('WWW-Authenticate') + + context "should allow key based auth using X-Redmine-API-Key header for #{http_method} #{url}" do + setup do + @user = User.generate! do |user| + user.admin = true + end + @token = Token.create!(:user => @user, :action => 'api') + send(http_method, url, parameters, {'X-Redmine-API-Key' => @token.value.to_s}) + end + should_respond_with success_code + should_respond_with_content_type_based_on_url(url) + should_be_a_valid_response_string_based_on_url(url) + should "login as the user" do + assert_equal @user, User.current + end + end + end + + # Uses should_respond_with_content_type based on what's in the url: + # + # '/project/issues.xml' => should_respond_with_content_type :xml + # '/project/issues.json' => should_respond_with_content_type :json + # + # @param [String] url Request + def self.should_respond_with_content_type_based_on_url(url) + case + when url.match(/xml/i) + should "respond with XML" do + assert_equal 'application/xml', @response.content_type + end + when url.match(/json/i) + should "respond with JSON" do + assert_equal 'application/json', @response.content_type + end + else + raise "Unknown content type for should_respond_with_content_type_based_on_url: #{url}" + end + end + + # Uses the url to assert which format the response should be in + # + # '/project/issues.xml' => should_be_a_valid_xml_string + # '/project/issues.json' => should_be_a_valid_json_string + # + # @param [String] url Request + def self.should_be_a_valid_response_string_based_on_url(url) + case + when url.match(/xml/i) + should_be_a_valid_xml_string + when url.match(/json/i) + should_be_a_valid_json_string + else + raise "Unknown content type for should_be_a_valid_response_based_on_url: #{url}" + end + end + + # Checks that the response is a valid JSON string + def self.should_be_a_valid_json_string + should "be a valid JSON string (or empty)" do + assert(response.body.blank? || ActiveSupport::JSON.decode(response.body)) + end + end + + # Checks that the response is a valid XML string + def self.should_be_a_valid_xml_string + should "be a valid XML string" do + assert REXML::Document.new(response.body) + end + end + + def self.should_respond_with(status) + should "respond with #{status}" do + assert_response status end end end end +end - # Test that a request allows the API key with HTTP BASIC - # - # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete) - # @param [String] url the request url - # @param [optional, Hash] parameters additional request parameters - # @param [optional, Hash] options additional options - # @option options [Symbol] :success_code Successful response code (:success) - # @option options [Symbol] :failure_code Failure response code (:unauthorized) - def self.should_allow_http_basic_auth_with_key(http_method, url, parameters={}, options={}) - success_code = options[:success_code] || :success - failure_code = options[:failure_code] || :unauthorized - - context "should allow http basic auth with a key for #{http_method} #{url}" do - context "with a valid HTTP authentication using the API token" do - setup do - @user = User.generate! do |user| - user.admin = true - end - @token = Token.create!(:user => @user, :action => 'api') - send(http_method, url, parameters, credentials(@token.value, 'X')) - end - should_respond_with success_code - should_respond_with_content_type_based_on_url(url) - should_be_a_valid_response_string_based_on_url(url) - should "login as the user" do - assert_equal @user, User.current - end - end - - context "with an invalid HTTP authentication" do - setup do - @user = User.generate! - @token = Token.create!(:user => @user, :action => 'feeds') - send(http_method, url, parameters, credentials(@token.value, 'X')) - end - should_respond_with failure_code - should_respond_with_content_type_based_on_url(url) - should "not login as the user" do - assert_equal User.anonymous, User.current - end - end - end - end - - # Test that a request allows full key authentication - # - # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete) - # @param [String] url the request url, without the key=ZXY parameter - # @param [optional, Hash] parameters additional request parameters - # @param [optional, Hash] options additional options - # @option options [Symbol] :success_code Successful response code (:success) - # @option options [Symbol] :failure_code Failure response code (:unauthorized) - def self.should_allow_key_based_auth(http_method, url, parameters={}, options={}) - success_code = options[:success_code] || :success - failure_code = options[:failure_code] || :unauthorized - - context "should allow key based auth using key=X for #{http_method} #{url}" do - context "with a valid api token" do - setup do - @user = User.generate! do |user| - user.admin = true - end - @token = Token.create!(:user => @user, :action => 'api') - # Simple url parse to add on ?key= or &key= - request_url = if url.match(/\?/) - url + "&key=#{@token.value}" - else - url + "?key=#{@token.value}" - end - send(http_method, request_url, parameters) - end - should_respond_with success_code - should_respond_with_content_type_based_on_url(url) - should_be_a_valid_response_string_based_on_url(url) - should "login as the user" do - assert_equal @user, User.current - end - end - - context "with an invalid api token" do - setup do - @user = User.generate! do |user| - user.admin = true - end - @token = Token.create!(:user => @user, :action => 'feeds') - # Simple url parse to add on ?key= or &key= - request_url = if url.match(/\?/) - url + "&key=#{@token.value}" - else - url + "?key=#{@token.value}" - end - send(http_method, request_url, parameters) - end - should_respond_with failure_code - should_respond_with_content_type_based_on_url(url) - should "not login as the user" do - assert_equal User.anonymous, User.current - end - end - end - - context "should allow key based auth using X-Redmine-API-Key header for #{http_method} #{url}" do - setup do - @user = User.generate! do |user| - user.admin = true - end - @token = Token.create!(:user => @user, :action => 'api') - send(http_method, url, parameters, {'X-Redmine-API-Key' => @token.value.to_s}) - end - should_respond_with success_code - should_respond_with_content_type_based_on_url(url) - should_be_a_valid_response_string_based_on_url(url) - should "login as the user" do - assert_equal @user, User.current - end - end - end - - # Uses should_respond_with_content_type based on what's in the url: - # - # '/project/issues.xml' => should_respond_with_content_type :xml - # '/project/issues.json' => should_respond_with_content_type :json - # - # @param [String] url Request - def self.should_respond_with_content_type_based_on_url(url) - case - when url.match(/xml/i) - should "respond with XML" do - assert_equal 'application/xml', @response.content_type - end - when url.match(/json/i) - should "respond with JSON" do - assert_equal 'application/json', @response.content_type - end - else - raise "Unknown content type for should_respond_with_content_type_based_on_url: #{url}" - end - end - - # Uses the url to assert which format the response should be in - # - # '/project/issues.xml' => should_be_a_valid_xml_string - # '/project/issues.json' => should_be_a_valid_json_string - # - # @param [String] url Request - def self.should_be_a_valid_response_string_based_on_url(url) - case - when url.match(/xml/i) - should_be_a_valid_xml_string - when url.match(/json/i) - should_be_a_valid_json_string - else - raise "Unknown content type for should_be_a_valid_response_based_on_url: #{url}" - end - end - - # Checks that the response is a valid JSON string - def self.should_be_a_valid_json_string - should "be a valid JSON string (or empty)" do - assert(response.body.blank? || ActiveSupport::JSON.decode(response.body)) - end - end - - # Checks that the response is a valid XML string - def self.should_be_a_valid_xml_string - should "be a valid XML string" do - assert REXML::Document.new(response.body) - end - end - - def self.should_respond_with(status) - should "respond with #{status}" do - assert_response status - end +# URL helpers do not work with config.threadsafe! +# https://github.com/rspec/rspec-rails/issues/476#issuecomment-4705454 +ActionView::TestCase::TestController.instance_eval do + helper Rails.application.routes.url_helpers +end +ActionView::TestCase::TestController.class_eval do + def _routes + Rails.application.routes end end - -# Simple module to "namespace" all of the API tests -module ApiTest -end diff -r 0a574315af3e -r 4f746d8966dd test/ui/base.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/ui/base.rb Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,63 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../test_helper', __FILE__) +require 'capybara/rails' + +Capybara.default_driver = :selenium +Capybara.register_driver :selenium do |app| + # Use the following driver definition to test locally using Chrome (also requires chromedriver to be in PATH) + # Capybara::Selenium::Driver.new(app, :browser => :chrome) + # Add :switches => %w[--lang=en] to force default browser locale to English + # Default for Selenium remote driver is to connect to local host on port 4444 + # This can be change using :url => 'http://localhost:9195' if necessary + # PhantomJS 1.8 now directly supports Webdriver Wire API, simply run it with `phantomjs --webdriver 4444` + # Add :desired_capabilities => Selenium::WebDriver::Remote::Capabilities.internet_explorer) to run on Selenium Grid Hub with IE + Capybara::Selenium::Driver.new(app, :browser => :remote) +end + +module Redmine + module UiTest + # Base class for UI tests + class Base < ActionDispatch::IntegrationTest + include Capybara::DSL + + # Stop ActiveRecord from wrapping tests in transactions + # Transactional fixtures do not work with Selenium tests, because Capybara + # uses a separate server thread, which the transactions would be hidden + self.use_transactional_fixtures = false + + # Should not depend on locale since Redmine displays login page + # using default browser locale which depend on system locale for "real" browsers drivers + def log_user(login, password) + visit '/my/page' + assert_equal '/login', current_path + within('#login-form form') do + fill_in 'username', :with => login + fill_in 'password', :with => password + find('input[name=login]').click + end + assert_equal '/my/page', current_path + end + + teardown do + Capybara.reset_sessions! # Forget the (simulated) browser state + Capybara.use_default_driver # Revert Capybara.current_driver to Capybara.default_driver + end + end + end +end diff -r 0a574315af3e -r 4f746d8966dd test/ui/issues_test.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/ui/issues_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,217 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../base', __FILE__) + +class Redmine::UiTest::IssuesTest < Redmine::UiTest::Base + fixtures :projects, :users, :roles, :members, :member_roles, + :trackers, :projects_trackers, :enabled_modules, :issue_statuses, :issues, + :enumerations, :custom_fields, :custom_values, :custom_fields_trackers, + :watchers + + def test_create_issue + log_user('jsmith', 'jsmith') + visit '/projects/ecookbook/issues/new' + within('form#issue-form') do + select 'Bug', :from => 'Tracker' + select 'Low', :from => 'Priority' + fill_in 'Subject', :with => 'new test issue' + fill_in 'Description', :with => 'new issue' + select '0 %', :from => 'Done' + fill_in 'Due date', :with => '' + select '', :from => 'Assignee' + fill_in 'Searchable field', :with => 'Value for field 2' + # click_button 'Create' would match both 'Create' and 'Create and continue' buttons + find('input[name=commit]').click + end + + # find created issue + issue = Issue.find_by_subject("new test issue") + assert_kind_of Issue, issue + + # check redirection + find 'div#flash_notice', :visible => true, :text => "Issue \##{issue.id} created." + assert_equal issue_path(:id => issue), current_path + + # check issue attributes + assert_equal 'jsmith', issue.author.login + assert_equal 1, issue.project.id + assert_equal IssueStatus.find_by_name('New'), issue.status + assert_equal Tracker.find_by_name('Bug'), issue.tracker + assert_equal IssuePriority.find_by_name('Low'), issue.priority + assert_equal 'Value for field 2', issue.custom_field_value(CustomField.find_by_name('Searchable field')) + end + + def test_create_issue_with_form_update + field1 = IssueCustomField.create!( + :field_format => 'string', + :name => 'Field1', + :is_for_all => true, + :trackers => Tracker.find_all_by_id([1, 2]) + ) + field2 = IssueCustomField.create!( + :field_format => 'string', + :name => 'Field2', + :is_for_all => true, + :trackers => Tracker.find_all_by_id(2) + ) + + Role.non_member.add_permission! :add_issues + Role.non_member.remove_permission! :edit_issues, :add_issue_notes + + log_user('someone', 'foo') + visit '/projects/ecookbook/issues/new' + assert page.has_no_content?(field2.name) + assert page.has_content?(field1.name) + + fill_in 'Subject', :with => 'New test issue' + fill_in 'Description', :with => 'New test issue description' + fill_in field1.name, :with => 'CF1 value' + select 'Low', :from => 'Priority' + + # field2 should show up when changing tracker + select 'Feature request', :from => 'Tracker' + assert page.has_content?(field2.name) + assert page.has_content?(field1.name) + + fill_in field2.name, :with => 'CF2 value' + assert_difference 'Issue.count' do + page.first(:button, 'Create').click + end + + issue = Issue.order('id desc').first + assert_equal 'New test issue', issue.subject + assert_equal 'New test issue description', issue.description + assert_equal 'Low', issue.priority.name + assert_equal 'CF1 value', issue.custom_field_value(field1) + assert_equal 'CF2 value', issue.custom_field_value(field2) + end + + def test_create_issue_with_watchers + User.generate!(:firstname => 'Some', :lastname => 'Watcher') + + log_user('jsmith', 'jsmith') + visit '/projects/ecookbook/issues/new' + fill_in 'Subject', :with => 'Issue with watchers' + # Add a project member as watcher + check 'Dave Lopper' + # Search for another user + click_link 'Search for watchers to add' + within('form#new-watcher-form') do + assert page.has_content?('Some One') + fill_in 'user_search', :with => 'watch' + assert page.has_no_content?('Some One') + check 'Some Watcher' + click_button 'Add' + end + assert_difference 'Issue.count' do + find('input[name=commit]').click + end + + issue = Issue.order('id desc').first + assert_equal ['Dave Lopper', 'Some Watcher'], issue.watcher_users.map(&:name).sort + end + + def test_preview_issue_description + log_user('jsmith', 'jsmith') + visit '/projects/ecookbook/issues/new' + within('form#issue-form') do + fill_in 'Subject', :with => 'new issue subject' + fill_in 'Description', :with => 'new issue description' + click_link 'Preview' + end + find 'div#preview fieldset', :visible => true, :text => 'new issue description' + assert_difference 'Issue.count' do + find('input[name=commit]').click + end + + issue = Issue.order('id desc').first + assert_equal 'new issue description', issue.description + end + + def test_update_issue_with_form_update + field = IssueCustomField.create!( + :field_format => 'string', + :name => 'Form update CF', + :is_for_all => true, + :trackers => Tracker.find_all_by_name('Feature request') + ) + + Role.non_member.add_permission! :edit_issues + Role.non_member.remove_permission! :add_issues, :add_issue_notes + + log_user('someone', 'foo') + visit '/issues/1' + assert page.has_no_content?('Form update CF') + + page.first(:link, 'Update').click + # the custom field should show up when changing tracker + select 'Feature request', :from => 'Tracker' + assert page.has_content?('Form update CF') + + fill_in 'Form update', :with => 'CF value' + assert_no_difference 'Issue.count' do + page.first(:button, 'Submit').click + end + + issue = Issue.find(1) + assert_equal 'CF value', issue.custom_field_value(field) + end + + def test_remove_issue_watcher_from_sidebar + user = User.find(3) + Watcher.create!(:watchable => Issue.find(1), :user => user) + + log_user('jsmith', 'jsmith') + visit '/issues/1' + assert page.first('#sidebar').has_content?('Watchers (1)') + assert page.first('#sidebar').has_content?(user.name) + assert_difference 'Watcher.count', -1 do + page.first('ul.watchers .user-3 a.delete').click + end + assert page.first('#sidebar').has_content?('Watchers (0)') + assert page.first('#sidebar').has_no_content?(user.name) + end + + def test_watch_issue_via_context_menu + log_user('jsmith', 'jsmith') + visit '/issues' + find('tr#issue-1 td.updated_on').click + page.execute_script "$('tr#issue-1 td.updated_on').trigger('contextmenu');" + assert_difference 'Watcher.count' do + within('#context-menu') do + click_link 'Watch' + end + end + assert Issue.find(1).watched_by?(User.find_by_login('jsmith')) + end + + def test_bulk_watch_issues_via_context_menu + log_user('jsmith', 'jsmith') + visit '/issues' + find('tr#issue-1 input[type=checkbox]').click + find('tr#issue-4 input[type=checkbox]').click + page.execute_script "$('tr#issue-1 td.updated_on').trigger('contextmenu');" + assert_difference 'Watcher.count', 2 do + within('#context-menu') do + click_link 'Watch' + end + end + assert Issue.find(1).watched_by?(User.find_by_login('jsmith')) + assert Issue.find(4).watched_by?(User.find_by_login('jsmith')) + end +end diff -r 0a574315af3e -r 4f746d8966dd test/unit/activity_test.rb --- a/test/unit/activity_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/unit/activity_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,7 +19,8 @@ class ActivityTest < ActiveSupport::TestCase fixtures :projects, :versions, :attachments, :users, :roles, :members, :member_roles, :issues, :journals, :journal_details, - :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages + :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages, :time_entries, + :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions def setup @project = Project.find(1) @@ -87,6 +88,39 @@ assert_equal %w(Project Version), events.collect(&:container_type).uniq.sort end + def test_event_group_for_issue + issue = Issue.find(1) + assert_equal issue, issue.event_group + end + + def test_event_group_for_journal + issue = Issue.find(1) + journal = issue.journals.first + assert_equal issue, journal.event_group + end + + def test_event_group_for_issue_time_entry + time = TimeEntry.where(:issue_id => 1).first + assert_equal time.issue, time.event_group + end + + def test_event_group_for_project_time_entry + time = TimeEntry.where(:issue_id => nil).first + assert_equal time, time.event_group + end + + def test_event_group_for_message + message = Message.find(1) + reply = message.children.first + assert_equal message, message.event_group + assert_equal message, reply.event_group + end + + def test_event_group_for_wiki_content_version + content = WikiContent::Version.find(1) + assert_equal content.page, content.event_group + end + private def find_events(user, options={}) diff -r 0a574315af3e -r 4f746d8966dd test/unit/attachment_test.rb --- a/test/unit/attachment_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/unit/attachment_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -52,10 +52,25 @@ assert_equal 'text/plain', a.content_type assert_equal 0, a.downloads assert_equal '1478adae0d4eb06d35897518540e25d6', a.digest + + assert a.disk_directory + assert_match %r{\A\d{4}/\d{2}\z}, a.disk_directory + assert File.exist?(a.diskfile) assert_equal 59, File.size(a.diskfile) end + def test_copy_should_preserve_attributes + a = Attachment.find(1) + copy = a.copy + + assert_save copy + copy = Attachment.order('id DESC').first + %w(filename filesize content_type author_id created_on description digest disk_filename disk_directory diskfile).each do |attribute| + assert_equal a.send(attribute), copy.send(attribute), "#{attribute} was different" + end + end + def test_size_should_be_validated_for_new_file with_settings :attachment_max_size => 0 do a = Attachment.new(:container => Issue.find(1), @@ -168,42 +183,55 @@ end end - context "Attachmnet.attach_files" do - should "attach the file" do - issue = Issue.first - assert_difference 'Attachment.count' do - Attachment.attach_files(issue, - '1' => { - 'file' => uploaded_test_file('testfile.txt', 'text/plain'), - 'description' => 'test' - }) - end + def test_move_from_root_to_target_directory_should_move_root_files + a = Attachment.find(20) + assert a.disk_directory.blank? + # Create a real file for this fixture + File.open(a.diskfile, "w") do |f| + f.write "test file at the root of files directory" + end + assert a.readable? + Attachment.move_from_root_to_target_directory - attachment = Attachment.first(:order => 'id DESC') - assert_equal issue, attachment.container - assert_equal 'testfile.txt', attachment.filename - assert_equal 59, attachment.filesize - assert_equal 'test', attachment.description - assert_equal 'text/plain', attachment.content_type - assert File.exists?(attachment.diskfile) - assert_equal 59, File.size(attachment.diskfile) + a.reload + assert_equal '2012/05', a.disk_directory + assert a.readable? + end + + test "Attachmnet.attach_files should attach the file" do + issue = Issue.first + assert_difference 'Attachment.count' do + Attachment.attach_files(issue, + '1' => { + 'file' => uploaded_test_file('testfile.txt', 'text/plain'), + 'description' => 'test' + }) end - should "add unsaved files to the object as unsaved attachments" do - # Max size of 0 to force Attachment creation failures - with_settings(:attachment_max_size => 0) do - @project = Project.find(1) - response = Attachment.attach_files(@project, { - '1' => {'file' => mock_file, 'description' => 'test'}, - '2' => {'file' => mock_file, 'description' => 'test'} - }) + attachment = Attachment.first(:order => 'id DESC') + assert_equal issue, attachment.container + assert_equal 'testfile.txt', attachment.filename + assert_equal 59, attachment.filesize + assert_equal 'test', attachment.description + assert_equal 'text/plain', attachment.content_type + assert File.exists?(attachment.diskfile) + assert_equal 59, File.size(attachment.diskfile) + end - assert response[:unsaved].present? - assert_equal 2, response[:unsaved].length - assert response[:unsaved].first.new_record? - assert response[:unsaved].second.new_record? - assert_equal response[:unsaved], @project.unsaved_attachments - end + test "Attachmnet.attach_files should add unsaved files to the object as unsaved attachments" do + # Max size of 0 to force Attachment creation failures + with_settings(:attachment_max_size => 0) do + @project = Project.find(1) + response = Attachment.attach_files(@project, { + '1' => {'file' => mock_file, 'description' => 'test'}, + '2' => {'file' => mock_file, 'description' => 'test'} + }) + + assert response[:unsaved].present? + assert_equal 2, response[:unsaved].length + assert response[:unsaved].first.new_record? + assert response[:unsaved].second.new_record? + assert_equal response[:unsaved], @project.unsaved_attachments end end diff -r 0a574315af3e -r 4f746d8966dd test/unit/auth_source_ldap_test.rb --- a/test/unit/auth_source_ldap_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/unit/auth_source_ldap_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -58,61 +58,48 @@ end if ldap_configured? - context '#authenticate' do - setup do - @auth = AuthSourceLdap.find(1) - @auth.update_attribute :onthefly_register, true + test '#authenticate with a valid LDAP user should return the user attributes' do + auth = AuthSourceLdap.find(1) + auth.update_attribute :onthefly_register, true + + attributes = auth.authenticate('example1','123456') + assert attributes.is_a?(Hash), "An hash was not returned" + assert_equal 'Example', attributes[:firstname] + assert_equal 'One', attributes[:lastname] + assert_equal 'example1@redmine.org', attributes[:mail] + assert_equal auth.id, attributes[:auth_source_id] + attributes.keys.each do |attribute| + assert User.new.respond_to?("#{attribute}="), "Unexpected :#{attribute} attribute returned" end + end - context 'with a valid LDAP user' do - should 'return the user attributes' do - attributes = @auth.authenticate('example1','123456') - assert attributes.is_a?(Hash), "An hash was not returned" - assert_equal 'Example', attributes[:firstname] - assert_equal 'One', attributes[:lastname] - assert_equal 'example1@redmine.org', attributes[:mail] - assert_equal @auth.id, attributes[:auth_source_id] - attributes.keys.each do |attribute| - assert User.new.respond_to?("#{attribute}="), "Unexpected :#{attribute} attribute returned" - end - end - end + test '#authenticate with an invalid LDAP user should return nil' do + auth = AuthSourceLdap.find(1) + assert_equal nil, auth.authenticate('nouser','123456') + end - context 'with an invalid LDAP user' do - should 'return nil' do - assert_equal nil, @auth.authenticate('nouser','123456') - end - end + test '#authenticate without a login should return nil' do + auth = AuthSourceLdap.find(1) + assert_equal nil, auth.authenticate('','123456') + end - context 'without a login' do - should 'return nil' do - assert_equal nil, @auth.authenticate('','123456') - end - end + test '#authenticate without a password should return nil' do + auth = AuthSourceLdap.find(1) + assert_equal nil, auth.authenticate('edavis','') + end - context 'without a password' do - should 'return nil' do - assert_equal nil, @auth.authenticate('edavis','') - end - end + test '#authenticate without filter should return any user' do + auth = AuthSourceLdap.find(1) + assert auth.authenticate('example1','123456') + assert auth.authenticate('edavis', '123456') + end - context 'without filter' do - should 'return any user' do - assert @auth.authenticate('example1','123456') - assert @auth.authenticate('edavis', '123456') - end - end + test '#authenticate with filter should return user who matches the filter only' do + auth = AuthSourceLdap.find(1) + auth.filter = "(mail=*@redmine.org)" - context 'with filter' do - setup do - @auth.filter = "(mail=*@redmine.org)" - end - - should 'return user who matches the filter only' do - assert @auth.authenticate('example1','123456') - assert_nil @auth.authenticate('edavis', '123456') - end - end + assert auth.authenticate('example1','123456') + assert_nil auth.authenticate('edavis', '123456') end def test_authenticate_should_timeout @@ -124,6 +111,30 @@ auth_source.authenticate 'example1', '123456' end end + + def test_search_should_return_matching_entries + results = AuthSource.search("exa") + assert_equal 1, results.size + result = results.first + assert_kind_of Hash, result + assert_equal "example1", result[:login] + assert_equal "Example", result[:firstname] + assert_equal "One", result[:lastname] + assert_equal "example1@redmine.org", result[:mail] + assert_equal 1, result[:auth_source_id] + end + + def test_search_with_no_match_should_return_an_empty_array + results = AuthSource.search("wro") + assert_equal [], results + end + + def test_search_with_exception_should_return_an_empty_array + Net::LDAP.stubs(:new).raises(Net::LDAP::LdapError, 'Cannot connect') + + results = AuthSource.search("exa") + assert_equal [], results + end else puts '(Test LDAP server not configured)' end diff -r 0a574315af3e -r 4f746d8966dd test/unit/board_test.rb --- a/test/unit/board_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/unit/board_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/unit/changeset_test.rb --- a/test/unit/changeset_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/unit/changeset_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/unit/comment_test.rb --- a/test/unit/comment_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/unit/comment_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/unit/custom_field_test.rb --- a/test/unit/custom_field_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/unit/custom_field_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -209,6 +209,24 @@ assert !f.valid_field_value?(['value1', 'abc']) end + def test_changing_multiple_to_false_should_delete_multiple_values + field = ProjectCustomField.create!(:name => 'field', :field_format => 'list', :multiple => 'true', :possible_values => ['field1', 'field2']) + other = ProjectCustomField.create!(:name => 'other', :field_format => 'list', :multiple => 'true', :possible_values => ['other1', 'other2']) + + item_with_multiple_values = Project.generate!(:custom_field_values => {field.id => ['field1', 'field2'], other.id => ['other1', 'other2']}) + item_with_single_values = Project.generate!(:custom_field_values => {field.id => ['field1'], other.id => ['other2']}) + + assert_difference 'CustomValue.count', -1 do + field.multiple = false + field.save! + end + + item_with_multiple_values = Project.find(item_with_multiple_values.id) + assert_kind_of String, item_with_multiple_values.custom_field_value(field) + assert_kind_of Array, item_with_multiple_values.custom_field_value(other) + assert_equal 2, item_with_multiple_values.custom_field_value(other).size + end + def test_value_class_should_return_the_class_used_for_fields_values assert_equal User, CustomField.new(:field_format => 'user').value_class assert_equal Version, CustomField.new(:field_format => 'version').value_class diff -r 0a574315af3e -r 4f746d8966dd test/unit/custom_field_user_format_test.rb --- a/test/unit/custom_field_user_format_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/unit/custom_field_user_format_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/unit/custom_field_version_format_test.rb --- a/test/unit/custom_field_version_format_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/unit/custom_field_version_format_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/unit/custom_value_test.rb --- a/test/unit/custom_value_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/unit/custom_value_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/unit/default_data_test.rb --- a/test/unit/default_data_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/unit/default_data_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/unit/document_category_test.rb --- a/test/unit/document_category_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/unit/document_category_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -34,14 +34,14 @@ end def test_default - assert_nil DocumentCategory.find(:first, :conditions => { :is_default => true }) + assert_nil DocumentCategory.where(:is_default => true).first e = Enumeration.find_by_name('Technical documentation') e.update_attributes(:is_default => true) assert_equal 3, DocumentCategory.default.id end def test_force_default - assert_nil DocumentCategory.find(:first, :conditions => { :is_default => true }) + assert_nil DocumentCategory.where(:is_default => true).first assert_equal 1, DocumentCategory.default.id end end diff -r 0a574315af3e -r 4f746d8966dd test/unit/document_test.rb --- a/test/unit/document_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/unit/document_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/unit/enabled_module_test.rb --- a/test/unit/enabled_module_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/unit/enabled_module_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 diff -r 0a574315af3e -r 4f746d8966dd test/unit/enumeration_test.rb --- a/test/unit/enumeration_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/unit/enumeration_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -86,7 +86,7 @@ def test_destroy_with_reassign Enumeration.find(4).destroy(Enumeration.find(6)) - assert_nil Issue.find(:first, :conditions => {:priority_id => 4}) + assert_nil Issue.where(:priority_id => 4).first assert_equal 6, Enumeration.find(6).objects_count end diff -r 0a574315af3e -r 4f746d8966dd test/unit/group_test.rb --- a/test/unit/group_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/unit/group_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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,11 @@ class GroupTest < ActiveSupport::TestCase fixtures :projects, :trackers, :issue_statuses, :issues, - :enumerations, :users, :issue_categories, + :enumerations, :users, :projects_trackers, :roles, :member_roles, :members, - :enabled_modules, - :workflows, :groups_users include Redmine::I18n @@ -37,6 +35,14 @@ assert_equal 'New group', g.name end + def test_name_should_accept_255_characters + name = 'a' * 255 + g = Group.new(:name => name) + assert g.save + g.reload + assert_equal name, g.name + end + def test_blank_name_error_message set_language_if_valid 'en' g = Group.new diff -r 0a574315af3e -r 4f746d8966dd test/unit/helpers/activities_helper_test.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/unit/helpers/activities_helper_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -0,0 +1,101 @@ +# Redmine - project management software +# Copyright (C) 2006-2013 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.expand_path('../../../test_helper', __FILE__) + +class ActivitiesHelperTest < ActionView::TestCase + include ActivitiesHelper + + class MockEvent + attr_reader :event_datetime, :event_group, :name + + def initialize(group=nil) + @@count ||= 0 + @name = "e#{@@count}" + @event_datetime = Time.now + @@count.hours + @event_group = group || self + @@count += 1 + end + + def self.clear + @@count = 0 + end + end + + def setup + MockEvent.clear + end + + def test_sort_activity_events_should_sort_by_datetime + events = [] + events << MockEvent.new + events << MockEvent.new + events << MockEvent.new + + assert_equal [ + ['e2', false], + ['e1', false], + ['e0', false] + ], sort_activity_events(events).map {|event, grouped| [event.name, grouped]} + end + + def test_sort_activity_events_should_group_events + events = [] + events << MockEvent.new + events << MockEvent.new(events[0]) + events << MockEvent.new(events[0]) + + assert_equal [ + ['e2', false], + ['e1', true], + ['e0', true] + ], sort_activity_events(events).map {|event, grouped| [event.name, grouped]} + end + + def test_sort_activity_events_with_group_not_in_set_should_group_events + e = MockEvent.new + events = [] + events << MockEvent.new(e) + events << MockEvent.new(e) + + assert_equal [ + ['e2', false], + ['e1', true] + ], sort_activity_events(events).map {|event, grouped| [event.name, grouped]} + end + + def test_sort_activity_events_should_sort_by_datetime_and_group + events = [] + events << MockEvent.new + events << MockEvent.new + events << MockEvent.new + events << MockEvent.new(events[1]) + events << MockEvent.new(events[2]) + events << MockEvent.new + events << MockEvent.new(events[2]) + + assert_equal [ + ['e6', false], + ['e4', true], + ['e2', true], + ['e5', false], + ['e3', false], + ['e1', true], + ['e0', false] + ], sort_activity_events(events).map {|event, grouped| [event.name, grouped]} + end +end diff -r 0a574315af3e -r 4f746d8966dd test/unit/helpers/application_helper_test.rb --- a/test/unit/helpers/application_helper_test.rb Fri Jun 14 09:07:32 2013 +0100 +++ b/test/unit/helpers/application_helper_test.rb Fri Jun 14 09:28:30 2013 +0100 @@ -1,7 +1,7 @@ # encoding: utf-8 # # Redmine - project management software -# Copyright (C) 2006-2012 Jean-Philippe Lang +# Copyright (C) 2006-2013 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 @@ -21,6 +21,7 @@ class ApplicationHelperTest < ActionView::TestCase include ERB::Util + include Rails.application.routes.url_helpers fixtures :projects, :roles, :enabled_modules, :users, :repositories, :changesets, @@ -83,7 +84,11 @@ # escaping 'http://foo"bar' => 'http://foo"bar', # wrap in angle brackets - '' => '<http://foo.bar>' + '' => '<http://foo.bar>', + # invalid urls + 'http://' => 'http://', + 'www.' => 'www.', + 'test-www.bar.com' => 'test-www.bar.com', } to_test.each { |text, result| assert_equal "

      #{result}

      ", textilizable(text) } end @@ -100,8 +105,11 @@ end def test_auto_mailto - assert_equal '

      ', - textilizable('test@foo.bar') + to_test = { + 'test@foo.bar' => '', + 'test@www.foo.bar' => '', + } + to_test.each { |text, result| assert_equal "

      #{result}

      ", textilizable(text) } end def test_inline_images @@ -131,14 +139,14 @@ def test_attached_images to_test = { - 'Inline image: !logo.gif!' => 'Inline image: This is a logo', - 'Inline image: !logo.GIF!' => 'Inline image: This is a logo', + 'Inline image: !logo.gif!' => 'Inline image: This is a logo', + 'Inline image: !logo.GIF!' => 'Inline image: This is a logo', 'No match: !ogo.gif!' => 'No match: ', 'No match: !ogo.GIF!' => 'No match: ', # link image - '!logo.gif!:http://foo.bar/' => 'This is a logo', + '!logo.gif!:http://foo.bar/' => 'This is a logo', } - attachments = Attachment.find(:all) + attachments = Attachment.all to_test.each { |text, result| assert_equal "

      #{result}

      ", textilizable(text, :attachments => attachments) } end @@ -182,13 +190,13 @@ to_test = { 'Inline image: !testtest.jpg!' => - 'Inline image: ', + 'Inline image: ', 'Inline image: !testtest.jpeg!' => - 'Inline image: ', + 'Inline image: ', 'Inline image: !testtest.jpe!' => - 'Inline image: ', + 'Inline image: ', 'Inline image: !testtest.bmp!' => - 'Inline image: ', + 'Inline image: ', } attachments = [a1, a2, a3, a4] @@ -211,9 +219,9 @@ to_test = { 'Inline image: !testfile.png!' => - 'Inline image: ', + 'Inline image: ', 'Inline image: !Testfile.PNG!' => - 'Inline image: ', + 'Inline image: ', } attachments = [a1, a2] to_test.each { |text, result| assert_equal "

      #{result}

      ", textilizable(text, :attachments => attachments) } @@ -252,15 +260,19 @@ def test_redmine_links issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3}, - :class => 'issue status-1 priority-4 priority-lowest overdue', :title => 'Error 281 when updating a recipe (New)') + :class => Issue.find(3).css_classes, :title => 'Error 281 when updating a recipe (New)') note_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3, :anchor => 'note-14'}, - :class => 'issue status-1 priority-4 priority-lowest overdue', :title => 'Error 281 when updating a recipe (New)') + :class => Issue.find(3).css_classes, :title => 'Error 281 when updating a recipe (New)') - changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1}, - :class => 'changeset', :title => 'My very first commit') - changeset_link2 = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2}, + revision_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1}, + :class => 'changeset', :title => 'My very first commit do not escaping #<>&') + revision_link2 = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2}, :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3') + changeset_link2 = link_to('691322a8eb01e11fd7', + {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1}, + :class => 'changeset', :title => 'My very first commit do not escaping #<>&') + document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1}, :class => 'document') @@ -279,11 +291,13 @@ source_url_with_rev = '/projects/ecookbook/repository/revisions/52/entry/some/file' source_url_with_ext = '/projects/ecookbook/repository/entry/some/file.ext' source_url_with_rev_and_ext = '/projects/ecookbook/repository/revisions/52/entry/some/file.ext' + source_url_with_branch = '/projects/ecookbook/repository/revisions/branch/entry/some/file' export_url = '/projects/ecookbook/repository/raw/some/file' export_url_with_rev = '/projects/ecookbook/repository/revisions/52/raw/some/file' export_url_with_ext = '/projects/ecookbook/repository/raw/some/file.ext' export_url_with_rev_and_ext = '/projects/ecookbook/repository/revisions/52/raw/some/file.ext' + export_url_with_branch = '/projects/ecookbook/repository/revisions/branch/raw/some/file' to_test = { # tickets @@ -294,10 +308,11 @@ # should not ignore leading zero '#03' => '#03', # changesets - 'r1' => changeset_link, - 'r1.' => "#{changeset_link}.", - 'r1, r2' => "#{changeset_link}, #{changeset_link2}", - 'r1,r2' => "#{changeset_link},#{changeset_link2}", + 'r1' => revision_link, + 'r1.' => "#{revision_link}.", + 'r1, r2' => "#{revision_link}, #{revision_link2}", + 'r1,r2' => "#{revision_link},#{revision_link2}", + 'commit:691322a8eb01e11fd7' => changeset_link2, # documents 'document#1' => document_link, 'document:"Test document"' => document_link, @@ -314,6 +329,7 @@ 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".", 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",", 'source:/some/file@52' => link_to('source:/some/file@52', source_url_with_rev, :class => 'source'), + 'source:/some/file@branch' => link_to('source:/some/file@branch', source_url_with_branch, :class => 'source'), 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_rev_and_ext, :class => 'source'), 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url + "#L110", :class => 'source'), 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext + "#L110", :class => 'source'), @@ -323,6 +339,7 @@ 'export:/some/file.ext' => link_to('export:/some/file.ext', export_url_with_ext, :class => 'source download'), 'export:/some/file@52' => link_to('export:/some/file@52', export_url_with_rev, :class => 'source download'), 'export:/some/file.ext@52' => link_to('export:/some/file.ext@52', export_url_with_rev_and_ext, :class => 'source download'), + 'export:/some/file@branch' => link_to('export:/some/file@branch', export_url_with_branch, :class => 'source download'), # forum 'forum#2' => link_to('Discussion', board_url, :class => 'board'), 'forum:Discussion' => link_to('Discussion', board_url, :class => 'board'), @@ -553,9 +570,8 @@ end def test_attachment_links - attachment_link = link_to('error281.txt', {:controller => 'attachments', :action => 'download', :id => '1'}, :class => 'attachment') to_test = { - 'attachment:error281.txt' => attachment_link + 'attachment:error281.txt' => 'error281.txt' } to_test.each { |text, result| assert_equal "

      #{result}

      ", textilizable(text, :attachments => Issue.find(3).attachments), "#{text} failed" } end @@ -565,7 +581,7 @@ a1 = Attachment.generate!(:filename => "test.txt", :created_on => 1.hour.ago) a2 = Attachment.generate!(:filename => "test.txt") - assert_equal %(

      test.txt

      ), + assert_equal %(

      test.txt

      ), textilizable('attachment:test.txt', :attachments => [a1, a2]) end @@ -741,7 +757,7 @@ expected = <<-EXPECTED

      CookBook documentation

      -

      #1

      +

      #1

       [[CookBook documentation]]
       
      @@ -1076,6 +1092,26 @@
           assert_equal ::I18n.t(:label_user_anonymous), t
         end
       
      +  def test_link_to_attachment
      +    a = Attachment.find(3)
      +    assert_equal 'logo.gif',
      +      link_to_attachment(a)
      +    assert_equal 'Text',
      +      link_to_attachment(a, :text => 'Text')
      +    assert_equal 'logo.gif',
      +      link_to_attachment(a, :class => 'foo')
      +    assert_equal 'logo.gif',
      +      link_to_attachment(a, :download => true)
      +    assert_equal 'logo.gif',
      +      link_to_attachment(a, :only_path => false)
      +  end
      +
      +  def test_thumbnail_tag
      +    a = Attachment.find(3)
      +    assert_equal '3',
      +      thumbnail_tag(a)
      +  end
      +
         def test_link_to_project
           project = Project.find(1)
           assert_equal %(eCookbook),
      @@ -1088,6 +1124,17 @@
                        link_to_project(project, {:action => 'settings'}, :class => "project")
         end
       
      +  def test_link_to_project_settings
      +    project = Project.find(1)
      +    assert_equal 'eCookbook', link_to_project_settings(project)
      +
      +    project.status = Project::STATUS_CLOSED
      +    assert_equal 'eCookbook', link_to_project_settings(project)
      +
      +    project.status = Project::STATUS_ARCHIVED
      +    assert_equal 'eCookbook', link_to_project_settings(project)
      +  end
      +
         def test_link_to_legacy_project_with_numerical_identifier_should_use_id
           # numeric identifier are no longer allowed
           Project.update_all "identifier=25", "id=1"
      @@ -1163,19 +1210,4 @@
         def test_javascript_include_tag_for_plugin_should_pick_the_plugin_javascript
           assert_match 'src="/plugin_assets/foo/javascripts/scripts.js"', javascript_include_tag("scripts", :plugin => :foo)
         end
      -
      -  def test_per_page_links_should_show_usefull_values
      -    set_language_if_valid 'en'
      -    stubs(:link_to).returns("[link]")
      -
      -    with_settings :per_page_options => '10, 25, 50, 100' do
      -      assert_nil per_page_links(10, 3)
      -      assert_nil per_page_links(25, 3)
      -      assert_equal "Per page: 10, [link]", per_page_links(10, 22)
      -      assert_equal "Per page: [link], 25", per_page_links(25, 22)
      -      assert_equal "Per page: [link], [link], 50", per_page_links(50, 22)
      -      assert_equal "Per page: [link], 25, [link]", per_page_links(25, 26)
      -      assert_equal "Per page: [link], 25, [link], [link]", per_page_links(25, 120)
      -    end
      -  end
       end
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/helpers/custom_fields_helper_test.rb
      --- a/test/unit/helpers/custom_fields_helper_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/helpers/custom_fields_helper_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/helpers/issues_helper_test.rb
      --- a/test/unit/helpers/issues_helper_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/helpers/issues_helper_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      @@ -30,7 +30,6 @@
                  :member_roles,
                  :members,
                  :enabled_modules,
      -           :workflows,
                  :custom_fields,
                  :attachments,
                  :versions
      @@ -64,164 +63,132 @@
           assert_equal l(:text_issues_destroy_confirmation), issues_destroy_confirmation_message(Issue.find([1, 2]))
         end
       
      -  context "IssuesHelper#show_detail" do
      -    context "with no_html" do
      -      should 'show a changing attribute' do
      -        @detail = JournalDetail.new(:property => 'attr', :old_value => '40', :value => '100', :prop_key => 'done_ratio')
      -        assert_equal "% Done changed from 40 to 100", show_detail(@detail, true)
      -      end
      +  test 'IssuesHelper#show_detail with no_html should show a changing attribute' do
      +    detail = JournalDetail.new(:property => 'attr', :old_value => '40', :value => '100', :prop_key => 'done_ratio')
      +    assert_equal "% Done changed from 40 to 100", show_detail(detail, true)
      +  end
       
      -      should 'show a new attribute' do
      -        @detail = JournalDetail.new(:property => 'attr', :old_value => nil, :value => '100', :prop_key => 'done_ratio')
      -        assert_equal "% Done set to 100", show_detail(@detail, true)
      -      end
      +  test 'IssuesHelper#show_detail with no_html should show a new attribute' do
      +    detail = JournalDetail.new(:property => 'attr', :old_value => nil, :value => '100', :prop_key => 'done_ratio')
      +    assert_equal "% Done set to 100", show_detail(detail, true)
      +  end
       
      -      should 'show a deleted attribute' do
      -        @detail = JournalDetail.new(:property => 'attr', :old_value => '50', :value => nil, :prop_key => 'done_ratio')
      -        assert_equal "% Done deleted (50)", show_detail(@detail, true)
      -      end
      -    end
      +  test 'IssuesHelper#show_detail with no_html should show a deleted attribute' do
      +    detail = JournalDetail.new(:property => 'attr', :old_value => '50', :value => nil, :prop_key => 'done_ratio')
      +    assert_equal "% Done deleted (50)", show_detail(detail, true)
      +  end
       
      -    context "with html" do
      -      should 'show a changing attribute with HTML highlights' do
      -        @detail = JournalDetail.new(:property => 'attr', :old_value => '40', :value => '100', :prop_key => 'done_ratio')
      -        html = show_detail(@detail, false)
      +  test 'IssuesHelper#show_detail with html should show a changing attribute with HTML highlights' do
      +    detail = JournalDetail.new(:property => 'attr', :old_value => '40', :value => '100', :prop_key => 'done_ratio')
      +    html = show_detail(detail, false)
       
      -        assert_include '% Done', html
      -        assert_include '40', html
      -        assert_include '100', html
      -      end
      +    assert_include '% Done', html
      +    assert_include '40', html
      +    assert_include '100', html
      +  end
       
      -      should 'show a new attribute with HTML highlights' do
      -        @detail = JournalDetail.new(:property => 'attr', :old_value => nil, :value => '100', :prop_key => 'done_ratio')
      -        html = show_detail(@detail, false)
      +  test 'IssuesHelper#show_detail with html should show a new attribute with HTML highlights' do
      +    detail = JournalDetail.new(:property => 'attr', :old_value => nil, :value => '100', :prop_key => 'done_ratio')
      +    html = show_detail(detail, false)
       
      -        assert_include '% Done', html
      -        assert_include '100', html
      -      end
      +    assert_include '% Done', html
      +    assert_include '100', html
      +  end
       
      -      should 'show a deleted attribute with HTML highlights' do
      -        @detail = JournalDetail.new(:property => 'attr', :old_value => '50', :value => nil, :prop_key => 'done_ratio')
      -        html = show_detail(@detail, false)
      +  test 'IssuesHelper#show_detail with html should show a deleted attribute with HTML highlights' do
      +    detail = JournalDetail.new(:property => 'attr', :old_value => '50', :value => nil, :prop_key => 'done_ratio')
      +    html = show_detail(detail, false)
       
      -        assert_include '% Done', html
      -        assert_include '50', html
      -      end
      -    end
      +    assert_include '% Done', html
      +    assert_include '50', html
      +  end
       
      -    context "with a start_date attribute" do
      -      should "format the current date" do
      -        @detail = JournalDetail.new(
      -                   :property  => 'attr',
      -                   :old_value => '2010-01-01',
      -                   :value     => '2010-01-31',
      -                   :prop_key  => 'start_date'
      -                )
      -        with_settings :date_format => '%m/%d/%Y' do
      -          assert_match "01/31/2010", show_detail(@detail, true)
      -        end
      -      end
      -
      -      should "format the old date" do
      -        @detail = JournalDetail.new(
      -                   :property  => 'attr',
      -                   :old_value => '2010-01-01',
      -                   :value     => '2010-01-31',
      -                   :prop_key  => 'start_date'
      -                )
      -        with_settings :date_format => '%m/%d/%Y' do
      -          assert_match "01/01/2010", show_detail(@detail, true)
      -        end
      -      end
      -    end
      -
      -    context "with a due_date attribute" do
      -      should "format the current date" do
      -        @detail = JournalDetail.new(
      -                  :property  => 'attr',
      -                  :old_value => '2010-01-01',
      -                  :value     => '2010-01-31',
      -                  :prop_key  => 'due_date'
      -                )
      -        with_settings :date_format => '%m/%d/%Y' do
      -          assert_match "01/31/2010", show_detail(@detail, true)
      -        end
      -      end
      -
      -      should "format the old date" do
      -        @detail = JournalDetail.new(
      -                  :property  => 'attr',
      -                  :old_value => '2010-01-01',
      -                  :value     => '2010-01-31',
      -                  :prop_key  => 'due_date'
      -                )
      -        with_settings :date_format => '%m/%d/%Y' do
      -          assert_match "01/01/2010", show_detail(@detail, true)
      -        end
      -      end
      -    end
      -
      -    should "show old and new values with a project attribute" do
      -      detail = JournalDetail.new(:property => 'attr', :prop_key => 'project_id', :old_value => 1, :value => 2)
      -      assert_match 'eCookbook', show_detail(detail, true)
      -      assert_match 'OnlineStore', show_detail(detail, true)
      -    end
      -
      -    should "show old and new values with a issue status attribute" do
      -      detail = JournalDetail.new(:property => 'attr', :prop_key => 'status_id', :old_value => 1, :value => 2)
      -      assert_match 'New', show_detail(detail, true)
      -      assert_match 'Assigned', show_detail(detail, true)
      -    end
      -
      -    should "show old and new values with a tracker attribute" do
      -      detail = JournalDetail.new(:property => 'attr', :prop_key => 'tracker_id', :old_value => 1, :value => 2)
      -      assert_match 'Bug', show_detail(detail, true)
      -      assert_match 'Feature request', show_detail(detail, true)
      -    end
      -
      -    should "show old and new values with a assigned to attribute" do
      -      detail = JournalDetail.new(:property => 'attr', :prop_key => 'assigned_to_id', :old_value => 1, :value => 2)
      -      assert_match 'redMine Admin', show_detail(detail, true)
      -      assert_match 'John Smith', show_detail(detail, true)
      -    end
      -
      -    should "show old and new values with a priority attribute" do
      -      detail = JournalDetail.new(:property => 'attr', :prop_key => 'priority_id', :old_value => 4, :value => 5)
      -      assert_match 'Low', show_detail(detail, true)
      -      assert_match 'Normal', show_detail(detail, true)
      -    end
      -
      -    should "show old and new values with a category attribute" do
      -      detail = JournalDetail.new(:property => 'attr', :prop_key => 'category_id', :old_value => 1, :value => 2)
      -      assert_match 'Printing', show_detail(detail, true)
      -      assert_match 'Recipes', show_detail(detail, true)
      -    end
      -
      -    should "show old and new values with a fixed version attribute" do
      -      detail = JournalDetail.new(:property => 'attr', :prop_key => 'fixed_version_id', :old_value => 1, :value => 2)
      -      assert_match '0.1', show_detail(detail, true)
      -      assert_match '1.0', show_detail(detail, true)
      -    end
      -
      -    should "show old and new values with a estimated hours attribute" do
      -      detail = JournalDetail.new(:property => 'attr', :prop_key => 'estimated_hours', :old_value => '5', :value => '6.3')
      -      assert_match '5.00', show_detail(detail, true)
      -      assert_match '6.30', show_detail(detail, true)
      -    end
      -
      -    should "show old and new values with a custom field" do
      -      detail = JournalDetail.new(:property => 'cf', :prop_key => '1', :old_value => 'MySQL', :value => 'PostgreSQL')
      -      assert_equal 'Database changed from MySQL to PostgreSQL', show_detail(detail, true)
      -    end
      -
      -    should "show added file" do
      -      detail = JournalDetail.new(:property => 'attachment', :prop_key => '1', :old_value => nil, :value => 'error281.txt')
      -      assert_match 'error281.txt', show_detail(detail, true)
      -    end
      -
      -    should "show removed file" do
      -      detail = JournalDetail.new(:property => 'attachment', :prop_key => '1', :old_value => 'error281.txt', :value => nil)
      -      assert_match 'error281.txt', show_detail(detail, true)
      +  test 'IssuesHelper#show_detail with a start_date attribute should format the dates' do
      +    detail = JournalDetail.new(
      +               :property  => 'attr',
      +               :old_value => '2010-01-01',
      +               :value     => '2010-01-31',
      +               :prop_key  => 'start_date'
      +            )
      +    with_settings :date_format => '%m/%d/%Y' do
      +      assert_match "01/31/2010", show_detail(detail, true)
      +      assert_match "01/01/2010", show_detail(detail, true)
           end
         end
      +
      +  test 'IssuesHelper#show_detail with a due_date attribute should format the dates' do
      +    detail = JournalDetail.new(
      +              :property  => 'attr',
      +              :old_value => '2010-01-01',
      +              :value     => '2010-01-31',
      +              :prop_key  => 'due_date'
      +            )
      +    with_settings :date_format => '%m/%d/%Y' do
      +      assert_match "01/31/2010", show_detail(detail, true)
      +      assert_match "01/01/2010", show_detail(detail, true)
      +    end
      +  end
      +
      +  test 'IssuesHelper#show_detail should show old and new values with a project attribute' do
      +    detail = JournalDetail.new(:property => 'attr', :prop_key => 'project_id', :old_value => 1, :value => 2)
      +    assert_match 'eCookbook', show_detail(detail, true)
      +    assert_match 'OnlineStore', show_detail(detail, true)
      +  end
      +
      +  test 'IssuesHelper#show_detail should show old and new values with a issue status attribute' do
      +    detail = JournalDetail.new(:property => 'attr', :prop_key => 'status_id', :old_value => 1, :value => 2)
      +    assert_match 'New', show_detail(detail, true)
      +    assert_match 'Assigned', show_detail(detail, true)
      +  end
      +
      +  test 'IssuesHelper#show_detail should show old and new values with a tracker attribute' do
      +    detail = JournalDetail.new(:property => 'attr', :prop_key => 'tracker_id', :old_value => 1, :value => 2)
      +    assert_match 'Bug', show_detail(detail, true)
      +    assert_match 'Feature request', show_detail(detail, true)
      +  end
      +
      +  test 'IssuesHelper#show_detail should show old and new values with a assigned to attribute' do
      +    detail = JournalDetail.new(:property => 'attr', :prop_key => 'assigned_to_id', :old_value => 1, :value => 2)
      +    assert_match 'Redmine Admin', show_detail(detail, true)
      +    assert_match 'John Smith', show_detail(detail, true)
      +  end
      +
      +  test 'IssuesHelper#show_detail should show old and new values with a priority attribute' do
      +    detail = JournalDetail.new(:property => 'attr', :prop_key => 'priority_id', :old_value => 4, :value => 5)
      +    assert_match 'Low', show_detail(detail, true)
      +    assert_match 'Normal', show_detail(detail, true)
      +  end
      +
      +  test 'IssuesHelper#show_detail should show old and new values with a category attribute' do
      +    detail = JournalDetail.new(:property => 'attr', :prop_key => 'category_id', :old_value => 1, :value => 2)
      +    assert_match 'Printing', show_detail(detail, true)
      +    assert_match 'Recipes', show_detail(detail, true)
      +  end
      +
      +  test 'IssuesHelper#show_detail should show old and new values with a fixed version attribute' do
      +    detail = JournalDetail.new(:property => 'attr', :prop_key => 'fixed_version_id', :old_value => 1, :value => 2)
      +    assert_match '0.1', show_detail(detail, true)
      +    assert_match '1.0', show_detail(detail, true)
      +  end
      +
      +  test 'IssuesHelper#show_detail should show old and new values with a estimated hours attribute' do
      +    detail = JournalDetail.new(:property => 'attr', :prop_key => 'estimated_hours', :old_value => '5', :value => '6.3')
      +    assert_match '5.00', show_detail(detail, true)
      +    assert_match '6.30', show_detail(detail, true)
      +  end
      +
      +  test 'IssuesHelper#show_detail should show old and new values with a custom field' do
      +    detail = JournalDetail.new(:property => 'cf', :prop_key => '1', :old_value => 'MySQL', :value => 'PostgreSQL')
      +    assert_equal 'Database changed from MySQL to PostgreSQL', show_detail(detail, true)
      +  end
      +
      +  test 'IssuesHelper#show_detail should show added file' do
      +    detail = JournalDetail.new(:property => 'attachment', :prop_key => '1', :old_value => nil, :value => 'error281.txt')
      +    assert_match 'error281.txt', show_detail(detail, true)
      +  end
      +
      +  test 'IssuesHelper#show_detail should show removed file' do
      +    detail = JournalDetail.new(:property => 'attachment', :prop_key => '1', :old_value => 'error281.txt', :value => nil)
      +    assert_match 'error281.txt', show_detail(detail, true)
      +  end
       end
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/helpers/projects_helper_test.rb
      --- a/test/unit/helpers/projects_helper_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/helpers/projects_helper_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      @@ -29,8 +29,7 @@
                  :member_roles,
                  :members,
                  :groups_users,
      -           :enabled_modules,
      -           :workflows
      +           :enabled_modules
       
         def setup
           super
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/helpers/queries_helper_test.rb
      --- a/test/unit/helpers/queries_helper_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/helpers/queries_helper_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      @@ -29,37 +29,41 @@
                  :projects_trackers,
                  :custom_fields_trackers
       
      -  def test_order
      -    User.current = User.find_by_login('admin')
      -    query = Query.new(:project => nil, :name => '_')
      -    assert_equal 30, query.available_filters.size
      +  def test_filters_options_should_be_ordered
      +    set_language_if_valid 'en'
      +    query = IssueQuery.new
      +    filter_count = query.available_filters.size
           fo = filters_options(query)
      -    assert_equal 31, fo.size
      +    assert_equal filter_count + 1, fo.size
           assert_equal [], fo[0]
      -    assert_equal "status_id", fo[1][1]
      -    assert_equal "project_id", fo[2][1]
      -    assert_equal "tracker_id", fo[3][1]
      -    assert_equal "priority_id", fo[4][1]
      -    assert_equal "watcher_id", fo[17][1]
      -    assert_equal "is_private", fo[18][1]
      +
      +    expected_order = [
      +      "Status",
      +      "Project",
      +      "Tracker",
      +      "Priority"
      +    ]
      +    assert_equal expected_order, (fo.map(&:first) & expected_order)
         end
       
      -  def test_order_custom_fields
      +  def test_filters_options_should_be_ordered_with_custom_fields
           set_language_if_valid 'en'
      -    field = UserCustomField.new(
      +    field = UserCustomField.create!(
                     :name => 'order test', :field_format => 'string',
                     :is_for_all => true, :is_filter => true
                   )
      -    assert field.save
      -    User.current = User.find_by_login('admin')
      -    query = Query.new(:project => nil, :name => '_')
      -    assert_equal 32, query.available_filters.size
      +    query = IssueQuery.new
      +    filter_count = query.available_filters.size
           fo = filters_options(query)
      -    assert_equal 33, fo.size
      -    assert_equal "Searchable field", fo[19][0]
      -    assert_equal "Database", fo[20][0]
      -    assert_equal "Project's Development status", fo[21][0]
      -    assert_equal "Assignee's order test", fo[22][0]
      -    assert_equal "Author's order test", fo[23][0]
      +    assert_equal filter_count + 1, fo.size
      +
      +    expected_order = [
      +      "Searchable field",
      +      "Database",
      +      "Project's Development status",
      +      "Author's order test",
      +      "Assignee's order test"
      +    ]
      +    assert_equal expected_order, (fo.map(&:first) & expected_order)
         end
       end
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/helpers/search_helper_test.rb
      --- a/test/unit/helpers/search_helper_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/helpers/search_helper_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,7 +1,7 @@
       # encoding: utf-8
       #
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/helpers/sort_helper_test.rb
      --- a/test/unit/helpers/sort_helper_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/helpers/sort_helper_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      @@ -30,21 +30,21 @@
           sort_init 'attr1', 'desc'
           sort_update(['attr1', 'attr2'])
       
      -    assert_equal 'attr1 DESC', sort_clause
      +    assert_equal ['attr1 DESC'], sort_clause
         end
       
         def test_default_sort_clause_with_hash
           sort_init 'attr1', 'desc'
           sort_update({'attr1' => 'table1.attr1', 'attr2' => 'table2.attr2'})
       
      -    assert_equal 'table1.attr1 DESC', sort_clause
      +    assert_equal ['table1.attr1 DESC'], sort_clause
         end
       
         def test_default_sort_clause_with_multiple_columns
           sort_init 'attr1', 'desc'
           sort_update({'attr1' => ['table1.attr1', 'table1.attr2'], 'attr2' => 'table2.attr2'})
       
      -    assert_equal 'table1.attr1 DESC, table1.attr2 DESC', sort_clause
      +    assert_equal ['table1.attr1 DESC', 'table1.attr2 DESC'], sort_clause
         end
       
         def test_params_sort
      @@ -53,7 +53,7 @@
           sort_init 'attr1', 'desc'
           sort_update({'attr1' => 'table1.attr1', 'attr2' => 'table2.attr2'})
       
      -    assert_equal 'table1.attr1, table2.attr2 DESC', sort_clause
      +    assert_equal ['table1.attr1', 'table2.attr2 DESC'], sort_clause
           assert_equal 'attr1,attr2:desc', @session['foo_bar_sort']
         end
       
      @@ -63,7 +63,7 @@
           sort_init 'attr1', 'desc'
           sort_update({'attr1' => 'table1.attr1', 'attr2' => 'table2.attr2'})
       
      -    assert_equal 'table1.attr1 DESC', sort_clause
      +    assert_equal ['table1.attr1 DESC'], sort_clause
           assert_equal 'attr1:desc', @session['foo_bar_sort']
         end
       
      @@ -73,7 +73,7 @@
           sort_init 'attr1', 'desc'
           sort_update({'attr1' => 'table1.attr1', 'attr2' => 'table2.attr2'})
       
      -    assert_equal 'table1.attr1, table2.attr2', sort_clause
      +    assert_equal ['table1.attr1', 'table2.attr2'], sort_clause
           assert_equal 'attr1,attr2', @session['foo_bar_sort']
         end
       
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/helpers/timelog_helper_test.rb
      --- a/test/unit/helpers/timelog_helper_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/helpers/timelog_helper_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/helpers/watchers_helper_test.rb
      --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
      +++ b/test/unit/helpers/watchers_helper_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -0,0 +1,69 @@
      +# Redmine - project management software
      +# Copyright (C) 2006-2013  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.expand_path('../../../test_helper', __FILE__)
      +
      +class WatchersHelperTest < ActionView::TestCase
      +  include WatchersHelper
      +  include Redmine::I18n
      +
      +  fixtures :users, :issues
      +
      +  def setup
      +    super
      +    set_language_if_valid('en')
      +    User.current = nil
      +  end
      +
      +  test '#watcher_link with a non-watched object' do
      +    expected = link_to(
      +      "Watch",
      +      "/watchers/watch?object_id=1&object_type=issue",
      +      :remote => true, :method => 'post', :class => "issue-1-watcher icon icon-fav-off"
      +    )
      +    assert_equal expected, watcher_link(Issue.find(1), User.find(1))
      +  end
      +
      +  test '#watcher_link with a single objet array' do
      +    expected = link_to(
      +      "Watch",
      +      "/watchers/watch?object_id=1&object_type=issue",
      +      :remote => true, :method => 'post', :class => "issue-1-watcher icon icon-fav-off"
      +    )
      +    assert_equal expected, watcher_link([Issue.find(1)], User.find(1))
      +  end
      +
      +  test '#watcher_link with a multiple objets array' do
      +    expected = link_to(
      +      "Watch",
      +      "/watchers/watch?object_id%5B%5D=1&object_id%5B%5D=3&object_type=issue",
      +      :remote => true, :method => 'post', :class => "issue-bulk-watcher icon icon-fav-off"
      +    )
      +    assert_equal expected, watcher_link([Issue.find(1), Issue.find(3)], User.find(1))
      +  end
      +
      +  test '#watcher_link with a watched object' do
      +    Watcher.create!(:watchable => Issue.find(1), :user => User.find(1))
      +
      +    expected = link_to(
      +      "Unwatch",
      +      "/watchers/watch?object_id=1&object_type=issue",
      +      :remote => true, :method => 'delete', :class => "issue-1-watcher icon icon-fav"
      +    )
      +    assert_equal expected, watcher_link(Issue.find(1), User.find(1))
      +  end
      +end
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/initializers/patches_test.rb
      --- a/test/unit/initializers/patches_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/initializers/patches_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/issue_category_test.rb
      --- a/test/unit/issue_category_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/issue_category_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/issue_nested_set_test.rb
      --- a/test/unit/issue_nested_set_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/issue_nested_set_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      @@ -18,14 +18,11 @@
       require File.expand_path('../../test_helper', __FILE__)
       
       class IssueNestedSetTest < ActiveSupport::TestCase
      -  fixtures :projects, :users, :members, :member_roles, :roles,
      +  fixtures :projects, :users, :roles,
                  :trackers, :projects_trackers,
      -           :versions,
      -           :issue_statuses, :issue_categories, :issue_relations, :workflows,
      +           :issue_statuses, :issue_categories, :issue_relations,
                  :enumerations,
      -           :issues,
      -           :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values,
      -           :time_entries
      +           :issues
       
         def test_create_root_issue
           issue1 = Issue.generate!
      @@ -169,22 +166,6 @@
           assert_not_nil child.errors[:parent_issue_id]
         end
       
      -  def test_moving_an_issue_should_keep_valid_relations_only
      -    issue1 = Issue.generate!
      -    issue2 = Issue.generate!
      -    issue3 = Issue.generate!(:parent_issue_id => issue2.id)
      -    issue4 = Issue.generate!
      -    r1 = IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES)
      -    r2 = IssueRelation.create!(:issue_from => issue1, :issue_to => issue3, :relation_type => IssueRelation::TYPE_PRECEDES)
      -    r3 = IssueRelation.create!(:issue_from => issue2, :issue_to => issue4, :relation_type => IssueRelation::TYPE_PRECEDES)
      -    issue2.reload
      -    issue2.parent_issue_id = issue1.id
      -    issue2.save!
      -    assert !IssueRelation.exists?(r1.id)
      -    assert !IssueRelation.exists?(r2.id)
      -    assert IssueRelation.exists?(r3.id)
      -  end
      -
         def test_destroy_should_destroy_children
           issue1 = Issue.generate!
           issue2 = Issue.generate!
      @@ -367,7 +348,7 @@
           c.reload
       
           assert_equal 5, c.issues.count
      -    ic1, ic2, ic3, ic4, ic5 = c.issues.find(:all, :order => 'subject')
      +    ic1, ic2, ic3, ic4, ic5 = c.issues.order('subject').all
           assert ic1.root?
           assert_equal ic1, ic2.parent
           assert_equal ic1, ic3.parent
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/issue_priority_test.rb
      --- a/test/unit/issue_priority_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/issue_priority_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/issue_relation_test.rb
      --- a/test/unit/issue_relation_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/issue_relation_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      @@ -30,6 +30,8 @@
                  :enumerations,
                  :trackers
       
      +  include Redmine::I18n
      +
         def test_create
           from = Issue.find(1)
           to = Issue.find(2)
      @@ -115,6 +117,39 @@
           assert_not_nil r.errors[:base]
         end
       
      +  def test_validates_circular_dependency_of_subtask
      +    set_language_if_valid 'en'
      +    issue1 = Issue.generate!
      +    issue2 = Issue.generate!
      +    IssueRelation.create!(
      +      :issue_from => issue1, :issue_to => issue2,
      +      :relation_type => IssueRelation::TYPE_PRECEDES
      +    )
      +    child = Issue.generate!(:parent_issue_id => issue2.id)
      +    issue1.reload
      +    child.reload
      +
      +    r = IssueRelation.new(
      +          :issue_from => child, :issue_to => issue1,
      +          :relation_type => IssueRelation::TYPE_PRECEDES
      +        )
      +    assert !r.save
      +    assert_include 'This relation would create a circular dependency', r.errors.full_messages
      +  end
      +
      +  def test_subtasks_should_allow_precedes_relation
      +    parent = Issue.generate!
      +    child1 = Issue.generate!(:parent_issue_id => parent.id)
      +    child2 = Issue.generate!(:parent_issue_id => parent.id)
      +
      +    r = IssueRelation.new(
      +          :issue_from => child1, :issue_to => child2,
      +          :relation_type => IssueRelation::TYPE_PRECEDES
      +        )
      +    assert r.valid?
      +    assert r.save
      +  end
      +
         def test_validates_circular_dependency_on_reverse_relations
           IssueRelation.delete_all
           assert IssueRelation.create!(
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/issue_status_test.rb
      --- a/test/unit/issue_status_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/issue_status_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/issue_test.rb
      --- a/test/unit/issue_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/issue_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      @@ -35,6 +35,19 @@
           User.current = nil
         end
       
      +  def test_initialize
      +    issue = Issue.new
      +
      +    assert_nil issue.project_id
      +    assert_nil issue.tracker_id
      +    assert_nil issue.author_id
      +    assert_nil issue.assigned_to_id
      +    assert_nil issue.category_id
      +
      +    assert_equal IssueStatus.default, issue.status
      +    assert_equal IssuePriority.default, issue.priority
      +  end
      +
         def test_create
           issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
                             :status_id => 1, :priority => IssuePriority.all.first,
      @@ -79,6 +92,15 @@
           assert_include 'Due date must be greater than start date', issue.errors.full_messages
         end
       
      +  def test_estimated_hours_should_be_validated
      +    set_language_if_valid 'en'
      +    ['-2'].each do |invalid|
      +      issue = Issue.new(:estimated_hours => invalid)
      +      assert !issue.valid?
      +      assert_include 'Estimated time is invalid', issue.errors.full_messages
      +    end
      +  end
      +
         def test_create_with_required_custom_field
           set_language_if_valid 'en'
           field = IssueCustomField.find_by_name('Database')
      @@ -300,6 +322,16 @@
           assert_equal issues, issues.select(&:closed?)
         end
       
      +  def test_fixed_version_scope_with_a_version_should_return_its_fixed_issues
      +    version = Version.find(2)
      +    assert version.fixed_issues.any?
      +    assert_equal version.fixed_issues.to_a.sort, Issue.fixed_version(version).to_a.sort
      +  end
      +
      +  def test_fixed_version_scope_with_empty_array_should_return_no_result
      +    assert_equal 0, Issue.fixed_version([]).count
      +  end
      +
         def test_errors_full_messages_should_include_custom_fields_errors
           field = IssueCustomField.find_by_name('Database')
       
      @@ -402,6 +434,21 @@
           assert_equal 'MySQL', issue.custom_field_value(1)
         end
       
      +  def test_reload_should_reload_custom_field_values
      +    issue = Issue.generate!
      +    issue.custom_field_values = {'2' => 'Foo'}
      +    issue.save!
      +
      +    issue = Issue.order('id desc').first
      +    assert_equal 'Foo', issue.custom_field_value(2)
      +
      +    issue.custom_field_values = {'2' => 'Bar'}
      +    assert_equal 'Bar', issue.custom_field_value(2)
      +
      +    issue.reload
      +    assert_equal 'Foo', issue.custom_field_value(2)
      +  end
      +
         def test_should_update_issue_with_disabled_tracker
           p = Project.find(1)
           issue = Issue.find(1)
      @@ -802,6 +849,49 @@
           assert_equal copy.author, child_copy.author
         end
       
      +  def test_copy_as_a_child_of_copied_issue_should_not_copy_itself
      +    parent = Issue.generate!
      +    child1 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 1')
      +    child2 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 2')
      +
      +    copy = parent.reload.copy
      +    copy.parent_issue_id = parent.id
      +    copy.author = User.find(7)
      +    assert_difference 'Issue.count', 3 do
      +      assert copy.save
      +    end
      +    parent.reload
      +    copy.reload
      +    assert_equal parent, copy.parent
      +    assert_equal 3, parent.children.count
      +    assert_equal 5, parent.descendants.count
      +    assert_equal 2, copy.children.count
      +    assert_equal 2, copy.descendants.count
      +  end
      +
      +  def test_copy_as_a_descendant_of_copied_issue_should_not_copy_itself
      +    parent = Issue.generate!
      +    child1 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 1')
      +    child2 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 2')
      +
      +    copy = parent.reload.copy
      +    copy.parent_issue_id = child1.id
      +    copy.author = User.find(7)
      +    assert_difference 'Issue.count', 3 do
      +      assert copy.save
      +    end
      +    parent.reload
      +    child1.reload
      +    copy.reload
      +    assert_equal child1, copy.parent
      +    assert_equal 2, parent.children.count
      +    assert_equal 5, parent.descendants.count
      +    assert_equal 1, child1.children.count
      +    assert_equal 3, child1.descendants.count
      +    assert_equal 2, copy.children.count
      +    assert_equal 2, copy.descendants.count
      +  end
      +
         def test_copy_should_copy_subtasks_to_target_project
           issue = Issue.generate_with_descendants!
       
      @@ -876,8 +966,8 @@
           assert issue1.reload.duplicates.include?(issue2)
       
           # Closing issue 1
      -    issue1.init_journal(User.find(:first), "Closing issue1")
      -    issue1.status = IssueStatus.find :first, :conditions => {:is_closed => true}
      +    issue1.init_journal(User.first, "Closing issue1")
      +    issue1.status = IssueStatus.where(:is_closed => true).first
           assert issue1.save
           # 2 and 3 should be also closed
           assert issue2.reload.closed?
      @@ -895,8 +985,8 @@
           assert !issue2.reload.duplicates.include?(issue1)
       
           # Closing issue 2
      -    issue2.init_journal(User.find(:first), "Closing issue2")
      -    issue2.status = IssueStatus.find :first, :conditions => {:is_closed => true}
      +    issue2.init_journal(User.first, "Closing issue2")
      +    issue2.status = IssueStatus.where(:is_closed => true).first
           assert issue2.save
           # 1 should not be also closed
           assert !issue1.reload.closed?
      @@ -1110,57 +1200,51 @@
           assert_nil copy.custom_value_for(2)
         end
       
      -  context "#copy" do
      -    setup do
      -      @issue = Issue.find(1)
      -    end
      +  test "#copy should not create a journal" do
      +    copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :assigned_to_id => 3)
      +    copy.save!
      +    assert_equal 0, copy.reload.journals.size
      +  end
       
      -    should "not create a journal" do
      -      copy = @issue.copy(:project_id => 3, :tracker_id => 2, :assigned_to_id => 3)
      -      copy.save!
      -      assert_equal 0, copy.reload.journals.size
      -    end
      +  test "#copy should allow assigned_to changes" do
      +    copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :assigned_to_id => 3)
      +    assert_equal 3, copy.assigned_to_id
      +  end
       
      -    should "allow assigned_to changes" do
      -      copy = @issue.copy(:project_id => 3, :tracker_id => 2, :assigned_to_id => 3)
      -      assert_equal 3, copy.assigned_to_id
      -    end
      +  test "#copy should allow status changes" do
      +    copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :status_id => 2)
      +    assert_equal 2, copy.status_id
      +  end
       
      -    should "allow status changes" do
      -      copy = @issue.copy(:project_id => 3, :tracker_id => 2, :status_id => 2)
      -      assert_equal 2, copy.status_id
      -    end
      +  test "#copy should allow start date changes" do
      +    date = Date.today
      +    copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :start_date => date)
      +    assert_equal date, copy.start_date
      +  end
       
      -    should "allow start date changes" do
      -      date = Date.today
      -      copy = @issue.copy(:project_id => 3, :tracker_id => 2, :start_date => date)
      -      assert_equal date, copy.start_date
      -    end
      +  test "#copy should allow due date changes" do
      +    date = Date.today
      +    copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :due_date => date)
      +    assert_equal date, copy.due_date
      +  end
       
      -    should "allow due date changes" do
      -      date = Date.today
      -      copy = @issue.copy(:project_id => 3, :tracker_id => 2, :due_date => date)
      -      assert_equal date, copy.due_date
      -    end
      +  test "#copy should set current user as author" do
      +    User.current = User.find(9)
      +    copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2)
      +    assert_equal User.current, copy.author
      +  end
       
      -    should "set current user as author" do
      -      User.current = User.find(9)
      -      copy = @issue.copy(:project_id => 3, :tracker_id => 2)
      -      assert_equal User.current, copy.author
      -    end
      +  test "#copy should create a journal with notes" do
      +    date = Date.today
      +    notes = "Notes added when copying"
      +    copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :start_date => date)
      +    copy.init_journal(User.current, notes)
      +    copy.save!
       
      -    should "create a journal with notes" do
      -      date = Date.today
      -      notes = "Notes added when copying"
      -      copy = @issue.copy(:project_id => 3, :tracker_id => 2, :start_date => date)
      -      copy.init_journal(User.current, notes)
      -      copy.save!
      -
      -      assert_equal 1, copy.journals.size
      -      journal = copy.journals.first
      -      assert_equal 0, journal.details.size
      -      assert_equal notes, journal.notes
      -    end
      +    assert_equal 1, copy.journals.size
      +    journal = copy.journals.first
      +    assert_equal 0, journal.details.size
      +    assert_equal notes, journal.notes
         end
       
         def test_valid_parent_project
      @@ -1425,99 +1509,139 @@
           end
         end
       
      +  def test_child_issue_should_consider_parent_soonest_start_on_create
      +    set_language_if_valid 'en'
      +    issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
      +    issue2 = Issue.generate!(:start_date => '2012-10-18', :due_date => '2012-10-20')
      +    IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
      +                          :relation_type => IssueRelation::TYPE_PRECEDES)
      +    issue1.reload
      +    issue2.reload
      +    assert_equal Date.parse('2012-10-18'), issue2.start_date
      +
      +    child = Issue.new(:parent_issue_id => issue2.id, :start_date => '2012-10-16',
      +      :project_id => 1, :tracker_id => 1, :status_id => 1, :subject => 'Child', :author_id => 1)
      +    assert !child.valid?
      +    assert_include 'Start date is invalid', child.errors.full_messages
      +    assert_equal Date.parse('2012-10-18'), child.soonest_start
      +    child.start_date = '2012-10-18'
      +    assert child.save
      +  end
      +
      +  def test_setting_parent_to_a_dependent_issue_should_not_validate
      +    set_language_if_valid 'en'
      +    issue1 = Issue.generate!
      +    issue2 = Issue.generate!
      +    issue3 = Issue.generate!
      +    IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES)
      +    IssueRelation.create!(:issue_from => issue3, :issue_to => issue1, :relation_type => IssueRelation::TYPE_PRECEDES)
      +    issue3.reload
      +    issue3.parent_issue_id = issue2.id
      +    assert !issue3.valid?
      +    assert_include 'Parent task is invalid', issue3.errors.full_messages
      +  end
      +
      +  def test_setting_parent_should_not_allow_circular_dependency
      +    set_language_if_valid 'en'
      +    issue1 = Issue.generate!
      +    issue2 = Issue.generate!
      +    IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES)
      +    issue3 = Issue.generate!
      +    issue2.reload
      +    issue2.parent_issue_id = issue3.id
      +    issue2.save!
      +    issue4 = Issue.generate!
      +    IssueRelation.create!(:issue_from => issue3, :issue_to => issue4, :relation_type => IssueRelation::TYPE_PRECEDES)
      +    issue4.reload
      +    issue4.parent_issue_id = issue1.id
      +    assert !issue4.valid?
      +    assert_include 'Parent task is invalid', issue4.errors.full_messages
      +  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})
      +                      :status => IssueStatus.where(:is_closed => true).first
                             ).overdue?
         end
       
      -  context "#behind_schedule?" do
      -    should "be false if the issue has no start_date" do
      -      assert !Issue.new(:start_date => nil,
      -                        :due_date => 1.day.from_now.to_date,
      -                        :done_ratio => 0).behind_schedule?
      -    end
      +  test "#behind_schedule? should be false if the issue has no start_date" do
      +    assert !Issue.new(:start_date => nil,
      +                      :due_date => 1.day.from_now.to_date,
      +                      :done_ratio => 0).behind_schedule?
      +  end
       
      -    should "be false if the issue has no end_date" do
      -      assert !Issue.new(:start_date => 1.day.from_now.to_date,
      -                        :due_date => nil,
      -                        :done_ratio => 0).behind_schedule?
      -    end
      +  test "#behind_schedule? should be false if the issue has no end_date" do
      +    assert !Issue.new(:start_date => 1.day.from_now.to_date,
      +                      :due_date => nil,
      +                      :done_ratio => 0).behind_schedule?
      +  end
       
      -    should "be false if the issue has more done than it's calendar time" do
      -      assert !Issue.new(:start_date => 50.days.ago.to_date,
      -                        :due_date => 50.days.from_now.to_date,
      -                        :done_ratio => 90).behind_schedule?
      -    end
      +  test "#behind_schedule? should be false if the issue has more done than it's calendar time" do
      +    assert !Issue.new(:start_date => 50.days.ago.to_date,
      +                      :due_date => 50.days.from_now.to_date,
      +                      :done_ratio => 90).behind_schedule?
      +  end
       
      -    should "be true if the issue hasn't been started at all" do
      -      assert Issue.new(:start_date => 1.day.ago.to_date,
      -                       :due_date => 1.day.from_now.to_date,
      -                       :done_ratio => 0).behind_schedule?
      -    end
      +  test "#behind_schedule? should be true if the issue hasn't been started at all" do
      +    assert Issue.new(:start_date => 1.day.ago.to_date,
      +                     :due_date => 1.day.from_now.to_date,
      +                     :done_ratio => 0).behind_schedule?
      +  end
       
      -    should "be true if the issue has used more calendar time than it's done ratio" do
      -      assert Issue.new(:start_date => 100.days.ago.to_date,
      -                       :due_date => Date.today,
      -                       :done_ratio => 90).behind_schedule?
      +  test "#behind_schedule? should be true if the issue has used more calendar time than it's done ratio" do
      +    assert Issue.new(:start_date => 100.days.ago.to_date,
      +                     :due_date => Date.today,
      +                     :done_ratio => 90).behind_schedule?
      +  end
      +
      +  test "#assignable_users should be Users" do
      +    assert_kind_of User, Issue.find(1).assignable_users.first
      +  end
      +
      +  test "#assignable_users should include the issue author" do
      +    non_project_member = User.generate!
      +    issue = Issue.generate!(:author => non_project_member)
      +
      +    assert issue.assignable_users.include?(non_project_member)
      +  end
      +
      +  test "#assignable_users should include the current assignee" do
      +    user = User.generate!
      +    issue = Issue.generate!(:assigned_to => user)
      +    user.lock!
      +
      +    assert Issue.find(issue.id).assignable_users.include?(user)
      +  end
      +
      +  test "#assignable_users should not show the issue author twice" do
      +    assignable_user_ids = Issue.find(1).assignable_users.collect(&:id)
      +    assert_equal 2, assignable_user_ids.length
      +
      +    assignable_user_ids.each do |user_id|
      +      assert_equal 1, assignable_user_ids.select {|i| i == user_id}.length,
      +                   "User #{user_id} appears more or less than once"
           end
         end
       
      -  context "#assignable_users" do
      -    should "be Users" do
      -      assert_kind_of User, Issue.find(1).assignable_users.first
      +  test "#assignable_users with issue_group_assignment should include groups" do
      +    issue = Issue.new(:project => Project.find(2))
      +
      +    with_settings :issue_group_assignment => '1' do
      +      assert_equal %w(Group User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
      +      assert issue.assignable_users.include?(Group.find(11))
           end
      +  end
       
      -    should "include the issue author" do
      -      non_project_member = User.generate!
      -      issue = Issue.generate!(:author => non_project_member)
      +  test "#assignable_users without issue_group_assignment should not include groups" do
      +    issue = Issue.new(:project => Project.find(2))
       
      -      assert issue.assignable_users.include?(non_project_member)
      -    end
      -
      -    should "include the current assignee" do
      -      user = User.generate!
      -      issue = Issue.generate!(:assigned_to => user)
      -      user.lock!
      -
      -      assert Issue.find(issue.id).assignable_users.include?(user)
      -    end
      -
      -    should "not show the issue author twice" do
      -      assignable_user_ids = Issue.find(1).assignable_users.collect(&:id)
      -      assert_equal 2, assignable_user_ids.length
      -
      -      assignable_user_ids.each do |user_id|
      -        assert_equal 1, assignable_user_ids.select {|i| i == user_id}.length,
      -                     "User #{user_id} appears more or less than once"
      -      end
      -    end
      -
      -    context "with issue_group_assignment" do
      -      should "include groups" do
      -        issue = Issue.new(:project => Project.find(2))
      -
      -        with_settings :issue_group_assignment => '1' do
      -          assert_equal %w(Group User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
      -          assert issue.assignable_users.include?(Group.find(11))
      -        end
      -      end
      -    end
      -
      -    context "without issue_group_assignment" do
      -      should "not include groups" do
      -        issue = Issue.new(:project => Project.find(2))
      -
      -        with_settings :issue_group_assignment => '0' do
      -          assert_equal %w(User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
      -          assert !issue.assignable_users.include?(Group.find(11))
      -        end
      -      end
      +    with_settings :issue_group_assignment => '0' do
      +      assert_equal %w(User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
      +      assert !issue.assignable_users.include?(Group.find(11))
           end
         end
       
      @@ -1630,7 +1754,7 @@
         end
       
         def test_saving_twice_should_not_duplicate_journal_details
      -    i = Issue.find(:first)
      +    i = Issue.first
           i.init_journal(User.find(2), 'Some notes')
           # initial changes
           i.subject = 'New subject'
      @@ -1639,7 +1763,7 @@
             assert i.save
           end
           # 1 more change
      -    i.priority = IssuePriority.find(:first, :conditions => ["id <> ?", i.priority_id])
      +    i.priority = IssuePriority.where("id <> ?", i.priority_id).first
           assert_no_difference 'Journal.count' do
             assert_difference 'JournalDetail.count', 1 do
               i.save
      @@ -1710,79 +1834,47 @@
           assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
         end
       
      -  context "#done_ratio" do
      -    setup do
      -      @issue = Issue.find(1)
      -      @issue_status = IssueStatus.find(1)
      -      @issue_status.update_attribute(:default_done_ratio, 50)
      -      @issue2 = Issue.find(2)
      -      @issue_status2 = IssueStatus.find(2)
      -      @issue_status2.update_attribute(:default_done_ratio, 0)
      +  test "#done_ratio should use the issue_status according to Setting.issue_done_ratio" do
      +    @issue = Issue.find(1)
      +    @issue_status = IssueStatus.find(1)
      +    @issue_status.update_attribute(:default_done_ratio, 50)
      +    @issue2 = Issue.find(2)
      +    @issue_status2 = IssueStatus.find(2)
      +    @issue_status2.update_attribute(:default_done_ratio, 0)
      +
      +    with_settings :issue_done_ratio => 'issue_field' do
      +      assert_equal 0, @issue.done_ratio
      +      assert_equal 30, @issue2.done_ratio
           end
       
      -    teardown do
      -      Setting.issue_done_ratio = 'issue_field'
      -    end
      -
      -    context "with Setting.issue_done_ratio using the issue_field" do
      -      setup do
      -        Setting.issue_done_ratio = 'issue_field'
      -      end
      -
      -      should "read the issue's field" do
      -        assert_equal 0, @issue.done_ratio
      -        assert_equal 30, @issue2.done_ratio
      -      end
      -    end
      -
      -    context "with Setting.issue_done_ratio using the issue_status" do
      -      setup do
      -        Setting.issue_done_ratio = 'issue_status'
      -      end
      -
      -      should "read the Issue Status's default done ratio" do
      -        assert_equal 50, @issue.done_ratio
      -        assert_equal 0, @issue2.done_ratio
      -      end
      +    with_settings :issue_done_ratio => 'issue_status' do
      +      assert_equal 50, @issue.done_ratio
      +      assert_equal 0, @issue2.done_ratio
           end
         end
       
      -  context "#update_done_ratio_from_issue_status" do
      -    setup do
      -      @issue = Issue.find(1)
      -      @issue_status = IssueStatus.find(1)
      -      @issue_status.update_attribute(:default_done_ratio, 50)
      -      @issue2 = Issue.find(2)
      -      @issue_status2 = IssueStatus.find(2)
      -      @issue_status2.update_attribute(:default_done_ratio, 0)
      +  test "#update_done_ratio_from_issue_status should update done_ratio according to Setting.issue_done_ratio" do
      +    @issue = Issue.find(1)
      +    @issue_status = IssueStatus.find(1)
      +    @issue_status.update_attribute(:default_done_ratio, 50)
      +    @issue2 = Issue.find(2)
      +    @issue_status2 = IssueStatus.find(2)
      +    @issue_status2.update_attribute(:default_done_ratio, 0)
      +
      +    with_settings :issue_done_ratio => 'issue_field' do
      +      @issue.update_done_ratio_from_issue_status
      +      @issue2.update_done_ratio_from_issue_status
      +
      +      assert_equal 0, @issue.read_attribute(:done_ratio)
      +      assert_equal 30, @issue2.read_attribute(:done_ratio)
           end
       
      -    context "with Setting.issue_done_ratio using the issue_field" do
      -      setup do
      -        Setting.issue_done_ratio = 'issue_field'
      -      end
      +    with_settings :issue_done_ratio => 'issue_status' do
      +      @issue.update_done_ratio_from_issue_status
      +      @issue2.update_done_ratio_from_issue_status
       
      -      should "not change the issue" do
      -        @issue.update_done_ratio_from_issue_status
      -        @issue2.update_done_ratio_from_issue_status
      -
      -        assert_equal 0, @issue.read_attribute(:done_ratio)
      -        assert_equal 30, @issue2.read_attribute(:done_ratio)
      -      end
      -    end
      -
      -    context "with Setting.issue_done_ratio using the issue_status" do
      -      setup do
      -        Setting.issue_done_ratio = 'issue_status'
      -      end
      -
      -      should "change the issue's done ratio" do
      -        @issue.update_done_ratio_from_issue_status
      -        @issue2.update_done_ratio_from_issue_status
      -
      -        assert_equal 50, @issue.read_attribute(:done_ratio)
      -        assert_equal 0, @issue2.read_attribute(:done_ratio)
      -      end
      +      assert_equal 50, @issue.read_attribute(:done_ratio)
      +      assert_equal 0, @issue2.read_attribute(:done_ratio)
           end
         end
       
      @@ -1855,48 +1947,42 @@
           assert_equal before, Issue.on_active_project.length
         end
       
      -  context "Issue#recipients" do
      -    setup do
      -      @project = Project.find(1)
      -      @author = User.generate!
      -      @assignee = User.generate!
      -      @issue = Issue.generate!(:project => @project, :assigned_to => @assignee, :author => @author)
      +  test "Issue#recipients should include project recipients" do
      +    issue = Issue.generate!
      +    assert issue.project.recipients.present?
      +    issue.project.recipients.each do |project_recipient|
      +      assert issue.recipients.include?(project_recipient)
           end
      +  end
       
      -    should "include project recipients" do
      -      assert @project.recipients.present?
      -      @project.recipients.each do |project_recipient|
      -        assert @issue.recipients.include?(project_recipient)
      -      end
      -    end
      +  test "Issue#recipients should include the author if the author is active" do
      +    issue = Issue.generate!(:author => User.generate!)
      +    assert issue.author, "No author set for Issue"
      +    assert issue.recipients.include?(issue.author.mail)
      +  end
       
      -    should "include the author if the author is active" do
      -      assert @issue.author, "No author set for Issue"
      -      assert @issue.recipients.include?(@issue.author.mail)
      -    end
      +  test "Issue#recipients should include the assigned to user if the assigned to user is active" do
      +    issue = Issue.generate!(:assigned_to => User.generate!)
      +    assert issue.assigned_to, "No assigned_to set for Issue"
      +    assert issue.recipients.include?(issue.assigned_to.mail)
      +  end
       
      -    should "include the assigned to user if the assigned to user is active" do
      -      assert @issue.assigned_to, "No assigned_to set for Issue"
      -      assert @issue.recipients.include?(@issue.assigned_to.mail)
      -    end
      +  test "Issue#recipients should not include users who opt out of all email" do
      +    issue = Issue.generate!(:author => User.generate!)
      +    issue.author.update_attribute(:mail_notification, :none)
      +    assert !issue.recipients.include?(issue.author.mail)
      +  end
       
      -    should "not include users who opt out of all email" do
      -      @author.update_attribute(:mail_notification, :none)
      +  test "Issue#recipients should not include the issue author if they are only notified of assigned issues" do
      +    issue = Issue.generate!(:author => User.generate!)
      +    issue.author.update_attribute(:mail_notification, :only_assigned)
      +    assert !issue.recipients.include?(issue.author.mail)
      +  end
       
      -      assert !@issue.recipients.include?(@issue.author.mail)
      -    end
      -
      -    should "not include the issue author if they are only notified of assigned issues" do
      -      @author.update_attribute(:mail_notification, :only_assigned)
      -
      -      assert !@issue.recipients.include?(@issue.author.mail)
      -    end
      -
      -    should "not include the assigned user if they are only notified of owned issues" do
      -      @assignee.update_attribute(:mail_notification, :only_owner)
      -
      -      assert !@issue.recipients.include?(@issue.assigned_to.mail)
      -    end
      +  test "Issue#recipients should not include the assigned user if they are only notified of owned issues" do
      +    issue = Issue.generate!(:assigned_to => User.generate!)
      +    issue.assigned_to.update_attribute(:mail_notification, :only_owner)
      +    assert !issue.recipients.include?(issue.assigned_to.mail)
         end
       
         def test_last_journal_id_with_journals_should_return_the_journal_id
      @@ -1916,6 +2002,12 @@
           assert_equal [Journal.find(1), Journal.find(2)], Issue.find(1).journals_after('')
         end
       
      +  def test_css_classes_should_include_tracker
      +    issue = Issue.new(:tracker => Tracker.find(2))
      +    classes = issue.css_classes.split(' ')
      +    assert_include 'tracker-2', classes
      +  end
      +
         def test_css_classes_should_include_priority
           issue = Issue.new(:priority => IssuePriority.find(8))
           classes = issue.css_classes.split(' ')
      @@ -1936,4 +2028,77 @@
           assert_equal 3, issue.reload.attachments.count
           assert_equal %w(upload foo bar), issue.attachments.map(&:filename)
         end
      +
      +  def test_closed_on_should_be_nil_when_creating_an_open_issue
      +    issue = Issue.generate!(:status_id => 1).reload
      +    assert !issue.closed?
      +    assert_nil issue.closed_on
      +  end
      +
      +  def test_closed_on_should_be_set_when_creating_a_closed_issue
      +    issue = Issue.generate!(:status_id => 5).reload
      +    assert issue.closed?
      +    assert_not_nil issue.closed_on
      +    assert_equal issue.updated_on, issue.closed_on
      +    assert_equal issue.created_on, issue.closed_on
      +  end
      +
      +  def test_closed_on_should_be_nil_when_updating_an_open_issue
      +    issue = Issue.find(1)
      +    issue.subject = 'Not closed yet'
      +    issue.save!
      +    issue.reload
      +    assert_nil issue.closed_on
      +  end
      +
      +  def test_closed_on_should_be_set_when_closing_an_open_issue
      +    issue = Issue.find(1)
      +    issue.subject = 'Now closed'
      +    issue.status_id = 5
      +    issue.save!
      +    issue.reload
      +    assert_not_nil issue.closed_on
      +    assert_equal issue.updated_on, issue.closed_on
      +  end
      +
      +  def test_closed_on_should_not_be_updated_when_updating_a_closed_issue
      +    issue = Issue.open(false).first
      +    was_closed_on = issue.closed_on
      +    assert_not_nil was_closed_on
      +    issue.subject = 'Updating a closed issue'
      +    issue.save!
      +    issue.reload
      +    assert_equal was_closed_on, issue.closed_on
      +  end
      +
      +  def test_closed_on_should_be_preserved_when_reopening_a_closed_issue
      +    issue = Issue.open(false).first
      +    was_closed_on = issue.closed_on
      +    assert_not_nil was_closed_on
      +    issue.subject = 'Reopening a closed issue'
      +    issue.status_id = 1
      +    issue.save!
      +    issue.reload
      +    assert !issue.closed?
      +    assert_equal was_closed_on, issue.closed_on
      +  end
      +
      +  def test_status_was_should_return_nil_for_new_issue
      +    issue = Issue.new
      +    assert_nil issue.status_was
      +  end
      +
      +  def test_status_was_should_return_status_before_change
      +    issue = Issue.find(1)
      +    issue.status = IssueStatus.find(2)
      +    assert_equal IssueStatus.find(1), issue.status_was
      +  end
      +
      +  def test_status_was_should_be_reset_on_save
      +    issue = Issue.find(1)
      +    issue.status = IssueStatus.find(2)
      +    assert_equal IssueStatus.find(1), issue.status_was
      +    assert issue.save!
      +    assert_equal IssueStatus.find(2), issue.status_was
      +  end
       end
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/issue_transaction_test.rb
      --- a/test/unit/issue_transaction_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/issue_transaction_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/journal_observer_test.rb
      --- a/test/unit/journal_observer_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/journal_observer_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      @@ -29,8 +29,8 @@
       
         # context: issue_updated notified_events
         def test_create_should_send_email_notification_with_issue_updated
      -    issue = Issue.find(:first)
      -    user = User.find(:first)
      +    issue = Issue.first
      +    user = User.first
           journal = issue.init_journal(user, issue)
       
           with_settings :notified_events => %w(issue_updated) do
      @@ -40,8 +40,8 @@
         end
       
         def test_create_should_not_send_email_notification_with_notify_set_to_false
      -    issue = Issue.find(:first)
      -    user = User.find(:first)
      +    issue = Issue.first
      +    user = User.first
           journal = issue.init_journal(user, issue)
           journal.notify = false
       
      @@ -52,8 +52,8 @@
         end
       
         def test_create_should_not_send_email_notification_without_issue_updated
      -    issue = Issue.find(:first)
      -    user = User.find(:first)
      +    issue = Issue.first
      +    user = User.first
           journal = issue.init_journal(user, issue)
       
           with_settings :notified_events => [] do
      @@ -64,8 +64,8 @@
       
         # context: issue_note_added notified_events
         def test_create_should_send_email_notification_with_issue_note_added
      -    issue = Issue.find(:first)
      -    user = User.find(:first)
      +    issue = Issue.first
      +    user = User.first
           journal = issue.init_journal(user, issue)
           journal.notes = 'This update has a note'
       
      @@ -76,8 +76,8 @@
         end
       
         def test_create_should_not_send_email_notification_without_issue_note_added
      -    issue = Issue.find(:first)
      -    user = User.find(:first)
      +    issue = Issue.first
      +    user = User.first
           journal = issue.init_journal(user, issue)
           journal.notes = 'This update has a note'
       
      @@ -89,8 +89,8 @@
       
         # context: issue_status_updated notified_events
         def test_create_should_send_email_notification_with_issue_status_updated
      -    issue = Issue.find(:first)
      -    user = User.find(:first)
      +    issue = Issue.first
      +    user = User.first
           issue.init_journal(user, issue)
           issue.status = IssueStatus.last
       
      @@ -101,8 +101,8 @@
         end
       
         def test_create_should_not_send_email_notification_without_issue_status_updated
      -    issue = Issue.find(:first)
      -    user = User.find(:first)
      +    issue = Issue.first
      +    user = User.first
           issue.init_journal(user, issue)
           issue.status = IssueStatus.last
       
      @@ -114,8 +114,8 @@
       
         # context: issue_priority_updated notified_events
         def test_create_should_send_email_notification_with_issue_priority_updated
      -    issue = Issue.find(:first)
      -    user = User.find(:first)
      +    issue = Issue.first
      +    user = User.first
           issue.init_journal(user, issue)
           issue.priority = IssuePriority.last
       
      @@ -126,8 +126,8 @@
         end
       
         def test_create_should_not_send_email_notification_without_issue_priority_updated
      -    issue = Issue.find(:first)
      -    user = User.find(:first)
      +    issue = Issue.first
      +    user = User.first
           issue.init_journal(user, issue)
           issue.priority = IssuePriority.last
       
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/journal_test.rb
      --- a/test/unit/journal_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/journal_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      @@ -41,8 +41,8 @@
       
         def test_create_should_send_email_notification
           ActionMailer::Base.deliveries.clear
      -    issue = Issue.find(:first)
      -    user = User.find(:first)
      +    issue = Issue.first
      +    user = User.first
           journal = issue.init_journal(user, issue)
       
           assert journal.save
      @@ -154,4 +154,25 @@
           # Admin should see issues on private projects that he does not belong to
           assert journals.detect {|journal| !journal.issue.project.is_public?}
         end
      +
      +  def test_details_should_normalize_dates
      +    j = JournalDetail.create!(:old_value => Date.parse('2012-11-03'), :value => Date.parse('2013-01-02'))
      +    j.reload
      +    assert_equal '2012-11-03', j.old_value
      +    assert_equal '2013-01-02', j.value
      +  end
      +
      +  def test_details_should_normalize_true_values
      +    j = JournalDetail.create!(:old_value => true, :value => true)
      +    j.reload
      +    assert_equal '1', j.old_value
      +    assert_equal '1', j.value
      +  end
      +
      +  def test_details_should_normalize_false_values
      +    j = JournalDetail.create!(:old_value => false, :value => false)
      +    j.reload
      +    assert_equal '0', j.old_value
      +    assert_equal '0', j.value
      +  end
       end
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/lib/redmine/access_control_test.rb
      --- a/test/unit/lib/redmine/access_control_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/lib/redmine/access_control_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/lib/redmine/ciphering_test.rb
      --- a/test/unit/lib/redmine/ciphering_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/lib/redmine/ciphering_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/lib/redmine/codeset_util_test.rb
      --- a/test/unit/lib/redmine/codeset_util_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/lib/redmine/codeset_util_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/lib/redmine/configuration_test.rb
      --- a/test/unit/lib/redmine/configuration_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/lib/redmine/configuration_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/lib/redmine/export/pdf_test.rb
      --- a/test/unit/lib/redmine/export/pdf_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/lib/redmine/export/pdf_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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,7 +16,6 @@
       # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
       
       require File.expand_path('../../../../../test_helper', __FILE__)
      -require 'iconv'
       
       class PdfTest < ActiveSupport::TestCase
         fixtures :users, :projects, :roles, :members, :member_roles,
      @@ -36,9 +35,9 @@
             txt_1 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_1, encoding)
             txt_2 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_2, encoding)
             txt_3 = Redmine::Export::PDF::RDMPdfEncoding::rdm_from_utf8(utf8_txt_3, encoding)
      -      assert_equal "?\x91\xd4", txt_1
      -      assert_equal "?\x91\xd4?", txt_2
      -      assert_equal "??\x91\xd4?", txt_3
      +      assert_equal "?\x91\xd4".force_encoding("ASCII-8BIT"), txt_1
      +      assert_equal "?\x91\xd4?".force_encoding("ASCII-8BIT"), txt_2
      +      assert_equal "??\x91\xd4?".force_encoding("ASCII-8BIT"), txt_3
             assert_equal "ASCII-8BIT", txt_1.encoding.to_s
             assert_equal "ASCII-8BIT", txt_2.encoding.to_s
             assert_equal "ASCII-8BIT", txt_3.encoding.to_s
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/lib/redmine/helpers/calendar_test.rb
      --- a/test/unit/lib/redmine/helpers/calendar_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/lib/redmine/helpers/calendar_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/lib/redmine/helpers/gantt_test.rb
      --- a/test/unit/lib/redmine/helpers/gantt_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/lib/redmine/helpers/gantt_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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,7 +26,6 @@
                  :member_roles,
                  :members,
                  :enabled_modules,
      -           :workflows,
                  :versions,
                  :groups_users
       
      @@ -34,6 +33,7 @@
         include ProjectsHelper
         include IssuesHelper
         include ERB::Util
      +  include Rails.application.routes.url_helpers
       
         def setup
           setup_with_controller
      @@ -49,7 +49,7 @@
           @project = project
           @gantt = Redmine::Helpers::Gantt.new(options)
           @gantt.project = @project
      -    @gantt.query = Query.create!(:project => @project, :name => 'Gantt')
      +    @gantt.query = IssueQuery.create!(:project => @project, :name => 'Gantt')
           @gantt.view = self
           @gantt.instance_variable_set('@date_from', options[:date_from] || (today - 14))
           @gantt.instance_variable_set('@date_to', options[:date_to] || (today + 14))
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/lib/redmine/hook_test.rb
      --- a/test/unit/lib/redmine/hook_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/lib/redmine/hook_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      @@ -23,7 +23,7 @@
                  :trackers, :projects_trackers,
                  :enabled_modules,
                  :versions,
      -           :issue_statuses, :issue_categories, :issue_relations, :workflows,
      +           :issue_statuses, :issue_categories, :issue_relations,
                  :enumerations,
                  :issues
       
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/lib/redmine/i18n_test.rb
      --- a/test/unit/lib/redmine/i18n_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/lib/redmine/i18n_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/lib/redmine/info_test.rb
      --- a/test/unit/lib/redmine/info_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/lib/redmine/info_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/lib/redmine/menu_manager/mapper_test.rb
      --- a/test/unit/lib/redmine/menu_manager/mapper_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/lib/redmine/menu_manager/mapper_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/lib/redmine/menu_manager/menu_helper_test.rb
      --- a/test/unit/lib/redmine/menu_manager/menu_helper_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/lib/redmine/menu_manager/menu_helper_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/lib/redmine/menu_manager_test.rb
      --- a/test/unit/lib/redmine/menu_manager_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/lib/redmine/menu_manager_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      @@ -18,11 +18,17 @@
       require File.expand_path('../../../../test_helper', __FILE__)
       
       class Redmine::MenuManagerTest < ActiveSupport::TestCase
      -  context "MenuManager#map" do
      -    should "be tested"
      +  def test_map_should_yield_a_mapper
      +    assert_difference 'Redmine::MenuManager.items(:project_menu).size' do
      +      Redmine::MenuManager.map :project_menu do |mapper|
      +        assert_kind_of  Redmine::MenuManager::Mapper, mapper
      +        mapper.push :new_item, '/'
      +      end
      +    end
         end
       
      -  context "MenuManager#items" do
      -    should "be tested"
      +  def test_items_should_return_menu_items
      +    items = Redmine::MenuManager.items(:project_menu)
      +    assert_kind_of Redmine::MenuManager::MenuNode, items.first
         end
       end
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/lib/redmine/mime_type_test.rb
      --- a/test/unit/lib/redmine/mime_type_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/lib/redmine/mime_type_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/lib/redmine/notifiable_test.rb
      --- a/test/unit/lib/redmine/notifiable_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/lib/redmine/notifiable_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/lib/redmine/pagination_helper_test.rb
      --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
      +++ b/test/unit/lib/redmine/pagination_helper_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -0,0 +1,34 @@
      +# Redmine - project management software
      +# Copyright (C) 2006-2013  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.expand_path('../../../../test_helper', __FILE__)
      +
      +class ApplicationHelperTest < ActionView::TestCase
      +  include Redmine::Pagination::Helper
      +
      +  def test_per_page_options_should_return_usefull_values
      +    with_settings :per_page_options => '10, 25, 50, 100' do
      +      assert_equal [], per_page_options(10, 3)
      +      assert_equal [], per_page_options(25, 3)
      +      assert_equal [10, 25], per_page_options(10, 22)
      +      assert_equal [10, 25], per_page_options(25, 22)
      +      assert_equal [10, 25, 50], per_page_options(50, 22)
      +      assert_equal [10, 25, 50], per_page_options(25, 26)
      +      assert_equal [10, 25, 50, 100], per_page_options(25, 120)
      +    end
      +  end
      +end
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/lib/redmine/pagination_test.rb
      --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
      +++ b/test/unit/lib/redmine/pagination_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -0,0 +1,94 @@
      +# Redmine - project management software
      +# Copyright (C) 2006-2013  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.expand_path('../../../../test_helper', __FILE__)
      +
      +class Redmine::PaginationTest < ActiveSupport::TestCase
      +
      +  def setup
      +    @klass = Redmine::Pagination::Paginator
      +  end
      +
      +  def test_count_is_zero
      +    p = @klass.new 0, 10, 1
      +
      +    assert_equal 0, p.offset
      +    assert_equal 10, p.per_page
      +    %w(first_page previous_page next_page last_page).each do |method|
      +      assert_nil p.send(method), "#{method} was not nil"
      +    end
      +    assert_equal 0, p.first_item
      +    assert_equal 0, p.last_item
      +    assert_equal [], p.linked_pages
      +  end
      +
      +  def test_count_is_less_than_per_page
      +    p = @klass.new 7, 10, 1
      +
      +    assert_equal 0, p.offset
      +    assert_equal 10, p.per_page
      +    assert_equal 1, p.first_page
      +    assert_nil p.previous_page
      +    assert_nil p.next_page
      +    assert_equal 1, p.last_page
      +    assert_equal 1, p.first_item
      +    assert_equal 7, p.last_item
      +    assert_equal [], p.linked_pages
      +  end
      +
      +  def test_count_is_equal_to_per_page
      +    p = @klass.new 10, 10, 1
      +
      +    assert_equal 0, p.offset
      +    assert_equal 10, p.per_page
      +    assert_equal 1, p.first_page
      +    assert_nil p.previous_page
      +    assert_nil p.next_page
      +    assert_equal 1, p.last_page
      +    assert_equal 1, p.first_item
      +    assert_equal 10, p.last_item
      +    assert_equal [], p.linked_pages
      +  end
      +
      +  def test_2_pages
      +    p = @klass.new 16, 10, 1
      +
      +    assert_equal 0, p.offset
      +    assert_equal 10, p.per_page
      +    assert_equal 1, p.first_page
      +    assert_nil p.previous_page
      +    assert_equal 2, p.next_page
      +    assert_equal 2, p.last_page
      +    assert_equal 1, p.first_item
      +    assert_equal 10, p.last_item
      +    assert_equal [1, 2], p.linked_pages
      +  end
      +
      +  def test_many_pages
      +    p = @klass.new 155, 10, 1
      +
      +    assert_equal 0, p.offset
      +    assert_equal 10, p.per_page
      +    assert_equal 1, p.first_page
      +    assert_nil p.previous_page
      +    assert_equal 2, p.next_page
      +    assert_equal 16, p.last_page
      +    assert_equal 1, p.first_item
      +    assert_equal 10, p.last_item
      +    assert_equal [1, 2, 3, 16], p.linked_pages
      +  end
      +end
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/lib/redmine/plugin_test.rb
      --- a/test/unit/lib/redmine/plugin_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/lib/redmine/plugin_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/lib/redmine/safe_attributes_test.rb
      --- a/test/unit/lib/redmine/safe_attributes_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/lib/redmine/safe_attributes_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/lib/redmine/scm/adapters/bazaar_adapter_test.rb
      --- a/test/unit/lib/redmine/scm/adapters/bazaar_adapter_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/lib/redmine/scm/adapters/bazaar_adapter_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/lib/redmine/scm/adapters/cvs_adapter_test.rb
      --- a/test/unit/lib/redmine/scm/adapters/cvs_adapter_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/lib/redmine/scm/adapters/cvs_adapter_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/lib/redmine/scm/adapters/darcs_adapter_test.rb
      --- a/test/unit/lib/redmine/scm/adapters/darcs_adapter_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/lib/redmine/scm/adapters/darcs_adapter_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/lib/redmine/scm/adapters/filesystem_adapter_test.rb
      --- a/test/unit/lib/redmine/scm/adapters/filesystem_adapter_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/lib/redmine/scm/adapters/filesystem_adapter_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/lib/redmine/scm/adapters/git_adapter_test.rb
      --- a/test/unit/lib/redmine/scm/adapters/git_adapter_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/lib/redmine/scm/adapters/git_adapter_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/lib/redmine/scm/adapters/mercurial_adapter_test.rb
      --- a/test/unit/lib/redmine/scm/adapters/mercurial_adapter_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/lib/redmine/scm/adapters/mercurial_adapter_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/lib/redmine/scm/adapters/subversion_adapter_test.rb
      --- a/test/unit/lib/redmine/scm/adapters/subversion_adapter_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/lib/redmine/scm/adapters/subversion_adapter_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/lib/redmine/themes_test.rb
      --- a/test/unit/lib/redmine/themes_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/lib/redmine/themes_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/lib/redmine/unified_diff_test.rb
      --- a/test/unit/lib/redmine/unified_diff_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/lib/redmine/unified_diff_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      @@ -221,6 +221,93 @@
           assert_equal "test02.txt", diff[0].file_name
         end
       
      +  def test_utf8_ja
      +    ja = "  text_tip_issue_end_day: "
      +    ja += "\xe3\x81\x93\xe3\x81\xae\xe6\x97\xa5\xe3\x81\xab\xe7\xb5\x82\xe4\xba\x86\xe3\x81\x99\xe3\x82\x8b\xe3\x82\xbf\xe3\x82\xb9\xe3\x82\xaf"
      +    ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding)
      +    with_settings :repositories_encodings => '' do
      +      diff = Redmine::UnifiedDiff.new(read_diff_fixture('issue-12641-ja.diff'), :type => 'inline')
      +      assert_equal 1, diff.size
      +      assert_equal 12, diff.first.size
      +      assert_equal ja, diff.first[4].html_line_left
      +    end
      +  end
      +
      +  def test_utf8_ru
      +    ru = "        other: "\xd0\xbe\xd0\xba\xd0\xbe\xd0\xbb\xd0\xbe %{count} \xd1\x87\xd0\xb0\xd1\x81\xd0\xb0""
      +    ru.force_encoding('UTF-8') if ru.respond_to?(:force_encoding)
      +    with_settings :repositories_encodings => '' do
      +      diff = Redmine::UnifiedDiff.new(read_diff_fixture('issue-12641-ru.diff'), :type => 'inline')
      +      assert_equal 1, diff.size
      +      assert_equal 8, diff.first.size
      +      assert_equal ru, diff.first[3].html_line_left
      +    end
      +  end
      +
      +  def test_offset_range_ascii_1
      +    raw = <<-DIFF
      +--- a.txt	2013-04-05 14:19:39.000000000 +0900
      ++++ b.txt	2013-04-05 14:19:51.000000000 +0900
      +@@ -1,3 +1,3 @@
      + aaaa
      +-abc
      ++abcd
      + bbbb
      +DIFF
      +    diff = Redmine::UnifiedDiff.new(raw, :type => 'sbs')
      +    assert_equal 1, diff.size
      +    assert_equal 3, diff.first.size
      +    assert_equal "abc", diff.first[1].html_line_left
      +    assert_equal "abcd", diff.first[1].html_line_right
      +  end
      +
      +  def test_offset_range_ascii_2
      +    raw = <<-DIFF
      +--- a.txt	2013-04-05 14:19:39.000000000 +0900
      ++++ b.txt	2013-04-05 14:19:51.000000000 +0900
      +@@ -1,3 +1,3 @@
      + aaaa
      +-abc
      ++zabc
      + bbbb
      +DIFF
      +    diff = Redmine::UnifiedDiff.new(raw, :type => 'sbs')
      +    assert_equal 1, diff.size
      +    assert_equal 3, diff.first.size
      +    assert_equal "abc", diff.first[1].html_line_left
      +    assert_equal "zabc", diff.first[1].html_line_right
      +  end
      +
      +  def test_offset_range_japanese_1
      +    ja1 = "\xe6\x97\xa5\xe6\x9c\xac"
      +    ja1.force_encoding('UTF-8') if ja1.respond_to?(:force_encoding)
      +    ja2 = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"
      +    ja2.force_encoding('UTF-8') if ja2.respond_to?(:force_encoding)
      +    with_settings :repositories_encodings => '' do
      +      diff = Redmine::UnifiedDiff.new(
      +               read_diff_fixture('issue-13644-1.diff'), :type => 'sbs')
      +      assert_equal 1, diff.size
      +      assert_equal 3, diff.first.size
      +      assert_equal ja1, diff.first[1].html_line_left
      +      assert_equal ja2, diff.first[1].html_line_right
      +    end
      +  end
      +
      +  def test_offset_range_japanese_2
      +    ja1 = "\xe6\x97\xa5\xe6\x9c\xac"
      +    ja1.force_encoding('UTF-8') if ja1.respond_to?(:force_encoding)
      +    ja2 = "\xe3\x81\xab\xe3\x81\xa3\xe3\x81\xbd\xe3\x82\x93\xe6\x97\xa5\xe6\x9c\xac"
      +    ja2.force_encoding('UTF-8') if ja2.respond_to?(:force_encoding)
      +    with_settings :repositories_encodings => '' do
      +      diff = Redmine::UnifiedDiff.new(
      +               read_diff_fixture('issue-13644-2.diff'), :type => 'sbs')
      +      assert_equal 1, diff.size
      +      assert_equal 3, diff.first.size
      +      assert_equal ja1, diff.first[1].html_line_left
      +      assert_equal ja2, diff.first[1].html_line_right
      +    end
      +  end
      +
         private
       
         def read_diff_fixture(filename)
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/lib/redmine/utils/date_calculation.rb
      --- a/test/unit/lib/redmine/utils/date_calculation.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/lib/redmine/utils/date_calculation.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/lib/redmine/views/builders/json_test.rb
      --- a/test/unit/lib/redmine/views/builders/json_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/lib/redmine/views/builders/json_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/lib/redmine/views/builders/xml_test.rb
      --- a/test/unit/lib/redmine/views/builders/xml_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/lib/redmine/views/builders/xml_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/lib/redmine/wiki_formatting/macros_test.rb
      --- a/test/unit/lib/redmine/wiki_formatting/macros_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/lib/redmine/wiki_formatting/macros_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/lib/redmine/wiki_formatting/textile_formatter_test.rb
      --- a/test/unit/lib/redmine/wiki_formatting/textile_formatter_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/lib/redmine/wiki_formatting/textile_formatter_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/lib/redmine/wiki_formatting_test.rb
      --- a/test/unit/lib/redmine/wiki_formatting_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/lib/redmine/wiki_formatting_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/lib/redmine_test.rb
      --- a/test/unit/lib/redmine_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/lib/redmine_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/mail_handler_test.rb
      --- a/test/unit/mail_handler_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/mail_handler_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,7 +1,7 @@
       # encoding: utf-8
       #
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      @@ -304,6 +304,51 @@
           end
         end
       
      +  def test_created_user_should_be_added_to_groups
      +    group1 = Group.generate!
      +    group2 = Group.generate!
      +
      +    assert_difference 'User.count' do
      +      submit_email(
      +        'ticket_by_unknown_user.eml',
      +        :issue => {:project => 'ecookbook'},
      +        :unknown_user => 'create',
      +        :default_group => "#{group1.name},#{group2.name}"
      +      )
      +    end
      +    user = User.order('id DESC').first
      +    assert_same_elements [group1, group2], user.groups
      +  end
      +
      +  def test_created_user_should_not_receive_account_information_with_no_account_info_option
      +    assert_difference 'User.count' do
      +      submit_email(
      +        'ticket_by_unknown_user.eml',
      +        :issue => {:project => 'ecookbook'},
      +        :unknown_user => 'create',
      +        :no_account_notice => '1'
      +      )
      +    end
      +
      +    # only 1 email for the new issue notification
      +    assert_equal 1, ActionMailer::Base.deliveries.size
      +    email = ActionMailer::Base.deliveries.first
      +    assert_include 'Ticket by unknown user', email.subject
      +  end
      +
      +  def test_created_user_should_have_mail_notification_to_none_with_no_notification_option
      +    assert_difference 'User.count' do
      +      submit_email(
      +        'ticket_by_unknown_user.eml',
      +        :issue => {:project => 'ecookbook'},
      +        :unknown_user => 'create',
      +        :no_notification => '1'
      +      )
      +    end
      +    user = User.order('id DESC').first
      +    assert_equal 'none', user.mail_notification
      +  end
      +
         def test_add_issue_without_from_header
           Role.anonymous.add_permission!(:add_issues)
           assert_equal false, submit_email('ticket_without_from_header.eml')
      @@ -646,73 +691,55 @@
           assert_equal 'This is a html-only email.', issue.description
         end
       
      -  context "truncate emails based on the Setting" do
      -    context "with no setting" do
      -      setup do
      -        Setting.mail_handler_body_delimiters = ''
      -      end
      +  test "truncate emails with no setting should add the entire email into the issue" do
      +    with_settings :mail_handler_body_delimiters => '' do
      +      issue = submit_email('ticket_on_given_project.eml')
      +      assert_issue_created(issue)
      +      assert issue.description.include?('---')
      +      assert issue.description.include?('This paragraph is after the delimiter')
      +    end
      +  end
       
      -      should "add the entire email into the issue" do
      -        issue = submit_email('ticket_on_given_project.eml')
      -        assert_issue_created(issue)
      -        assert issue.description.include?('---')
      -        assert issue.description.include?('This paragraph is after the delimiter')
      -      end
      +  test "truncate emails with a single string should truncate the email at the delimiter for the issue" do
      +    with_settings :mail_handler_body_delimiters => '---' do
      +      issue = submit_email('ticket_on_given_project.eml')
      +      assert_issue_created(issue)
      +      assert issue.description.include?('This paragraph is before delimiters')
      +      assert issue.description.include?('--- This line starts with a delimiter')
      +      assert !issue.description.match(/^---$/)
      +      assert !issue.description.include?('This paragraph is after the delimiter')
           end
      +  end
       
      -    context "with a single string" do
      -      setup do
      -        Setting.mail_handler_body_delimiters = '---'
      -      end
      -      should "truncate the email at the delimiter for the issue" do
      -        issue = submit_email('ticket_on_given_project.eml')
      -        assert_issue_created(issue)
      -        assert issue.description.include?('This paragraph is before delimiters')
      -        assert issue.description.include?('--- This line starts with a delimiter')
      -        assert !issue.description.match(/^---$/)
      -        assert !issue.description.include?('This paragraph is after the delimiter')
      -      end
      +  test "truncate emails with a single quoted reply should truncate the email at the delimiter with the quoted reply symbols (>)" do
      +    with_settings :mail_handler_body_delimiters => '--- Reply above. Do not remove this line. ---' do
      +      journal = submit_email('issue_update_with_quoted_reply_above.eml')
      +      assert journal.is_a?(Journal)
      +      assert journal.notes.include?('An update to the issue by the sender.')
      +      assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
      +      assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
           end
      +  end
       
      -    context "with a single quoted reply (e.g. reply to a Redmine email notification)" do
      -      setup do
      -        Setting.mail_handler_body_delimiters = '--- Reply above. Do not remove this line. ---'
      -      end
      -      should "truncate the email at the delimiter with the quoted reply symbols (>)" do
      -        journal = submit_email('issue_update_with_quoted_reply_above.eml')
      -        assert journal.is_a?(Journal)
      -        assert journal.notes.include?('An update to the issue by the sender.')
      -        assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
      -        assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
      -      end
      +  test "truncate emails with multiple quoted replies should truncate the email at the delimiter with the quoted reply symbols (>)" do
      +    with_settings :mail_handler_body_delimiters => '--- Reply above. Do not remove this line. ---' do
      +      journal = submit_email('issue_update_with_multiple_quoted_reply_above.eml')
      +      assert journal.is_a?(Journal)
      +      assert journal.notes.include?('An update to the issue by the sender.')
      +      assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
      +      assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
           end
      +  end
       
      -    context "with multiple quoted replies (e.g. reply to a reply of a Redmine email notification)" do
      -      setup do
      -        Setting.mail_handler_body_delimiters = '--- Reply above. Do not remove this line. ---'
      -      end
      -      should "truncate the email at the delimiter with the quoted reply symbols (>)" do
      -        journal = submit_email('issue_update_with_multiple_quoted_reply_above.eml')
      -        assert journal.is_a?(Journal)
      -        assert journal.notes.include?('An update to the issue by the sender.')
      -        assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---"))
      -        assert !journal.notes.include?('Looks like the JSON api for projects was missed.')
      -      end
      -    end
      -
      -    context "with multiple strings" do
      -      setup do
      -        Setting.mail_handler_body_delimiters = "---\nBREAK"
      -      end
      -      should "truncate the email at the first delimiter found (BREAK)" do
      -        issue = submit_email('ticket_on_given_project.eml')
      -        assert_issue_created(issue)
      -        assert issue.description.include?('This paragraph is before delimiters')
      -        assert !issue.description.include?('BREAK')
      -        assert !issue.description.include?('This paragraph is between delimiters')
      -        assert !issue.description.match(/^---$/)
      -        assert !issue.description.include?('This paragraph is after the delimiter')
      -      end
      +  test "truncate emails with multiple strings should truncate the email at the first delimiter found (BREAK)" do
      +    with_settings :mail_handler_body_delimiters => "---\nBREAK" do
      +      issue = submit_email('ticket_on_given_project.eml')
      +      assert_issue_created(issue)
      +      assert issue.description.include?('This paragraph is before delimiters')
      +      assert !issue.description.include?('BREAK')
      +      assert !issue.description.include?('This paragraph is between delimiters')
      +      assert !issue.description.match(/^---$/)
      +      assert !issue.description.include?('This paragraph is after the delimiter')
           end
         end
       
      @@ -741,6 +768,7 @@
             assert_equal expected[0], user.login
             assert_equal expected[1], user.firstname
             assert_equal expected[2], user.lastname
      +      assert_equal 'only_my_events', user.mail_notification
           end
         end
       
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/mailer_test.rb
      --- a/test/unit/mailer_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/mailer_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      @@ -210,7 +210,7 @@
         end
       
         def test_should_not_send_email_without_recipient
      -    news = News.find(:first)
      +    news = News.first
           user = news.author
           # Remove members except news author
           news.project.memberships.each {|m| m.destroy unless m.user == user}
      @@ -279,44 +279,40 @@
           end
         end
       
      -  context("#issue_add") do
      -    setup do
      -      ActionMailer::Base.deliveries.clear
      -      Setting.bcc_recipients = '1'
      -      @issue = Issue.find(1)
      -    end
      +  test "#issue_add should notify project members" do
      +    issue = Issue.find(1)
      +    assert Mailer.issue_add(issue).deliver
      +    assert last_email.bcc.include?('dlopper@somenet.foo')
      +  end
       
      -    should "notify project members" do
      -      assert Mailer.issue_add(@issue).deliver
      -      assert last_email.bcc.include?('dlopper@somenet.foo')
      -    end
      +  test "#issue_add should not notify project members that are not allow to view the issue" do
      +    issue = Issue.find(1)
      +    Role.find(2).remove_permission!(:view_issues)
      +    assert Mailer.issue_add(issue).deliver
      +    assert !last_email.bcc.include?('dlopper@somenet.foo')
      +  end
       
      -    should "not notify project members that are not allow to view the issue" do
      -      Role.find(2).remove_permission!(:view_issues)
      -      assert Mailer.issue_add(@issue).deliver
      -      assert !last_email.bcc.include?('dlopper@somenet.foo')
      -    end
      +  test "#issue_add should notify issue watchers" do
      +    issue = Issue.find(1)
      +    user = User.find(9)
      +    # minimal email notification options
      +    user.pref[:no_self_notified] = '1'
      +    user.pref.save
      +    user.mail_notification = false
      +    user.save
       
      -    should "notify issue watchers" do
      -      user = User.find(9)
      -      # minimal email notification options
      -      user.pref[:no_self_notified] = '1'
      -      user.pref.save
      -      user.mail_notification = false
      -      user.save
      +    Watcher.create!(:watchable => issue, :user => user)
      +    assert Mailer.issue_add(issue).deliver
      +    assert last_email.bcc.include?(user.mail)
      +  end
       
      -      Watcher.create!(:watchable => @issue, :user => user)
      -      assert Mailer.issue_add(@issue).deliver
      -      assert last_email.bcc.include?(user.mail)
      -    end
      -
      -    should "not notify watchers not allowed to view the issue" do
      -      user = User.find(9)
      -      Watcher.create!(:watchable => @issue, :user => user)
      -      Role.non_member.remove_permission!(:view_issues)
      -      assert Mailer.issue_add(@issue).deliver
      -      assert !last_email.bcc.include?(user.mail)
      -    end
      +  test "#issue_add should not notify watchers not allowed to view the issue" do
      +    issue = Issue.find(1)
      +    user = User.find(9)
      +    Watcher.create!(:watchable => issue, :user => user)
      +    Role.non_member.remove_permission!(:view_issues)
      +    assert Mailer.issue_add(issue).deliver
      +    assert !last_email.bcc.include?(user.mail)
         end
       
         # test mailer methods for each language
      @@ -402,7 +398,7 @@
         end
       
         def test_news_added
      -    news = News.find(:first)
      +    news = News.first
           valid_languages.each do |lang|
             Setting.default_language = lang.to_s
             assert Mailer.news_added(news).deliver
      @@ -418,7 +414,7 @@
         end
       
         def test_message_posted
      -    message = Message.find(:first)
      +    message = Message.first
           recipients = ([message.root] + message.root.children).collect {|m| m.author.mail if m.author}
           recipients = recipients.compact.uniq
           valid_languages.each do |lang|
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/member_test.rb
      --- a/test/unit/member_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/member_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      @@ -25,7 +25,6 @@
                  :member_roles,
                  :members,
                  :enabled_modules,
      -           :workflows,
                  :groups_users,
                  :watchers,
                  :journals, :journal_details,
      @@ -122,70 +121,4 @@
           assert_equal -1, a <=> b
           assert_equal 1,  b <=> a
         end
      -
      -  context "removing permissions" do
      -    setup do
      -      Watcher.delete_all("user_id = 9")
      -      user = User.find(9)
      -      # public
      -      Watcher.create!(:watchable => Issue.find(1), :user => user)
      -      # private
      -      Watcher.create!(:watchable => Issue.find(4), :user => user)
      -      Watcher.create!(:watchable => Message.find(7), :user => user)
      -      Watcher.create!(:watchable => Wiki.find(2), :user => user)
      -      Watcher.create!(:watchable => WikiPage.find(3), :user => user)
      -    end
      -
      -    context "of user" do
      -      setup do
      -        @member = Member.create!(:project => Project.find(2), :principal => User.find(9), :role_ids => [1, 2])
      -      end
      -
      -      context "by deleting membership" do
      -        should "prune watchers" do
      -          assert_difference 'Watcher.count', -4 do
      -            @member.destroy
      -          end
      -        end
      -      end
      -
      -      context "by updating roles" do
      -        should "prune watchers" do
      -          Role.find(2).remove_permission! :view_wiki_pages
      -          member = Member.first(:order => 'id desc')
      -          assert_difference 'Watcher.count', -2 do
      -            member.role_ids = [2]
      -            member.save
      -          end
      -          assert !Message.find(7).watched_by?(@user)
      -        end
      -      end
      -    end
      -
      -    context "of group" do
      -      setup do
      -        group = Group.find(10)
      -        @member = Member.create!(:project => Project.find(2), :principal => group, :role_ids => [1, 2])
      -        group.users << User.find(9)
      -      end
      -
      -      context "by deleting membership" do
      -        should "prune watchers" do
      -          assert_difference 'Watcher.count', -4 do
      -            @member.destroy
      -          end
      -        end
      -      end
      -
      -      context "by updating roles" do
      -        should "prune watchers" do
      -          Role.find(2).remove_permission! :view_wiki_pages
      -          assert_difference 'Watcher.count', -2 do
      -            @member.role_ids = [2]
      -            @member.save
      -          end
      -        end
      -      end
      -    end
      -  end
       end
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/message_test.rb
      --- a/test/unit/message_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/message_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      @@ -36,9 +36,9 @@
           assert message.save
           @board.reload
           # topics count incremented
      -    assert_equal topics_count+1, @board[:topics_count]
      +    assert_equal topics_count + 1, @board[:topics_count]
           # messages count incremented
      -    assert_equal messages_count+1, @board[:messages_count]
      +    assert_equal messages_count + 1, @board[:messages_count]
           assert_equal message, @board.last_message
           # author should be watching the message
           assert message.watched_by?(@user)
      @@ -47,13 +47,13 @@
         def test_reply
           topics_count = @board.topics_count
           messages_count = @board.messages_count
      -    @message = Message.find(1)
      -    replies_count = @message.replies_count
      +    message = Message.find(1)
      +    replies_count = message.replies_count
       
           reply_author = User.find(2)
           reply = Message.new(:board => @board, :subject => 'Test reply',
                               :content => 'Test reply content',
      -                        :parent => @message, :author => reply_author)
      +                        :parent => message, :author => reply_author)
           assert reply.save
           @board.reload
           # same topics count
      @@ -61,42 +61,42 @@
           # messages count incremented
           assert_equal messages_count+1, @board[:messages_count]
           assert_equal reply, @board.last_message
      -    @message.reload
      +    message.reload
           # replies count incremented
      -    assert_equal replies_count+1, @message[:replies_count]
      -    assert_equal reply, @message.last_reply
      +    assert_equal replies_count+1, message[:replies_count]
      +    assert_equal reply, message.last_reply
           # author should be watching the message
      -    assert @message.watched_by?(reply_author)
      +    assert message.watched_by?(reply_author)
         end
       
         def test_cannot_reply_to_locked_topic
           topics_count = @board.topics_count
           messages_count = @board.messages_count
      -    @message = Message.find(1)
      -    replies_count = @message.replies_count
      -    assert_equal false, @message.locked
      -    @message.locked = true
      -    assert @message.save
      -    assert_equal true, @message.locked
      +    message = Message.find(1)
      +    replies_count = message.replies_count
      +    assert_equal false, message.locked
      +    message.locked = true
      +    assert message.save
      +    assert_equal true, message.locked
       
           reply_author = User.find(2)
           reply = Message.new(:board => @board, :subject => 'Test reply',
                               :content => 'Test reply content',
      -                        :parent => @message, :author => reply_author)
      +                        :parent => message, :author => reply_author)
           reply.save
           assert_equal 1, reply.errors.count
         end
       
         def test_moving_message_should_update_counters
      -    @message = Message.find(1)
      +    message = Message.find(1)
           assert_no_difference 'Message.count' do
             # Previous board
             assert_difference 'Board.find(1).topics_count', -1 do
      -        assert_difference 'Board.find(1).messages_count', -(1 + @message.replies_count) do
      +        assert_difference 'Board.find(1).messages_count', -(1 + message.replies_count) do
                 # New board
                 assert_difference 'Board.find(2).topics_count' do
      -            assert_difference 'Board.find(2).messages_count', (1 + @message.replies_count) do
      -              @message.update_attributes(:board_id => 2)
      +            assert_difference 'Board.find(2).messages_count', (1 + message.replies_count) do
      +              message.update_attributes(:board_id => 2)
                   end
                 end
               end
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/news_test.rb
      --- a/test/unit/news_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/news_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      @@ -21,7 +21,7 @@
         fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules, :news
       
         def valid_news
      -    { :title => 'Test news', :description => 'Lorem ipsum etc', :author => User.find(:first) }
      +    { :title => 'Test news', :description => 'Lorem ipsum etc', :author => User.first }
         end
       
         def setup
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/principal_test.rb
      --- a/test/unit/principal_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/principal_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,7 +1,7 @@
       # encoding: utf-8
       #
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      @@ -49,64 +49,60 @@
           assert_equal [], Principal.not_member_of([]).sort
         end
       
      -  context "#like" do
      -    setup do
      -      Principal.create!(:login => 'login')
      -      Principal.create!(:login => 'login2')
      +  def test_sorted_scope_should_sort_users_before_groups
      +    scope = Principal.where("type <> ?", 'AnonymousUser')
      +    expected_order = scope.all.sort do |a, b|
      +      if a.is_a?(User) && b.is_a?(Group)
      +        -1
      +      elsif a.is_a?(Group) && b.is_a?(User)
      +        1
      +      else
      +        a.name.downcase <=> b.name.downcase
      +      end
      +    end
      +    assert_equal expected_order.map(&:name).map(&:downcase), scope.sorted.all.map(&:name).map(&:downcase)
      +  end
       
      -      Principal.create!(:firstname => 'firstname')
      -      Principal.create!(:firstname => 'firstname2')
      +  test "like scope should search login" do
      +    results = Principal.like('jsmi')
       
      -      Principal.create!(:lastname => 'lastname')
      -      Principal.create!(:lastname => 'lastname2')
      +    assert results.any?
      +    assert results.all? {|u| u.login.match(/jsmi/i) }
      +  end
       
      -      Principal.create!(:mail => 'mail@example.com')
      -      Principal.create!(:mail => 'mail2@example.com')
      +  test "like scope should search firstname" do
      +    results = Principal.like('john')
       
      -      @palmer = Principal.create!(:firstname => 'David', :lastname => 'Palmer')
      -    end
      +    assert results.any?
      +    assert results.all? {|u| u.firstname.match(/john/i) }
      +  end
       
      -    should "search login" do
      -      results = Principal.like('login')
      +  test "like scope should search lastname" do
      +    results = Principal.like('smi')
       
      -      assert_equal 2, results.count
      -      assert results.all? {|u| u.login.match(/login/) }
      -    end
      +    assert results.any?
      +    assert results.all? {|u| u.lastname.match(/smi/i) }
      +  end
       
      -    should "search firstname" do
      -      results = Principal.like('firstname')
      +  test "like scope should search mail" do
      +    results = Principal.like('somenet')
       
      -      assert_equal 2, results.count
      -      assert results.all? {|u| u.firstname.match(/firstname/) }
      -    end
      +    assert results.any?
      +    assert results.all? {|u| u.mail.match(/somenet/i) }
      +  end
       
      -    should "search lastname" do
      -      results = Principal.like('lastname')
      +  test "like scope should search firstname and lastname" do
      +    results = Principal.like('john smi')
       
      -      assert_equal 2, results.count
      -      assert results.all? {|u| u.lastname.match(/lastname/) }
      -    end
      +    assert_equal 1, results.count
      +    assert_equal User.find(2), results.first
      +  end
       
      -    should "search mail" do
      -      results = Principal.like('mail')
      +  test "like scope should search lastname and firstname" do
      +    results = Principal.like('smith joh')
       
      -      assert_equal 2, results.count
      -      assert results.all? {|u| u.mail.match(/mail/) }
      -    end
      -
      -    should "search firstname and lastname" do
      -      results = Principal.like('david palm')
      -
      -      assert_equal 1, results.count
      -      assert_equal @palmer, results.first
      -    end
      -
      -    should "search lastname and firstname" do
      -      results = Principal.like('palmer davi')
      -
      -      assert_equal 1, results.count
      -      assert_equal @palmer, results.first
      -    end
      +    assert_equal 1, results.count
      +    assert_equal User.find(2), results.first
         end
       
         def test_like_scope_with_cyrillic_name
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/project_copy_test.rb
      --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
      +++ b/test/unit/project_copy_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -0,0 +1,337 @@
      +# Redmine - project management software
      +# Copyright (C) 2006-2013  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.expand_path('../../test_helper', __FILE__)
      +
      +class ProjectCopyTest < ActiveSupport::TestCase
      +  fixtures :projects, :trackers, :issue_statuses, :issues,
      +           :journals, :journal_details,
      +           :enumerations, :users, :issue_categories,
      +           :projects_trackers,
      +           :custom_fields,
      +           :custom_fields_projects,
      +           :custom_fields_trackers,
      +           :custom_values,
      +           :roles,
      +           :member_roles,
      +           :members,
      +           :enabled_modules,
      +           :versions,
      +           :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions,
      +           :groups_users,
      +           :boards, :messages,
      +           :repositories,
      +           :news, :comments,
      +           :documents
      +
      +  def setup
      +    ProjectCustomField.destroy_all
      +    @source_project = Project.find(2)
      +    @project = Project.new(:name => 'Copy Test', :identifier => 'copy-test')
      +    @project.trackers = @source_project.trackers
      +    @project.enabled_module_names = @source_project.enabled_modules.collect(&:name)
      +  end
      +
      +  test "#copy should copy issues" do
      +    @source_project.issues << Issue.generate!(:status => IssueStatus.find_by_name('Closed'),
      +                                              :subject => "copy issue status",
      +                                              :tracker_id => 1,
      +                                              :assigned_to_id => 2,
      +                                              :project_id => @source_project.id)
      +    assert @project.valid?
      +    assert @project.issues.empty?
      +    assert @project.copy(@source_project)
      +
      +    assert_equal @source_project.issues.size, @project.issues.size
      +    @project.issues.each do |issue|
      +      assert issue.valid?
      +      assert ! issue.assigned_to.blank?
      +      assert_equal @project, issue.project
      +    end
      +
      +    copied_issue = @project.issues.first(:conditions => {:subject => "copy issue status"})
      +    assert copied_issue
      +    assert copied_issue.status
      +    assert_equal "Closed", copied_issue.status.name
      +  end
      +
      +  test "#copy should copy issues custom values" do
      +    field = IssueCustomField.generate!(:is_for_all => true, :trackers => Tracker.all)
      +    issue = Issue.generate!(:project => @source_project, :subject => 'Custom field copy')
      +    issue.custom_field_values = {field.id => 'custom'}
      +    issue.save!
      +    assert_equal 'custom', issue.reload.custom_field_value(field)
      +
      +    assert @project.copy(@source_project)
      +    copy = @project.issues.find_by_subject('Custom field copy')
      +    assert copy
      +    assert_equal 'custom', copy.reload.custom_field_value(field)
      +  end
      +
      +  test "#copy should copy issues assigned to a locked version" do
      +    User.current = User.find(1)
      +    assigned_version = Version.generate!(:name => "Assigned Issues")
      +    @source_project.versions << assigned_version
      +    Issue.generate!(:project => @source_project,
      +                    :fixed_version_id => assigned_version.id,
      +                    :subject => "copy issues assigned to a locked version")
      +    assigned_version.update_attribute :status, 'locked'
      +
      +    assert @project.copy(@source_project)
      +    @project.reload
      +    copied_issue = @project.issues.first(:conditions => {:subject => "copy issues assigned to a locked version"})
      +
      +    assert copied_issue
      +    assert copied_issue.fixed_version
      +    assert_equal "Assigned Issues", copied_issue.fixed_version.name # Same name
      +    assert_equal 'locked', copied_issue.fixed_version.status
      +  end
      +
      +  test "#copy should change the new issues to use the copied version" do
      +    User.current = User.find(1)
      +    assigned_version = Version.generate!(:name => "Assigned Issues", :status => 'open')
      +    @source_project.versions << assigned_version
      +    assert_equal 3, @source_project.versions.size
      +    Issue.generate!(:project => @source_project,
      +                    :fixed_version_id => assigned_version.id,
      +                    :subject => "change the new issues to use the copied version")
      +
      +    assert @project.copy(@source_project)
      +    @project.reload
      +    copied_issue = @project.issues.first(:conditions => {:subject => "change the new issues to use the copied version"})
      +
      +    assert copied_issue
      +    assert copied_issue.fixed_version
      +    assert_equal "Assigned Issues", copied_issue.fixed_version.name # Same name
      +    assert_not_equal assigned_version.id, copied_issue.fixed_version.id # Different record
      +  end
      +
      +  test "#copy should keep target shared versions from other project" do
      +    assigned_version = Version.generate!(:name => "Assigned Issues", :status => 'open', :project_id => 1, :sharing => 'system')
      +    issue = Issue.generate!(:project => @source_project,
      +                            :fixed_version => assigned_version,
      +                            :subject => "keep target shared versions")
      +
      +    assert @project.copy(@source_project)
      +    @project.reload
      +    copied_issue = @project.issues.first(:conditions => {:subject => "keep target shared versions"})
      +
      +    assert copied_issue
      +    assert_equal assigned_version, copied_issue.fixed_version
      +  end
      +
      +  test "#copy should copy issue relations" do
      +    Setting.cross_project_issue_relations = '1'
      +
      +    second_issue = Issue.generate!(:status_id => 5,
      +                                   :subject => "copy issue relation",
      +                                   :tracker_id => 1,
      +                                   :assigned_to_id => 2,
      +                                   :project_id => @source_project.id)
      +    source_relation = IssueRelation.create!(:issue_from => Issue.find(4),
      +                                              :issue_to => second_issue,
      +                                              :relation_type => "relates")
      +    source_relation_cross_project = IssueRelation.create!(:issue_from => Issue.find(1),
      +                                                            :issue_to => second_issue,
      +                                                            :relation_type => "duplicates")
      +
      +    assert @project.copy(@source_project)
      +    assert_equal @source_project.issues.count, @project.issues.count
      +    copied_issue = @project.issues.find_by_subject("Issue on project 2") # Was #4
      +    copied_second_issue = @project.issues.find_by_subject("copy issue relation")
      +
      +    # First issue with a relation on project
      +    assert_equal 1, copied_issue.relations.size, "Relation not copied"
      +    copied_relation = copied_issue.relations.first
      +    assert_equal "relates", copied_relation.relation_type
      +    assert_equal copied_second_issue.id, copied_relation.issue_to_id
      +    assert_not_equal source_relation.id, copied_relation.id
      +
      +    # Second issue with a cross project relation
      +    assert_equal 2, copied_second_issue.relations.size, "Relation not copied"
      +    copied_relation = copied_second_issue.relations.select {|r| r.relation_type == 'duplicates'}.first
      +    assert_equal "duplicates", copied_relation.relation_type
      +    assert_equal 1, copied_relation.issue_from_id, "Cross project relation not kept"
      +    assert_not_equal source_relation_cross_project.id, copied_relation.id
      +  end
      +
      +  test "#copy should copy issue attachments" do
      +    issue = Issue.generate!(:subject => "copy with attachment", :tracker_id => 1, :project_id => @source_project.id)
      +    Attachment.create!(:container => issue, :file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 1)
      +    @source_project.issues << issue
      +    assert @project.copy(@source_project)
      +
      +    copied_issue = @project.issues.first(:conditions => {:subject => "copy with attachment"})
      +    assert_not_nil copied_issue
      +    assert_equal 1, copied_issue.attachments.count, "Attachment not copied"
      +    assert_equal "testfile.txt", copied_issue.attachments.first.filename
      +  end
      +
      +  test "#copy should copy memberships" do
      +    assert @project.valid?
      +    assert @project.members.empty?
      +    assert @project.copy(@source_project)
      +
      +    assert_equal @source_project.memberships.size, @project.memberships.size
      +    @project.memberships.each do |membership|
      +      assert membership
      +      assert_equal @project, membership.project
      +    end
      +  end
      +
      +  test "#copy should copy memberships with groups and additional roles" do
      +    group = Group.create!(:lastname => "Copy group")
      +    user = User.find(7)
      +    group.users << user
      +    # group role
      +    Member.create!(:project_id => @source_project.id, :principal => group, :role_ids => [2])
      +    member = Member.find_by_user_id_and_project_id(user.id, @source_project.id)
      +    # additional role
      +    member.role_ids = [1]
      +
      +    assert @project.copy(@source_project)
      +    member = Member.find_by_user_id_and_project_id(user.id, @project.id)
      +    assert_not_nil member
      +    assert_equal [1, 2], member.role_ids.sort
      +  end
      +
      +  test "#copy should copy project specific queries" do
      +    assert @project.valid?
      +    assert @project.queries.empty?
      +    assert @project.copy(@source_project)
      +
      +    assert_equal @source_project.queries.size, @project.queries.size
      +    @project.queries.each do |query|
      +      assert query
      +      assert_equal @project, query.project
      +    end
      +    assert_equal @source_project.queries.map(&:user_id).sort, @project.queries.map(&:user_id).sort
      +  end
      +
      +  test "#copy should copy versions" do
      +    @source_project.versions << Version.generate!
      +    @source_project.versions << Version.generate!
      +
      +    assert @project.versions.empty?
      +    assert @project.copy(@source_project)
      +
      +    assert_equal @source_project.versions.size, @project.versions.size
      +    @project.versions.each do |version|
      +      assert version
      +      assert_equal @project, version.project
      +    end
      +  end
      +
      +  test "#copy should copy wiki" do
      +    assert_difference 'Wiki.count' do
      +      assert @project.copy(@source_project)
      +    end
      +
      +    assert @project.wiki
      +    assert_not_equal @source_project.wiki, @project.wiki
      +    assert_equal "Start page", @project.wiki.start_page
      +  end
      +
      +  test "#copy should copy wiki without wiki module" do
      +    project = Project.new(:name => 'Copy Test', :identifier => 'copy-test', :enabled_module_names => [])
      +    assert_difference 'Wiki.count' do
      +      assert project.copy(@source_project)
      +    end
      +
      +    assert project.wiki
      +  end
      +
      +  test "#copy should copy wiki pages and content with hierarchy" do
      +    assert_difference 'WikiPage.count', @source_project.wiki.pages.size do
      +      assert @project.copy(@source_project)
      +    end
      +
      +    assert @project.wiki
      +    assert_equal @source_project.wiki.pages.size, @project.wiki.pages.size
      +
      +    @project.wiki.pages.each do |wiki_page|
      +      assert wiki_page.content
      +      assert !@source_project.wiki.pages.include?(wiki_page)
      +    end
      +
      +    parent = @project.wiki.find_page('Parent_page')
      +    child1 = @project.wiki.find_page('Child_page_1')
      +    child2 = @project.wiki.find_page('Child_page_2')
      +    assert_equal parent, child1.parent
      +    assert_equal parent, child2.parent
      +  end
      +
      +  test "#copy should copy issue categories" do
      +    assert @project.copy(@source_project)
      +
      +    assert_equal 2, @project.issue_categories.size
      +    @project.issue_categories.each do |issue_category|
      +      assert !@source_project.issue_categories.include?(issue_category)
      +    end
      +  end
      +
      +  test "#copy should copy boards" do
      +    assert @project.copy(@source_project)
      +
      +    assert_equal 1, @project.boards.size
      +    @project.boards.each do |board|
      +      assert !@source_project.boards.include?(board)
      +    end
      +  end
      +
      +  test "#copy should change the new issues to use the copied issue categories" do
      +    issue = Issue.find(4)
      +    issue.update_attribute(:category_id, 3)
      +
      +    assert @project.copy(@source_project)
      +
      +    @project.issues.each do |issue|
      +      assert issue.category
      +      assert_equal "Stock management", issue.category.name # Same name
      +      assert_not_equal IssueCategory.find(3), issue.category # Different record
      +    end
      +  end
      +
      +  test "#copy should limit copy with :only option" do
      +    assert @project.members.empty?
      +    assert @project.issue_categories.empty?
      +    assert @source_project.issues.any?
      +
      +    assert @project.copy(@source_project, :only => ['members', 'issue_categories'])
      +
      +    assert @project.members.any?
      +    assert @project.issue_categories.any?
      +    assert @project.issues.empty?
      +  end
      +
      +  test "#copy should copy subtasks" do
      +    source = Project.generate!(:tracker_ids => [1])
      +    issue = Issue.generate_with_descendants!(:project => source)
      +    project = Project.new(:name => 'Copy', :identifier => 'copy', :tracker_ids => [1])
      +
      +    assert_difference 'Project.count' do
      +      assert_difference 'Issue.count', 1+issue.descendants.count do
      +        assert project.copy(source.reload)
      +      end
      +    end
      +    copy = Issue.where(:parent_id => nil).order("id DESC").first
      +    assert_equal project, copy.project
      +    assert_equal issue.descendants.count, copy.descendants.count
      +    child_copy = copy.children.detect {|c| c.subject == 'Child1'}
      +    assert child_copy.descendants.any?
      +  end
      +end
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/project_members_inheritance_test.rb
      --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
      +++ b/test/unit/project_members_inheritance_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -0,0 +1,260 @@
      +# Redmine - project management software
      +# Copyright (C) 2006-2013  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.expand_path('../../test_helper', __FILE__)
      +
      +class ProjectMembersInheritanceTest < ActiveSupport::TestCase
      +  fixtures :roles, :users
      +
      +  def setup
      +    @parent = Project.generate!
      +    @member = Member.create!(:principal => User.find(2), :project => @parent, :role_ids => [1, 2])
      +    assert_equal 2, @member.reload.roles.size
      +  end
      +
      +  def test_project_created_with_inherit_members_disabled_should_not_inherit_members
      +    assert_no_difference 'Member.count' do
      +      project = Project.generate_with_parent!(@parent, :inherit_members => false)
      +
      +      assert_equal 0, project.memberships.count
      +    end
      +  end
      +
      +  def test_project_created_with_inherit_members_should_inherit_members
      +    assert_difference 'Member.count', 1 do
      +      project = Project.generate_with_parent!(@parent, :inherit_members => true)
      +      project.reload
      +
      +      assert_equal 1, project.memberships.count
      +      member = project.memberships.first
      +      assert_equal @member.principal, member.principal
      +      assert_equal @member.roles.sort, member.roles.sort
      +    end
      +  end
      +
      +  def test_turning_on_inherit_members_should_inherit_members
      +    Project.generate_with_parent!(@parent, :inherit_members => false)
      +
      +    assert_difference 'Member.count', 1 do
      +      project = Project.order('id desc').first
      +      project.inherit_members = true
      +      project.save!
      +      project.reload
      +
      +      assert_equal 1, project.memberships.count
      +      member = project.memberships.first
      +      assert_equal @member.principal, member.principal
      +      assert_equal @member.roles.sort, member.roles.sort
      +    end
      +  end
      +
      +  def test_turning_off_inherit_members_should_remove_inherited_members
      +    Project.generate_with_parent!(@parent, :inherit_members => true)
      +
      +    assert_difference 'Member.count', -1 do
      +      project = Project.order('id desc').first
      +      project.inherit_members = false
      +      project.save!
      +      project.reload
      +
      +      assert_equal 0, project.memberships.count
      +    end
      +  end
      +
      +  def test_moving_a_root_project_under_a_parent_should_inherit_members
      +    Project.generate!(:inherit_members => true)
      +    project = Project.order('id desc').first
      +
      +    assert_difference 'Member.count', 1 do
      +      project.set_parent!(@parent)
      +      project.reload
      +
      +      assert_equal 1, project.memberships.count
      +      member = project.memberships.first
      +      assert_equal @member.principal, member.principal
      +      assert_equal @member.roles.sort, member.roles.sort
      +    end
      +  end
      +
      +  def test_moving_a_subproject_as_root_should_loose_inherited_members
      +    Project.generate_with_parent!(@parent, :inherit_members => true)
      +    project = Project.order('id desc').first
      +
      +    assert_difference 'Member.count', -1 do
      +      project.set_parent!(nil)
      +      project.reload
      +
      +      assert_equal 0, project.memberships.count
      +    end
      +  end
      +
      +  def test_moving_a_subproject_to_another_parent_should_change_inherited_members
      +    other_parent = Project.generate!
      +    other_member = Member.create!(:principal => User.find(4), :project => other_parent, :role_ids => [3])
      +
      +    Project.generate_with_parent!(@parent, :inherit_members => true)
      +    project = Project.order('id desc').first
      +    project.set_parent!(other_parent.reload)
      +    project.reload
      +
      +    assert_equal 1, project.memberships.count
      +    member = project.memberships.first
      +    assert_equal other_member.principal, member.principal
      +    assert_equal other_member.roles.sort, member.roles.sort
      +  end
      +
      +  def test_inheritance_should_propagate_to_subprojects
      +    project = Project.generate_with_parent!(@parent, :inherit_members => false)
      +    subproject = Project.generate_with_parent!(project, :inherit_members => true)
      +    project.reload
      +
      +    assert_difference 'Member.count', 2 do
      +      project.inherit_members = true
      +      project.save
      +      project.reload
      +      subproject.reload
      +
      +      assert_equal 1, project.memberships.count
      +      assert_equal 1, subproject.memberships.count
      +      member = subproject.memberships.first
      +      assert_equal @member.principal, member.principal
      +      assert_equal @member.roles.sort, member.roles.sort
      +    end
      +  end
      +
      +  def test_inheritance_removal_should_propagate_to_subprojects
      +    project = Project.generate_with_parent!(@parent, :inherit_members => true)
      +    subproject = Project.generate_with_parent!(project, :inherit_members => true)
      +    project.reload
      +
      +    assert_difference 'Member.count', -2 do
      +      project.inherit_members = false
      +      project.save
      +      project.reload
      +      subproject.reload
      +
      +      assert_equal 0, project.memberships.count
      +      assert_equal 0, subproject.memberships.count
      +    end
      +  end
      +
      +  def test_adding_a_member_should_propagate
      +    project = Project.generate_with_parent!(@parent, :inherit_members => true)
      +
      +    assert_difference 'Member.count', 2 do
      +      member = Member.create!(:principal => User.find(4), :project => @parent, :role_ids => [1, 3])
      +
      +      inherited_member = project.memberships.order('id desc').first
      +      assert_equal member.principal, inherited_member.principal
      +      assert_equal member.roles.sort, inherited_member.roles.sort
      +    end
      +  end
      +
      +  def test_adding_a_member_should_not_propagate_if_child_does_not_inherit
      +    project = Project.generate_with_parent!(@parent, :inherit_members => false)
      +
      +    assert_difference 'Member.count', 1 do
      +      member = Member.create!(:principal => User.find(4), :project => @parent, :role_ids => [1, 3])
      +
      +      assert_nil project.reload.memberships.detect {|m| m.principal == member.principal}
      +    end
      +  end
      +
      +  def test_removing_a_member_should_propagate
      +    project = Project.generate_with_parent!(@parent, :inherit_members => true)
      +
      +    assert_difference 'Member.count', -2 do
      +      @member.reload.destroy
      +      project.reload
      +
      +      assert_equal 0, project.memberships.count
      +    end
      +  end
      +
      +  def test_adding_a_group_member_should_propagate_with_its_users
      +    project = Project.generate_with_parent!(@parent, :inherit_members => true)
      +    group = Group.generate!
      +    user = User.find(4)
      +    group.users << user
      +
      +    assert_difference 'Member.count', 4 do
      +      assert_difference 'MemberRole.count', 8 do
      +        member = Member.create!(:principal => group, :project => @parent, :role_ids => [1, 3])
      +        project.reload
      +  
      +        inherited_group_member = project.memberships.detect {|m| m.principal == group}
      +        assert_not_nil inherited_group_member
      +        assert_equal member.roles.sort, inherited_group_member.roles.sort
      +  
      +        inherited_user_member = project.memberships.detect {|m| m.principal == user}
      +        assert_not_nil inherited_user_member
      +        assert_equal member.roles.sort, inherited_user_member.roles.sort
      +      end
      +    end
      +  end
      +
      +  def test_removing_a_group_member_should_propagate
      +    project = Project.generate_with_parent!(@parent, :inherit_members => true)
      +    group = Group.generate!
      +    user = User.find(4)
      +    group.users << user
      +    member = Member.create!(:principal => group, :project => @parent, :role_ids => [1, 3])
      +
      +    assert_difference 'Member.count', -4 do
      +      assert_difference 'MemberRole.count', -8 do
      +        member.destroy
      +        project.reload
      +  
      +        inherited_group_member = project.memberships.detect {|m| m.principal == group}
      +        assert_nil inherited_group_member
      +  
      +        inherited_user_member = project.memberships.detect {|m| m.principal == user}
      +        assert_nil inherited_user_member
      +      end
      +    end
      +  end
      +
      +  def test_adding_user_who_use_is_already_a_member_to_parent_project_should_merge_roles
      +    project = Project.generate_with_parent!(@parent, :inherit_members => true)
      +    user = User.find(4)
      +    Member.create!(:principal => user, :project => project, :role_ids => [1, 2])
      +
      +    assert_difference 'Member.count', 1 do
      +      Member.create!(:principal => User.find(4), :project => @parent.reload, :role_ids => [1, 3])
      +
      +      member = project.reload.memberships.detect {|m| m.principal == user}
      +      assert_not_nil member
      +      assert_equal [1, 2, 3], member.roles.uniq.sort.map(&:id)
      +    end
      +  end
      +
      +  def test_turning_on_inheritance_with_user_who_is_already_a_member_should_merge_roles
      +    project = Project.generate_with_parent!(@parent)
      +    user = @member.user
      +    Member.create!(:principal => user, :project => project, :role_ids => [1, 3])
      +    project.reload
      +
      +    assert_no_difference 'Member.count' do
      +      project.inherit_members = true
      +      project.save!
      +
      +      member = project.reload.memberships.detect {|m| m.principal == user}
      +      assert_not_nil member
      +      assert_equal [1, 2, 3], member.roles.uniq.sort.map(&:id)
      +    end
      +  end
      +end
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/project_nested_set_test.rb
      --- a/test/unit/project_nested_set_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/project_nested_set_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/project_test.rb
      --- a/test/unit/project_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/project_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      @@ -30,7 +30,6 @@
                  :member_roles,
                  :members,
                  :enabled_modules,
      -           :workflows,
                  :versions,
                  :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions,
                  :groups_users,
      @@ -75,9 +74,30 @@
           with_settings :default_projects_modules => ['issue_tracking', 'repository'] do
             assert_equal ['issue_tracking', 'repository'], Project.new.enabled_module_names
           end
      +  end
       
      -    assert_equal Tracker.all.sort, Project.new.trackers.sort
      -    assert_equal Tracker.find(1, 3).sort, Project.new(:tracker_ids => [1, 3]).trackers.sort
      +  def test_default_trackers_should_match_default_tracker_ids_setting
      +    with_settings :default_projects_tracker_ids => ['1', '3'] do
      +      assert_equal Tracker.find(1, 3).sort, Project.new.trackers.sort
      +    end
      +  end
      +
      +  def test_default_trackers_should_be_all_trackers_with_blank_setting
      +    with_settings :default_projects_tracker_ids => nil do
      +      assert_equal Tracker.all.sort, Project.new.trackers.sort
      +    end
      +  end
      +
      +  def test_default_trackers_should_be_empty_with_empty_setting
      +    with_settings :default_projects_tracker_ids => [] do
      +      assert_equal [], Project.new.trackers
      +    end
      +  end
      +
      +  def test_default_trackers_should_not_replace_initialized_trackers
      +    with_settings :default_projects_tracker_ids => ['1', '3'] do
      +      assert_equal Tracker.find(1, 2).sort, Project.new(:tracker_ids => [1, 2]).trackers.sort
      +    end
         end
       
         def test_update
      @@ -183,7 +203,7 @@
           # 2 active members
           assert_equal 2, @ecookbook.members.size
           # and 1 is locked
      -    assert_equal 3, Member.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).size
      +    assert_equal 3, Member.where('project_id = ?', @ecookbook.id).all.size
           # some boards
           assert @ecookbook.boards.any?
       
      @@ -435,56 +455,67 @@
           assert_equal [1,2], parent.rolled_up_trackers.collect(&:id)
         end
       
      -  context "#rolled_up_versions" do
      -    setup do
      -      @project = Project.generate!
      -      @parent_version_1 = Version.generate!(:project => @project)
      -      @parent_version_2 = Version.generate!(:project => @project)
      -    end
      +  test "#rolled_up_trackers should ignore projects with issue_tracking module disabled" do
      +    parent = Project.generate!
      +    parent.trackers = Tracker.find([1, 2])
      +    child = Project.generate_with_parent!(parent)
      +    child.trackers = Tracker.find([2, 3])
       
      -    should "include the versions for the current project" do
      -      assert_same_elements [@parent_version_1, @parent_version_2], @project.rolled_up_versions
      -    end
      +    assert_equal [1, 2, 3], parent.rolled_up_trackers.collect(&:id).sort
       
      -    should "include versions for a subproject" do
      -      @subproject = Project.generate!
      -      @subproject.set_parent!(@project)
      -      @subproject_version = Version.generate!(:project => @subproject)
      +    assert child.disable_module!(:issue_tracking)
      +    parent.reload
      +    assert_equal [1, 2], parent.rolled_up_trackers.collect(&:id).sort
      +  end
       
      -      assert_same_elements [
      -                            @parent_version_1,
      -                            @parent_version_2,
      -                            @subproject_version
      -                           ], @project.rolled_up_versions
      -    end
      +  test "#rolled_up_versions should include the versions for the current project" do
      +    project = Project.generate!
      +    parent_version_1 = Version.generate!(:project => project)
      +    parent_version_2 = Version.generate!(:project => project)
      +    assert_same_elements [parent_version_1, parent_version_2], project.rolled_up_versions
      +  end
       
      -    should "include versions for a sub-subproject" do
      -      @subproject = Project.generate!
      -      @subproject.set_parent!(@project)
      -      @sub_subproject = Project.generate!
      -      @sub_subproject.set_parent!(@subproject)
      -      @sub_subproject_version = Version.generate!(:project => @sub_subproject)
      +  test "#rolled_up_versions should include versions for a subproject" do
      +    project = Project.generate!
      +    parent_version_1 = Version.generate!(:project => project)
      +    parent_version_2 = Version.generate!(:project => project)
      +    subproject = Project.generate_with_parent!(project)
      +    subproject_version = Version.generate!(:project => subproject)
       
      -      @project.reload
      +    assert_same_elements [
      +                          parent_version_1,
      +                          parent_version_2,
      +                          subproject_version
      +                         ], project.rolled_up_versions
      +  end
       
      -      assert_same_elements [
      -                            @parent_version_1,
      -                            @parent_version_2,
      -                            @sub_subproject_version
      -                           ], @project.rolled_up_versions
      -    end
      +  test "#rolled_up_versions should include versions for a sub-subproject" do
      +    project = Project.generate!
      +    parent_version_1 = Version.generate!(:project => project)
      +    parent_version_2 = Version.generate!(:project => project)
      +    subproject = Project.generate_with_parent!(project)
      +    sub_subproject = Project.generate_with_parent!(subproject)
      +    sub_subproject_version = Version.generate!(:project => sub_subproject)
      +    project.reload
       
      -    should "only check active projects" do
      -      @subproject = Project.generate!
      -      @subproject.set_parent!(@project)
      -      @subproject_version = Version.generate!(:project => @subproject)
      -      assert @subproject.archive
      +    assert_same_elements [
      +                          parent_version_1,
      +                          parent_version_2,
      +                          sub_subproject_version
      +                         ], project.rolled_up_versions
      +  end
       
      -      @project.reload
      +  test "#rolled_up_versions should only check active projects" do
      +    project = Project.generate!
      +    parent_version_1 = Version.generate!(:project => project)
      +    parent_version_2 = Version.generate!(:project => project)
      +    subproject = Project.generate_with_parent!(project)
      +    subproject_version = Version.generate!(:project => subproject)
      +    assert subproject.archive
      +    project.reload
       
      -      assert !@subproject.active?
      -      assert_same_elements [@parent_version_1, @parent_version_2], @project.rolled_up_versions
      -    end
      +    assert !subproject.active?
      +    assert_same_elements [parent_version_1, parent_version_2], project.rolled_up_versions
         end
       
         def test_shared_versions_none_sharing
      @@ -611,52 +642,49 @@
           end
         end
       
      -  context "enabled_modules" do
      -    setup do
      -      @project = Project.find(1)
      +  test "enabled_modules should define module by names and preserve ids" do
      +    @project = Project.find(1)
      +    # Remove one module
      +    modules = @project.enabled_modules.slice(0..-2)
      +    assert modules.any?
      +    assert_difference 'EnabledModule.count', -1 do
      +      @project.enabled_module_names = modules.collect(&:name)
           end
      +    @project.reload
      +    # Ids should be preserved
      +    assert_equal @project.enabled_module_ids.sort, modules.collect(&:id).sort
      +  end
       
      -    should "define module by names and preserve ids" do
      -      # Remove one module
      -      modules = @project.enabled_modules.slice(0..-2)
      -      assert modules.any?
      -      assert_difference 'EnabledModule.count', -1 do
      -        @project.enabled_module_names = modules.collect(&:name)
      -      end
      -      @project.reload
      -      # Ids should be preserved
      -      assert_equal @project.enabled_module_ids.sort, modules.collect(&:id).sort
      -    end
      +  test "enabled_modules should enable a module" do
      +    @project = Project.find(1)
      +    @project.enabled_module_names = []
      +    @project.reload
      +    assert_equal [], @project.enabled_module_names
      +    #with string
      +    @project.enable_module!("issue_tracking")
      +    assert_equal ["issue_tracking"], @project.enabled_module_names
      +    #with symbol
      +    @project.enable_module!(:gantt)
      +    assert_equal ["issue_tracking", "gantt"], @project.enabled_module_names
      +    #don't add a module twice
      +    @project.enable_module!("issue_tracking")
      +    assert_equal ["issue_tracking", "gantt"], @project.enabled_module_names
      +  end
       
      -    should "enable a module" do
      -      @project.enabled_module_names = []
      -      @project.reload
      -      assert_equal [], @project.enabled_module_names
      -      #with string
      -      @project.enable_module!("issue_tracking")
      -      assert_equal ["issue_tracking"], @project.enabled_module_names
      -      #with symbol
      -      @project.enable_module!(:gantt)
      -      assert_equal ["issue_tracking", "gantt"], @project.enabled_module_names
      -      #don't add a module twice
      -      @project.enable_module!("issue_tracking")
      -      assert_equal ["issue_tracking", "gantt"], @project.enabled_module_names
      -    end
      -
      -    should "disable a module" do
      -      #with string
      -      assert @project.enabled_module_names.include?("issue_tracking")
      -      @project.disable_module!("issue_tracking")
      -      assert ! @project.reload.enabled_module_names.include?("issue_tracking")
      -      #with symbol
      -      assert @project.enabled_module_names.include?("gantt")
      -      @project.disable_module!(:gantt)
      -      assert ! @project.reload.enabled_module_names.include?("gantt")
      -      #with EnabledModule object
      -      first_module = @project.enabled_modules.first
      -      @project.disable_module!(first_module)
      -      assert ! @project.reload.enabled_module_names.include?(first_module.name)
      -    end
      +  test "enabled_modules should disable a module" do
      +    @project = Project.find(1)
      +    #with string
      +    assert @project.enabled_module_names.include?("issue_tracking")
      +    @project.disable_module!("issue_tracking")
      +    assert ! @project.reload.enabled_module_names.include?("issue_tracking")
      +    #with symbol
      +    assert @project.enabled_module_names.include?("gantt")
      +    @project.disable_module!(:gantt)
      +    assert ! @project.reload.enabled_module_names.include?("gantt")
      +    #with EnabledModule object
      +    first_module = @project.enabled_modules.first
      +    @project.disable_module!(first_module)
      +    assert ! @project.reload.enabled_module_names.include?(first_module.name)
         end
       
         def test_enabled_module_names_should_not_recreate_enabled_modules
      @@ -693,7 +721,7 @@
       
         def test_activities_should_use_the_system_activities
           project = Project.find(1)
      -    assert_equal project.activities, TimeEntryActivity.find(:all, :conditions => {:active => true} )
      +    assert_equal project.activities, TimeEntryActivity.where(:active => true).all
         end
       
       
      @@ -707,7 +735,7 @@
       
         def test_activities_should_not_include_the_inactive_project_specific_activities
           project = Project.find(1)
      -    overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.find(:first), :active => false})
      +    overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.first, :active => false})
           assert overridden_activity.save!
       
           assert !project.activities.include?(overridden_activity), "Inactive Project specific Activity found"
      @@ -722,7 +750,7 @@
         end
       
         def test_activities_should_handle_nils
      -    overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(1), :parent => TimeEntryActivity.find(:first)})
      +    overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(1), :parent => TimeEntryActivity.first})
           TimeEntryActivity.delete_all
       
           # No activities
      @@ -737,7 +765,7 @@
       
         def test_activities_should_override_system_activities_with_project_activities
           project = Project.find(1)
      -    parent_activity = TimeEntryActivity.find(:first)
      +    parent_activity = TimeEntryActivity.first
           overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => parent_activity})
           assert overridden_activity.save!
       
      @@ -747,7 +775,7 @@
       
         def test_activities_should_include_inactive_activities_if_specified
           project = Project.find(1)
      -    overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.find(:first), :active => false})
      +    overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.first, :active => false})
           assert overridden_activity.save!
       
           assert project.activities(true).include?(overridden_activity), "Inactive Project specific Activity not found"
      @@ -775,438 +803,135 @@
           assert_not_nil project.versions.detect {|v| !v.completed? && v.status == 'open'}
         end
       
      -  context "Project#copy" do
      -    setup do
      -      ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
      -      Project.destroy_all :identifier => "copy-test"
      -      @source_project = Project.find(2)
      -      @project = Project.new(:name => 'Copy Test', :identifier => 'copy-test')
      -      @project.trackers = @source_project.trackers
      -      @project.enabled_module_names = @source_project.enabled_modules.collect(&:name)
      -    end
      -
      -    should "copy issues" do
      -      @source_project.issues << Issue.generate!(:status => IssueStatus.find_by_name('Closed'),
      -                                                :subject => "copy issue status",
      -                                                :tracker_id => 1,
      -                                                :assigned_to_id => 2,
      -                                                :project_id => @source_project.id)
      -      assert @project.valid?
      -      assert @project.issues.empty?
      -      assert @project.copy(@source_project)
      -
      -      assert_equal @source_project.issues.size, @project.issues.size
      -      @project.issues.each do |issue|
      -        assert issue.valid?
      -        assert ! issue.assigned_to.blank?
      -        assert_equal @project, issue.project
      -      end
      -
      -      copied_issue = @project.issues.first(:conditions => {:subject => "copy issue status"})
      -      assert copied_issue
      -      assert copied_issue.status
      -      assert_equal "Closed", copied_issue.status.name
      -    end
      -
      -    should "copy issues assigned to a locked version" do
      -      User.current = User.find(1)
      -      assigned_version = Version.generate!(:name => "Assigned Issues")
      -      @source_project.versions << assigned_version
      -      Issue.generate!(:project => @source_project,
      -                      :fixed_version_id => assigned_version.id,
      -                      :subject => "copy issues assigned to a locked version")
      -      assigned_version.update_attribute :status, 'locked'
      -
      -      assert @project.copy(@source_project)
      -      @project.reload
      -      copied_issue = @project.issues.first(:conditions => {:subject => "copy issues assigned to a locked version"})
      -
      -      assert copied_issue
      -      assert copied_issue.fixed_version
      -      assert_equal "Assigned Issues", copied_issue.fixed_version.name # Same name
      -      assert_equal 'locked', copied_issue.fixed_version.status
      -    end
      -
      -    should "change the new issues to use the copied version" do
      -      User.current = User.find(1)
      -      assigned_version = Version.generate!(:name => "Assigned Issues", :status => 'open')
      -      @source_project.versions << assigned_version
      -      assert_equal 3, @source_project.versions.size
      -      Issue.generate!(:project => @source_project,
      -                      :fixed_version_id => assigned_version.id,
      -                      :subject => "change the new issues to use the copied version")
      -
      -      assert @project.copy(@source_project)
      -      @project.reload
      -      copied_issue = @project.issues.first(:conditions => {:subject => "change the new issues to use the copied version"})
      -
      -      assert copied_issue
      -      assert copied_issue.fixed_version
      -      assert_equal "Assigned Issues", copied_issue.fixed_version.name # Same name
      -      assert_not_equal assigned_version.id, copied_issue.fixed_version.id # Different record
      -    end
      -
      -    should "keep target shared versions from other project" do
      -      assigned_version = Version.generate!(:name => "Assigned Issues", :status => 'open', :project_id => 1, :sharing => 'system')
      -      issue = Issue.generate!(:project => @source_project,
      -                              :fixed_version => assigned_version,
      -                              :subject => "keep target shared versions")
      -
      -      assert @project.copy(@source_project)
      -      @project.reload
      -      copied_issue = @project.issues.first(:conditions => {:subject => "keep target shared versions"})
      -
      -      assert copied_issue
      -      assert_equal assigned_version, copied_issue.fixed_version
      -    end
      -
      -    should "copy issue relations" do
      -      Setting.cross_project_issue_relations = '1'
      -
      -      second_issue = Issue.generate!(:status_id => 5,
      -                                     :subject => "copy issue relation",
      -                                     :tracker_id => 1,
      -                                     :assigned_to_id => 2,
      -                                     :project_id => @source_project.id)
      -      source_relation = IssueRelation.create!(:issue_from => Issue.find(4),
      -                                                :issue_to => second_issue,
      -                                                :relation_type => "relates")
      -      source_relation_cross_project = IssueRelation.create!(:issue_from => Issue.find(1),
      -                                                              :issue_to => second_issue,
      -                                                              :relation_type => "duplicates")
      -
      -      assert @project.copy(@source_project)
      -      assert_equal @source_project.issues.count, @project.issues.count
      -      copied_issue = @project.issues.find_by_subject("Issue on project 2") # Was #4
      -      copied_second_issue = @project.issues.find_by_subject("copy issue relation")
      -
      -      # First issue with a relation on project
      -      assert_equal 1, copied_issue.relations.size, "Relation not copied"
      -      copied_relation = copied_issue.relations.first
      -      assert_equal "relates", copied_relation.relation_type
      -      assert_equal copied_second_issue.id, copied_relation.issue_to_id
      -      assert_not_equal source_relation.id, copied_relation.id
      -
      -      # Second issue with a cross project relation
      -      assert_equal 2, copied_second_issue.relations.size, "Relation not copied"
      -      copied_relation = copied_second_issue.relations.select {|r| r.relation_type == 'duplicates'}.first
      -      assert_equal "duplicates", copied_relation.relation_type
      -      assert_equal 1, copied_relation.issue_from_id, "Cross project relation not kept"
      -      assert_not_equal source_relation_cross_project.id, copied_relation.id
      -    end
      -
      -    should "copy issue attachments" do
      -      issue = Issue.generate!(:subject => "copy with attachment", :tracker_id => 1, :project_id => @source_project.id)
      -      Attachment.create!(:container => issue, :file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 1)
      -      @source_project.issues << issue
      -      assert @project.copy(@source_project)
      -
      -      copied_issue = @project.issues.first(:conditions => {:subject => "copy with attachment"})
      -      assert_not_nil copied_issue
      -      assert_equal 1, copied_issue.attachments.count, "Attachment not copied"
      -      assert_equal "testfile.txt", copied_issue.attachments.first.filename
      -    end
      -
      -    should "copy memberships" do
      -      assert @project.valid?
      -      assert @project.members.empty?
      -      assert @project.copy(@source_project)
      -
      -      assert_equal @source_project.memberships.size, @project.memberships.size
      -      @project.memberships.each do |membership|
      -        assert membership
      -        assert_equal @project, membership.project
      -      end
      -    end
      -
      -    should "copy memberships with groups and additional roles" do
      -      group = Group.create!(:lastname => "Copy group")
      -      user = User.find(7)
      -      group.users << user
      -      # group role
      -      Member.create!(:project_id => @source_project.id, :principal => group, :role_ids => [2])
      -      member = Member.find_by_user_id_and_project_id(user.id, @source_project.id)
      -      # additional role
      -      member.role_ids = [1]
      -
      -      assert @project.copy(@source_project)
      -      member = Member.find_by_user_id_and_project_id(user.id, @project.id)
      -      assert_not_nil member
      -      assert_equal [1, 2], member.role_ids.sort
      -    end
      -
      -    should "copy project specific queries" do
      -      assert @project.valid?
      -      assert @project.queries.empty?
      -      assert @project.copy(@source_project)
      -
      -      assert_equal @source_project.queries.size, @project.queries.size
      -      @project.queries.each do |query|
      -        assert query
      -        assert_equal @project, query.project
      -      end
      -      assert_equal @source_project.queries.map(&:user_id).sort, @project.queries.map(&:user_id).sort
      -    end
      -
      -    should "copy versions" do
      -      @source_project.versions << Version.generate!
      -      @source_project.versions << Version.generate!
      -
      -      assert @project.versions.empty?
      -      assert @project.copy(@source_project)
      -
      -      assert_equal @source_project.versions.size, @project.versions.size
      -      @project.versions.each do |version|
      -        assert version
      -        assert_equal @project, version.project
      -      end
      -    end
      -
      -    should "copy wiki" do
      -      assert_difference 'Wiki.count' do
      -        assert @project.copy(@source_project)
      -      end
      -
      -      assert @project.wiki
      -      assert_not_equal @source_project.wiki, @project.wiki
      -      assert_equal "Start page", @project.wiki.start_page
      -    end
      -
      -    should "copy wiki pages and content with hierarchy" do
      -      assert_difference 'WikiPage.count', @source_project.wiki.pages.size do
      -        assert @project.copy(@source_project)
      -      end
      -
      -      assert @project.wiki
      -      assert_equal @source_project.wiki.pages.size, @project.wiki.pages.size
      -
      -      @project.wiki.pages.each do |wiki_page|
      -        assert wiki_page.content
      -        assert !@source_project.wiki.pages.include?(wiki_page)
      -      end
      -
      -      parent = @project.wiki.find_page('Parent_page')
      -      child1 = @project.wiki.find_page('Child_page_1')
      -      child2 = @project.wiki.find_page('Child_page_2')
      -      assert_equal parent, child1.parent
      -      assert_equal parent, child2.parent
      -    end
      -
      -    should "copy issue categories" do
      -      assert @project.copy(@source_project)
      -
      -      assert_equal 2, @project.issue_categories.size
      -      @project.issue_categories.each do |issue_category|
      -        assert !@source_project.issue_categories.include?(issue_category)
      -      end
      -    end
      -
      -    should "copy boards" do
      -      assert @project.copy(@source_project)
      -
      -      assert_equal 1, @project.boards.size
      -      @project.boards.each do |board|
      -        assert !@source_project.boards.include?(board)
      -      end
      -    end
      -
      -    should "change the new issues to use the copied issue categories" do
      -      issue = Issue.find(4)
      -      issue.update_attribute(:category_id, 3)
      -
      -      assert @project.copy(@source_project)
      -
      -      @project.issues.each do |issue|
      -        assert issue.category
      -        assert_equal "Stock management", issue.category.name # Same name
      -        assert_not_equal IssueCategory.find(3), issue.category # Different record
      -      end
      -    end
      -
      -    should "limit copy with :only option" do
      -      assert @project.members.empty?
      -      assert @project.issue_categories.empty?
      -      assert @source_project.issues.any?
      -
      -      assert @project.copy(@source_project, :only => ['members', 'issue_categories'])
      -
      -      assert @project.members.any?
      -      assert @project.issue_categories.any?
      -      assert @project.issues.empty?
      -    end
      +  test "#start_date should be nil if there are no issues on the project" do
      +    project = Project.generate!
      +    assert_nil project.start_date
         end
       
      -  def test_copy_should_copy_subtasks
      -    source = Project.generate!(:tracker_ids => [1])
      -    issue = Issue.generate_with_descendants!(:project => source)
      -    project = Project.new(:name => 'Copy', :identifier => 'copy', :tracker_ids => [1])
      +  test "#start_date should be nil when issues have no start date" do
      +    project = Project.generate!
      +    project.trackers << Tracker.generate!
      +    early = 7.days.ago.to_date
      +    Issue.generate!(:project => project, :start_date => nil)
       
      -    assert_difference 'Project.count' do
      -      assert_difference 'Issue.count', 1+issue.descendants.count do
      -        assert project.copy(source.reload)
      -      end
      -    end
      -    copy = Issue.where(:parent_id => nil).order("id DESC").first
      -    assert_equal project, copy.project
      -    assert_equal issue.descendants.count, copy.descendants.count
      -    child_copy = copy.children.detect {|c| c.subject == 'Child1'}
      -    assert child_copy.descendants.any?
      +    assert_nil project.start_date
         end
       
      -  context "#start_date" do
      -    setup do
      -      ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
      -      @project = Project.generate!(:identifier => 'test0')
      -      @project.trackers << Tracker.generate!
      -    end
      +  test "#start_date should be the earliest start date of it's issues" do
      +    project = Project.generate!
      +    project.trackers << Tracker.generate!
      +    early = 7.days.ago.to_date
      +    Issue.generate!(:project => project, :start_date => Date.today)
      +    Issue.generate!(:project => project, :start_date => early)
       
      -    should "be nil if there are no issues on the project" do
      -      assert_nil @project.start_date
      -    end
      -
      -    should "be tested when issues have no start date"
      -
      -    should "be the earliest start date of it's issues" do
      -      early = 7.days.ago.to_date
      -      Issue.generate!(:project => @project, :start_date => Date.today)
      -      Issue.generate!(:project => @project, :start_date => early)
      -
      -      assert_equal early, @project.start_date
      -    end
      -
      +    assert_equal early, project.start_date
         end
       
      -  context "#due_date" do
      -    setup do
      -      ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
      -      @project = Project.generate!(:identifier => 'test0')
      -      @project.trackers << Tracker.generate!
      -    end
      -
      -    should "be nil if there are no issues on the project" do
      -      assert_nil @project.due_date
      -    end
      -
      -    should "be tested when issues have no due date"
      -
      -    should "be the latest due date of it's issues" do
      -      future = 7.days.from_now.to_date
      -      Issue.generate!(:project => @project, :due_date => future)
      -      Issue.generate!(:project => @project, :due_date => Date.today)
      -
      -      assert_equal future, @project.due_date
      -    end
      -
      -    should "be the latest due date of it's versions" do
      -      future = 7.days.from_now.to_date
      -      @project.versions << Version.generate!(:effective_date => future)
      -      @project.versions << Version.generate!(:effective_date => Date.today)
      -
      -
      -      assert_equal future, @project.due_date
      -
      -    end
      -
      -    should "pick the latest date from it's issues and versions" do
      -      future = 7.days.from_now.to_date
      -      far_future = 14.days.from_now.to_date
      -      Issue.generate!(:project => @project, :due_date => far_future)
      -      @project.versions << Version.generate!(:effective_date => future)
      -
      -      assert_equal far_future, @project.due_date
      -    end
      -
      +  test "#due_date should be nil if there are no issues on the project" do
      +    project = Project.generate!
      +    assert_nil project.due_date
         end
       
      -  context "Project#completed_percent" do
      -    setup do
      -      ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
      -      @project = Project.generate!(:identifier => 'test0')
      -      @project.trackers << Tracker.generate!
      -    end
      +  test "#due_date should be nil if there are no issues with due dates" do
      +    project = Project.generate!
      +    project.trackers << Tracker.generate!
      +    Issue.generate!(:project => project, :due_date => nil)
       
      -    context "no versions" do
      -      should "be 100" do
      -        assert_equal 100, @project.completed_percent
      -      end
      -    end
      -
      -    context "with versions" do
      -      should "return 0 if the versions have no issues" do
      -        Version.generate!(:project => @project)
      -        Version.generate!(:project => @project)
      -
      -        assert_equal 0, @project.completed_percent
      -      end
      -
      -      should "return 100 if the version has only closed issues" do
      -        v1 = Version.generate!(:project => @project)
      -        Issue.generate!(:project => @project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v1)
      -        v2 = Version.generate!(:project => @project)
      -        Issue.generate!(:project => @project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v2)
      -
      -        assert_equal 100, @project.completed_percent
      -      end
      -
      -      should "return the averaged completed percent of the versions (not weighted)" do
      -        v1 = Version.generate!(:project => @project)
      -        Issue.generate!(:project => @project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v1)
      -        v2 = Version.generate!(:project => @project)
      -        Issue.generate!(:project => @project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v2)
      -
      -        assert_equal 50, @project.completed_percent
      -      end
      -
      -    end
      +    assert_nil project.due_date
         end
       
      -  context "#notified_users" do
      -    setup do
      -      @project = Project.generate!
      -      @role = Role.generate!
      +  test "#due_date should be the latest due date of it's issues" do
      +    project = Project.generate!
      +    project.trackers << Tracker.generate!
      +    future = 7.days.from_now.to_date
      +    Issue.generate!(:project => project, :due_date => future)
      +    Issue.generate!(:project => project, :due_date => Date.today)
       
      -      @user_with_membership_notification = User.generate!(:mail_notification => 'selected')
      -      Member.create!(:project => @project, :roles => [@role], :principal => @user_with_membership_notification, :mail_notification => true)
      -
      -      @all_events_user = User.generate!(:mail_notification => 'all')
      -      Member.create!(:project => @project, :roles => [@role], :principal => @all_events_user)
      -
      -      @no_events_user = User.generate!(:mail_notification => 'none')
      -      Member.create!(:project => @project, :roles => [@role], :principal => @no_events_user)
      -
      -      @only_my_events_user = User.generate!(:mail_notification => 'only_my_events')
      -      Member.create!(:project => @project, :roles => [@role], :principal => @only_my_events_user)
      -
      -      @only_assigned_user = User.generate!(:mail_notification => 'only_assigned')
      -      Member.create!(:project => @project, :roles => [@role], :principal => @only_assigned_user)
      -
      -      @only_owned_user = User.generate!(:mail_notification => 'only_owner')
      -      Member.create!(:project => @project, :roles => [@role], :principal => @only_owned_user)
      -    end
      -
      -    should "include members with a mail notification" do
      -      assert @project.notified_users.include?(@user_with_membership_notification)
      -    end
      -
      -    should "include users with the 'all' notification option" do
      -      assert @project.notified_users.include?(@all_events_user)
      -    end
      -
      -    should "not include users with the 'none' notification option" do
      -      assert !@project.notified_users.include?(@no_events_user)
      -    end
      -
      -    should "not include users with the 'only_my_events' notification option" do
      -      assert !@project.notified_users.include?(@only_my_events_user)
      -    end
      -
      -    should "not include users with the 'only_assigned' notification option" do
      -      assert !@project.notified_users.include?(@only_assigned_user)
      -    end
      -
      -    should "not include users with the 'only_owner' notification option" do
      -      assert !@project.notified_users.include?(@only_owned_user)
      -    end
      +    assert_equal future, project.due_date
         end
       
      +  test "#due_date should be the latest due date of it's versions" do
      +    project = Project.generate!
      +    future = 7.days.from_now.to_date
      +    project.versions << Version.generate!(:effective_date => future)
      +    project.versions << Version.generate!(:effective_date => Date.today)
      +
      +    assert_equal future, project.due_date
      +  end
      +
      +  test "#due_date should pick the latest date from it's issues and versions" do
      +    project = Project.generate!
      +    project.trackers << Tracker.generate!
      +    future = 7.days.from_now.to_date
      +    far_future = 14.days.from_now.to_date
      +    Issue.generate!(:project => project, :due_date => far_future)
      +    project.versions << Version.generate!(:effective_date => future)
      +
      +    assert_equal far_future, project.due_date
      +  end
      +
      +  test "#completed_percent with no versions should be 100" do
      +    project = Project.generate!
      +    assert_equal 100, project.completed_percent
      +  end
      +
      +  test "#completed_percent with versions should return 0 if the versions have no issues" do
      +    project = Project.generate!
      +    Version.generate!(:project => project)
      +    Version.generate!(:project => project)
      +
      +    assert_equal 0, project.completed_percent
      +  end
      +
      +  test "#completed_percent with versions should return 100 if the version has only closed issues" do
      +    project = Project.generate!
      +    project.trackers << Tracker.generate!
      +    v1 = Version.generate!(:project => project)
      +    Issue.generate!(:project => project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v1)
      +    v2 = Version.generate!(:project => project)
      +    Issue.generate!(:project => project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v2)
      +
      +    assert_equal 100, project.completed_percent
      +  end
      +
      +  test "#completed_percent with versions should return the averaged completed percent of the versions (not weighted)" do
      +    project = Project.generate!
      +    project.trackers << Tracker.generate!
      +    v1 = Version.generate!(:project => project)
      +    Issue.generate!(:project => project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v1)
      +    v2 = Version.generate!(:project => project)
      +    Issue.generate!(:project => project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v2)
      +
      +    assert_equal 50, project.completed_percent
      +  end
      +
      +  test "#notified_users" do
      +    project = Project.generate!
      +    role = Role.generate!
      +
      +    user_with_membership_notification = User.generate!(:mail_notification => 'selected')
      +    Member.create!(:project => project, :roles => [role], :principal => user_with_membership_notification, :mail_notification => true)
      +
      +    all_events_user = User.generate!(:mail_notification => 'all')
      +    Member.create!(:project => project, :roles => [role], :principal => all_events_user)
      +
      +    no_events_user = User.generate!(:mail_notification => 'none')
      +    Member.create!(:project => project, :roles => [role], :principal => no_events_user)
      +
      +    only_my_events_user = User.generate!(:mail_notification => 'only_my_events')
      +    Member.create!(:project => project, :roles => [role], :principal => only_my_events_user)
      +
      +    only_assigned_user = User.generate!(:mail_notification => 'only_assigned')
      +    Member.create!(:project => project, :roles => [role], :principal => only_assigned_user)
      +
      +    only_owned_user = User.generate!(:mail_notification => 'only_owner')
      +    Member.create!(:project => project, :roles => [role], :principal => only_owned_user)
      +
      +    assert project.notified_users.include?(user_with_membership_notification), "should include members with a mail notification"
      +    assert project.notified_users.include?(all_events_user), "should include users with the 'all' notification option"
      +    assert !project.notified_users.include?(no_events_user), "should not include users with the 'none' notification option"
      +    assert !project.notified_users.include?(only_my_events_user), "should not include users with the 'only_my_events' notification option"
      +    assert !project.notified_users.include?(only_assigned_user), "should not include users with the 'only_assigned' notification option"
      +    assert !project.notified_users.include?(only_owned_user), "should not include users with the 'only_owner' notification option"
      +  end
       end
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/query_test.rb
      --- a/test/unit/query_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/query_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      @@ -28,21 +28,26 @@
                  :projects_trackers,
                  :custom_fields_trackers
       
      +  def test_available_filters_should_be_ordered
      +    query = IssueQuery.new
      +    assert_equal 0, query.available_filters.keys.index('status_id')
      +  end
      +
         def test_custom_fields_for_all_projects_should_be_available_in_global_queries
      -    query = Query.new(:project => nil, :name => '_')
      +    query = IssueQuery.new(:project => nil, :name => '_')
           assert query.available_filters.has_key?('cf_1')
           assert !query.available_filters.has_key?('cf_3')
         end
       
         def test_system_shared_versions_should_be_available_in_global_queries
           Version.find(2).update_attribute :sharing, 'system'
      -    query = Query.new(:project => nil, :name => '_')
      +    query = IssueQuery.new(:project => nil, :name => '_')
           assert query.available_filters.has_key?('fixed_version_id')
           assert query.available_filters['fixed_version_id'][:values].detect {|v| v.last == '2'}
         end
       
         def test_project_filter_in_global_queries
      -    query = Query.new(:project => nil, :name => '_')
      +    query = IssueQuery.new(:project => nil, :name => '_')
           project_filter = query.available_filters["project_id"]
           assert_not_nil project_filter
           project_ids = project_filter[:values].map{|p| p[1]}
      @@ -63,7 +68,7 @@
         end
       
         def assert_query_statement_includes(query, condition)
      -    assert query.statement.include?(condition), "Query statement condition not found in: #{query.statement}"
      +    assert_include condition, query.statement
         end
         
         def assert_query_result(expected, query)
      @@ -75,14 +80,14 @@
       
         def test_query_should_allow_shared_versions_for_a_project_query
           subproject_version = Version.find(4)
      -    query = Query.new(:project => Project.find(1), :name => '_')
      +    query = IssueQuery.new(:project => Project.find(1), :name => '_')
           query.add_filter('fixed_version_id', '=', [subproject_version.id.to_s])
       
           assert query.statement.include?("#{Issue.table_name}.fixed_version_id IN ('4')")
         end
       
         def test_query_with_multiple_custom_fields
      -    query = Query.find(1)
      +    query = IssueQuery.find(1)
           assert query.valid?
           assert query.statement.include?("#{CustomValue.table_name}.value IN ('MySQL')")
           issues = find_issues_with_query(query)
      @@ -91,7 +96,7 @@
         end
       
         def test_operator_none
      -    query = Query.new(:project => Project.find(1), :name => '_')
      +    query = IssueQuery.new(:project => Project.find(1), :name => '_')
           query.add_filter('fixed_version_id', '!*', [''])
           query.add_filter('cf_1', '!*', [''])
           assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NULL")
      @@ -100,7 +105,7 @@
         end
       
         def test_operator_none_for_integer
      -    query = Query.new(:project => Project.find(1), :name => '_')
      +    query = IssueQuery.new(:project => Project.find(1), :name => '_')
           query.add_filter('estimated_hours', '!*', [''])
           issues = find_issues_with_query(query)
           assert !issues.empty?
      @@ -108,7 +113,7 @@
         end
       
         def test_operator_none_for_date
      -    query = Query.new(:project => Project.find(1), :name => '_')
      +    query = IssueQuery.new(:project => Project.find(1), :name => '_')
           query.add_filter('start_date', '!*', [''])
           issues = find_issues_with_query(query)
           assert !issues.empty?
      @@ -116,7 +121,7 @@
         end
       
         def test_operator_none_for_string_custom_field
      -    query = Query.new(:project => Project.find(1), :name => '_')
      +    query = IssueQuery.new(:project => Project.find(1), :name => '_')
           query.add_filter('cf_2', '!*', [''])
           assert query.has_filter?('cf_2')
           issues = find_issues_with_query(query)
      @@ -125,7 +130,7 @@
         end
       
         def test_operator_all
      -    query = Query.new(:project => Project.find(1), :name => '_')
      +    query = IssueQuery.new(:project => Project.find(1), :name => '_')
           query.add_filter('fixed_version_id', '*', [''])
           query.add_filter('cf_1', '*', [''])
           assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NOT NULL")
      @@ -134,7 +139,7 @@
         end
       
         def test_operator_all_for_date
      -    query = Query.new(:project => Project.find(1), :name => '_')
      +    query = IssueQuery.new(:project => Project.find(1), :name => '_')
           query.add_filter('start_date', '*', [''])
           issues = find_issues_with_query(query)
           assert !issues.empty?
      @@ -142,7 +147,7 @@
         end
       
         def test_operator_all_for_string_custom_field
      -    query = Query.new(:project => Project.find(1), :name => '_')
      +    query = IssueQuery.new(:project => Project.find(1), :name => '_')
           query.add_filter('cf_2', '*', [''])
           assert query.has_filter?('cf_2')
           issues = find_issues_with_query(query)
      @@ -151,7 +156,7 @@
         end
       
         def test_numeric_filter_should_not_accept_non_numeric_values
      -    query = Query.new(:name => '_')
      +    query = IssueQuery.new(:name => '_')
           query.add_filter('estimated_hours', '=', ['a'])
       
           assert query.has_filter?('estimated_hours')
      @@ -161,7 +166,7 @@
         def test_operator_is_on_float
           Issue.update_all("estimated_hours = 171.2", "id=2")
       
      -    query = Query.new(:name => '_')
      +    query = IssueQuery.new(:name => '_')
           query.add_filter('estimated_hours', '=', ['171.20'])
           issues = find_issues_with_query(query)
           assert_equal 1, issues.size
      @@ -174,7 +179,7 @@
           CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12')
           CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
       
      -    query = Query.new(:name => '_')
      +    query = IssueQuery.new(:name => '_')
           query.add_filter("cf_#{f.id}", '=', ['12'])
           issues = find_issues_with_query(query)
           assert_equal 1, issues.size
      @@ -187,7 +192,7 @@
           CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12')
           CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
       
      -    query = Query.new(:name => '_')
      +    query = IssueQuery.new(:name => '_')
           query.add_filter("cf_#{f.id}", '=', ['-12'])
           assert query.valid?
           issues = find_issues_with_query(query)
      @@ -201,7 +206,7 @@
           CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12.7')
           CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
       
      -    query = Query.new(:name => '_')
      +    query = IssueQuery.new(:name => '_')
           query.add_filter("cf_#{f.id}", '=', ['12.7'])
           issues = find_issues_with_query(query)
           assert_equal 1, issues.size
      @@ -214,7 +219,7 @@
           CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12.7')
           CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
       
      -    query = Query.new(:name => '_')
      +    query = IssueQuery.new(:name => '_')
           query.add_filter("cf_#{f.id}", '=', ['-12.7'])
           assert query.valid?
           issues = find_issues_with_query(query)
      @@ -229,12 +234,12 @@
           CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2')
           CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1')
       
      -    query = Query.new(:name => '_')
      +    query = IssueQuery.new(:name => '_')
           query.add_filter("cf_#{f.id}", '=', ['value1'])
           issues = find_issues_with_query(query)
           assert_equal [1, 3], issues.map(&:id).sort
       
      -    query = Query.new(:name => '_')
      +    query = IssueQuery.new(:name => '_')
           query.add_filter("cf_#{f.id}", '=', ['value2'])
           issues = find_issues_with_query(query)
           assert_equal [1], issues.map(&:id).sort
      @@ -247,13 +252,13 @@
           CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2')
           CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1')
       
      -    query = Query.new(:name => '_')
      +    query = IssueQuery.new(:name => '_')
           query.add_filter("cf_#{f.id}", '!', ['value1'])
           issues = find_issues_with_query(query)
           assert !issues.map(&:id).include?(1)
           assert !issues.map(&:id).include?(3)
       
      -    query = Query.new(:name => '_')
      +    query = IssueQuery.new(:name => '_')
           query.add_filter("cf_#{f.id}", '!', ['value2'])
           issues = find_issues_with_query(query)
           assert !issues.map(&:id).include?(1)
      @@ -264,7 +269,7 @@
           # is_private filter only available for those who can set issues private
           User.current = User.find(2)
       
      -    query = Query.new(:name => '_')
      +    query = IssueQuery.new(:name => '_')
           assert query.available_filters.key?('is_private')
       
           query.add_filter("is_private", '=', ['1'])
      @@ -279,7 +284,7 @@
           # is_private filter only available for those who can set issues private
           User.current = User.find(2)
       
      -    query = Query.new(:name => '_')
      +    query = IssueQuery.new(:name => '_')
           assert query.available_filters.key?('is_private')
       
           query.add_filter("is_private", '!', ['1'])
      @@ -291,14 +296,14 @@
         end
       
         def test_operator_greater_than
      -    query = Query.new(:project => Project.find(1), :name => '_')
      +    query = IssueQuery.new(:project => Project.find(1), :name => '_')
           query.add_filter('done_ratio', '>=', ['40'])
           assert query.statement.include?("#{Issue.table_name}.done_ratio >= 40.0")
           find_issues_with_query(query)
         end
       
         def test_operator_greater_than_a_float
      -    query = Query.new(:project => Project.find(1), :name => '_')
      +    query = IssueQuery.new(:project => Project.find(1), :name => '_')
           query.add_filter('estimated_hours', '>=', ['40.5'])
           assert query.statement.include?("#{Issue.table_name}.estimated_hours >= 40.5")
           find_issues_with_query(query)
      @@ -310,7 +315,7 @@
           CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12')
           CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
       
      -    query = Query.new(:project => Project.find(1), :name => '_')
      +    query = IssueQuery.new(:project => Project.find(1), :name => '_')
           query.add_filter("cf_#{f.id}", '>=', ['8'])
           issues = find_issues_with_query(query)
           assert_equal 1, issues.size
      @@ -318,7 +323,7 @@
         end
       
         def test_operator_lesser_than
      -    query = Query.new(:project => Project.find(1), :name => '_')
      +    query = IssueQuery.new(:project => Project.find(1), :name => '_')
           query.add_filter('done_ratio', '<=', ['30'])
           assert query.statement.include?("#{Issue.table_name}.done_ratio <= 30.0")
           find_issues_with_query(query)
      @@ -326,14 +331,28 @@
       
         def test_operator_lesser_than_on_custom_field
           f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
      -    query = Query.new(:project => Project.find(1), :name => '_')
      +    query = IssueQuery.new(:project => Project.find(1), :name => '_')
           query.add_filter("cf_#{f.id}", '<=', ['30'])
      -    assert query.statement.include?("CAST(custom_values.value AS decimal(60,3)) <= 30.0")
      +    assert_match /CAST.+ <= 30\.0/, query.statement
           find_issues_with_query(query)
         end
       
      +  def test_operator_lesser_than_on_date_custom_field
      +    f = IssueCustomField.create!(:name => 'filter', :field_format => 'date', :is_filter => true, :is_for_all => true)
      +    CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '2013-04-11')
      +    CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '2013-05-14')
      +    CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
      +
      +    query = IssueQuery.new(:project => Project.find(1), :name => '_')
      +    query.add_filter("cf_#{f.id}", '<=', ['2013-05-01'])
      +    issue_ids = find_issues_with_query(query).map(&:id)
      +    assert_include 1, issue_ids
      +    assert_not_include 2, issue_ids
      +    assert_not_include 3, issue_ids
      +  end
      +
         def test_operator_between
      -    query = Query.new(:project => Project.find(1), :name => '_')
      +    query = IssueQuery.new(:project => Project.find(1), :name => '_')
           query.add_filter('done_ratio', '><', ['30', '40'])
           assert_include "#{Issue.table_name}.done_ratio BETWEEN 30.0 AND 40.0", query.statement
           find_issues_with_query(query)
      @@ -341,14 +360,14 @@
       
         def test_operator_between_on_custom_field
           f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
      -    query = Query.new(:project => Project.find(1), :name => '_')
      +    query = IssueQuery.new(:project => Project.find(1), :name => '_')
           query.add_filter("cf_#{f.id}", '><', ['30', '40'])
      -    assert_include "CAST(custom_values.value AS decimal(60,3)) BETWEEN 30.0 AND 40.0", query.statement
      +    assert_match /CAST.+ BETWEEN 30.0 AND 40.0/, query.statement
           find_issues_with_query(query)
         end
       
         def test_date_filter_should_not_accept_non_date_values
      -    query = Query.new(:name => '_')
      +    query = IssueQuery.new(:name => '_')
           query.add_filter('created_on', '=', ['a'])
       
           assert query.has_filter?('created_on')
      @@ -356,7 +375,7 @@
         end
       
         def test_date_filter_should_not_accept_invalid_date_values
      -    query = Query.new(:name => '_')
      +    query = IssueQuery.new(:name => '_')
           query.add_filter('created_on', '=', ['2011-01-34'])
       
           assert query.has_filter?('created_on')
      @@ -364,7 +383,7 @@
         end
       
         def test_relative_date_filter_should_not_accept_non_integer_values
      -    query = Query.new(:name => '_')
      +    query = IssueQuery.new(:name => '_')
           query.add_filter('created_on', '>t-', ['a'])
       
           assert query.has_filter?('created_on')
      @@ -372,28 +391,28 @@
         end
       
         def test_operator_date_equals
      -    query = Query.new(:name => '_')
      +    query = IssueQuery.new(:name => '_')
           query.add_filter('due_date', '=', ['2011-07-10'])
           assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?' AND issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
           find_issues_with_query(query)
         end
       
         def test_operator_date_lesser_than
      -    query = Query.new(:name => '_')
      +    query = IssueQuery.new(:name => '_')
           query.add_filter('due_date', '<=', ['2011-07-10'])
           assert_match /issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
           find_issues_with_query(query)
         end
       
         def test_operator_date_greater_than
      -    query = Query.new(:name => '_')
      +    query = IssueQuery.new(:name => '_')
           query.add_filter('due_date', '>=', ['2011-07-10'])
           assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?'/, query.statement
           find_issues_with_query(query)
         end
       
         def test_operator_date_between
      -    query = Query.new(:name => '_')
      +    query = IssueQuery.new(:name => '_')
           query.add_filter('due_date', '><', ['2011-06-23', '2011-07-10'])
           assert_match /issues\.due_date > '2011-06-22 23:59:59(\.9+)?' AND issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
           find_issues_with_query(query)
      @@ -401,7 +420,7 @@
       
         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 = IssueQuery.new(:project => Project.find(1), :name => '_')
           query.add_filter('due_date', '>t+', ['15'])
           issues = find_issues_with_query(query)
           assert !issues.empty?
      @@ -409,7 +428,7 @@
         end
       
         def test_operator_in_less_than
      -    query = Query.new(:project => Project.find(1), :name => '_')
      +    query = IssueQuery.new(:project => Project.find(1), :name => '_')
           query.add_filter('due_date', ' Project.find(1), :name => '_')
      +    query = IssueQuery.new(:project => Project.find(1), :name => '_')
           query.add_filter('due_date', '> Project.find(1), :name => '_')
      +    query = IssueQuery.new(:project => Project.find(1), :name => '_')
           query.add_filter('due_date', '>t-', ['3'])
           issues = find_issues_with_query(query)
           assert !issues.empty?
      @@ -435,7 +454,7 @@
       
         def test_operator_in_the_past_days
           Issue.find(7).update_attribute(:due_date, (Date.today - 3))
      -    query = Query.new(:project => Project.find(1), :name => '_')
      +    query = IssueQuery.new(:project => Project.find(1), :name => '_')
           query.add_filter('due_date', '> Project.find(1), :name => '_')
      +    query = IssueQuery.new(:project => Project.find(1), :name => '_')
           query.add_filter('due_date', ' Project.find(1), :name => '_')
      +    query = IssueQuery.new(:project => Project.find(1), :name => '_')
           query.add_filter('due_date', 'w', [''])
           assert query.statement.match(/issues\.due_date > '2011-04-24 23:59:59(\.9+)?' AND issues\.due_date <= '2011-05-01 23:59:59(\.9+)?/), "range not found in #{query.statement}"
           I18n.locale = :en
      @@ -517,13 +536,13 @@
       
           Date.stubs(:today).returns(Date.parse('2011-04-29'))
       
      -    query = Query.new(:project => Project.find(1), :name => '_')
      +    query = IssueQuery.new(:project => Project.find(1), :name => '_')
           query.add_filter('due_date', 'w', [''])
           assert query.statement.match(/issues\.due_date > '2011-04-23 23:59:59(\.9+)?' AND issues\.due_date <= '2011-04-30 23:59:59(\.9+)?/), "range not found in #{query.statement}"
         end
       
         def test_operator_does_not_contains
      -    query = Query.new(:project => Project.find(1), :name => '_')
      +    query = IssueQuery.new(:project => Project.find(1), :name => '_')
           query.add_filter('subject', '!~', ['uNable'])
           assert query.statement.include?("LOWER(#{Issue.table_name}.subject) NOT LIKE '%unable%'")
           find_issues_with_query(query)
      @@ -538,7 +557,7 @@
           i3 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => Group.find(11))
           group.users << user
       
      -    query = Query.new(:name => '_', :filters => { 'assigned_to_id' => {:operator => '=', :values => ['me']}})
      +    query = IssueQuery.new(:name => '_', :filters => { 'assigned_to_id' => {:operator => '=', :values => ['me']}})
           result = query.issues
           assert_equal Issue.visible.all(:conditions => {:assigned_to_id => ([2] + user.reload.group_ids)}).sort_by(&:id), result.sort_by(&:id)
       
      @@ -553,7 +572,7 @@
           issue1 = Issue.create!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '2'}, :subject => 'Test', :author_id => 1)
           issue2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '3'})
       
      -    query = Query.new(:name => '_', :project => Project.find(1))
      +    query = IssueQuery.new(:name => '_', :project => Project.find(1))
           filter = query.available_filters["cf_#{cf.id}"]
           assert_not_nil filter
           assert_include 'me', filter[:values].map{|v| v[1]}
      @@ -566,7 +585,7 @@
       
         def test_filter_my_projects
           User.current = User.find(2)
      -    query = Query.new(:name => '_')
      +    query = IssueQuery.new(:name => '_')
           filter = query.available_filters['project_id']
           assert_not_nil filter
           assert_include 'mine', filter[:values].map{|v| v[1]}
      @@ -578,7 +597,7 @@
       
         def test_filter_watched_issues
           User.current = User.find(1)
      -    query = Query.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me']}})
      +    query = IssueQuery.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me']}})
           result = find_issues_with_query(query)
           assert_not_nil result
           assert !result.empty?
      @@ -588,7 +607,7 @@
       
         def test_filter_unwatched_issues
           User.current = User.find(1)
      -    query = Query.new(:name => '_', :filters => { 'watcher_id' => {:operator => '!', :values => ['me']}})
      +    query = IssueQuery.new(:name => '_', :filters => { 'watcher_id' => {:operator => '!', :values => ['me']}})
           result = find_issues_with_query(query)
           assert_not_nil result
           assert !result.empty?
      @@ -601,7 +620,7 @@
           CustomValue.create!(:custom_field => field, :customized => Project.find(3), :value => 'Foo')
           CustomValue.create!(:custom_field => field, :customized => Project.find(5), :value => 'Foo')
       
      -    query = Query.new(:name => '_')
      +    query = IssueQuery.new(:name => '_')
           filter_name = "project.cf_#{field.id}"
           assert_include filter_name, query.available_filters.keys
           query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
      @@ -612,7 +631,7 @@
           field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
           CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo')
       
      -    query = Query.new(:name => '_')
      +    query = IssueQuery.new(:name => '_')
           filter_name = "author.cf_#{field.id}"
           assert_include filter_name, query.available_filters.keys
           query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
      @@ -623,7 +642,7 @@
           field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
           CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo')
       
      -    query = Query.new(:name => '_')
      +    query = IssueQuery.new(:name => '_')
           filter_name = "assigned_to.cf_#{field.id}"
           assert_include filter_name, query.available_filters.keys
           query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
      @@ -634,7 +653,7 @@
           field = VersionCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
           CustomValue.create!(:custom_field => field, :customized => Version.find(2), :value => 'Foo')
       
      -    query = Query.new(:name => '_')
      +    query = IssueQuery.new(:name => '_')
           filter_name = "fixed_version.cf_#{field.id}"
           assert_include filter_name, query.available_filters.keys
           query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
      @@ -646,11 +665,11 @@
           IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
           IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1))
       
      -    query = Query.new(:name => '_')
      +    query = IssueQuery.new(:name => '_')
           query.filters = {"relates" => {:operator => '=', :values => ['1']}}
           assert_equal [2, 3], find_issues_with_query(query).map(&:id).sort
       
      -    query = Query.new(:name => '_')
      +    query = IssueQuery.new(:name => '_')
           query.filters = {"relates" => {:operator => '=', :values => ['2']}}
           assert_equal [1], find_issues_with_query(query).map(&:id).sort
         end
      @@ -663,15 +682,15 @@
             IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(3).issues.first)
           end
       
      -    query = Query.new(:name => '_')
      +    query = IssueQuery.new(:name => '_')
           query.filters = {"relates" => {:operator => '=p', :values => ['2']}}
           assert_equal [1, 2], find_issues_with_query(query).map(&:id).sort
       
      -    query = Query.new(:name => '_')
      +    query = IssueQuery.new(:name => '_')
           query.filters = {"relates" => {:operator => '=p', :values => ['3']}}
           assert_equal [1], find_issues_with_query(query).map(&:id).sort
       
      -    query = Query.new(:name => '_')
      +    query = IssueQuery.new(:name => '_')
           query.filters = {"relates" => {:operator => '=p', :values => ['4']}}
           assert_equal [], find_issues_with_query(query).map(&:id).sort
         end
      @@ -684,7 +703,7 @@
             IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(3).issues.first)
           end
       
      -    query = Query.new(:name => '_')
      +    query = IssueQuery.new(:name => '_')
           query.filters = {"relates" => {:operator => '=!p', :values => ['1']}}
           assert_equal [1], find_issues_with_query(query).map(&:id).sort
         end
      @@ -697,7 +716,7 @@
             IssueRelation.create!(:relation_type => "relates", :issue_to => Project.find(2).issues.first, :issue_from => Issue.find(3))
           end
       
      -    query = Query.new(:name => '_')
      +    query = IssueQuery.new(:name => '_')
           query.filters = {"relates" => {:operator => '!p', :values => ['2']}}
           ids = find_issues_with_query(query).map(&:id).sort
           assert_include 2, ids
      @@ -710,7 +729,7 @@
           IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
           IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1))
       
      -    query = Query.new(:name => '_')
      +    query = IssueQuery.new(:name => '_')
           query.filters = {"relates" => {:operator => '!*', :values => ['']}}
           ids = find_issues_with_query(query).map(&:id)
           assert_equal [], ids & [1, 2, 3]
      @@ -722,13 +741,13 @@
           IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
           IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1))
       
      -    query = Query.new(:name => '_')
      +    query = IssueQuery.new(:name => '_')
           query.filters = {"relates" => {:operator => '*', :values => ['']}}
           assert_equal [1, 2, 3], find_issues_with_query(query).map(&:id).sort
         end
       
         def test_statement_should_be_nil_with_no_filters
      -    q = Query.new(:name => '_')
      +    q = IssueQuery.new(:name => '_')
           q.filters = {}
       
           assert q.valid?
      @@ -736,44 +755,62 @@
         end
       
         def test_default_columns
      -    q = Query.new
      +    q = IssueQuery.new
           assert q.columns.any?
           assert q.inline_columns.any?
           assert q.block_columns.empty?
         end
       
         def test_set_column_names
      -    q = Query.new
      +    q = IssueQuery.new
           q.column_names = ['tracker', :subject, '', 'unknonw_column']
      -    assert_equal [:tracker, :subject], q.columns.collect {|c| c.name}
      -    c = q.columns.first
      -    assert q.has_column?(c)
      +    assert_equal [:id, :tracker, :subject], q.columns.collect {|c| c.name}
      +  end
      +
      +  def test_has_column_should_accept_a_column_name
      +    q = IssueQuery.new
      +    q.column_names = ['tracker', :subject]
      +    assert q.has_column?(:tracker)
      +    assert !q.has_column?(:category)
      +  end
      +
      +  def test_has_column_should_accept_a_column
      +    q = IssueQuery.new
      +    q.column_names = ['tracker', :subject]
      +
      +    tracker_column = q.available_columns.detect {|c| c.name==:tracker}
      +    assert_kind_of QueryColumn, tracker_column
      +    category_column = q.available_columns.detect {|c| c.name==:category}
      +    assert_kind_of QueryColumn, category_column
      +
      +    assert q.has_column?(tracker_column)
      +    assert !q.has_column?(category_column)
         end
       
         def test_inline_and_block_columns
      -    q = Query.new
      +    q = IssueQuery.new
           q.column_names = ['subject', 'description', 'tracker']
       
      -    assert_equal [:subject, :tracker], q.inline_columns.map(&:name)
      +    assert_equal [:id, :subject, :tracker], q.inline_columns.map(&:name)
           assert_equal [:description], q.block_columns.map(&:name)
         end
       
         def test_custom_field_columns_should_be_inline
      -    q = Query.new
      +    q = IssueQuery.new
           columns = q.available_columns.select {|column| column.is_a? QueryCustomFieldColumn}
           assert columns.any?
           assert_nil columns.detect {|column| !column.inline?}
         end
       
         def test_query_should_preload_spent_hours
      -    q = Query.new(:name => '_', :column_names => [:subject, :spent_hours])
      +    q = IssueQuery.new(:name => '_', :column_names => [:subject, :spent_hours])
           assert q.has_column?(:spent_hours)
           issues = q.issues
           assert_not_nil issues.first.instance_variable_get("@spent_hours")
         end
       
         def test_groupable_columns_should_include_custom_fields
      -    q = Query.new
      +    q = IssueQuery.new
           column = q.groupable_columns.detect {|c| c.name == :cf_1}
           assert_not_nil column
           assert_kind_of QueryCustomFieldColumn, column
      @@ -783,7 +820,7 @@
           field = CustomField.find(1)
           field.update_attribute :multiple, true
       
      -    q = Query.new
      +    q = IssueQuery.new
           column = q.groupable_columns.detect {|c| c.name == :cf_1}
           assert_nil column
         end
      @@ -791,19 +828,19 @@
         def test_groupable_columns_should_include_user_custom_fields
           cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'user')
       
      -    q = Query.new
      +    q = IssueQuery.new
           assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym}
         end
       
         def test_groupable_columns_should_include_version_custom_fields
           cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'version')
       
      -    q = Query.new
      +    q = IssueQuery.new
           assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym}
         end
       
         def test_grouped_with_valid_column
      -    q = Query.new(:group_by => 'status')
      +    q = IssueQuery.new(:group_by => 'status')
           assert q.grouped?
           assert_not_nil q.group_by_column
           assert_equal :status, q.group_by_column.name
      @@ -812,7 +849,7 @@
         end
       
         def test_grouped_with_invalid_column
      -    q = Query.new(:group_by => 'foo')
      +    q = IssueQuery.new(:group_by => 'foo')
           assert !q.grouped?
           assert_nil q.group_by_column
           assert_nil q.group_by_statement
      @@ -820,7 +857,7 @@
         
         def test_sortable_columns_should_sort_assignees_according_to_user_format_setting
           with_settings :user_format => 'lastname_coma_firstname' do
      -      q = Query.new
      +      q = IssueQuery.new
             assert q.sortable_columns.has_key?('assigned_to')
             assert_equal %w(users.lastname users.firstname users.id), q.sortable_columns['assigned_to']
           end
      @@ -828,14 +865,14 @@
         
         def test_sortable_columns_should_sort_authors_according_to_user_format_setting
           with_settings :user_format => 'lastname_coma_firstname' do
      -      q = Query.new
      +      q = IssueQuery.new
             assert q.sortable_columns.has_key?('author')
             assert_equal %w(authors.lastname authors.firstname authors.id), q.sortable_columns['author']
           end
         end
       
         def test_sortable_columns_should_include_custom_field
      -    q = Query.new
      +    q = IssueQuery.new
           assert q.sortable_columns['cf_1']
         end
       
      @@ -843,29 +880,29 @@
           field = CustomField.find(1)
           field.update_attribute :multiple, true
       
      -    q = Query.new
      +    q = IssueQuery.new
           assert !q.sortable_columns['cf_1']
         end
       
         def test_default_sort
      -    q = Query.new
      +    q = IssueQuery.new
           assert_equal [], q.sort_criteria
         end
       
         def test_set_sort_criteria_with_hash
      -    q = Query.new
      +    q = IssueQuery.new
           q.sort_criteria = {'0' => ['priority', 'desc'], '2' => ['tracker']}
           assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
         end
       
         def test_set_sort_criteria_with_array
      -    q = Query.new
      +    q = IssueQuery.new
           q.sort_criteria = [['priority', 'desc'], 'tracker']
           assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
         end
       
         def test_create_query_with_sort
      -    q = Query.new(:name => 'Sorted')
      +    q = IssueQuery.new(:name => 'Sorted')
           q.sort_criteria = [['priority', 'desc'], 'tracker']
           assert q.save
           q.reload
      @@ -873,53 +910,47 @@
         end
       
         def test_sort_by_string_custom_field_asc
      -    q = Query.new
      +    q = IssueQuery.new
           c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
           assert c
           assert c.sortable
      -    issues = Issue.includes([:assigned_to, :status, :tracker, :project, :priority]).where(
      -         q.statement
      -       ).order("#{c.sortable} ASC").all
      +    issues = q.issues(: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
      +    q = IssueQuery.new
           c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
           assert c
           assert c.sortable
      -    issues = Issue.includes([:assigned_to, :status, :tracker, :project, :priority]).where(
      -         q.statement
      -       ).order("#{c.sortable} DESC").all
      +    issues = q.issues(: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
      +    q = IssueQuery.new
           c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'float' }
           assert c
           assert c.sortable
      -    issues = Issue.includes([:assigned_to, :status, :tracker, :project, :priority]).where(
      -         q.statement
      -       ).order("#{c.sortable} ASC").all
      +    issues = q.issues(: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_invalid_query_should_raise_query_statement_invalid_error
      -    q = Query.new
      +    q = IssueQuery.new
           assert_raise Query::StatementInvalid do
             q.issues(:conditions => "foo = 1")
           end
         end
       
         def test_issue_count
      -    q = Query.new(:name => '_')
      +    q = IssueQuery.new(:name => '_')
           issue_count = q.issue_count
           assert_equal q.issues.size, issue_count
         end
      @@ -935,7 +966,7 @@
         end
       
         def test_issue_count_by_association_group
      -    q = Query.new(:name => '_', :group_by => 'assigned_to')
      +    q = IssueQuery.new(:name => '_', :group_by => 'assigned_to')
           count_by_group = q.issue_count_by_group
           assert_kind_of Hash, count_by_group
           assert_equal %w(NilClass User), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
      @@ -944,7 +975,7 @@
         end
       
         def test_issue_count_by_list_custom_field_group
      -    q = Query.new(:name => '_', :group_by => 'cf_1')
      +    q = IssueQuery.new(:name => '_', :group_by => 'cf_1')
           count_by_group = q.issue_count_by_group
           assert_kind_of Hash, count_by_group
           assert_equal %w(NilClass String), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
      @@ -953,7 +984,7 @@
         end
       
         def test_issue_count_by_date_custom_field_group
      -    q = Query.new(:name => '_', :group_by => 'cf_8')
      +    q = IssueQuery.new(:name => '_', :group_by => 'cf_8')
           count_by_group = q.issue_count_by_group
           assert_kind_of Hash, count_by_group
           assert_equal %w(Date NilClass), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
      @@ -963,7 +994,7 @@
         def test_issue_count_with_nil_group_only
           Issue.update_all("assigned_to_id = NULL")
       
      -    q = Query.new(:name => '_', :group_by => 'assigned_to')
      +    q = IssueQuery.new(:name => '_', :group_by => 'assigned_to')
           count_by_group = q.issue_count_by_group
           assert_kind_of Hash, count_by_group
           assert_equal 1, count_by_group.keys.size
      @@ -971,7 +1002,7 @@
         end
       
         def test_issue_ids
      -    q = Query.new(:name => '_')
      +    q = IssueQuery.new(:name => '_')
           order = "issues.subject, issues.id"
           issues = q.issues(:order => order)
           assert_equal issues.map(&:id), q.issue_ids(:order => order)
      @@ -979,13 +1010,13 @@
       
         def test_label_for
           set_language_if_valid 'en'
      -    q = Query.new
      +    q = IssueQuery.new
           assert_equal 'Assignee', q.label_for('assigned_to_id')
         end
       
         def test_label_for_fr
           set_language_if_valid 'fr'
      -    q = Query.new
      +    q = IssueQuery.new
           s = "Assign\xc3\xa9 \xc3\xa0"
           s.force_encoding('UTF-8') if s.respond_to?(:force_encoding)
           assert_equal s, q.label_for('assigned_to_id')
      @@ -997,32 +1028,32 @@
           developer = User.find(3)
       
           # Public query on project 1
      -    q = Query.find(1)
      +    q = IssueQuery.find(1)
           assert q.editable_by?(admin)
           assert q.editable_by?(manager)
           assert !q.editable_by?(developer)
       
           # Private query on project 1
      -    q = Query.find(2)
      +    q = IssueQuery.find(2)
           assert q.editable_by?(admin)
           assert !q.editable_by?(manager)
           assert q.editable_by?(developer)
       
           # Private query for all projects
      -    q = Query.find(3)
      +    q = IssueQuery.find(3)
           assert q.editable_by?(admin)
           assert !q.editable_by?(manager)
           assert q.editable_by?(developer)
       
           # Public query for all projects
      -    q = Query.find(4)
      +    q = IssueQuery.find(4)
           assert q.editable_by?(admin)
           assert !q.editable_by?(manager)
           assert !q.editable_by?(developer)
         end
       
         def test_visible_scope
      -    query_ids = Query.visible(User.anonymous).map(&:id)
      +    query_ids = IssueQuery.visible(User.anonymous).map(&:id)
       
           assert query_ids.include?(1), 'public query on public project was not visible'
           assert query_ids.include?(4), 'public query for all projects was not visible'
      @@ -1031,81 +1062,50 @@
           assert !query_ids.include?(7), 'public query on private project was visible'
         end
       
      -  context "#available_filters" do
      -    setup do
      -      @query = Query.new(:name => "_")
      -    end
      +  test "#available_filters should include users of visible projects in cross-project view" do
      +    users = IssueQuery.new.available_filters["assigned_to_id"]
      +    assert_not_nil users
      +    assert users[:values].map{|u|u[1]}.include?("3")
      +  end
       
      -    should "include users of visible projects in cross-project view" do
      -      users = @query.available_filters["assigned_to_id"]
      -      assert_not_nil users
      -      assert users[:values].map{|u|u[1]}.include?("3")
      -    end
      +  test "#available_filters should include users of subprojects" do
      +    user1 = User.generate!
      +    user2 = User.generate!
      +    project = Project.find(1)
      +    Member.create!(:principal => user1, :project => project.children.visible.first, :role_ids => [1])
       
      -    should "include users of subprojects" do
      -      user1 = User.generate!
      -      user2 = User.generate!
      -      project = Project.find(1)
      -      Member.create!(:principal => user1, :project => project.children.visible.first, :role_ids => [1])
      -      @query.project = project
      +    users = IssueQuery.new(:project => project).available_filters["assigned_to_id"]
      +    assert_not_nil users
      +    assert users[:values].map{|u|u[1]}.include?(user1.id.to_s)
      +    assert !users[:values].map{|u|u[1]}.include?(user2.id.to_s)
      +  end
       
      -      users = @query.available_filters["assigned_to_id"]
      -      assert_not_nil users
      -      assert users[:values].map{|u|u[1]}.include?(user1.id.to_s)
      -      assert !users[:values].map{|u|u[1]}.include?(user2.id.to_s)
      -    end
      +  test "#available_filters should include visible projects in cross-project view" do
      +    projects = IssueQuery.new.available_filters["project_id"]
      +    assert_not_nil projects
      +    assert projects[:values].map{|u|u[1]}.include?("1")
      +  end
       
      -    should "include visible projects in cross-project view" do
      -      projects = @query.available_filters["project_id"]
      -      assert_not_nil projects
      -      assert projects[:values].map{|u|u[1]}.include?("1")
      -    end
      +  test "#available_filters should include 'member_of_group' filter" do
      +    query = IssueQuery.new
      +    assert query.available_filters.keys.include?("member_of_group")
      +    assert_equal :list_optional, query.available_filters["member_of_group"][:type]
      +    assert query.available_filters["member_of_group"][:values].present?
      +    assert_equal Group.all.sort.map {|g| [g.name, g.id.to_s]},
      +      query.available_filters["member_of_group"][:values].sort
      +  end
       
      -    context "'member_of_group' filter" do
      -      should "be present" do
      -        assert @query.available_filters.keys.include?("member_of_group")
      -      end
      +  test "#available_filters should include 'assigned_to_role' filter" do
      +    query = IssueQuery.new
      +    assert query.available_filters.keys.include?("assigned_to_role")
      +    assert_equal :list_optional, query.available_filters["assigned_to_role"][:type]
       
      -      should "be an optional list" do
      -        assert_equal :list_optional, @query.available_filters["member_of_group"][:type]
      -      end
      +    assert query.available_filters["assigned_to_role"][:values].include?(['Manager','1'])
      +    assert query.available_filters["assigned_to_role"][:values].include?(['Developer','2'])
      +    assert query.available_filters["assigned_to_role"][:values].include?(['Reporter','3'])
       
      -      should "have a list of the groups as values" do
      -        Group.destroy_all # No fixtures
      -        group1 = Group.generate!.reload
      -        group2 = Group.generate!.reload
      -
      -        expected_group_list = [
      -                               [group1.name, group1.id.to_s],
      -                               [group2.name, group2.id.to_s]
      -                              ]
      -        assert_equal expected_group_list.sort, @query.available_filters["member_of_group"][:values].sort
      -      end
      -
      -    end
      -
      -    context "'assigned_to_role' filter" do
      -      should "be present" do
      -        assert @query.available_filters.keys.include?("assigned_to_role")
      -      end
      -
      -      should "be an optional list" do
      -        assert_equal :list_optional, @query.available_filters["assigned_to_role"][:type]
      -      end
      -
      -      should "have a list of the Roles as values" do
      -        assert @query.available_filters["assigned_to_role"][:values].include?(['Manager','1'])
      -        assert @query.available_filters["assigned_to_role"][:values].include?(['Developer','2'])
      -        assert @query.available_filters["assigned_to_role"][:values].include?(['Reporter','3'])
      -      end
      -
      -      should "not include the built in Roles as values" do
      -        assert ! @query.available_filters["assigned_to_role"][:values].include?(['Non member','4'])
      -        assert ! @query.available_filters["assigned_to_role"][:values].include?(['Anonymous','5'])
      -      end
      -
      -    end
      -
      +    assert ! query.available_filters["assigned_to_role"][:values].include?(['Non member','4'])
      +    assert ! query.available_filters["assigned_to_role"][:values].include?(['Anonymous','5'])
         end
       
         context "#statement" do
      @@ -1127,34 +1127,34 @@
             end
       
             should "search assigned to for users in the group" do
      -        @query = Query.new(:name => '_')
      +        @query = IssueQuery.new(:name => '_')
               @query.add_filter('member_of_group', '=', [@group.id.to_s])
       
      -        assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}')"
      +        assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@group.id}')"
               assert_find_issues_with_query_is_successful @query
             end
       
             should "search not assigned to any group member (none)" do
      -        @query = Query.new(:name => '_')
      +        @query = IssueQuery.new(:name => '_')
               @query.add_filter('member_of_group', '!*', [''])
       
               # Users not in a group
      -        assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IS NULL OR #{Issue.table_name}.assigned_to_id NOT IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}')"
      +        assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IS NULL OR #{Issue.table_name}.assigned_to_id NOT IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}','#{@group.id}','#{@group2.id}')"
               assert_find_issues_with_query_is_successful @query
             end
       
             should "search assigned to any group member (all)" do
      -        @query = Query.new(:name => '_')
      +        @query = IssueQuery.new(:name => '_')
               @query.add_filter('member_of_group', '*', [''])
       
               # Only users in a group
      -        assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}')"
      +        assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}','#{@group.id}','#{@group2.id}')"
               assert_find_issues_with_query_is_successful @query
             end
       
             should "return an empty set with = empty group" do
               @empty_group = Group.generate!
      -        @query = Query.new(:name => '_')
      +        @query = IssueQuery.new(:name => '_')
               @query.add_filter('member_of_group', '=', [@empty_group.id.to_s])
       
               assert_equal [], find_issues_with_query(@query)
      @@ -1162,7 +1162,7 @@
       
             should "return issues with ! empty group" do
               @empty_group = Group.generate!
      -        @query = Query.new(:name => '_')
      +        @query = IssueQuery.new(:name => '_')
               @query.add_filter('member_of_group', '!', [@empty_group.id.to_s])
       
               assert_find_issues_with_query_is_successful @query
      @@ -1191,7 +1191,7 @@
             end
       
             should "search assigned to for users with the Role" do
      -        @query = Query.new(:name => '_', :project => @project)
      +        @query = IssueQuery.new(:name => '_', :project => @project)
               @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
       
               assert_query_result [@issue1, @issue3], @query
      @@ -1201,7 +1201,7 @@
               other_project = Project.generate!
               User.add_to_project(@developer, other_project, @manager_role)
               
      -        @query = Query.new(:name => '_', :project => @project)
      +        @query = IssueQuery.new(:name => '_', :project => @project)
               @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
       
               assert_query_result [@issue1, @issue3], @query
      @@ -1209,28 +1209,28 @@
       
             should "return an empty set with empty role" do
               @empty_role = Role.generate!
      -        @query = Query.new(:name => '_', :project => @project)
      +        @query = IssueQuery.new(:name => '_', :project => @project)
               @query.add_filter('assigned_to_role', '=', [@empty_role.id.to_s])
       
               assert_query_result [], @query
             end
       
             should "search assigned to for users without the Role" do
      -        @query = Query.new(:name => '_', :project => @project)
      +        @query = IssueQuery.new(:name => '_', :project => @project)
               @query.add_filter('assigned_to_role', '!', [@manager_role.id.to_s])
       
               assert_query_result [@issue2, @issue4, @issue5], @query
             end
       
             should "search assigned to for users not assigned to any Role (none)" do
      -        @query = Query.new(:name => '_', :project => @project)
      +        @query = IssueQuery.new(:name => '_', :project => @project)
               @query.add_filter('assigned_to_role', '!*', [''])
       
               assert_query_result [@issue4, @issue5], @query
             end
       
             should "search assigned to for users assigned to any Role (all)" do
      -        @query = Query.new(:name => '_', :project => @project)
      +        @query = IssueQuery.new(:name => '_', :project => @project)
               @query.add_filter('assigned_to_role', '*', [''])
       
               assert_query_result [@issue1, @issue2, @issue3], @query
      @@ -1238,12 +1238,11 @@
       
             should "return issues with ! empty role" do
               @empty_role = Role.generate!
      -        @query = Query.new(:name => '_', :project => @project)
      +        @query = IssueQuery.new(:name => '_', :project => @project)
               @query.add_filter('assigned_to_role', '!', [@empty_role.id.to_s])
       
               assert_query_result [@issue1, @issue2, @issue3, @issue4, @issue5], @query
             end
           end
         end
      -
       end
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/repository_bazaar_test.rb
      --- a/test/unit/repository_bazaar_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/repository_bazaar_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      @@ -105,7 +105,7 @@
             @project.reload
             assert_equal NUM_REV, @repository.changesets.count
             # Remove changesets with revision > 5
      -      @repository.changesets.find(:all).each {|c| c.destroy if c.revision.to_i > 2}
      +      @repository.changesets.all.each {|c| c.destroy if c.revision.to_i > 2}
             @project.reload
             assert_equal 2, @repository.changesets.count
       
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/repository_cvs_test.rb
      --- a/test/unit/repository_cvs_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/repository_cvs_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      @@ -116,12 +116,12 @@
             assert_equal CHANGESETS_NUM, @repository.changesets.count
       
             # Remove changesets with revision > 3
      -      @repository.changesets.find(:all).each {|c| c.destroy if c.revision.to_i > 3}
      +      @repository.changesets.all.each {|c| c.destroy if c.revision.to_i > 3}
             @project.reload
             assert_equal 3, @repository.changesets.count
             assert_equal %w|3 2 1|, @repository.changesets.all.collect(&:revision)
       
      -      rev3_commit = @repository.changesets.find(:first, :order => 'committed_on DESC')
      +      rev3_commit = @repository.changesets.reorder('committed_on DESC').first
             assert_equal '3', rev3_commit.revision
              # 2007-12-14 01:27:22 +0900
             rev3_committed_on = Time.gm(2007, 12, 13, 16, 27, 22)
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/repository_darcs_test.rb
      --- a/test/unit/repository_darcs_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/repository_darcs_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      @@ -79,7 +79,7 @@
             assert_equal NUM_REV, @repository.changesets.count
       
             # Remove changesets with revision > 3
      -      @repository.changesets.find(:all).each {|c| c.destroy if c.revision.to_i > 3}
      +      @repository.changesets.all.each {|c| c.destroy if c.revision.to_i > 3}
             @project.reload
             assert_equal 3, @repository.changesets.count
       
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/repository_filesystem_test.rb
      --- a/test/unit/repository_filesystem_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/repository_filesystem_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/repository_git_test.rb
      --- a/test/unit/repository_git_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/repository_git_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/repository_mercurial_test.rb
      --- a/test/unit/repository_mercurial_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/repository_mercurial_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      @@ -102,7 +102,7 @@
             @project.reload
             assert_equal NUM_REV, @repository.changesets.count
             # Remove changesets with revision > 2
      -      @repository.changesets.find(:all).each {|c| c.destroy if c.revision.to_i > 2}
      +      @repository.changesets.all.each {|c| c.destroy if c.revision.to_i > 2}
             @project.reload
             assert_equal 3, @repository.changesets.count
       
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/repository_subversion_test.rb
      --- a/test/unit/repository_subversion_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/repository_subversion_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      @@ -47,7 +47,7 @@
             assert_equal NUM_REV, @repository.changesets.count
       
             # Remove changesets with revision > 5
      -      @repository.changesets.find(:all).each {|c| c.destroy if c.revision.to_i > 5}
      +      @repository.changesets.all.each {|c| c.destroy if c.revision.to_i > 5}
             @project.reload
             assert_equal 5, @repository.changesets.count
       
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/repository_test.rb
      --- a/test/unit/repository_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/repository_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      @@ -209,7 +209,7 @@
           assert_equal [101], fixed_issue.changeset_ids
       
           # issue change
      -    journal = fixed_issue.journals.find(:first, :order => 'created_on desc')
      +    journal = fixed_issue.journals.reorder('created_on desc').first
           assert_equal User.find_by_login('dlopper'), journal.user
           assert_equal 'Applied in changeset r2.', journal.notes
       
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/role_test.rb
      --- a/test/unit/role_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/role_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/search_test.rb
      --- a/test/unit/search_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/search_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/setting_test.rb
      --- a/test/unit/setting_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/setting_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/time_entry_activity_test.rb
      --- a/test/unit/time_entry_activity_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/time_entry_activity_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      @@ -84,5 +84,33 @@
           e.reload
           assert_equal "0", e.custom_value_for(field).value
         end
      +
      +  def test_system_activity_with_child_in_use_should_be_in_use
      +    project = Project.generate!
      +    system_activity = TimeEntryActivity.create!(:name => 'Activity')
      +    project_activity = TimeEntryActivity.create!(:name => 'Activity', :project => project, :parent_id => system_activity.id)
      +
      +    TimeEntry.generate!(:project => project, :activity => project_activity)
      +
      +    assert project_activity.in_use?
      +    assert system_activity.in_use?
      +  end
      +
      +  def test_destroying_a_system_activity_should_reassign_children_activities
      +    project = Project.generate!
      +    system_activity = TimeEntryActivity.create!(:name => 'Activity')
      +    project_activity = TimeEntryActivity.create!(:name => 'Activity', :project => project, :parent_id => system_activity.id)
      +
      +    entries = [
      +      TimeEntry.generate!(:project => project, :activity => system_activity),
      +      TimeEntry.generate!(:project => project, :activity => project_activity)
      +    ]
      +
      +    assert_difference 'TimeEntryActivity.count', -2 do
      +      assert_nothing_raised do
      +        assert system_activity.destroy(TimeEntryActivity.find_by_name('Development'))
      +      end
      +    end
      +    assert entries.all? {|entry| entry.reload.activity.name == 'Development'}
      +  end
       end
      -
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/time_entry_test.rb
      --- a/test/unit/time_entry_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/time_entry_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      @@ -25,8 +25,7 @@
                  :journals, :journal_details,
                  :issue_categories, :enumerations,
                  :groups_users,
      -           :enabled_modules,
      -           :workflows
      +           :enabled_modules
       
         def test_hours_format
           assertions = { "2"      => 2.0,
      @@ -111,6 +110,13 @@
           assert_equal 1, te.errors.count
         end
       
      +  def test_spent_on_with_2_digits_year_should_not_be_valid
      +    entry = TimeEntry.new(:project => Project.find(1), :user => User.find(1), :activity => TimeEntryActivity.first, :hours => 1)
      +    entry.spent_on = "09-02-04"
      +    assert !entry.valid?
      +    assert_include I18n.translate('activerecord.errors.messages.not_a_date'), entry.errors[:spent_on]
      +  end
      +
         def test_set_project_if_nil
           anon     = User.anonymous
           project  = Project.find(1)
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/token_test.rb
      --- a/test/unit/token_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/token_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      @@ -58,4 +58,56 @@
             assert_equal 2, Token.destroy_expired
           end
         end
      +
      +  def test_find_active_user_should_return_user
      +    token = Token.create!(:user_id => 1, :action => 'api')
      +    assert_equal User.find(1), Token.find_active_user('api', token.value)
      +  end
      +
      +  def test_find_active_user_should_return_nil_for_locked_user
      +    token = Token.create!(:user_id => 1, :action => 'api')
      +    User.find(1).lock!
      +    assert_nil Token.find_active_user('api', token.value)
      +  end
      +
      +  def test_find_user_should_return_user
      +    token = Token.create!(:user_id => 1, :action => 'api')
      +    assert_equal User.find(1), Token.find_user('api', token.value)
      +  end
      +
      +  def test_find_user_should_return_locked_user
      +    token = Token.create!(:user_id => 1, :action => 'api')
      +    User.find(1).lock!
      +    assert_equal User.find(1), Token.find_user('api', token.value)
      +  end
      +
      +  def test_find_token_should_return_the_token
      +    token = Token.create!(:user_id => 1, :action => 'api')
      +    assert_equal token, Token.find_token('api', token.value)
      +  end
      +
      +  def test_find_token_should_return_the_token_with_validity
      +    token = Token.create!(:user_id => 1, :action => 'api', :created_on => 1.hour.ago)
      +    assert_equal token, Token.find_token('api', token.value, 1)
      +  end
      +
      +  def test_find_token_should_return_nil_with_wrong_action
      +    token = Token.create!(:user_id => 1, :action => 'feeds')
      +    assert_nil Token.find_token('api', token.value)
      +  end
      +
      +  def test_find_token_should_return_nil_with_wrong_action
      +    token = Token.create!(:user_id => 1, :action => 'feeds')
      +    assert_nil Token.find_token('api', Token.generate_token_value)
      +  end
      +
      +  def test_find_token_should_return_nil_without_user
      +    token = Token.create!(:user_id => 999, :action => 'api')
      +    assert_nil Token.find_token('api', token.value)
      +  end
      +
      +  def test_find_token_should_return_nil_with_validity_expired
      +    token = Token.create!(:user_id => 999, :action => 'api', :created_on => 2.days.ago)
      +    assert_nil Token.find_token('api', token.value, 1)
      +  end
       end
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/user_preference_test.rb
      --- a/test/unit/user_preference_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/user_preference_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/user_test.rb
      --- a/test/unit/user_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/user_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      @@ -25,8 +25,7 @@
                   :issue_categories, :enumerations, :issues,
                   :journals, :journal_details,
                   :groups_users,
      -            :enabled_modules,
      -            :workflows
      +            :enabled_modules
       
         def setup
           @admin = User.find(1)
      @@ -34,6 +33,10 @@
           @dlopper = User.find(3)
         end
       
      +  def test_sorted_scope_should_sort_user_by_display_name
      +    assert_equal User.all.map(&:name).map(&:downcase).sort, User.sorted.all.map(&:name).map(&:downcase)
      +  end
      +
         def test_generate
           User.generate!(:firstname => 'Testing connection')
           User.generate!(:firstname => 'Testing connection')
      @@ -724,6 +727,32 @@
           assert_equal true, User.default_admin_account_changed?
         end
       
      +  def test_membership_with_project_should_return_membership
      +    project = Project.find(1)
      +
      +    membership = @jsmith.membership(project)
      +    assert_kind_of Member, membership
      +    assert_equal @jsmith, membership.user
      +    assert_equal project, membership.project
      +  end
      +
      +  def test_membership_with_project_id_should_return_membership
      +    project = Project.find(1)
      +
      +    membership = @jsmith.membership(1)
      +    assert_kind_of Member, membership
      +    assert_equal @jsmith, membership.user
      +    assert_equal project, membership.project
      +  end
      +
      +  def test_membership_for_non_member_should_return_nil
      +    project = Project.find(1)
      +
      +    user = User.generate!
      +    membership = user.membership(1)
      +    assert_nil membership
      +  end
      +
         def test_roles_for_project
           # user with a role
           roles = @jsmith.roles_for_project(Project.find(1))
      @@ -901,7 +930,7 @@
             should "authorize nearly everything for admin users" do
               project = Project.find(1)
               assert ! @admin.member_of?(project)
      -        %w(edit_issues delete_issues manage_news manage_documents manage_wiki).each do |p|
      +        %w(edit_issues delete_issues manage_news add_documents manage_wiki).each do |p|
                 assert_equal true, @admin.allowed_to?(p.to_sym, project)
               end
             end
      @@ -1014,9 +1043,15 @@
               assert ! @user.notify_about?(@issue)
             end
           end
      +  end
       
      -    context "other events" do
      -      should 'be added and tested'
      +  def test_notify_about_news
      +    user = User.generate!
      +    news = News.new
      +
      +    User::MAIL_NOTIFICATION_OPTIONS.map(&:first).each do |option|
      +      user.mail_notification = option
      +      assert_equal (option != 'none'), user.notify_about?(news)
           end
         end
       
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/version_test.rb
      --- a/test/unit/version_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/version_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      @@ -37,6 +37,8 @@
           assert !v.valid?
           v.effective_date = '2012-31-11'
           assert !v.valid?
      +    v.effective_date = '-2012-31-11'
      +    assert !v.valid?
           v.effective_date = 'ABC'
           assert !v.valid?
           assert_include I18n.translate('activerecord.errors.messages.not_a_date'),
      @@ -46,8 +48,8 @@
         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
      +    assert_equal 0, v.completed_percent
      +    assert_equal 0, v.closed_percent
         end
       
         def test_progress_should_be_0_with_unbegun_assigned_issues
      @@ -55,20 +57,20 @@
           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
      +    assert_progress_equal 0, v.completed_percent
      +    assert_progress_equal 0, v.closed_percent
         end
       
         def test_progress_should_be_100_with_closed_assigned_issues
           project = Project.find(1)
      -    status = IssueStatus.find(:first, :conditions => {:is_closed => true})
      +    status = IssueStatus.where(:is_closed => true).first
           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
      +    assert_progress_equal 100.0, v.completed_percent
      +    assert_progress_equal 100.0, v.closed_percent
         end
       
         def test_progress_should_consider_done_ratio_of_open_assigned_issues
      @@ -77,8 +79,8 @@
           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
      +    assert_progress_equal (0.0 + 20.0 + 70.0)/3, v.completed_percent
      +    assert_progress_equal 0, v.closed_percent
         end
       
         def test_progress_should_consider_closed_issues_as_completed
      @@ -86,9 +88,9 @@
           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
      +    add_issue(v, :status => IssueStatus.where(:is_closed => true).first)
      +    assert_progress_equal (0.0 + 20.0 + 100.0)/3, v.completed_percent
      +    assert_progress_equal (100.0)/3, v.closed_percent
         end
       
         def test_progress_should_consider_estimated_hours_to_weigth_issues
      @@ -97,20 +99,20 @@
           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
      +    add_issue(v, :estimated_hours => 25, :status => IssueStatus.where(:is_closed => true).first)
      +    assert_progress_equal (10.0*0 + 20.0*0.3 + 40*0.1 + 25.0*1)/95.0*100, v.completed_percent
      +    assert_progress_equal 25.0/95.0*100, v.closed_percent
         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, :status => IssueStatus.where(:is_closed => true).first)
           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
      +    assert_progress_equal (25.0*0.2 + 25.0*1 + 10.0*0.3 + 40.0*0.1)/100.0*100, v.completed_percent
      +    assert_progress_equal 25.0/100.0*100, v.closed_percent
         end
       
         def test_should_sort_scheduled_then_unscheduled_versions
      @@ -152,7 +154,7 @@
             @version.update_attribute(:effective_date, 7.days.from_now.to_date)
             add_issue(@version, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left
             add_issue(@version, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left
      -      assert_equal 60, @version.completed_pourcent
      +      assert_equal 60, @version.completed_percent
             assert_equal false, @version.behind_schedule?
           end
       
      @@ -160,7 +162,7 @@
             @version.update_attribute(:effective_date, 7.days.from_now.to_date)
             add_issue(@version, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left
             add_issue(@version, :start_date => 7.days.ago, :done_ratio => 20) # 14 day span, 20% done, 50% time left
      -      assert_equal 40, @version.completed_pourcent
      +      assert_equal 40, @version.completed_percent
             assert_equal true, @version.behind_schedule?
           end
       
      @@ -168,7 +170,7 @@
             @version.update_attribute(:effective_date, 7.days.from_now.to_date)
             add_issue(@version, :start_date => 14.days.ago, :done_ratio => 100, :status => IssueStatus.find(5)) # 7 day span
             add_issue(@version, :start_date => 14.days.ago, :done_ratio => 100, :status => IssueStatus.find(5)) # 7 day span
      -      assert_equal 100, @version.completed_pourcent
      +      assert_equal 100, @version.completed_percent
             assert_equal false, @version.behind_schedule?
           end
         end
      @@ -242,8 +244,8 @@
           Issue.create!({:project => version.project,
                          :fixed_version => version,
                          :subject => 'Test',
      -                   :author => User.find(:first),
      -                   :tracker => version.project.trackers.find(:first)}.merge(attributes))
      +                   :author => User.first,
      +                   :tracker => version.project.trackers.first}.merge(attributes))
         end
       
         def assert_progress_equal(expected_float, actual_float, message="")
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/watcher_test.rb
      --- a/test/unit/watcher_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/watcher_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/wiki_content_test.rb
      --- a/test/unit/wiki_content_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/wiki_content_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/wiki_content_version_test.rb
      --- a/test/unit/wiki_content_version_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/wiki_content_version_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/wiki_page_test.rb
      --- a/test/unit/wiki_page_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/wiki_page_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/wiki_redirect_test.rb
      --- a/test/unit/wiki_redirect_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/wiki_redirect_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      @@ -69,6 +69,6 @@
           assert WikiRedirect.create(:wiki => @wiki, :title => 'An_old_page', :redirects_to => 'Original_title')
       
           @original.destroy
      -    assert !@wiki.redirects.find(:first)
      +    assert_nil @wiki.redirects.first
         end
       end
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/wiki_test.rb
      --- a/test/unit/wiki_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/wiki_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,7 +1,7 @@
       # encoding: utf-8
       #
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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
      diff -r 0a574315af3e -r 4f746d8966dd test/unit/workflow_test.rb
      --- a/test/unit/workflow_test.rb	Fri Jun 14 09:07:32 2013 +0100
      +++ b/test/unit/workflow_test.rb	Fri Jun 14 09:28:30 2013 +0100
      @@ -1,5 +1,5 @@
       # Redmine - project management software
      -# Copyright (C) 2006-2012  Jean-Philippe Lang
      +# Copyright (C) 2006-2013  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